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 AsyncCa开发者_运维技巧llback(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:
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.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 theclient
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?
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");
}
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 ;)
精彩评论