Search code examples
c#.netnetwork-programmingtcptcpclient

TCP sender machine receiver-window-size shrinking to 0 on windows machine after sending tiny amounts of data for a few minutes


I'm writing an app that works on a windows laptop that sends TCP bytes in a loop as a "homemade keep-alive" message to keep a connection active (the server machine will disconnect after 15 seconds of no TCP data received). The "server machine" will send the laptop small chunks of data (about .5K bytes/second) as long as a connection is alive (according to the server documentation, this is ideally this is an "echo" packet, but I was unable to find how this is accomplished in .NET). My problem is that when I view this data in Wireshark I can see good network activity, then, after a few minutes, the "win" (receive window size available on the laptop) shrinks from 65K to 0 in increments of about 240 bytes each packet. Why is this happening and how can I prevent it? I can't seem to get the "keep-alive" flags in .Net to work, so this was supposed to be my workaround. I do not see any missed ACK messages, and my data rate is about 2Kb/sec, so I don't understand why the laptop window size is dropping. I definitely assume there is a misconception on my part about TCP and or windows/.NET use of TCPsockets since I have no experience with TCP (I've always used UDP).

                    TcpClient client = new TcpClient(iPEndpoint);
                    //Socket s = client.Client; none of these flags actually work on the keep alive feature
                    //s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
                    //s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 10);
                    //s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 10);
    
                    // Translate the passed message into ASCII and store it as a Byte array.
                    // Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
    
                    IPAddress ipAdd = IPAddress.Parse("192.168.1.10"); 
                    IPEndPoint ipEndPoin = new IPEndPoint(ipAdd, 13000);
                    client.Connect(ipEndPoin);
    
                    NetworkStream stream = client.GetStream();
    
                    // Send the message to the connected TcpServer.
                    bool finished = false;
                    while (!finished)
                    {
                        try
                        {
                            stream.Write(data, 0, data.Length);
                            Thread.Sleep(5000);
                        }
                        catch (System.IO.IOException ioe)
                        {
                            if (ioe.InnerException is System.Net.Sockets.SocketException)
                            {
                                client.Dispose();
                                client = new TcpClient(iPEndpoint);
                                client.Connect(ipEndPoin); 
                                stream = client.GetStream(); 
                                Console.Write("reconnected");
                                // this imediately fails again after exiting this catch to go back to the while loop because send window is still 0
                            }
                        }
                    }

Solution

  • You should really be familiar with RFC 793, Transmission Control Protocol, which is the definition of TCP. It explains the wondow and how it is used for flow control:

    Flow Control:

    TCP provides a means for the receiver to govern the amount of data sent by the sender. This is achieved by returning a "window" with every ACK indicating a range of acceptable sequence numbers beyond the last segment successfully received. The window indicates an allowed number of octets that the sender may transmit before receiving further permission.

    -and-

    To govern the flow of data between TCPs, a flow control mechanism is employed. The receiving TCP reports a "window" to the sending TCP. This window specifies the number of octets, starting with the acknowledgment number, that the receiving TCP is currently prepared to receive.

    -and-

    Window: 16 bits

    The number of data octets beginning with the one indicated in the acknowledgment field which the sender of this segment is willing to accept.

    The window size is dictated by the receiver of the data in its ACK segments where it acknowledges receipt of the data. If your laptop receive window shrinks to 0, it is setting the window to that because it has no more space to receive, and it needs time to process and free up space in the receive buffer. When it has more space, it will send an ACK segment with a larger window.

    Segment Receive  Test
    Length  Window
    ------- -------  -------------------------------------------
    
       0       0     SEG.SEQ = RCV.NXT
    
       0      >0     RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
    
      >0       0     not acceptable
    
      >0      >0     RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
                  or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND
    

    Note that when the receive window is zero no segments should be acceptable except ACK segments. Thus, it is be possible for a TCP to maintain a zero receive window while transmitting data and receiving ACKs. However, even when the receive window is zero, a TCP must process the RST and URG fields of all incoming segments.