Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TcpClient.Close() works only with Thread.Sleep()

I have simple server that gets string from client and prints it on screen. I also have simple client, sending data and closing:

static void Main()
{
        var client = new TcpClient("localhost", 26140);

        var stream = client.GetStream();
        Byte[] data = System.Text.Encoding.UTF8.GetBytes("CALC qwer"); 
        stream.Write(data, 0, data.Length);
        stream.Close();
        client.Close();
        //Thread.Sleep(100);
}

And with uncommented string 'Thread.Sleep(100)' it works ok. But when commenting, sometimes ( 1 of 5-10 runs ) client doesn't send the string. Watching wireshark and netstat I've noticed that client sends SYN,ACK package, establishes connection and exits without sending anything and without closing the socket.

Could anyone explain this behaivor? Why sleep helps? What am I doing wrong?

UPD:

With this sample code adding flush() before closing really works, thanks Fox32.

But after it I returned to my initial code:

var client = new TcpClient("localhost", 26140);
client.NoDelay = true;
var stream = client.GetStream();
var writer = new StreamWriter(stream);
writer.WriteLine("CALC qwer");
writer.Flush();
stream.Flush();
stream.Close();
client.Close();

And it isn't working, even with NoDelay. It's bad - using StreamWriter over network stream?

UPD:

Here is server code:

static void Main(string[] args)
    {
        (new Server(26140)).Run();
    }

In Server class:

public void Run()
    {
        var listener = new TcpListener(IPAddress.Any, port);
        listener.Start();
        while (true)
        {
            try
            {
                var client = listener.AcceptTcpClient();
                Console.WriteLine("Client accepted: " + client.Client.RemoteEndPoint);
                var stream = client.GetStream();
                stream.ReadTimeout = 2000;
                byte[] buffer = new byte[1000];
                stream.Read(buffer, 0, 1000);
                var s = Encoding.UTF8.GetString(buffer);
                Console.WriteLine(s);
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR! " + ex.Message);
            }
        }
    }

UPD:

Adding even Sleep(1) makes crashes happen in 1 of 30-50 clients running at the same time. And adding Sleep(10) seems to be solving it totally, I can't catch any crash. Don't understand, why socket needs this several milliseconds to close correctly.

like image 254
VorobeY1326 Avatar asked Mar 10 '13 17:03

VorobeY1326


People also ask

How do I close TcpClient?

You have to close the stream before closing the connection: tcpClient. GetStream(). Close(); tcpClient.

What is TcpClient?

The TcpClient class provides simple methods for connecting, sending, and receiving stream data over a network in synchronous blocking mode. In order for TcpClient to connect and exchange data, a TcpListener or Socket created with the TCP ProtocolType must be listening for incoming connection requests.


3 Answers

The TcpClient is using the Nagle's algorithm and waits for more data before sending it over the wire. If you close the socket to fast, no data is trasmitted.

You have multiple ways to solve this problem:

The NetworkStream has a Flush method for flushing the stream content (I'm not sure if this method does anything from the comment on MSDN)

Disable Nagle's algorithm: Set NoDelay of the TcpCLient to true.

The last option is to set the LingerState of the TcpClient. The Close method documentation states, that the LingerState is used while calling Close

like image 198
Fox32 Avatar answered Oct 17 '22 12:10

Fox32


In almost all cases you are supposed to call Shutdown on a Socket or TcpClient before disposing it. Disposing rudely kills the connection.

Your code basically contains a race condition with the TCP stack.

Setting NoDelay is also a fix for this but hurts performance. Calling Flush IMHO still results an an disorderly shutdown. Don't do it because they are just hacks which paint over the problem by hiding the symptoms. Call Shutdown.

I want to stress that Shutdown being called on the Socket is the only valid solution that I know of. Even Flush just forces the data onto the network. It can still be lost due to a network hickup. It will not be retransmitted after Close has been called because Close is a rude kill on the socket.

Unfortunately TcpClient has a bug which forces you to go to the underlying Socket to shut it down:

tcpClient.Client.Shutdown();
tcpClient.Close();

According to Reflector, if you have ever accessed GetStream this problem arises and Close does not close the underlying socket. In my estimation this bug was produced because the developer did not really know about the importance of Shutdown. Few people know and many apps are buggy because of it. A related question.

like image 25
usr Avatar answered Oct 17 '22 13:10

usr


In your server side code you are only calling Read() once, but you can't assume the data will be available when you call read. You have to continue reading in a loop until no more data is available. See the full example below.

I have tried to reproduce your issue with the minimal amount of code and was not able to. The server prints out the clients message everytime. No special settings such as NoDelay and no explicit Close() or Flush(), just Using statements which ensures all resources are properly disposed.

class Program
{
    static int port = 123;
    static string ip = "1.1.1.1";
    static AutoResetEvent waitHandle = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        StartServer();
        waitHandle.WaitOne();

        for (int x=0; x<1000; x++)
        {
            StartClient(x);
        }

        Console.WriteLine("Done starting clients");
        Console.ReadLine();
    }

    static void StartClient(int count)
    {
        Task.Factory.StartNew((paramCount) =>
        {
            int myCount = (int)paramCount;

            using (TcpClient client = new TcpClient(ip, port))
            {
                using (NetworkStream networkStream = client.GetStream())
                {
                    using (StreamWriter writer = new StreamWriter(networkStream))
                    {
                        writer.WriteLine("hello, tcp world #" + myCount);
                    }
                }
            }
        }, count);
    }

    static void StartServer()
    {
        Task.Factory.StartNew(() => 
        {
            try
            {
                TcpListener listener = new TcpListener(port);
                listener.Start();

                Console.WriteLine("Listening...");
                waitHandle.Set();

                while (true)
                {
                    TcpClient theClient = listener.AcceptTcpClient();

                    Task.Factory.StartNew((paramClient) => {
                        TcpClient client = (TcpClient)paramClient;

                        byte[] buffer = new byte[32768];
                        MemoryStream memory = new MemoryStream();
                        using (NetworkStream networkStream = client.GetStream())
                        {
                            do
                            {
                                int read = networkStream.Read(buffer, 0, buffer.Length);
                                memory.Write(buffer, 0, read);
                            }
                            while (networkStream.DataAvailable);
                        }

                        string text = Encoding.UTF8.GetString(memory.ToArray());
                        Console.WriteLine("from client: " + text);
                    }, theClient);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }, TaskCreationOptions.LongRunning);
    }
}
like image 1
Despertar Avatar answered Oct 17 '22 12:10

Despertar