【问题标题】:C# Socket Server how to cancel thread that is stuck at socket.accept()C# Socket Server 如何取消卡在 socket.accept() 的线程
【发布时间】:2019-02-16 10:07:45
【问题描述】:

我正在编写一个非常简单的 RPC 库,使用 C# 和套接字进行类分配。我的发送功能很好,但我正在测试我的 KillServer 功能是如何工作的。我已经在服务器库中实现了很多线程,我不确定我是否已经找到了取消和软终止线程的正确方法。

我的 StartServerThread 方法:

public void StartServer()
        {
            ServerThread = new Task(()=> 
            {
                _Socket.Listen(100);
                // Enter a loop waiting for connection requests
                // Or because I am RDM I don't need connections
                while (!_TokenSource.IsCancellationRequested)
                {
                    try
                    {
                        var newClient = _Socket.Accept();

                        _Clients.Add(newClient, ClientThread(newClient));
                    }
                    catch (OperationCanceledException)
                    {
                        Debug.WriteLine("Canceled");
                    }
                }


                foreach (var client in _Clients)
                {
                    client.Key.Disconnect(false);
                }

            },_TokenSource.Token, TaskCreationOptions.LongRunning);

            ServerThread.Start();
        }

我的问题是一旦线程到达 _Socket.Accept() 行,它就会被阻塞。因此,即使我在 TokenSource 上调用 Cancel,线程仍然停留在该行并且不会很好地结束。我很难以使用线程并允许连接多个客户端的良好性能的方式设计这个库。

【问题讨论】:

标签: c# multithreading sockets


【解决方案1】:

解决此问题的方法有多种。您可以使用已经提到的非阻塞套接字,也可以使用异步函数,如Socket.​Begin​Accept。这是来自 MSDN 的一些 example

重要的代码如下。

while (true)
{  
    // Set the event to nonsignaled state.  
    allDone.Reset();  

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

    // Wait until a connection is made before continuing.  
    allDone.WaitOne();  
}  

这里手动重置事件allDone在回调函数中设置,当新的TCP连接被接受时唤醒,同时在之前的代码中循环再次调用Socket.​Begin​Accept

public static void AcceptCallback(IAsyncResult ar)    
{  
    // Signal the main thread to continue.  
    allDone.Set();  
    // Get the socket that handles the client request.  
    Socket listener = (Socket) ar.AsyncState;  
    Socket handler = listener.EndAccept(ar);
    ...

现在,如果您想阻止线程接受新调用,您可以“滥用”事件allDone 并将其设置为唤醒正在等待事件allDone 的“BeginAccept”线程。然后“BeginAccept”线程可以检查TokenSource.IsCancellationRequested 循环是否应该继续或退出。

// MODIFY while contition.
while (!_TokenSource.IsCancellationRequested)
{  
    // Set the event to nonsignaled state.  
    allDone.Reset();  

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

    // Wait until a connection is made before continuing.  
    allDone.WaitOne();  
}  

这是您想要退出“BeginAccept”线程时调用的代码。

 // Don't continue with the while cycle.
 _TokenSource.Cancel();

 // Wake up thread calling BeginAccept().
 allDone.Set()

另一种可能性是使用 2 个事件实例。像这样。

// Declared somewhere to be accessible from both threads.
ManualResetEvent exitThread;


while (true)
{  
    // Set the event to nonsignaled state.  
    allDone.Reset();  

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

    // Wait until a connection is made or thread is exiting 
    if (WaitHandle.WaitAny(new WaitHandle[]{exitThread,allDone})==0)
    {
        break;      
    }
}  

现在可以通过执行来停止来自第二个线程的循环。

exitThread.Set();

代码 WaitHandle.WaitAny() 正在等待,直到发出 2 个事件中的任何一个,当 exitThread 发出信号(WaitHandle.Wait() 返回 0)时,它会跳出循环。

但是现在出现了另一个问题。因为您在调用 Socket.​Close 时已经调用了 Socket.​Begin​Accept - 回调 AcceptCallback 被触发。由于套接字已经关闭,方法Socket.EndAccept 将抛出ObjectDisposedException。所以你必须在回调方法AcceptCallback 中捕获并忽略这个异常。这里有更多关于Socket.​Begin​Receivedetails,也适用于Socket.​Begin​Accept

随着 .NET 任务库的到来,通过使用扩展方法 Accept​Async,还有另一个可能更优雅的解决方案,但我在 Internet 上找不到一些很好的例子。而且我个人更喜欢“老派” BeginXYZ()-EndXYZ() 异步模型——也许是因为我太老了……

【讨论】:

    【解决方案2】:

    在同一应用程序中创建本地连接以解锁此调用:

    Socket handler = listener.Accept();
    

    代码将如下所示:

    Socket clientToUnlockAccept = new Socket(AddressFamily.InterNetwork,
        SocketType.Stream, ProtocolType.Tcp);
    
    clientToUnlockAccept.Connect(localEndPoint) 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-07
      • 1970-01-01
      • 2020-09-03
      • 1970-01-01
      • 1970-01-01
      • 2017-04-29
      • 1970-01-01
      • 2011-10-13
      相关资源
      最近更新 更多