我正在使用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,该线程仍然停留在该行,并且不会很好地结束。我在设计这种库时遇到了麻烦,该方法使用线程并在连接多个客户端时允许良好的性能。
答案 0 :(得分:0)
有几种方法可以解决此问题。您可以使用已经提到的非阻塞套接字,也可以使用诸如Socket.BeginAccept
之类的异步函数。这是来自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.BeginAccept
时唤醒。
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.BeginAccept
时已经调用了Socket.Close
-触发了回调AcceptCallback
。而且由于套接字已经关闭,所以方法Socket.EndAccept
将抛出ObjectDisposedException
。因此,您必须在回调方法AcceptCallback
中捕获并忽略此异常。关于Socket.BeginReceive
的更多details也适用于Socket.BeginAccept
。
随着.NET Task库的到来,通过使用扩展方法AcceptAsync,还有另一个更优雅的解决方案,但是我在Internet上找不到一些很好的示例。而且我个人更喜欢“老式”的BeginXYZ()-EndXYZ()异步模型-也许是因为我太老了...
答案 1 :(得分:0)
在同一应用程序中创建本地连接以解锁此呼叫:
Socket handler = listener.Accept();
代码如下:
Socket clientToUnlockAccept = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
clientToUnlockAccept.Connect(localEndPoint)