In what order is the Socket.BeginReceive/EndReceive functions called?
For instance, I call BeginReceive twice, once to get the message length and the second time to get the message itself. Now the scenario is like that, for every message I send, I start waiting for its completion (actually acknowledgment of the message sent, also I wait for the action's completion after receiving the acknowledgment), so I call BeginReceive with each BeginSend, but in each BeginReceive's callback, I check if I'm receiving the length or the message. If I'm receiving the message and have received it completely, then I call another BeginReceive to receive the completion of the action. Now this is where things get out of sync. Because one of my receive callback is receiving bytes which it interprets as the length of them message when in fact it is the message itself.
Now how do I resolve it?
EDIT: This is a C#.NET question :)
Here is the code, basically it is too big, sorry for that
public void Send(string message)
{
try
{
bytesSent = 0;
writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
writeDataBuffer = WrapMessage(writeDataBuffer);
messageSendSize = writeDataBuffer.Length;
clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
new AsyncCallback(SendComplete), clientSocket);
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void WaitForData()
{
try
{
if (!messageLengthReceived)
{
clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
}
}
public void Send(string message)
{
try
{
bytesSent = 0;
writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
writeDataBuffer = WrapMessage(writeDataBuffer);
messageSendSize = writeDataBuffer.Length;
clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
new AsyncCallback(SendComplete), clientSocket);
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void WaitForData()
{
try
{
if (! messageLengthReceived)
{
clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
}
else
{
clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, messageLength - bytesReceived,
SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
}
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void RecieveComplete(IAsyncResult result)
{
try
{
Socket socket = result.AsyncState as Socket;
bytesReceived = socket.EndReceive(result);
if (! messageLengthReceived)
{
if (bytesReceived != MESSAGE_LENGTH_SIZE)
{
WaitForData();
return;
}
// unwrap message length
int length = BitConverter.ToInt32(receiveDataBuffer, 0);
length = IPAddress.NetworkToHostOrder(length);
messageLength = length;
messageLengthReceived = true;
bytesReceived = 0;
// now wait for getting the message itself
WaitForData();
}
else
{
if (bytesReceived != messageLength)
{
WaitForData();
}
else
{
string message = Encoding.ASCII.GetString(receiveDataBuffer);
MessageBox.Show(message);
bytesReceived = 0;
messageLengthReceived = false;
// clear buffer
receiveDataBuffer = new byte[AsyncClient.BUFFER_SIZE];
WaitForData();
}
}
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void SendComplete(IAsyncResult result)
{
try
{
Socket socket = result.AsyncState as Socket;
bytesSent = socket.EndSend(result);
if (bytesSent != messageSendSize)
{
messageSendSize -= bytesSent;
socket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
new AsyncCallback(SendComplete), clientSocket);
return;
}
// wait for data
messageLengthReceived = false;
bytesReceived = 0;
WaitForData();
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
The order in time should be:
BeginReceive
for message lengthEndReceive
for the completion of #1BeginReceive
for the message bodyEndReceive
for the completion of #3E.g. not using callbacks you could have:
var sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res = socket.EndReceive(sync);
sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res2 = socket.EndReceive(sync);
But then, you would be better just using Receive
!
I think you might find it easier to use separate handlers for the two different receives:
... Start(....) {
sync = socket.BeginReceive(.... MessageLengthReceived, null);
}
private void MessageLengthReceived(IAsyncResult sync) {
var len = socket.EndReceive(sync);
// ... set up buffer etc. for message receive
sync = socket.BeginReceive(... MessageReceived, null);
}
private void MessageReceived(IAsyncResult sync) {
var len = socket.EndReceive(sync);
// ... process message
}
Ultimately putting all the associated in a state object and passing that around (in the completion delegate access via IAsyncResult.AsyncState
) from BeginReceive can make things easier, but does take a shift from the linear thinking of imperative code and fulling embracing a event driven approach.
2012 Addendum:
With the async support in C#5 there is a new option. This uses the compiler to generate the manual continuations (the separate callback methods) and closures (state) from inline code. However there are two things to work around:
While System.Net.Sockets.Socket
has various …Async
methods these are for the event based asynchronous pattern, not the Task
based pattern that C#5's await
uses. Solution: use TaskFactory.FromAsync
to get a single Task<T>
from a Begin…
End…
pair.
TaskFactory.FromAsync
only supports passing up to three additional arguments (in addition to the callback and state) to Begin…
. Solution: a lambda taking zero additional arguments has the right signature, and C# will give us the right closure to pass the arguments in.
Hence (and more fully realised with Message
being another type that handles the conversion from an initial send of the length encoded in some fixed number of bytes then the content bytes into a length for the content's buffer):
private async Task<Message> ReceiveAMessage() {
var prefix = new byte[Message.PrefixLength];
var revcLen = await Task.Factory.FromAsync(
(cb, s) => clientSocket.BeginReceive(prefix, 0, prefix.Length, SocketFlags.None, cb, s),
ias => clientSocket.EndReceive(ias),
null);
if (revcLen != prefix.Length) { throw new ApplicationException("Failed to receive prefix"); }
int contentLength = Message.GetLengthFromPrefix(prefix);
var content = new byte[contentLength];
revcLen = await Task.Factory.FromAsync(
(cb, s) => clientSocket.BeginReceive(content, 0, content.Length, SocketFlags.None, cb, s),
ias => clientSocket.EndReceive(ias),
null);
if (revcLen != content.Length) { throw new ApplicationException("Failed to receive content"); }
return new Message(content);
}