Multi-threaded TcpClient send timeout after long open connection

元气小坏坏 提交于 2019-12-06 12:43:18

问题


I'm having a problem with a TcpClient closing with a send timeout in a multi-threaded application after the connection has been open for a long period of time (several hours or overnight). The NetworkStream is being used by two threads, a UI thread and a background thread. There is a StreamReader used for reading incoming data, and a StreamWriter used for outgoing data. The StreamReader is only ever accessed one thread (the background one), but the StreamWriter is accessed by both the UI thread and the background thread.

What happens is that if I open a connection and connect to a remote server, I can immediately send and receive data without any problems. I do not get any send timeouts and data is correctly sent and received. However, if I then walk away and do not send any data for several hours and then return and start sending data (this is a chat application if that helps make it make sense), the socket will timeout on the send. During the time that I walk away there is no problem at all receiving data. Additionally, the remote server polls for an active connection and my client must respond to that, and since the connection is open for several hours it must be correctly sending a response. This polling response is only sent on the background thread, though. Data I enter is sent from the UI thread, and that's where the timeout occurs.

I'm guessing it's something to do with concurrent access, but I can't figure out what's causing it and why I can initially send data from the UI without a problem and only have it timeout after being idle for several hours.

Below is the relevant code. The variables at the top are declared in the class. Address and Port are properties in the class. WriteLine is the only method anywhere in the application that sends data with the StreamWriter. I put a lock around the call to StreamWriter.WriteLine hoping that would correct any synchronization issues. WriteLine is called from the background thread inside ParseMessage, and elsewhere from the UI.

If I increase TcpClient.SendTimeout to something larger, that doesn't fix anything. It just takes longer for the socket to timeout. I can't have the background thread both read and write because the background thread is blocking on ReadLine, so nothing would ever get written.

private TcpClient _connection;
private StreamWriter _output;
private Thread _parsingThread;
private object _outputLock = new object();


public void Connect(string address, int port)
{           
    Address = address;
    Port = port;

    _parsingThread = new Thread(new ThreadStart(Run));
    _parsingThread.IsBackground = true;
    _parsingThread.Start();
}

private void Run()
{
    try
    {
        using (_connection = new TcpClient())
        {
            _connection.Connect(Address, Port);
            _connection.ReceiveTimeout = 180000;
            _connection.SendTimeout = 60000;

            StreamReader input = new StreamReader(_connection.GetStream());
            _output = new StreamWriter(_connection.GetStream());

            string line;
            do
            {
                line = input.ReadLine();

                if (!string.IsNullOrEmpty(line))
                {
                    ParseMessage(line);
                }
            }
            while (line != null);
        }
    }
    catch (Exception ex)
    {
        //not actually catching exception, just compressing example
    }
    finally
    {
       //stuff
    }
}

protected void WriteLine(string line)
{
    lock (_outputLock)
    {
        _output.WriteLine(line);
        _output.Flush();
    }
}

回答1:


The blocking methods (Read and Write) of the NetworkStream class are not designed to be used concurrently from multiple threads. From MSDN:

Use the Write and Read methods for simple single thread synchronous blocking I/O. If you want to process your I/O using separate threads, consider using the BeginWrite and EndWrite methods, or the BeginRead and EndRead methods for communication.

My assumption is that, when you call WriteLine (and, internally, NetworkStream.Write) from your UI thread, it would block until the concurrent ReadLine (internally, NetworkStream.Read) operation completes in the background thread. If the latter does not do so within the SendTimeout, then the Write would time out.

To work around your issue, you should convert your implementation to use non-blocking methods. However, as a quick hack to first test whether this is really the issue, try introducing a DataAvailable poll before your ReadLine:

NetworkStream stream = _connection.GetStream();
StreamReader input = new StreamReader(stream);
_output = new StreamWriter(stream);

string line;
do
{
    // Poll for data availability.
    while (!stream.DataAvailable)
        Thread.Sleep(300);

    line = input.ReadLine();

    if (!string.IsNullOrEmpty(line))
    {
        ParseMessage(line);
    }
}
while (line != null);

line = input.ReadLine();


来源:https://stackoverflow.com/questions/10864721/multi-threaded-tcpclient-send-timeout-after-long-open-connection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!