How do you close TCP connections gracefully withou

2019-05-30 16:32发布

问题:

What is the pattern and/or how would you change the following code so that a connection can be closed gracefully from either server or client, without needing exception handling.

  1. TcpClient vs. Socket: I'm not tied to using the TcpClient client class. I wrote the following code to try to demonstrate the simplest case possible. I had been using a Socket & SocketAsyncEventArgs but things were getting too complicated dealing with this issue.

  2. Blocking vs. Asynchronous: Because of the blocking calls this may be difficult or impossible. If so, that's fine, but how do you solve it in the asynchronous case?

  3. Quit Token?: I've experimented with sending some kind of "Quit" token to other side so that it knows to shutdown, but haven't gotten it working and I wanted to present a minimal example here.

  4. Exception Handling Needed Anyways: I know exception handling will be necessary in the real application as network connections etc will fail. But can not the expected case of a graceful shutdown be handled without exceptions??

Edit: I moved original and answer code to gist.

Original failing Example: Moved to here: https://gist.github.com/958955#file_original.cs

Current Working Answer: https://gist.github.com/958955#file_answer.cs

class Channel
{
    private readonly TcpClient client;
    private readonly NetworkStream stream;
    private readonly string name;
    private readonly ManualResetEvent quit = new ManualResetEvent(false);

    public Channel(string name, TcpClient client)
    {
        this.name = name;
        this.client = client;
        stream = client.GetStream();
    }

    public void Run()
    {
        Console.WriteLine(name + ": connected");
        byte[] buffer = new byte[client.Client.ReceiveBufferSize];
        stream.BeginRead(buffer, 0, buffer.Length, this.Read, buffer);

        var writer = new StreamWriter(stream, Encoding.ASCII);

        while (true)
        {
            var line = Console.ReadLine();
            if (String.IsNullOrEmpty(line) || !this.client.Connected) break;

            writer.WriteLine(line);
            writer.Flush();
        }

        if (client.Connected)
        {
            Console.WriteLine(name + " shutting down send.");
            client.Client.Shutdown(SocketShutdown.Send);
            Console.WriteLine(name + " waiting for read to quit.");
            quit.WaitOne();
        }
        else
        {
            Console.WriteLine(name + " socket already closed");
        }

        Console.WriteLine(name + " quit, press key to exit.");
        Console.ReadKey();
    }

    private void Read(IAsyncResult result)
    {
        var bytesRead = this.stream.EndRead(result);
        if (bytesRead == 0)
        {
            Console.WriteLine(name + " read stopped, closing socket.");
            this.client.Close();
            this.quit.Set();
            return;
        }

        var buffer = (byte[])result.AsyncState;
        Console.WriteLine(name + " recieved:" + Encoding.ASCII.GetString((byte[])result.AsyncState, 0, bytesRead));

        stream.BeginRead(buffer, 0, buffer.Length, this.Read, buffer);
    }
}

回答1:

Use Socket.Shutdown() before calling Socket.Close(). Wait for shutdown processing to complete (e.g. ReceiveAsync() will return 0 bytes).

  1. Abstraction choice is (mostly) a non-issue.
  2. Async I/O means never having to see your sorry.
  3. From MS documentation for Socket.ReceiveAsync() method: For byte streams, zero bytes having been read indicates graceful closure and that no more bytes will ever be read.


回答2:

TcpClient implements IDisposable, so you should be able to use it like this and then just not worry about closing the client- the using statement should do it for you whether or not an exception is thrown:

class Client
{
    static void Main(string[] args)
    {
        using (TcpClient client = new TcpClient(AddressFamily.InterNetwork))
        {

            Console.WriteLine("client: created, press key to connect");
            Console.ReadKey();

            client.Connect(IPAddress.Loopback, 5000);

            var channel = new Channel("client", client);
            channel.Run();
        }
    }
}

That said, normally CLR types that implement IDisposable will explicitly say they close their specific underlying resource in the documentation (ex: SqlConnection), but the TcpClient docs on the matter are strangely quiet — you may want to test this first.



回答3:

I haven't done sockets for a while, but I remember that you had to call shutdown() for them.

The .NET equivalent, I guess, would be socket.Shutdown(SocketShutdown.Both)



标签: .net tcp