Search code examples
c#socketsshutdown

Shutdown inbound socket causes old data to be re-read


I'm looking for some assistance with shutting down a listening socket please. The problem I have is that when I call Shutdown() just before Close(), one of these two calls seems to trigger a bogus read on the socket of the last data received. How can I stop that please?

My app has two pairs of sockets, rather like FTP. One is a client that it connects to a remote server, and one is a server that listens and accepts a second connection from the remote host. This inbound connection is wired up to an asynch OnReceived to handle the unsolicited data coming down form the remote host.

This all works fine and both sockets can remain connected for days or weeks. But if something goes wrong then I react by shutting down everything and starting again. During the call to either inboundSocket.Shutdown() or inbounSocket.Close() (not sure which, it's hard to debug in a second thread), it's as if I am re-reading the last inboudn packet on the inbound socket. This then causes even more problems.

How can I shutdown. close, disconnect, etc etc without forcing this re-read?

Sample code below, stripped down to show the nitty gritty.

Thanks in advance.

Daniel

public class TcpIpSenderReceiver
{ 
    /// <summary>
    /// Main thread, main entry point 
    /// </summary>
    public void ConnectAndLogonAndStartReceivingInboundMessages()
    {   
        CreateListenerAndStartListening();
        AcceptInboundSocketAndHookUpDataReceiptCallBack();          
    }

    /// <summary>
    /// Main thread
    /// </summary>
    int CreateListenerAndStartListening()
    { 
        tcpListener = new TcpListener(LocalBoundIpAddress, listeningPort);
        tcpListener.Start();        
    }


    /// <summary>
    /// SECOND thread
    /// </summary>
    void AcceptInboundSocketAndHookUpDataReceiptCallBack()
    {
        int i = 0;
        while (i < 100 && !tcpListener.Pending())
        {
            i++;
            Thread.Sleep(100);
        }
        inboundSocket = tcpListener.AcceptSocket();
        bufferForReceive = new byte[bufferSize];
        WireUpCallbackForAsynchReceive();
    }

    /// <summary>
    /// SECOND thread
    /// </summary>
    void WireUpCallbackForAsynchReceive()
    {
        if (asynchCallbackFunctionForReceive == null)
        {
            asynchCallbackFunctionForReceive = new AsyncCallback(OnDataReceived);
        }
        if (inboundSocket.Connected)
        {
            try
            {
                asyncResultForReceive = inboundSocket.BeginReceive(bufferForReceive, 0, bufferForReceive.Length, SocketFlags.None, asynchCallbackFunctionForReceive, null);
            }
            catch (Exception)
            {
                //...
            }
        }
    }


    /// <summary>
    /// SECOND thread
    /// </summary>
    void OnDataReceived(IAsyncResult asyn)
    {
        // Read data call goes here.....

        if (asyncResultForReceive != null)
        {
            inboundSocket.EndReceive(asyncResultForReceive);  
        }
        WireUpCallbackForAsynchReceive();  // listen again for next inbound message
    } 


    void Shutdown()
    {
        shouldAbortThread = true;
        asyncResultForReceive = null;
        asynchCallbackFunctionForReceive = null;
        if (outboundStreamWriter != null)
        {
            try
            {
                outboundStreamWriter.Close();
                outboundStreamWriter.Dispose();
                outboundStreamWriter = null;
            }
            finally { }
        }
        if (outboundNetworkStream != null)
        {
            try
            {
                outboundNetworkStream.Close();
                outboundNetworkStream.Dispose();
                outboundNetworkStream = null;
            }
            finally { }
        }
        if (tcpClient != null)
        {
            try
            {
                tcpClient.Close();
                tcpClient = null;
            }
            catch (SocketException)
            {
                // ...
            }
        }
        if (inboundSocket != null)
        {
            try
            {
                // I think this is where it's puking
                inboundSocket.Shutdown(SocketShutdown.Both);
                inboundSocket.Close();
                inboundSocket = null;
            }
            catch (SocketException)
            {
                //...
            }
        }
        if (tcpListener != null)
        {
            try
            {
                tcpListener.Stop();
                tcpListener = null;
            }
            catch (SocketException)
            {
                //...
            }
        }
    }


    #region Local variables

    volatile bool shouldAbortThread;
    TcpListener tcpListener;
    TcpClient tcpClient;
    StreamWriter outboundStreamWriter;
    NetworkStream outboundNetworkStream;
    Socket inboundSocket = null;
    IAsyncResult asyncResultForReceive;
    public AsyncCallback asynchCallbackFunctionForReceive;
    byte[] bufferForReceive;
    static string HostnameShared;
    #endregion
}

Solution

  • I got around this as follows. Instead of providing a callback function and letting the framework handle the second thread, I explicitly started my own worker thread and let it block until data is received. If zero bytes are received, I know that the other end is shutting down and can terminate my end nicely. The only gotcha I wanted to share was that I needed to try-catch around the blocking socket.Receive() function and handle a SocketError.Interrupted exception. This occurs when the main program is terminated.

    Code looks a bit like this:

            Thread dataReceivedWorkerThread;
    
        void AcceptInboundSocketAndStartWorkerThread()
        {            
            inboundSocket = tcpListener.AcceptSocket();
            dataReceivedWorkerThread = new Thread(new ThreadStart(ListenForAndWaitToReceiveInboundData));
            dataReceivedWorkerThread.Start();
        }
    
        void ListenForAndWaitToReceiveInboundData()
        {
                var bytesRead = 0;
                try
                {
                    bytesRead = inboundSocket.Receive(bufferForReceive, 0, bufferForReceive.Length, SocketFlags.None); // blocks here                   
                }
                catch (SocketException se)
                {
                    if (se.SocketErrorCode == SocketError.Interrupted) 
                    {
                        // Handle shutdown by (e.g.) parent thread....                      
                    }
                    else
                    {
                        // handle other
                    }
                    return;
                }
                if (bytesRead == 0)
                {
                    // handle shutdown from other end
                    return;
                }
    
                // Do stuff with received data....
            }
        }