简体   繁体   中英

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.

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

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.

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);
    }
}

UPD:

I've tested this bug on several computers and nothing crashed. Seems like it is a local bug on my computer.

ENDOFUPD

So, what I've found about reproducing this bug. @Despertar - your code works well. But it isn't reproduce conditions of this bug. On client you need to send data and quit after it. And in your code many clients are sending data and after all application is closing.

This is how I'm testing this on my computer: I have server ( just accepting connection and print incoming data ), client ( just sends data once end exits ) and running utility ( runs client exe several times ).

So, I starts server, copies running utility to the clients folder and runs it. Running ulility starts 150 clients connecting to server and 5-10 of them dies ( I see error in the server console ). And uncommenting Thread.Sleep() on client works well, no errors.

Can anyone try to reproduce this version of code?

Client code:

private static void Main(string[] args)
{
try
{
    using (TcpClient client = new TcpClient(ip, port))
    {
        using (NetworkStream networkStream = client.GetStream())
        {
            using (StreamWriter writer = new StreamWriter(networkStream))
            {
                writer.WriteLine("# hello, tcp world #");
                writer.Flush();
            }
            networkStream.Flush();
            networkStream.Close();
        }
        client.Close();
        //Thread.Sleep(10);
     }
     }
     catch (Exception ex)
     {
         Console.WriteLine(ex.Message);
     }
}

Code, running client several times ( compile it in exe file and put near client's exe - this code will run many clients one by one ):

static void Main(string[] args)
{
    string path = "YOU_CLIENT_PROJECT_NAME.exe";
    for (int i = 0; i < 150; i++ )
    {
        Console.WriteLine(i);
        Process.Start(path);
        Thread.Sleep(50);
    }
    Console.WriteLine("Done");
    Console.ReadLine();
}

( don't forget to change path to corrent exe filename )

Server code:

class Program
{

    static int port = 26140;
    static AutoResetEvent waitHandle = new AutoResetEvent(false);

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

    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 =>
                    {
                        try
                        {
                            TcpClient client = (TcpClient) paramClient;
                            byte[] buffer = new byte[32768];
                            MemoryStream memory = new MemoryStream();
                            using (NetworkStream networkStream = client.GetStream())
                            {
                                networkStream.ReadTimeout = 2000;
                                do
                                {
                                    int read = networkStream.Read(buffer, 0, buffer.Length);
                                    memory.Write(buffer, 0, read);
                                } while (networkStream.DataAvailable);
                                string text = Encoding.UTF8.GetString(memory.ToArray());
                            }
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("ERROR: " + e.Message);
                        }
                    }, theClient);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }, TaskCreationOptions.LongRunning);
    }
}

I've tried code, reproducing this bug on several computers. No one crashes. Seems like it's my local computer bug.

Thanks for everybody for trying to help me.

Anyway, it's so strange. If I'll found out why this bug exists on my computer, I'll write about it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM