C#中的异步套接字

3
我对在C#中使用异步套接字方法的正确方式感到困惑。我将参考这两篇文章来解释问题并提出我的问题:MSDN有关异步客户端套接字的文章devarticles.com有关套接字编程的文章
我的问题是关于BeginReceive()方法。MSDN文章使用这两个函数来处理接收数据:
private static void Receive(Socket client) 
{
  try 
  {
    // Create the state object.
    StateObject state = new StateObject();
    state.workSocket = client;

    // Begin receiving the data from the remote device.
    client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
        new AsyncCallback(ReceiveCallback), state);
  } 
  catch (Exception e) 
  {
    Console.WriteLine(e.ToString());
  }
}

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;
     // Read data from the remote device.
     int bytesRead = client.EndReceive(ar);
     if (bytesRead > 0) 
     {
         // There might be more data, so store the data received so far.
        state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
         //  Get the rest of the data.
        client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
            new AsyncCallback(ReceiveCallback), state);
     } 
     else 
     {
        // All the data has arrived; put it in response.
       if (state.sb.Length > 1) 
       {
         response = state.sb.ToString();
       }
       // Signal that all bytes have been received.
       receiveDone.Set();
     }
   } 
   catch (Exception e) 
   {
     Console.WriteLine(e.ToString());
   }
 }

在devarticles.com的教程中,BeginReceive方法的最后一个参数被传递了null,并解释了当我们处理多个套接字时,最后一个参数是有用的。现在我的问题是:

  1. 如果我们只使用单个套接字,那么将状态传递给BeginReceive方法有什么意义?这是为了避免使用类字段吗?似乎这样做没有太大意义,但也许我漏掉了什么。

  2. 当处理多个套接字时,状态参数如何帮助?如果我调用client.BeginReceive(...),那么所有数据都将从client套接字读取吗?devarticles.com的教程使它听起来像在这个调用中:m_asynResult = m_socClient.BeginReceive (theSocPkt.dataBuffer,0,theSocPkt.dataBuffer.Length, SocketFlags.None,pfnCallBack,theSocPkt);

数据将从theSocPkt.thisSocket套接字中读取,而不是从m_socClient套接字中读取。在他们的示例中,两者是相同的,但如果情况不是这样会发生什么呢?

我真的看不出最后一个参数有什么用处,或者至少它如何在处理多个套接字时有所帮助。如果我有多个套接字,我仍然需要在每个套接字上调用BeginReceive,对吧?

2个回答

2
如果我们只使用一个socket,那么把状态传递给BeginReceive方法的意义何在?是否为了避免使用类字段?看起来这样做没有什么意义,但也许我漏掉了什么。
你是对的,如果没有使用状态,那么必须使用成员变量。但这比状态变量更少见。事物越局部,当您在代码的其他部分进行更改时它们就越不容易出错。
比较普通的方法调用。为什么我们不只将参数设置为成员,然后在没有任何参数的情况下调用所有函数呢?它可以工作......但是阅读代码会很可怕。通过尽可能保持范围本地化,使设计更易于理解和修改。改进的封装导致更健壮的代码。
同样适用于此处。如果您只有一个异步回调,则可能仅需在类上设置一个成员,但是如果您有许多这种调用,则此策略很快将导致与上述类似的问题-混乱和脆弱的代码。
在处理多个套接字时,状态参数如何帮助?
每次调用都可以传递不同的状态对象,每个对象都包含自己的客户端对象。请注意,客户端是从状态中获取而不是从成员变量中获取:
//Socket client = this.client; // Don't do this.
Socket client = state.workSocket; 

如果您注意到MSDN文档中的所有其他方法都需要一个客户端作为参数。状态只是一种传递参数的方式,因为方法签名是固定的。


更新:关于您在评论中提出的问题,.NET会检查您是否使用了正确的客户端对象,如果没有,则会抛出ArgumentException异常。从反编译.NET Reflector中的EndReceive方法我们可以看到这一点:

if ((result == null) || (result.AsyncObject != this))
{
    throw new ArgumentException(SR.GetString("net_io_invalidasyncresult"), "asyncResult");
}

程序员要确保状态对象中包含的套接字与调用BeginReceive的套接字对象相同,是吗?如果它们不同,那么可能会导致错误。 - IVlad
不需要这样做 - .NET 保证即使您有多个同时调用,状态也会得到可靠传输。当您调用普通函数时,无需检查您接收的参数是否与调用者用于调用您的对象相同。如果您这样做了,能够传递参数就没有实际价值,因为您仍然需要参考原始对象以进行比较。 - Mark Byers
这不是我的意思。例如,考虑在BeginReceive回调函数中使用Socket client = state.workSocket;。假设套接字A在执行state.workSocket = B后调用了BeginReceive并传递了一个StateObject state。只有当A == B时,这才是有效的,对吧?我有点困惑,因为所有的示例都将相同的套接字传递给用于调用接收方法的状态,但它们没有明确说明两者必须相同。我认为它们应该相同,但我想确保这一点。 - IVlad
@IVlad:是的,它们必须相同。如果您使用错误的客户端,.NET会注意到并抛出ArgumentException异常。请参阅我的答案更新(接近底部)。 - Mark Byers

1
“state”参数在处理多个套接字时有什么作用?
我认为这里的误解在于“state”参数的确切用途;当调用时:
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
    ReceiveCallback, state);

当你在网站上进行调用时,很明显可以知道调用是在哪个“客户端”上进行的,但是当调用ReceiveCallback方法时,该方法无法知道客户端。请注意,许多不同套接字上的接收可能同时进行,而所有这些接收都共享相同的回调处理程序来处理结果。

当将数据传递到state参数中时,您有机会向回调传递其他上下文,以便它可以确定(在本例中)启动接收的套接字。

如果您在状态参数中传递了“错误的值”,那么显然回调无法保护您...后果取决于您如何使用状态中的数据。最好的情况是,如果状态没有真正使用,则可能不会有任何影响;最坏的情况是,数据可能被处理为来自错误的套接字,并且您可能最终在未经请求的帐户上执行“删除所有帖子”的命令。但这与任何其他编程错误没有区别 ;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接