views:

473

answers:

2

I'm confused about the correct way of using asynchronous socket methods in C#. I will refer to these two articles to explain things and ask my questions: MSDN article on asynchronous client sockets and devarticles.com article on socket programming.

My question is about the BeginReceive() method. The MSDN article uses these two functions to handle receiving data:

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());
   }
 }

While the devarticles.com tutorial passes null for the last parameter of the BeginReceive method, and goes on to explain that the last parameter is useful when we're dealing with multiple sockets. Now my questions are:

  1. What is the point of passing a state to the BeginReceive method if we're only working with a single socket? Is it to avoid using a class field? It seems like there's little point in doing it, but maybe I'm missing something.

  2. How can the state parameter help when dealing with multiple sockets? If I'm calling client.BeginReceive(...), won't all the data be read from the client socket? The devarticles.com tutorial makes it sound like in this call: m_asynResult = m_socClient.BeginReceive (theSocPkt.dataBuffer,0,theSocPkt.dataBuffer.Length, SocketFlags.None,pfnCallBack,theSocPkt);

Data will be read from the theSocPkt.thisSocket socket, instead of from the m_socClient socket. In their example the two are one and the same, but what happens if that is not the case?

I just don't really see where that last argument is useful or at least how it helps with multiple sockets. If I have multiple sockets, I still need to call BeginReceive on each of them, right?

+2  A: 

What is the point of passing a state to the BeginReceive method if we're only working with a single socket? Is it to avoid using a class field? It seems like there's little point in doing it, but maybe I'm missing something.

You're right that if you didn't use the state then you'd have to use a member instead. But this is less local than a state variable. The more local things are, the less likely they are to break when you make changes in other parts of the code.

Compare it to ordinary method calls. Why don't we just set the parameters as members and then call all functions without any parameters? It would work... but it would be horrible to read the code. By keeping the scope as local as possible it makes the design easier to understand and modify. The improved encapsulation leads to more robust code.

The same applies here. If you only have one asynchoronous callback then you can probably get away with just setting a member on the class but if you have many such calls then this strategy will soon lead to similar problems as described above - confusing and fragile code.

How can the state parameter help when dealing with multiple sockets?

You can pass a different state object for each call, each containing its own client object. Note that the client is fetched from the state, not from a member variable:

//Socket client = this.client; // Don't do this.
Socket client = state.workSocket; 

If you notice all the other methods in the MSDN documentation take a Client as parameter. The state is just a way of passing parameters since the method signature is fixed.


Update: Regarding your question in the comments, .NET checks that you use the correct client object and if not throws an ArgumentException. From decompiling EndReceive in .NET Reflector we see this:

if ((result == null) || (result.AsyncObject != this))
{
    throw new ArgumentException(SR.GetString("net_io_invalidasyncresult"), "asyncResult");
}
Mark Byers
And the programmer is responsible for making sure that the socket contained in the state object is the same as the socket object from which `BeginReceive` was called, correct? I'm guessing if they're different, that will cause errors.
IVlad
It's not required - .NET guarantees that the state will be transferred reliably even if you have multiple calls simultaneously. When you call an ordinary function you don't have to check that the parameters you received are the same objects as the caller used to call you. If you did, being able to pass parameters would have no practical value as you'd have to have a reference to the original object anyway in order to compare them.
Mark Byers
That's not what I mean. For example, consider this: `Socket client = state.workSocket;` inside the `BeginReceive` callback function. Consider that socket `A` called `BeginReceive` passing it a `StateObject state` after doing `state.workSocket = B`. This is only valid if `A == B`, right? I'm a bit confused because all the examples pass the same socket to the state that's used to call the receive method, yet none of them explicitly say that the two must be the same. I think they should, but I'd like to make sure this is the case.
IVlad
@IVlad: Yes, they have to be the same. If you use the wrong client .NET will notice this and throw an `ArgumentException`. See my update to the answer (near the bottom).
Mark Byers
+1  A: 

How can the state parameter help when dealing with multiple sockets?

I think the misunderstanding here is what the state parameter is for exactly; when calling:

client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
    ReceiveCallback, state);

Of course, at the site you make the call it is clear what 'client' the call is made on, but when the ReceiveCallback method is called, that method has no way of knowing the client. Note that many receives on different sockets may be in flight at the same time, while all sharing the same callback handler to process the results.

When passing data into the state parameter, you have an opportunity to pass additional context to the callback so that it can figure out (in this case) which socket the receive was started on.

If you pass 'the wrong value' in the state parameter, then there is obviously nothing the callback can do to protect you... the consequences depend on what you do with the data in the state. Best-case it could not make a difference if the state is not really used, worst-case, the data could be processed as if it came from the wrong socket and you might end up executing that 'delete all posts' command in the context of an account that did not make the request. But that is no different from any other programming bug ;)

jerryjvl