0

There are multiple posts that describe the performance benefit of keeping a TCP connection open, instead of closing and opening each time you need to read or write. For example:

Best practice: Keep TCP/IP connection open or close it after each transfer?

I'm communicating with an RPC based device that takes json commands. The example I have from the device vendor opens and closes a connection each time they send a command. This is what I currently do via TcpClient in a using statement, but I'd like to see if there's anyway I could improve upon what I've already done. In fact, I had attempted this when starting the project, but couldn't figure out how to do so, so closed each time out of frustration and necessity. My latest experiment using sockets because all posts indicate doing so as a necessity for lower level control:

public class Connection
{
    private Socket tcpSocket = null;
    public string IpAddress = "192.168.0.30";
    public int Port = 50002;

    public Connection(string ipAddress, int port)
    {
        this.IpAddress = ipAddress;
        this.Port = port;
    }

    public void Connect()
    {
        DnsEndPoint ipe = new DnsEndPoint(this.IpAddress, this.Port);
        Socket tempSocket =
            new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        tempSocket.Connect(ipe);
        if (tempSocket.Connected)
        {
            this.tcpSocket = tempSocket;
            this.tcpSocket.NoDelay = true;
            this.tcpSocket.
            //this.tcpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive,true);
            Console.WriteLine("Successfully connected.");
        }
        else
        {
            Console.WriteLine("Error.");
        }

    }

    public void Disconnect()
    {
        this.tcpSocket.Disconnect(true);
        this.tcpSocket.Dispose();
        Console.WriteLine("Successfuly closed.");
    }

    public string SendCommand()
    {
        string path = @"C:\Users\me\Desktop\request.json";
        string request = File.ReadAllText(path);
        Byte[] bytesSent = Encoding.UTF8.GetBytes(request);
        this.tcpSocket.Send(bytesSent);
        this.tcpSocket.Shutdown(SocketShutdown.Send);
        var respBytes = ReceiveAll();
        string s = System.Text.Encoding.UTF8.GetString(respBytes, 0, respBytes.Length);
        return s;
    }

    public byte[] ReceiveAll()
    {
        var buffer = new List<byte>();
        var currByte = new Byte[1];
        var byteCounter = this.tcpSocket.Receive(currByte, currByte.Length, SocketFlags.None);
        while (this.tcpSocket.Available > 0)
        {
            currByte = new Byte[1];
            byteCounter = this.tcpSocket.Receive(currByte, currByte.Length, SocketFlags.None);
            if (byteCounter.Equals(1))
            {
                buffer.Add(currByte[0]);
            }
        }

        return buffer.ToArray();
    }

}

Console app:

    static void Main(string[] args)
    {
        Connection s = new Connection();
        s.Connect();
        Console.WriteLine(s.SendCommand());
        Console.WriteLine(s.SendCommand());
        Thread.Sleep(5000);
        s.Disconnect();
        Console.ReadKey();
    }

This approach works once. The first time I call send command. It doesn't the second time (throws an exception), because I call socket.Shutdown() on Send in my SendCommand(). I do so because of this post:

TCPClient not receiving data

However, there doesn't seem to be a way to re-enable the ability to Send after calling Shutdown(). So now I just don't know if it's even possible to keep a tcp connection open if you have to both read and write. Moreover, I can't really find a useful example online. Does anyone know how to do so in .NET? Is this even possible?

hallibut
  • 136
  • 1
  • 12
  • Why do you think keeping the connection open is an improvement? Unless there is an actual need for this that you can point to, it is very likely not an improvement, and you'll likely be wasting a lot of your time for no good reason. There's also a good chance you'll run into problems in deployment you won't know how to fix quickly enough. Do you know whether the protocol in question is designed to be used like that? If not, you may be asking for trouble. – Bent Tranberg Nov 01 '21 at 19:56
  • Ultimately, the device I'm using will be incorporated in a manufacturing environment for the factory production of other devices. The speed in which devices can be made has a direct correlation with the economics of manufacturing, and these types of systems are online and running nonstop for long periods of time. So any type of performance boost (which I assume is execution speed in the case of opening and closing connections) is desirable. But I'm coming to realize, I probably just need to open and close a connection each time. – hallibut Nov 01 '21 at 20:05
  • It is extremely common that smaller companies release TCP based protocols that are hopelessly flawed, and has inherent performance problems. If this turns out to be the case here too, then keeping the connection open will not solve the problem. In such a case the solution is to ask the creator to fix the protocol. – Bent Tranberg Nov 01 '21 at 20:05
  • The normal problem with flawed protocols is that they rely on the client to open, send and close, or to open, receive and close. Since TCP is a stream and not packets, that won't work. You can miss data, or worse, you're slowed down. I won't explain why. At a minimum the client should open, send, receive, and close. Is your protocol doing this? If yes, great! Both sides must know how much is expected, and keep reading, maybe more than once, until ALL that is expected is received. How much to expect is usually given by a length field in a header, or by text markers (e.g. eol) in text protocols. – Bent Tranberg Nov 01 '21 at 20:32
  • I'm not an expert on TCP, despite having a lot of experience creating protocols and figuring out issues. If somebody sees a reason for correcting my explanation, please do so. – Bent Tranberg Nov 01 '21 at 20:34
  • There may be another option, since you're communicating with a device. Often UDP is an alternative to TCP in industrial devices. That means you don't have to worry at all about the intricacies of streams in TCP. It does require however that the message is no longer than the maximum packet size. This can be as much as around 9000 bytes on a local network where jumbo packets are allowed. UDP is also fast. – Bent Tranberg Nov 01 '21 at 20:46
  • I had investigated this previously using nmap to scan the device and see what protocols were available. I only saw TCP via nmap, and failed to connect/kept getting timeout exceptions when just trying to connect anyway. – hallibut Nov 01 '21 at 20:58
  • Take a look at the answers [here](https://stackoverflow.com/questions/5523565/socket-flush-by-temporarily-enabling-nodelay) if you have speed problems. Not sure what they're worth. There are plenty of other advice on SO too I'm sure. – Bent Tranberg Nov 01 '21 at 21:13

2 Answers2

2

If it is possible to keep the connection open for more messages depends on the application protocol. There is no way to enforce this if the protocol does not supports it. Thus, ask the vendor or look into the protocol specification (if it exists) for information if and how this is supported.

However, there doesn't seem to be a way to re-enable the ability to Send after calling Shutdown().

There is no way. TCP write shutdown means that one does not want to send any more information. It is impossible to take this back. If the protocol supports multiple message exchanges then it needs to have a different way to detect the end of a message than calling Shutdown.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • That makes sense about calling Shutdown. But I'm not sure I follow what you mean about the application protocol (but I will reach out to the vendor). From your response, I take it that TCP is a means of communication, but not necessarily the application protocol. The application protocol, would be how the device (server) handles incoming requests? Just trying to understand what to ask. – hallibut Nov 01 '21 at 19:42
  • 2
    @hallibut: Application protocols are something like HTTP, where each message (HTTP request, HTTP response) has a clearly defined syntax and a clearly defined end - which makes it possible to send multiple message using the same TCP stream since it is clear where one ends and the other starts. – Steffen Ullrich Nov 01 '21 at 20:14
  • 1
    Thanks for the clarification, I believe I understand now. The vendors api documentation does include expected json response from a user provided json request. But from further reading on framing protocols, I believe in order for the server to understand the end of the message I'm sending, it either needs to follow an expected length prefixing or delimiter. And I guess the reason, I must call shutdown is because it does neither, so the the server doesn't know when the outgoing message ends until the clients outgoing stream closes? – hallibut Nov 01 '21 at 20:55
  • The entire specification should contain a description of how the TCP server and client interacts with messages, and what the messages look like. A more complex protocol will have transactions. A simple transaction could be "What is your name?", "My name is Server1." A complex transaction could be a long list of back and forth messages to e.g. transfer your list of mail messages from the MS Exchange server to your Outlook client. A dirt simple protocol for a device might not have anything else than one transaction, which is "Give me your data." and "{some json...}" and "Thanks, now I close". – Bent Tranberg Nov 01 '21 at 20:57
  • Is there nothing in the example that shows what the possible missing part(s) might be? Often the json is prefixed by some non-json length expressed as text. Worst case, there's some binary header. Also, line endings can be in various forms. – Bent Tranberg Nov 01 '21 at 21:00
  • But I think I may be wrong. You've already got it working, but it's slow? – Bent Tranberg Nov 01 '21 at 21:04
2

TCP/IP is a streaming protocol. To pass messages with it you need a “framing protocol” so peers can determine when a message is finished.

One simple way to signal the end of a message is to close the socket when you’ve sent the last byte. But this prevents socket reuse. See the evolution of HTTP for an example of this.

If this is what your device does, there’s no way to reuse a socket.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67