c# - C#套接字服务器如何取消卡在socket.accept()中的线程

标签 c# multithreading sockets

我正在使用C#和套接字为类分配编写一个非常简单的RPC库。我的发送功能正常工作,但是我正在测试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,该线程仍然停留在该行,并且不会很好地结束。我在设计这种库时遇到了麻烦,该方法使用线程并在连接多个客户端的情况下具有良好的性能。

最佳答案

有几种解决方法。您可以使用已经提到的非阻塞套接字,也可以使用诸如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();  
}  

在此处,当接受新的TCP连接时,将在回调函数中设置手动重置事件allDone,该连接会在循环前一个代码以再次调用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.​Begin​Accept时已经调用了Socket.​Close,所以会触发回调AcceptCallback。并且因为套接字已经被关闭,所以方法Socket.EndAccept将抛出ObjectDisposedException。因此,您必须在回调方法AcceptCallback中捕获并忽略此异常。这里还有更多关于Socket.​Begin​Receivedetails,它也适用于Socket.​Begin​Accept

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

关于c# - C#套接字服务器如何取消卡在socket.accept()中的线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52283776/

相关文章:

c# - 逐步应用更好的设计

ios - 如何在通过线程执行工作负载时为微调器设置动画

delphi,使用indy10从客户端向服务器发送图像

c# - 如何在 C# Socket 上从 android 连续接收位图?

c - 如何检查套接字的接收或发送点是否已关闭

c# - 如何使用多态避免 'If cascade' 和类型转换?

javascript - Blazor WebAssembly 环境变量

c# - 为什么在 DataGridTextcolumn 中找不到 ObservableCollection 中实际类的属性,但父类属性是?

python - 为什么我的线程比我在 Linux 下的 py3k 多处理中要求我的池的进程多?

java - HashSet 存储 Singleton 对象两次