views:

57

answers:

2

I've this TcpClient code which works fine. It connects to perl server on linux system and receives anything that server sents to it. Works nicely.

 public static void Main() {
         foreach (ProtocolConnection tcpConnection in TcpConnectionsList) {
                ProtocolConnection connection = tcpConnection;
                ThreadPool.QueueUserWorkItem(_ => {
                                                 ThreadTcpClient(connection);
                                                 ManualResetEventTcp.Set();
                                             });
         }
         ... Some code...      
 }

 public static void TcpConnect(ProtocolConnection varConnection) {
        int retryCountSeconds = varConnection.RetryEverySeconds*Program.MilisecondsMultiplier;
        int count = 0;
        while (true) {

            try {
                using (var client = new TcpClient(varConnection.IpAddress.ToString(), varConnection.Port) { NoDelay = true })
                using (var stream = client.GetStream()) {
                    var data = new Byte[256];
                    while (!Program.PrepareExit) {
                        Int32 bytes = stream.Read(data, 0, data.Length);
                        string varReadData = Encoding.ASCII.GetString(data, 0, bytes).Trim();
                        if (varReadData != "" && varReadData != "PONG") {
                            VerificationQueue.EnqueueData(varReadData);
                            Logging.AddToLog("[TCP][" + varConnection.Name + "][DATA ARRIVED]" + varReadData);
                        } else {
                            Logging.AddToLog("[TCP]" + varReadData);
                        }
                    }
                }
            } catch (Exception e) {
                if (e.ToString().Contains("No connection could be made because the target machine actively refused it")) {
                    Logging.AddToLog("[TCP][ERROR] Can't connect to server (" + varConnection.Name + ") " + varConnection.IpAddress + ":" + varConnection.Port );
                } else {
                    Logging.AddToLog(e.ToString());
                }

            }
            DateTime startTimeFunction = DateTime.Now;
            do {
                Thread.Sleep(1000);
            } while (((DateTime.Now - startTimeFunction).TotalSeconds < retryCountSeconds));
        }
    }

However in certain conditions I'm having some problems with it:

  1. My work connection often drops connection after some idle time so I've implemented in server so when it receives PING it responds with PONG. I can send PING with UDP to server and it will respond with PONG on tcp but i would prefer built-in way into tcp client so it does send PING every 60 seconds or so. Even if UDP solution would be acceptable I have no timeout on string varReadData = Encoding.ASCII.GetString(data, 0, bytes).Trim(); so when PONG doesn't arrive my client doesn't even notice it anyway. It just keeps waiting ... which brings me to..
  2. My other problem is that at some point string varReadData = Encoding.ASCII.GetString(data, 0, bytes).Trim(); this is waiting for data all the time. When server crashes or disconnects my client i don't even notice that. I would like server to have some kind of timeout or check if connection is active. If it's not active it should try to reconnect.

What would be simplest way to fix this TcpClient ? How do i implement both way communication making sure that if server drops my connections or my net gets disconnected client will notice it and reestablish connection ?

+1  A: 

It's not Encoding.ASCII.GetString(data, 0, bytes).Trim(); that blocks forever, it's the stream.Read() If you're reading, you can't easily distinguish between the server(or any NAT gateway inbetween) dropping your connection , and the case where the server simply doesn't have anything to send you. Atleast in the case where the TCP FIN/RST packets doesn't reach your client in case of failure, or a NAT gateway silently dropping your connection.

What you can do;

  • Set a Send/ReceiveTimeout , and ping the server if a timeout occurs, or implement your own heartbeat messages over your TCP connection. Reestablish or take other actions if you don't receive a heartbeat within a reasonable time.
  • Set the TCP keepalive option, and rely on that to tell you if the server is gone. See code here.

The last point will tell you if the tcp connection fails, it won't tell you if the server has somewhat failed - e.g. if you CTRL+Z your perl server, it'll just sit there not doing anything as the tcp window closes , so you might need to implement your own heatbeat messges to cover such a case too if you need to.

nos
Well that's what I wanted ... to have my own heartbeat over TCP just I didn't knew how to make it. Will test how ReceiveTimeout works and see if it works as i think it does. If it makes stream.Read() to let go when no data is received after lets say 50 seconds then i could add "Send message PING" later on in the code as the server already is waiting for that. If it receives PING it sends PONG so i will know whether it's working or not.
MadBoy
The only problem with `ReceiveTimeout` is that it throws exception and drops connection so i would have to continuously make new ones to be able to send heartbeat.
MadBoy
+1  A: 

You should get rid of the UDP heartbeat attempt and put in a real TCP heartbeat. "Pinging" the server using UDP is almost meaningless.

Your protocol is also missing message framing.

Read both of those linked articles carefully (especially message framing). The protocol you're currently using does need serious revision.

Stephen Cleary
By pinging the server i meant I have TCP/UDP server in perl. It listens on both UDP and TCP. So i can send PING values to UDP and TCP server seeeing PING on UDP could send back TCP with PONG which would keep connection running. However you are right it's not the best implementation of it as it is now.
MadBoy