Attempting to Create Multithreaded UDP Server lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- gregv · 2016-11-02 18:24 I'm trying to change some of the Synapse examples from being a TCP implementation to being a UDP implementation.  It doesn't seem to be working.  What I expect to have happen is that a client connects and then "hello there" is transmitted back.  Right now, it just gets stuck in the loop and "CanRead" is displayed over and over.  No message is sent to the client. I haven't created the udp client thread yet, but I didn't think that would be absolutely necessary for simply testing - I might be wrong. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TUDPListener.Execute;var  ClientSocket: TSocket;  _cli: TUDPBlockSocket;begin  FSocket.CreateSocket;  FSocket.SetLinger(True, 10000);  FSocket.Bind(FEndPoint.Address, FEndPoint.Port);  if FSocket.LastError = 0 then    WriteLn('Bound on ' + FEndPoint.Address + ':' + FEndPoint.Port)  else    WriteLn('Bind Error: ' + FSocket.LastErrorDesc);   FSocket.Listen;  while not FTerminated do  begin    if FSocket.CanRead(100) then    begin      WriteLn('CanRead');      ClientSocket := FSocket.Accept;      _cli := TUDPBlockSocket.Create;      _cli.Socket := ClientSocket;      _cli.GetSins;      WriteLn(_cli.RecvPacket(5000));      _cli.SendString('Hello There');       if FSocket.LastError = 0 then      WriteLn('Client Accepted!')      else        WriteLn('Unable to Accept Client');    end;    // Relinquish the CPU    sleep(10);  end; end; ---------------------------------------------------------------------- Remy Lebeau · 2016-11-02 19:07 Quote from: gregv on November 02, 2016, 07:24:44 pmI'm trying to change some of the Synapse examples from being a TCP implementation to being a UDP implementation.  It doesn't seem to be working. You are trying to adapt an example for a connection-oriented transport into a connection-less transport.  There are no connections in UDP, so don't call Accept() at all.  You should be reading/writing using the listening socket itself. Quote from: gregv on November 02, 2016, 07:24:44 pmWhat I expect to have happen is that a client connects and then "hello there" is transmitted back.  Right now, it just gets stuck in the loop and "CanRead" is displayed over and over.  No message is sent to the client. Try something more like this instead: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TUDPListener.Execute;var  _svr : TUDPBlockSocket;begin  _svr := TUDPBlockSocket.Create;  try    _svr.CreateSocket;    _svr.SetLinger(True, 10000);    _svr.Bind(FEndPoint.Address, FEndPoint.Port);    if _svr.LastError <> 0 then    begin      WriteLn('Bind Error: ' + _svr.LastErrorDesc);      Exit;    end;    WriteLn('Bound on ' + FEndPoint.Address + ':' + FEndPoint.Port)    _svr.Listen;    if _svr.LastError <> 0 then    begin      WriteLn('Listen Error: ' + _svr.LastErrorDesc);      Exit;    end;    while not FTerminated do    begin      if _svr.CanRead(100) then      begin        WriteLn('CanRead');        WriteLn(_svr.RecvPacket(5000));        _svr.SendString('Hello There');      end;      // Relinquish the CPU      Sleep(10);    end;  finally    _svr.Free;  end;end;  That being said, if you are interested in a multi-threaded UDP server, consider using Indy instead of Synapse.  Indy's TIdUDPServer component is multi-threaded, and has an OnUDPRead event when packets arrive. ---------------------------------------------------------------------- gregv · 2016-11-02 19:49 Well that is an embarrassing mistake.  Thank you though. My goal is to create a somewhat asynchronous environment so a server doesn't need to respond immediately if there is long running task.  Is there a way to store connection information in synapse so that a response can be sent to the client without the client needing to be listening? I've already built some aspects in synapse so i don't really want to switch over unless I absolutely need to. ---------------------------------------------------------------------- Remy Lebeau · 2016-11-03 00:15 Quote from: gregv on November 02, 2016, 08:49:38 pmMy goal is to create a somewhat asynchronous environment so a server doesn't need to respond immediately if there is long running task.  Is there a way to store connection information in synapse so that a response can be sent to the client without the client needing to be listening? Obviously, the client has to be listening in order to receive the server's response. I think what you are looking for is Synapse's RemoteSin property.  RecvPacket() updates RemoteSin with the address of the sender.  SendString() and other send functions send to the current RemoteSin.  So, you can save the value of RemoteSin somewhere, and then use SetRemoteSin() to update the RemoteSin property before sending something. ---------------------------------------------------------------------- gregv · 2016-11-03 17:02 Ok, I guess the question then becomes how to manage the clients.  Right now I create a hashkey comprising the remote IP and the remote Port.  If a client sends a message with a different remote port, the key would obviously change.  So, there needs to be a better way to deal with which client is which.  Especially if a client disconnects since a key might be stuck in a list somewhere. I'll be honest, this "communication subsystem" (aka UDP wrapper) is far more complex than it needs to be and this is not how I would have preferred to complete this.  Unfortunately, requirements are requirements I suppose :| ---------------------------------------------------------------------- Remy Lebeau · 2016-11-03 17:36 Quote from: gregv on November 03, 2016, 06:02:42 pmOk, I guess the question then becomes how to manage the clients.  Right now I create a hashkey comprising the remote IP and the remote Port.  If a client sends a message with a different remote port, the key would obviously change.  So, there needs to be a better way to deal with which client is which.  Especially if a client disconnects since a key might be stuck in a list somewhere. Again, UDP is connection-less.  There is no connection to disconnect. You should define a message that the client sends as its last message when it doesn't want to talk to the server anymore.  Then you would be able to remove that client's hash from the list immediately.  But you should also use a timeout to remove the hash from the list if no messages are received from the client after a period of time, in case that last message is not sent or gets lost in transit (UDP does not guarantee message delivery). Since UDP is connection-less, there has to be periodic data exchange between client and server to ensure their communication session remains alive.  You should define another message that the client has to send to the server periodically, and that the server has to respond to, to let the server know the client still wants to keep communicating with the server.  When the server receives that message (or any message), it can reset that client's idle timeout. ---------------------------------------------------------------------- gregv · 2016-11-03 19:58 Right, I think that is going to be an ongoing problem.  What is the term that you would use for UDP communication? Right.  I guess I had thought that a heartbeat message would be necessary, but struggled with how best to implement it.  Perhaps things would be less complex if I could remember that UDP doesn't have connections. Also, I'm publishing code for this system on my GitHub account, not that I believe anyone will find it useful: https://github.com/therealgregvernon/pascal-communicator ---------------------------------------------------------------------- gregv · 2016-11-14 07:54 As part of my broader project, I was hoping to do some file transfers over TCP.  I can't seem to get data to transfer, though.  I have this code, which executes inside a TSimpleThread execute procedure: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TFileTransfer.Run(Thread: TSimpleThread);var  Socket: TTCPBlockSocket;  Packet: String;  LastPacketRecv: TDateTime;  M: TMessageRecord;begin  M := Default(TMessageRecord);  try    Socket := TTCPBlockSocket.Create;    Socket.Bind(FBind.Address, FBind.Port);    Socket.SetLinger(True, 1000);    Socket.Listen;     LastPacketRecv := Now;    while not Thread.Terminated do    begin      if Socket.CanRead(60000) then      begin        Packet := Socket.RecvPacket(10000);        WriteLn(Socket.LastErrorDesc);      WriteLn(Packet);        LastPacketRecv := Now;      end;       if MinutesBetween(Now, LastPacketRecv) > 2 then      begin        WriteLn('Time Out Problem');      end;       // Minimal sleep      Sleep(50);    end;  finally    Socket.Free;  end;end; After connection, the "CanRead()" condition is triggered as soon as a client connects and the only output data is "Socket is not connected" over and over - even after data is sent from the client.  What else needs to happen to use TCP correctly? Just an aside, but most examples are multi-threaded, where TSockets are passed between threads.  This socket is dedicated to a single client, so that is unnecessary. ---------------------------------------------------------------------- gregv · 2016-11-14 19:16 This code works, but requires a reconnection after each string in order to function. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  ServerSocket, ClientSocket: TTCPBlockSocket;  SocketHandle: TSocket;   Packet: String;  LastPacketRecv: TDateTime;  M: TMessageRecord;begin  M := Default(TMessageRecord);  try    ServerSocket := TTCPBlockSocket.Create;    ServerSocket.Bind(FBind.Address, FBind.Port);    ServerSocket.SetLinger(True, 1000);    ServerSocket.Listen;     LastPacketRecv := Now;    while not Thread.Terminated do    begin      if ServerSocket.CanRead(100) then    begin        SocketHandle := ServerSocket.Accept;        try        ClientSocket := TTCPBlockSocket.Create;          ClientSocket.Socket := SocketHandle;          ClientSocket.GetSins;          WriteLn(ClientSocket.RecvPacket(2000));           LastPacketRecv:=Now;        finally          ClientSocket.Free;      end;      end;       if MinutesBetween(Now, LastPacketRecv) > 2 then    begin        WriteLn('Time Out Problem');      end;       // Minimal sleep      Sleep(50);    end;  finally    ServerSocket.Free;  end;end; ---------------------------------------------------------------------- Remy Lebeau · 2016-11-14 22:01 Quote from: gregv on November 14, 2016, 08:54:36 amAfter connection, the "CanRead()" condition is triggered as soon as a client connects and the only output data is "Socket is not connected" over and over - even after data is sent from the client.  What else needs to happen to use TCP correctly? TCP is connection-oriented.  You are creating a TCP listening socket.  Calling CanRead() on such a socket will tell you if a client is waiting to be accepted.  You have to call the listening socket's Accept() method to accept the connection.  That will return a new TSocket, which you can then use to exchange data with the client until it disconnects. Quote from: gregv on November 14, 2016, 08:16:03 pmThis code works, but requires a reconnection after each string in order to function. You are disconnecting the client after calling RecvPacket() one time.  TCP is a byte stream, it has no concept of messages/packets.  RecvPacket() will just give you whatever raw data is available at that moment.  You have to handle message/packet boundaries in your own code.  You need to loop your reading until you have received all of the data you are expecting, THEN disconnect if that is what you want to do (or leave the connection open so the client can send other things to you). Your client and server need to coordinate with each other so your server knows what kind of data is being sent and to know when that data is complete.  It could be as simple as having the client disconnect when finished, but the server won't know if it had received everything the client had sent.  It would be better to have the client send the size of the data before sending the data itself, and then the server can read that size and then read the specified number of bytes.  If the client disconnects before the server has read all of the bytes, the server will know the transfer has failed. ---------------------------------------------------------------------- gregv · 2016-11-14 22:35 QuoteYou are disconnecting the client after calling RecvPacket() one time. Right, that is what I figured is happening.  Only one message type will be transferred between the client/server so I know how much data is going to be in the buffer. I can see how creating both the client and server TCPBlockSocket objects at the same time, and freeing them at the same time, would allow this to work.  But since there should only be one client connecting, is there a better way of doing it? (Though I'll admit after typing that out, I'm realizing that there maybe a scenario where I would need to allow for 2 clients to connect.  I.e., one client is not authorized to connect, but connects anyway.) ---------------------------------------------------------------------- Remy Lebeau · 2016-11-15 19:33 Quote from: gregv on November 14, 2016, 11:35:59 pmOnly one message type will be transferred between the client/server so I know how much data is going to be in the buffer. UDP is message-oriented, so each read will receive a whole message, not a partial message.  As long as your buffer is large enough to hold the largest possible message, then you can call RecvPacket() only one time. But TCP is stream-oriented, it doesn't know about messages, so a given read operation simply receives raw bytes, and can return as few as 1 byte and at most as many bytes as your buffer can hold.  You can't rely on a single read operation giving you a complete message.  It is your responsibility to keep track of what you have already received and know when you need to keep reading more bytes to complete the message.  It may take several reads to complete one message.  So your message protocol needs to be designed in such a way to allow the server to know how many bytes to expect from the client so it knows when to stop reading. Quote from: gregv on November 14, 2016, 11:35:59 pmI can see how creating both the client and server TCPBlockSocket objects at the same time, and freeing them at the same time, would allow this to work. No, it wouldn't.  Not reliably, anyway.  Which is why you have to design your protocol and read/write code to ensure reliability. Quote from: gregv on November 14, 2016, 11:35:59 pmBut since there should only be one client connecting, is there a better way of doing it? I already answered that earlier. Quote from: gregv on November 14, 2016, 11:35:59 pm(Though I'll admit after typing that out, I'm realizing that there maybe a scenario where I would need to allow for 2 clients to connect.  I.e., one client is not authorized to connect, but connects anyway.) Well, that certainly complicates things.  If you Accept() the wrong client, you will have to drop that connection and call Accept() again to keep waiting for the right client.  But if the right client attempted to connect while the wrong client was already connected, you will likely miss your opportunity to accept the right client, unless it tries to reconnect at a later time after failing to connect. This also means you need some kind of identification/authentication logic in your protocol so you can differentiate right clients from wrong clients. ---------------------------------------------------------------------- gregv · 2016-11-15 21:13 Your answer on how to do things better must have gone completely over my head because I simply don't see where you addressed my question on creating/freeing sockets at the same time. QuoteBut TCP is stream-oriented, it doesn't know about messages, so a given read operation simply receives raw bytes, and can return as few as 1 byte and at most as many bytes as your buffer can hold. Yes, but only one type of "message" (just as a generic term, not a datagram, if you have a term better than message let me know) is going to be transferred, and the size of that "message" is fixed.  Since it is a fixed size, the boundaries should be fairly well known. Yes, I know, I output the data immediately in my earlier code without checking the size - but I also interested in seeing what, if anything, was being received and the more basic behavior of the class. QuoteWell, that certainly complicates things.  If you Accept() the wrong client, you will have to drop that connection and call Accept() again to keep waiting for the right client.  But if the right client attempted to connect while the wrong client was already connected, you will likely miss your opportunity to accept the right client, unless it tries to reconnect at a later time after failing to connect. This also means you need some kind of identification/authentication logic in your protocol so you can differentiate right clients from wrong clients. Yes, the authentication hasn't been handled yet and I'm not quite sure how I would do it.  It may be as simple as checking the IP/port of the connecting client - though I don't know how happy I would be with that. The big solution here might be to not create a data channel unique to an individual client... ---------------------------------------------------------------------- Remy Lebeau · 2016-11-15 23:55 Quote from: gregv on November 15, 2016, 10:13:07 pmYour answer on how to do things better must have gone completely over my head because I simply don't see where you addressed my question on creating/freeing sockets at the same time. Creating/freeing sockets at the same time does not fix your issue.  It only causes a potential race condition.  And if you have multiple clients connecting, then the server needs a loop to handle the individual accepts. Quote from: gregv on November 15, 2016, 10:13:07 pmYes, but only one type of "message" (just as a generic term, not a datagram, if you have a term better than message let me know) is going to be transferred, and the size of that "message" is fixed.  Since it is a fixed size, the boundaries should be fairly well known. Well, you didn't describe your message layout, so I had to assume.  Most protocols use some amount of variable-length data. Ok, then you would simply read from the socket until you have received however many bytes constitute that fixed-length message, and then stop reading.  UDP guarantees that in a single read operation, but TCP does not, so you need a reading loop.  RecvPacket() and RecvBuffer() will not do the looping for you, but RecvBufferEx(), RecvBufferStr(), and RecvStreamSize() do loop internally, so use one of them instead of RecvPacket(). Quote from: gregv on November 15, 2016, 10:13:07 pmYes, the authentication hasn't been handled yet and I'm not quite sure how I would do it. By having the client send more messages to the server containing client info/credentials for the server to validate. Quote from: gregv on November 15, 2016, 10:13:07 pmIt may be as simple as checking the IP/port of the connecting client - though I don't know how happy I would be with that. That only works in an isolated environment when you have full control over the network and its clients.  If you don't know who is going to be connecting to your server, you will have to authenticate the clients that try to connect. Quote from: gregv on November 15, 2016, 10:13:07 pmThe big solution here might be to not create a data channel unique to an individual client... That is a requirement for TCP connections. ---------------------------------------------------------------------- gregv · 2016-11-16 00:31 QuoteQuoteThe big solution here might be to not create a data channel unique to an individual client...That is a requirement for TCP connections. The way my system is setup, control messages are sent over udp with data transfer over tcp (so that the data remains ordered).  When data needs to be transferred, the server would start listening on a "new" port, and send the port number to the client via a control message.  That is what I mean by a unique data channel - probably not the best terminology. The alternative I was thinking of is having the system listening on a single port and synchronizing threads so the 'control' thread knows the data transfer is progressing. QuoteMost protocols use some amount of variable-length data. The control messages are variable length.  Using authentication for data transfer would also require being able to handle variable length messages.  Different from what I'm doing currently, but something that is easily changed, hopefully. In regards to the race condition, here is how my code currently works (not real code): Code: [Select]Server := TTCPCreateSocket.Create; Client := TTCPBLocKSocket.Create; try while SomeCondition do begin   if Server.CanRead(100) then   begin     SocketHandle := Server.Accept;     Client.Socket := SocketHandle;     Client.GetSins;     // Do something with the client   end; end; finally   Server.Free;   Client.Free; end; Obviously, quite a bit of room for improvement... ---------------------------------------------------------------------- Remy Lebeau · 2016-11-16 01:07 Quote from: gregv on November 16, 2016, 01:31:50 amThe way my system is setup, control messages are sent over udp with data transfer over tcp (so that the data remains ordered). Are you aware of Reliable UDP?  Which brings some of TCP's guarantees into UDP. Quote from: gregv on November 16, 2016, 01:31:50 amWhen data needs to be transferred, the server would start listening on a "new" port, and send the port number to the client via a control message. In other words, you are basically recreating what the FTP protocol does, but using UDP instead of TCP for the control channel. Are you aware of Trivial FTP?  It is a UDP-based file transfer protocol. Quote from: gregv on November 16, 2016, 01:31:50 amThat is what I mean by a unique data channel - probably not the best terminology. I knew exactly what you meant. Quote from: gregv on November 16, 2016, 01:31:50 amThe alternative I was thinking of is having the system listening on a single port and synchronizing threads so the 'control' thread knows the data transfer is progressing. Well, you should know that anyway, regardless of how you manage the ports. Quote from: gregv on November 16, 2016, 01:31:50 amThe control messages are variable length. Not really relevant, since UDP handles that for you. Quote from: gregv on November 16, 2016, 01:31:50 amUsing authentication for data transfer would also require being able to handle variable length messages. It can.  And if you are sending variable-length data over TCP, you have to express that data length in the message data. ---------------------------------------------------------------------- gregv · 2016-11-16 01:31 Quote from: Remy Lebeau on November 16, 2016, 02:07:09 amAre you aware of Reliable UDP?  Which brings some of TCP's guarantees into UDP. In other words, you are basically recreating what the FTP protocol does, but using UDP instead of TCP for the control channel. Are you aware of Trivial FTP?  It is a UDP-based file transfer protocol. Yes.  I am aware of all of those.  This isn't for a production, instead this is to learn about network programming.  If this was for production, I would likely be using established standards. Quote from: Remy Lebeau on November 16, 2016, 02:07:09 amWell, you should know that anyway, regardless of how you manage the ports. Yes.  How I handle synchronization might depend on how the system is setup to transfer data. Quote from: Remy Lebeau on November 16, 2016, 02:07:09 amNot really relevant, since UDP handles that for you. I was acknowledging that my protocol does in fact use variable length messages. Quote from: Remy Lebeau on November 16, 2016, 02:07:09 amIt can.  And if you are sending variable-length data over TCP, you have to express that data length in the message data. Yes.  I would need to change my code a bit to allow this, but that is trivial.