C# 调用 BeginAcceptTcpClient 后停止 TcpListener

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1173774/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:01:23  来源:igfitidea点击:

Stopping a TcpListener after calling BeginAcceptTcpClient

c#socketstcpclient

提问by Anthony D

I have this code...

我有这个代码...

internal static void Start()
{
    TcpListener listenerSocket = new TcpListener(IPAddress.Any, 32599);
    listenerSocket.Start();
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}

Then my call back function looks like this...

然后我的回调函数看起来像这样......

private static void AcceptClient(IAsyncResult asyncResult)
{
    MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
    ThreadPool.QueueUserWorkItem((object state) => handler.Process());
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}

Now, I call BeginAcceptTcpClient, then some time later I want to stop the server. To do this I have been calling TcpListener.Stop(), or TcpListener.Server.Close(). Both of these however execute my AcceptClient function. This then throws an exception when I call EndAcceptTcpClient. What is the best practice way around this? I could just put a flag in to stop the execution of AcceptClient once I have called stop, but I wonder if I am missing something.

现在,我调用 BeginAcceptTcpClient,然后一段时间后我想停止服务器。为此,我一直在调用 TcpListener.Stop() 或 TcpListener.Server.Close()。然而,这两个都执行我的 AcceptClient 函数。当我调用 EndAcceptTcpClient 时,这将引发异常。解决此问题的最佳实践方法是什么?我可以在调用 stop 后放置一个标志来停止 AcceptClient 的执行,但我想知道我是否遗漏了什么。

Update 1

更新 1

Currently I have patched it by changing the code to look like this.

目前我已经通过将代码更改为如下所示来修补它。

private static void AcceptClient(IAsyncResult asyncResult)
{
     if (!shutdown)
     {
          MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
          ThreadPool.QueueUserWorkItem((object state) => handler.Process());
          listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
     }
}

private static bool shutdown = false;
internal static void Stop()
{
     shutdown = true;
     listenerSocket.Stop();
}

Update 2

更新 2

I changed it to impliment the answer from Spencer Ruport.

我将其更改为暗示 Spencer Ruport 的答案。

private static void AcceptClient(IAsyncResult asyncResult)
{
    if (listenerSocket.Server.IsBound)
    {
            MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
            ThreadPool.QueueUserWorkItem((object state) => handler.Process());
            listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
    }
}

采纳答案by David Pope

I just ran into this issue myself, and I believe your current solution is incomplete/incorrect. There is no guarantee of atomicity between the check for IsBoundand the subsequent call to EndAcceptTcpClient(). You can still get an exception if the listener is Stop()'d between those two statements. You didn't say what exception you're getting but I assume it's the same one I'm getting, ObjectDisposedException(complaining that the underlying socket has already been disposed).

我自己刚刚遇到了这个问题,我相信您当前的解决方案不完整/不正确。无法保证检查IsBound和随后调用之间的原子性EndAcceptTcpClient()。如果侦听器Stop()位于这两个语句之间,您仍然可以获得异常。你没有说你得到了什么异常,但我认为它与我得到的相同,ObjectDisposedException(抱怨底层套接字已经被处理)。

You should be able to check this by simulating the thread scheduling:

您应该能够通过模拟线程调度来检查这一点:

  • Set a breakpoint on the line after the IsBoundcheck in your callback
  • Freeze the thread that hits the breakpoint (Threads window -> right click, "Freeze")
  • Run/trigger the code that calls TcpListener.Stop()
  • Break in and step through the EndAcceptTcpClient()call. You should see the ObjectDisposedException.
  • IsBound检查回调后在行上设置断点
  • 冻结遇到断点的线程(线程窗口 -> 右键单击​​,“冻结”)
  • 运行/触发调用的代码 TcpListener.Stop()
  • 闯入并逐步完成EndAcceptTcpClient()呼叫。你应该看到ObjectDisposedException.

IMO the ideal solution would be for Microsoft to throw a different exception from EndAcceptTcpClientin this case, e.g. ListenCanceledExceptionor something like that.

IMO 的理想解决方案是让 MicrosoftEndAcceptTcpClient在这种情况下抛出不同的异常,例如ListenCanceledException或类似的异常。

As it is, we have to infer what's happening from the ObjectDisposedException. Just catch the exception and behave accordingly. In my code I silently eat the exception, since I have code elsewhere that's doing the real shutdown work (i.e. the code that called TcpListener.Stop()in the first place). You should already have exception handling in that area anyway, since you can get various SocketExceptions. This is just tacking another catch handler onto that try block.

事实上,我们必须从ObjectDisposedException. 只需捕获异常并相应地采取行动即可。在我的代码中,我默默地处理异常,因为我在其他地方有代码在做真正的关闭工作(即TcpListener.Stop()首先调用的代码)。无论如何,您应该已经在该区域进行了异常处理,因为您可以获得各种SocketExceptions. 这只是将另一个 catch 处理程序添加到该 try 块上。

I admit I'm uncomfortable with this approach since in principle the catch could be a false positive, with a genuine "bad" object access in there. But on the other hand there aren't too many object accesses in the EndAcceptTcpClient()call that could otherwise trigger this exception. I hope.

我承认我对这种方法感到不舒服,因为原则上捕获可能是误报,其中有真正的“坏”对象访问。但另一方面,EndAcceptTcpClient()调用中没有太多的对象访问可能会触发此异常。我希望。

Here's my code. This is early/prototype stuff, ignore the Console calls.

这是我的代码。这是早期/原型的东西,忽略控制台调用。

    private void OnAccept(IAsyncResult iar)
    {
        TcpListener l = (TcpListener) iar.AsyncState;
        TcpClient c;
        try
        {
            c = l.EndAcceptTcpClient(iar);
            // keep listening
            l.BeginAcceptTcpClient(new AsyncCallback(OnAccept), l);
        }
        catch (SocketException ex)
        {
            Console.WriteLine("Error accepting TCP connection: {0}", ex.Message);

            // unrecoverable
            _doneEvent.Set();
            return;
        }
        catch (ObjectDisposedException)
        {
            // The listener was Stop()'d, disposing the underlying socket and
            // triggering the completion of the callback. We're already exiting,
            // so just return.
            Console.WriteLine("Listen canceled.");
            return;
        }

        // meanwhile...
        SslStream s = new SslStream(c.GetStream());
        Console.WriteLine("Authenticating...");
        s.BeginAuthenticateAsServer(_cert, new AsyncCallback(OnAuthenticate), s);
    }

回答by Spencer Ruport

No you're not missing anything. You can check the IsBound property of the Socket object. At least for TCP connections, while the socket is listening this will be set to true and after you call close it's value will be false. Though, your own implementation can work just as well.

不,你没有错过任何东西。您可以检查 Socket 对象的 IsBound 属性。至少对于 TCP 连接,当套接字正在侦听时,这将设置为 true,在您调用 close 后,它的值为 false。不过,您自己的实现也可以正常工作。

回答by Stefan Gerunde

try this one. it works fine for me without catching exceptions.

试试这个。它对我来说很好,没有捕获异常。

private void OnAccept(IAsyncResult pAsyncResult)
{
    TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
    if(listener.Server == null)
    {
        //stop method was called
        return;
    }
    ...
}

回答by Andreas Dirnberger

i think that all tree things are needed and that the restart of BeginAcceptTcpClient should be placed outside the tryctach of EndAcceptTcpClient.

我认为所有树的东西都是需要的,并且应该将 BeginAcceptTcpClient 的重新启动放在 EndAcceptTcpClient 的 tryctach 之外。

    private void AcceptTcpClientCallback(IAsyncResult ar)
    {
        var listener = (TcpListener)ar.AsyncState;

        //Sometimes the socket is null and somethimes the socket was set
        if (listener.Server == null || !listener.Server.IsBound)
            return;

        TcpClient client = null;

        try
        {
            client = listener.EndAcceptTcpClient(ar);
        }
        catch (SocketException ex)
        {
            //the client is corrupt
            OnError(ex);
        }
        catch (ObjectDisposedException)
        {
            //Listener canceled
            return;
        }

        //Get the next Client
        listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), listener);

        if (client == null)
            return; //Abort if there was an error with the client

        MyConnection connection = null;
        try
        {
            //Client-Protocoll init
            connection = Connect(client.GetStream()); 
        }
        catch (Exception ex)
        {
            //The client is corrupt/invalid
            OnError(ex);

            client.Close();
        }            
    }

回答by Andrey Goncharov

This is a simple example how to start listening, how to process requests asynchronously, and how to stop listening.

这是一个简单的例子,如何开始监听,如何异步处理请求,以及如何停止监听。

Full example here.

完整示例在这里

public class TcpServer
{
    #region Public.     
    // Create new instance of TcpServer.
    public TcpServer(string ip, int port)
    {
        _listener = new TcpListener(IPAddress.Parse(ip), port);
    }

    // Starts receiving incoming requests.      
    public void Start()
    {
        _listener.Start();
        _ct = _cts.Token;
        _listener.BeginAcceptTcpClient(ProcessRequest, _listener);
    }

    // Stops receiving incoming requests.
    public void Stop()
    { 
        // If listening has been cancelled, simply go out from method.
        if(_ct.IsCancellationRequested)
        {
            return;
        }

        // Cancels listening.
        _cts.Cancel();

        // Waits a little, to guarantee 
        // that all operation receive information about cancellation.
        Thread.Sleep(100);
        _listener.Stop();
    }
    #endregion

    #region Private.
    // Process single request.
    private void ProcessRequest(IAsyncResult ar)
    { 
        //Stop if operation was cancelled.
        if(_ct.IsCancellationRequested)
        {
            return;
        }

        var listener = ar.AsyncState as TcpListener;
        if(listener == null)
        {
            return;
        }

        // Check cancellation again. Stop if operation was cancelled.
        if(_ct.IsCancellationRequested)
        {
            return;
        }

        // Starts waiting for the next request.
        listener.BeginAcceptTcpClient(ProcessRequest, listener);

        // Gets client and starts processing received request.
        using(TcpClient client = listener.EndAcceptTcpClient(ar))
        {
            var rp = new RequestProcessor();
            rp.Proccess(client);
        }
    }
    #endregion

    #region Fields.
    private CancellationToken _ct;
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private TcpListener _listener;
    #endregion
}