当我在另一个线程上睡眠时,为什么套接字被阻止接收?

时间:2018-09-07 12:34:27

标签: c# multithreading sockets tcp

我有一个简单的套接字侦听器应用程序。它需要能够接收请求并给出响应,还必须自己发送请求并接收请求的响应。

应用程序启动后,它将开始在一个单独的线程中接收并发送响应。这部分工作正常。

但是,当我我通过SendRequest()方法发送请求时,我需要过滤传入的响应,因此正确的响应将传递给之前进行的正确记录。我使用类ResponseHandler进行此操作(如下面的代码所示),它使我可以注册一个请求,并在收到正确的响应后立即通知我的注册请求。但是,放置的请求应该在10秒,所以我使用了CountdownEvent,它会等待这10秒钟,但是如果响应时间较早,则会更早发布。

问题::我的CountdownEvent总是等待整整10秒钟,只有在那之后,我接收消息的线程才会继续,因此会收到响应。 当我在其他线程上接收时,这怎么可能? 我想,即使CountdownEvent.Wait()处于活动状态,我的程序仍会继续在该单独的线程中接收。

注意:正如我在NetworkTool WireShark中看到的那样,发出请求后,等待的响应实际上会立即返回。因此超时是不正确的。

  

编辑:在一个简单的WPF应用程序中,可以通过按钮调用SendRequest()。不幸的是,这意味着我的大程序出了问题。

服务:

public class Service
{
    private readonly ResponseHandler _responseHandler;
    private readonly SyncSocketServer _serverSocket;

    private static readonly int ServerPort = 9090;

    public Service()
    {
        _responseHandler = new ResponseHandler();

        _serverSocket = new SyncSocketServer(ServerPort);
        _serverSocket.StartListening();
        _serverSocket.DataReceived += ServerSocket_DataReceived;
    }

    public void ServerSocket_DataReceived(object sender, string message)
    {
        // Here I left irrelevant code out: Originally, I check here,
        // whether the message is a request or response and so on, and 
        // I only forward the message to the _responseHandler, if it is
        // indeed a response. If it is a request I send an answer.

        string messageId = GetIdFromMessage(message);
        _responseHandler.DataReceived(messageId, message);
    }

    public void SendRequest(string message)
    {
        string messageId = Guid.NewGuid().ToString();
        string request = CreateRequest(messageId, message);

        _responseHandler.Register(messageId);
        _serverSocket.Send(request);
        string response = _responseHandler.WaitForResponse(messageId);

        Debug.WriteLine("I got the correct response: " + response);
    }
}

SyncSocketServer:

public class SyncSocketServer
{
    public event EventHandler<string> DataReceived;

    private const int BufferSize = 1024;
    private const string EndDelimiter = "\n";

    private Socket _listenerSocket;
    private Socket _client;
    private string _data;
    private Byte[] _buffer;

    private readonly int _port;

    public SyncSocketServer(int port)
    {
        _port = port;
        _buffer = new Byte[BufferSize];
    }

    public void StartListening()
    {
        IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
        IPAddress ipAddress = ipHostInfo.AddressList[3];
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, _port);

        _listenerSocket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        _listenerSocket.Bind(localEndPoint);
        _listenerSocket.Listen(5);

        _client = _listenerSocket.Accept();
        Debug.WriteLine("Local socket opened on: {0}", _listenerSocket.LocalEndPoint);

        StartReceiving();
    }

    private void StartReceiving()
    {
        Thread d = new Thread(() => {
            Thread.CurrentThread.IsBackground = true;
            while (true)
            {
                _data = null;

                while (true)
                {
                    int bytesReceived = _client.Receive(_buffer);
                    _data += Encoding.ASCII.GetString(_buffer, 0, bytesReceived);

                    if (_data.IndexOf(EndDelimiter, StringComparison.OrdinalIgnoreCase) > -1)
                        break;
                }

                Debug.WriteLine("Message received:" + _data);
                OnDataReceived(_data);
            }
        });
        d.Start();
    }

    public void Send(string message)
    {
        byte[] bytesMessage = Encoding.ASCII.GetBytes(message + EndDelimiter);
        _client.Send(bytesMessage);
        Debug.WriteLine("Message sent: " + message);
    }

    protected virtual void OnDataReceived(string data)
    {
        EventHandler<string> handler = DataReceived;

        if (handler != null)
            handler(this, data);
    }
}

ResponseHandler:

public class ResponseHandler
{
    private const int WaitForResponseTimeout = 10000;

    private readonly Dictionary<string, PendingRequest> _pendingRequests;

    public ResponseHandler()
    {
        _pendingRequests = new Dictionary<string, PendingRequest>();
    }

    public void DataReceived(string messageId, string response)
    {
        _pendingRequests.TryGetValue(messageId, out var pendingRequest);

        if (pendingRequest == null)
            Debug.WriteLine("Received response for request, that has been removed");
        else
        {
            pendingRequest.ResponseReceived(response);
            _pendingRequests.Remove(messageId);
        }
    }

    public void Register(string messageId)
    {
        _pendingRequests.Add(messageId, new PendingRequest());
    }

    public string WaitForResponse(string messageId)
    {
        _pendingRequests.TryGetValue(messageId, out var pendingRequest);

        if (pendingRequest == null)
            return null;

        pendingRequest.Await();
        return pendingRequest.Response;
    }

    private class PendingRequest
    {
        public string Response { get; private set; }

        private readonly CountdownEvent _countdownEvent;

        public PendingRequest()
        {
            _countdownEvent = new CountdownEvent(1);
        }

        public void Await()
        {
            // Here, the current thread gets blocked, but
            // I expect, that the thread, where I receive
            // would continue receiving
            _countdownEvent.Wait(WaitForResponseTimeout);
        }

        public void ResponseReceived(stringresponse)
        {
            Response = response;
            _countdownEvent.Signal();
        }
    }
}

2 个答案:

答案 0 :(得分:1)

因此,您的PendingRequestResponseHandler类是从不同的线程访问的。因此,为了使程序保持理智,您需要做几件事:

a)确保在从挂起的请求字典中添加和删除请求时,获得了一个锁,因为您同时从不同的线程访问共享的数据结构。否则,您可能会破坏数据结构。

b)您最直接的问题是Await()中的PendingRequest方法。您正在呼叫CountdownEvent.Wait(),而无需验证您的响应是否已设置。如果已经设置了响应,则意味着您将等待10秒钟,然后再进行处理。如果您的响应到达,甚至在您调用CountdownEvent.Wait()之前,也会发生这种情况。在这种情况下,CountdownEvent.Signal()将被忽略。您应按以下方式更改PendingRequest.Wait()

while (Response is not set) {
      CountdownEvent.Await();
}

此外,您的CountdownEvent.Wait()信号量是否需要互斥量传递给它?请记住,您的Response对象正在线程之间共享。这是使用wait()方法的一般范例:

mutex.lock();
while (Response is not set) {
          CountdownEvent.Await(mutex);
    }

// Do your stuff, since your condition is satisfied
mutext.unlock();

答案 1 :(得分:0)

问题实际上是错误的假设,就像我在下面所做的那样,触发事件会导致火灾并忘记:

protected virtual void OnDataReceived(string data)
{
    EventHandler<string> handler = DataReceived;

    if (handler != null)
        handler(this, data);
}

在函数StartReceiving()中,我在其中接收数据并将其转发给订户,它将在呼叫时暂停,从而触发事件并等待所有订户完成工作(当然,其中包括等待10秒钟的响应)。这导致事实,我的接收器线程正在等待另一个线程。


解决方案是实施呼叫,因此会引起火灾并忘记:

protected virtual void OnDataReceived(string data)
{
    EventHandler<string> handler = DataReceived;

    if (handler != null)
        handler.BeginInvoke(this, data, null, null);
}