Link to home
Start Free TrialLog in
Avatar of Psylord
Psylord

asked on

ICS Sockets, Buffering and numerics.

Baically what I aim to do with the following code is to use command prefixes (numerics) to allow easy handling of incoming data to both clients and servers. I'm looking to do this in an IRC style way. The problem that arises is that I know nothing about doing it. Plainly hehe.

My question is really a request for code completion/improvement here. Preferably, the person answering this has done quite a bit of work with ICS, and handled buffering and numeric style command/response handling. This code is only a base model, I'm very open here.

Here's how it should work...
When the data is recieved, I want to make sure I have all of it, before I do anything with it. I also want to avoid having peices from the NEXT command (which can happen I've been told).

bacially I guess if you can see what I'm trying to do below and can improve it and make it work fully, you're the person for the answer hehe. I'll let my code do the talking...

procedure TForm1.SendCommand(Numeric: Integer; Length: Integer; Content: String);
var
Temp: TStringList;
begin
Data:= TStringList.Create;
Data.Insert(0, IntToStr(Numeric));
Data.Insert(1, IntToStr(Length));
Temp := TStringList.Create;
Temp.Text := Content;
Data.Insert(2, Temp.CommaText);
TextClient.Send(Data.CommaText); {Error on this line}
end;

procedure TForm1.TextServerDataAvailable(Sender: TObject; Error: Word);
var
ReciveBuffer: String;
FCurrentlyReciving: Boolean;
begin
Data:= TStringList.Create;

 ReciveBuffer := ReciveBuffer + TextServer.ReceiveStr;
 Data.CommaText := ReciveBuffer;

 if Data.Count < 3 then
 begin
 FCurrentlyReciving := True;
 exit;
  if Length(Data[2]) < StrToInt(Data[1]) then
   begin
    FCurrentlyReciving := True;
    exit;
   end else
   begin
    FCurrentlyReciving := False;
    Data.CommaText:= TextServer.ReceiveStr;
    Echo(Data.CommaText);
    ParseCommand(StrToInt(Data[0]), Data[2]);
   end;
   end;
end;

procedure TForm1.ParseCommand(Numeric: Integer; Content: String);
begin
{This is where I'll be parsing all commands}
end;


Thanks!
Psylord
Avatar of AvonWyss
AvonWyss
Flag of Switzerland image

Stream sockets do not guarantee any availability or data length to be transmitted at once. So you cannot rely on getting the data in as chunks as they were sent (and that's why you can have an incomplete command or onw tiah a part of the next). It looks like you're going to implement a text based protocol, so it would make sense to use a CR (carriage return, that is, ASCII char 13) as data separator, e.g. everytime you see a CRLF you know that one command was transmitted completely.

You need to add a buffer which is kept across your TextServerDataAvailable; to do this, just add a "FReceiveBuffer: string" to your TForm1 private declaration. Then work like this:


procedure TForm1.SendCommand(Numeric: Integer; Content: String);
begin
     TextClient.Send(Format('%d %s',[Numeric,StringReplace(StringReplace(Line,'\','\\',[rfReplaceAll]),#13,'\n',[rfReplaceAll])]));
end;


procedure TForm1.TextServerDataAvailable(Sender: TObject; Error: Word);
var
     Line: string;
     I: Integer;
begin
     FReciveBuffer:=FReciveBuffer+TextServer.ReceiveStr;
     I:=Pos(#13,FReceiveBuffer);
     while I>0 do begin
          Line:=Copy(FReceiveBuffer,1,I-1);
          Delete(FReceiveBuffer,1,I);
          Line:=StringReplace(StringReplace(Line,'\n',#13,[rfReplaceAll]),'\\','\',[rfReplaceAll]);
          I:=Pos(' ',Line);
          ParseCommand(StrToInt(Copy(Line,1,I-1)),Copy(Line,I+1,Length(Line));
          I:=Pos(#13,FReceiveBuffer);
     end;
end;


Of course, the code is made so that it will handle #13 in the data alright. BTW, in your code, you careate tons of TStringLists - but you never free them. That is called a memory leak and makes an application use more and more memory until the app is terminated or the available memory is exhausted. Try to free the mem you allocate, Delphi does *not* destroy class instances automatically!
Oops. Damn copy-paste... ;-)
Should be like this of yourse:

procedure TForm1.SendCommand(Numeric: Integer; const Content: String);
begin
     TextClient.Send(Format('%d %s',[Numeric,StringReplace(StringReplace(Content,'\','\\',[rfReplaceAll]),#13,'\n',[rfReplaceAll])]));
end;
Avatar of Psylord
Psylord

ASKER

Ok don't get upset yet... I've decided to use the borland sockets, as they're much easier... Especially when sending to specific clients...

Is there any way you can change the code around to just use '#13#10' as the end of the command? It think I might understand it a little better... In other words, instead of everything else I said, just wait until #13#10 is recieved and BOOM! we have the command.

Your code might (and probably does) have the same effect, but it's just a little confusing to me and I couldn't get it working with ICS because ICS SUCKS! So, if you would, with the normal Borland internete client/server...

If you can I'll increase the points 50 more. <:D

Here's what the recieve events look like for the sockets I'm using now:


{Server}
procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin

end;

{Client}
procedure TForm1.ServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin

end;


When I parse these, I'll need to send the socket along with it so I know who to reply to when I parse the command, so my parsecommand will look like this:

procedure TForm1.ParseCommand(Socket: TCustomWinSocket; Numeric: Integer; Content: String);
begin

 case Numeric of
  100:
   begin
    {The numeric was "100"!}
   end;

 end; {Case}

end; {Proc}

Stupid comments and example code provided. :)

So basically I'll be sending my commands like this:

    Client.Socket.SendText(IntToStr(Numeric) + ' ' + Content);
    Server.Socket.SendText(IntToStr(Numeric) + ' ' + Content);

Appreciate the help,
Psylord
Avatar of Psylord

ASKER

Ok don't get upset yet... I've decided to use the borland sockets, as they're much easier... Especially when sending to specific clients...

Is there any way you can change the code around to just use '#13#10' as the end of the command? It think I might understand it a little better... In other words, instead of everything else I said, just wait until #13#10 is recieved and BOOM! we have the command.

Your code might (and probably does) have the same effect, but it's just a little confusing to me and I couldn't get it working with ICS because ICS SUCKS! So, if you would, with the normal Borland internete client/server...

If you can I'll increase the points 50 more. <:D

Here's what the recieve events look like for the sockets I'm using now:


{Server}
procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin

end;

{Client}
procedure TForm1.ServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin

end;


When I parse these, I'll need to send the socket along with it so I know who to reply to when I parse the command, so my parsecommand will look like this:

procedure TForm1.ParseCommand(Socket: TCustomWinSocket; Numeric: Integer; Content: String);
begin

 case Numeric of
  100:
   begin
    {The numeric was "100"!}
   end;

 end; {Case}

end; {Proc}

Stupid comments and example code provided. :)

So basically I'll be sending my commands like this:

    Client.Socket.SendText(IntToStr(Numeric) + ' ' + Content);
    Server.Socket.SendText(IntToStr(Numeric) + ' ' + Content);

Appreciate the help,
Psylord
Avatar of Psylord

ASKER

Ok don't get upset yet... I've decided to use the borland sockets, as they're much easier... Especially when sending to specific clients...

Is there any way you can change the code around to just use '#13#10' as the end of the command? It think I might understand it a little better... In other words, instead of everything else I said, just wait until #13#10 is recieved and BOOM! we have the command.

Your code might (and probably does) have the same effect, but it's just a little confusing to me and I couldn't get it working with ICS because ICS SUCKS! So, if you would, with the normal Borland internete client/server...

If you can I'll increase the points 50 more. <:D

Here's what the recieve events look like for the sockets I'm using now:


{Server}
procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin

end;

{Client}
procedure TForm1.ServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin

end;


When I parse these, I'll need to send the socket along with it so I know who to reply to when I parse the command, so my parsecommand will look like this:

procedure TForm1.ParseCommand(Socket: TCustomWinSocket; Numeric: Integer; Content: String);
begin

 case Numeric of
  100:
   begin
    {The numeric was "100"!}
   end;

 end; {Case}

end; {Proc}

Stupid comments and example code provided. :)

So basically I'll be sending my commands like this:

    Client.Socket.SendText(IntToStr(Numeric) + ' ' + Content);
    Server.Socket.SendText(IntToStr(Numeric) + ' ' + Content);

Appreciate the help,
Psylord
Avatar of Psylord

ASKER

Holy crap, it posted 3 times. Whoops. :)
ASKER CERTIFIED SOLUTION
Avatar of AvonWyss
AvonWyss
Flag of Switzerland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Psylord

ASKER

*Sigh* I will be specific in my qeustions from now on. Reviewing the code now. By the way, I have YET to get that winsock question's code to work...
Avatar of Psylord

ASKER

Still doesn't work. but ok.
Avatar of Psylord

ASKER

still can't seem to get it working but YEAH. Here's your points.
Psylord, what does not work?