Solved

ICS Sockets, Buffering and numerics.

Posted on 2001-06-10
11
224 Views
Last Modified: 2010-04-06
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
0
Comment
Question by:Psylord
  • 7
  • 4
11 Comments
 
LVL 14

Expert Comment

by:AvonWyss
Comment Utility
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!
0
 
LVL 14

Expert Comment

by:AvonWyss
Comment Utility
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;
0
 

Author Comment

by:Psylord
Comment Utility
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
0
 

Author Comment

by:Psylord
Comment Utility
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
0
 

Author Comment

by:Psylord
Comment Utility
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
0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 

Author Comment

by:Psylord
Comment Utility
Holy crap, it posted 3 times. Whoops. :)
0
 
LVL 14

Accepted Solution

by:
AvonWyss earned 200 total points
Comment Utility
My code takes #13 as command terminator. Plus it does change all #13 which might have been there when sending the command so that they are not recognized as end-of-command. This happens transparently, e.g. you can send and receive *any* data (including #13) as command parameter. Still the actual transfer over the sockets is plain text.

You basically have to change two minor things in both the send and the receive procs to make it work with the borland sockets:

instead of
        FReciveBuffer:=FReciveBuffer+TextServer.ReceiveStr;
use
        FReciveBuffer:=FReciveBuffer+Socket.ReceiveText;

and when sending, use the following:  
        Socket.SendText(Format('%d %s',[Numeric,StringReplace(StringReplace(Command,'\','\\',[rfReplaceAll]),#13,'\n',[rfReplaceAll])]));

The StringReplace takes care of handling your #13 in the command properly (that is, handing them over to ParseCommand also).


I hope that you are aware that EE policy does state that a question in their terms is exactly one question. If you change your mind about what you want to use, but already got an answer, its not correct to reject the answer if it was correct (and mine was). Please review the EE Q&A guidelines at http://www.experts-exchange.com/jsp/cmtyQuestAnswer.jsp

It's the second time you rejected an answer posted by me for no reason. The other one is
        http://www.experts-exchange.com/jsp/qShow.jsp?ta=delphi&qid=20132897
and I expect you to rectify this ASAP.
0
 

Author Comment

by:Psylord
Comment Utility
*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...
0
 

Author Comment

by:Psylord
Comment Utility
Still doesn't work. but ok.
0
 

Author Comment

by:Psylord
Comment Utility
still can't seem to get it working but YEAH. Here's your points.
0
 
LVL 14

Expert Comment

by:AvonWyss
Comment Utility
Psylord, what does not work?
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now