【问题标题】:How to abort socket's BeginReceive()?如何中止套接字 BeginReceive()?
【发布时间】:2011-01-11 20:55:15
【问题描述】:

当然,如果没有数据,BeginReceive() 将永远不会结束。 MSDN suggests 调用 Close() 将中止 BeginReceive()

但是,在套接字上调用Close() 也会对其执行Dispose(),如this great answer 中所示,因此EndReceive() 会抛出异常,因为该对象已被释放(确实如此!) .

我应该如何进行?

【问题讨论】:

标签: c# sockets asynchronous dispose


【解决方案1】:

这似乎是(非常愚蠢的)设计。您必须在代码中抛出并捕获此异常。

MSDN 确实对此保持沉默,但如果您查看另一个异步套接字方法 BeginConnect() 的文档,我们会发现:

取消对 BeginConnect() 方法,关闭 插座。当 Close() 方法是 在异步操作时调用 正在进行中,提供的回调 BeginConnect() 方法是 叫。随后调用 EndConnect(IAsyncResult) 方法将 抛出一个 ObjectDisposedException 到 表示操作已完成 取消。

如果这是 BeginConnect 的正确做法,那么 BeginReceive 也可能如此。对于 Microsoft 的异步 API 而言,这无疑是一个糟糕的设计,因为让用户必须将异常作为正常流程的一部分抛出和捕获会惹恼调试器。你真的没有办法“等待”直到操作完成,因为 Close() 是首先完成它的。

【讨论】:

  • 不敢相信它可以像这样工作,但我想它就是这样。
  • @Kelly:作为设计有什么问题?无论是否抛出异常,调用EndReceive 的代码都需要处理失败。如果条件可以在EndReceive 处直接处理,则不让它抛出异常将允许catchif 替换,但如果不能处理,则必须添加@ 987654326@ 并且仍然需要捕获。连接不应该经常被中止,因为使用异常和if 的性能开销很重要。
  • 因为抛出和捕获异常作为正常输入的一部分,而不是“异常”流,无论性能影响如何,都是一个糟糕的设计。它还可能会在“break on throw”设置下进行调试,这通常很有用。
  • .Net 中的很多东西都是愚蠢的,StreamReader, BinaryReader, (leaveOpen, DiscardBufferedData...) ASync 和 Begin 方法实际上只是封装了 Send 和 Receive 的逻辑,所以在这个与大多数其他情况一样,同步操作必须是可取消的以支持这样的概念。
【解决方案2】:

我很惊讶没有人推荐使用 SocketOptions。

一旦堆栈进行了发送或接收操作,它就会被套接字的套接字选项绑定。

使用较小的发送或接收超时并在操作之前使用它,这样您就不必在意在同一操作期间它是否更改为更短或更长的时间。

这将导致更多的上下文切换,但不需要在任何协议下关闭套接字。

例如:

1) 设置一个小的超时时间

2) 执行操作

3) 将超时设置得更大

这与使用 Blocking = false 类似,但具有您指定的自动超时。

【讨论】:

  • 套接字发送/接收超时选项仅在阻塞 I/O 操作正在进行时适用。我还没有研究过框架的异步 I/O 是如何实现的,但我希望它具有实际的异步机制,例如 WSASelect 或 I/O 完成端口,而不是某个处于阻塞操作中的线程。
【解决方案3】:

你可以在这里阅读我对这个问题的解决方案(在这里使用 Pavel Radzivilovsky 的评论): UdpClient.ReceiveAsync correct early termination

【讨论】:

    【解决方案4】:

    对于 TCP 套接字连接,您可以使用 Connected 属性来确定套接字的状态,然后再尝试访问任何已释放的方法。根据 MSDN:

    “Connected 属性获取 Socket 在最后一次 I/O 操作时的连接状态。当它返回 false 时,Socket 要么从未连接,要么不再连接。”

    由于它说“不再连接”,这意味着之前在套接字上调用了 Close()。如果在receive回调开始时检查socket是否Connected,不会有异常。

    【讨论】:

    • 您知道这些异步套接字是在多线程程序中吗?您可以检查线程 A 中的一行,然后执行可以在线程 B 中关闭,然后回到线程 A,您的条件不在窗口。
    【解决方案5】:

    在 ReceiveCallback 中,我在 try 块中检查了 client.Connected。 现在,当 BeginReceive 之后收到数据时,我可以调用 client.Close(); 这样,我看不到异常。我每 200 毫秒发送一次 modbus-TCP 请求,并及时得到响应。控制台输出看起来很干净。我使用了一个 Windows 窗体应用程序来测试它。

        private static void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the state object and the client socket
                // from the asynchronous state object.  
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.workSocket;
                if (client.Connected)
                {
                    // Read data from the remote device.  
                    state.dataSize = client.EndReceive(ar);
                    if (state.dataSize > 0)
                    {
                        Console.WriteLine("Received: " + state.dataSize.ToString() + " bytes from server");
                        // There might be more data, so store the data received so far.  
                        state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, state.dataSize));
                        //  Get the rest of the data.  
                        client.BeginReceive(state.buffer, 0, StateObject.BUFFER_SIZE, 0,
                            new AsyncCallback(ReceiveCallback), state);
    
                        state.dataSizeReceived = true;           //received data size?
    
                        dataSize = state.dataSize;
                        buffer = state.buffer.ToArray();
                        dataSizeReceived = state.dataSizeReceived;
    
                        string hex = ByteArrayToString(state.buffer, state.dataSize);
                        Console.WriteLine("<- " + hex);
    
                        receiveDone.Set();
                        client.Close();
                    }
                    else
                    {
                        Console.WriteLine("All the data has arrived");
                        // All the data has arrived; put it in response.  
                        if (state.sb.Length > 1)
                        {
                            Console.WriteLine("Length: " + state.sb.Length.ToString());
                        }
                        // Signal that all bytes have been received.  
                        receiveDone.Set();
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    

    【讨论】:

      【解决方案6】:

      另一种解决方案是使用绑定到不同端口的套​​接字向“你自己”发送“控制消息”。这并不完全是中止,但它会结束您的异步操作。

      【讨论】:

      • 他们也可以使用等待句柄或事件。他们也可以使用我在回答中指出的选项。这需要每个客户端和服务器使用另一个套接字。
      【解决方案7】:

      我也在为此苦苦挣扎,但据我所知,在调用 .BeginReceive() 之前使用简单的布尔标志也可以工作(因此不需要异常处理)。由于我已经进行了启动/停止处理,因此此修复只需一个 if 语句(向下滚动到 OnReceive() 方法的底部)。

      if (_running)
      {
          _mainSocket.BeginReceive(_data, 0, _data.Length, SocketFlags.None, OnReceive, null);
      }                
      

      如果我用这种方法忽略了什么,请告诉我!

      【讨论】:

      • 如果他想在调用 BeginReceive 后中止,这不起作用。鉴于 BeginReceive 创建了一个异步回调,它将让该异步回调等待直到有数据要接收。如果他想在调用 BeginReceive 之后但在 Async 回调执行之前中止(这是他的问题所暗示的)怎么办?在这种情况下,您的解决方案将不起作用。
      • 因此在 OP 的情况下需要异常处理,因为中止异步回调的唯一方法是关闭套接字。这将导致在 EndReceive 上引发 ObjectDisposedException。
      猜你喜欢
      • 2016-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多