Search code examples
c#.nettcpclient

Half-closing TcpClient


I'm having serious issue with half-closing a TcpClient. What I am trying to do is:

On the client:

  1. Send a message
  2. Shutdown the underlying socket for sending
  3. Receive a response
  4. Shutdown the underlying socket for reading (or, at this point, just close it)

On the server:

  1. Receive a message
  2. Shutdown the underlying socket for reading
  3. Send a response
  4. Shutdown the underlying socket for writing (or, at this point, just close it)

However, after the step 2 on either the client, or the server, I can't use the TcpClient's stream. Here's a very simplified version of my code (without asynchronous calls, processing and cleaning up, and also using StreamReader and StreamWriter instead of an XmlSerializer):

        //initialize the connection between the server and the client
        var listener = new TcpListener(IPAddress.Any, 13546);
        listener.Start();
        var client = new TcpClient("127.0.0.1", 13546);
        var server = listener.AcceptTcpClient();
        listener.Stop();

        //CLIENT: send the message
        var cwriter = new StreamWriter(client.GetStream());

        cwriter.Write("client's message");
        cwriter.Flush();

        client.Client.Shutdown(SocketShutdown.Send);

        //SERVER: receive the message
        string msg;
        var sreader = new StreamReader(server.GetStream());

        msg = sreader.ReadToEnd();

        server.Client.Shutdown(SocketShutdown.Receive);

        //SERVER: send a response
        //Here the code fails on server.GetStream() - 
        //InvalidOperationException, apparently the whole connection is closed now

        var swriter = new StreamWriter(server.GetStream());

        swriter.Write(msg + " with server's response");
        swriter.Flush();

        server.Client.Shutdown(SocketShutdown.Send);

        //CLIENT: receive the message
        var creader = new StreamReader(client.GetStream());

        var response = creader.ReadToEnd();

        client.Client.Shutdown(SocketShutdown.Receive);

Is there any way to do this without using a raw socket? Is there something I'm getting wrong?


Solution

  • The problem is that ReadToEnd reads data up to the end of stream. By issuing Client.Shutdown you actually close the socket making it impossible to reuse it (at least in case of TCPClient). Here's the code of GetStream()

    public NetworkStream GetStream() {
        if(Logging.On)Logging.Enter(Logging.Sockets, this, "GetStream", "");
        if (m_CleanedUp){
            throw new ObjectDisposedException(this.GetType().FullName);        
        }
        if (!Client.Connected) {
            throw new InvalidOperationException(SR.GetString(SR.net_notconnected));
        }
        if (m_DataStream==null) {
            m_DataStream = new NetworkStream(Client, true);
        }
        if(Logging.On)Logging.Exit(Logging.Sockets, this, "GetStream", m_DataStream);
        return m_DataStream;
    }
    

    As you can see, the error occures due to closed socket.

    EDIT: It is ridiculously strange but I think I found the reason why it doesn't work properly. The reason is that Shutdown always sets flags for the entire socket as disconnected. Even though we are actually not closing it that way! If we preserve stream at the begining of the method we will not face this problem since the problem lies in GetStream method which checks socket's state. But we are probably exposed to some bugs when other code would check socket's state.