views:

3452

answers:

6

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);
    }
}
+1  A: 

Usually BeginXXX methods indicate an asynchronous operation, and you seem to want to do it in a synchronous manner.

If indeed you want a synchronous client/server maybe this will help http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

Mihai Lazar
No, I want it asynchronous. Thanks
hab
Manzoor, you can perform synchronous I/O on another Thread. Much easier and more reliable.
Henk Holterman
Ok lemme give it a try
hab
+3  A: 

The order in time should be:

  1. BeginReceive for message length
  2. EndReceive for the completion of #1
  3. BeginReceive for the message body
  4. EndReceive for the completion of #3

E.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.

Richard
Did you spot what I was doing wrong? :( Can you tell me how should I improve my current code
hab
@Manzoo: not directly, which is why I suggested a simpler approach of keeping different operations (message length and message body). Simpler code will be easier to debug.
Richard
@Richard: Can you provide an example which uses state object as you mentioned
hab
@Manzoo: That would take more time than I have (especially to verify it). Essentially: put all the state (buffers, socket, state flag) into a helper type. Pass that from operation to operation rather than using global state.
Richard
+2  A: 

Perhaps what you want to do is chain your call-backs :

pseudo code:



// read the first 2 bytes as message length
BeginReceive(msg,0,2,-,-,new AsyncCallback(LengthReceived),-)

LengthReceived(ar) {
  StateObject so = (StateObject) ar.AsyncState;
  Socket s = so.workSocket;
  int read = s.EndReceive(ar);
  msg_length = GetLengthFromBytes(so.buffer);
  BeginReceive(so.buffer,0,msg_length,-,-,new AsyncCallback(DataReceived),-)
}

DataReceived(ar) {
  StateObject so = (StateObject) ar.AsyncState;
  Socket s = so.workSocket;
  int read = s.EndReceive(ar);
  ProcessMessage(so.buffer);
  BeginReceive(so.buffer,0,2,-,-,new AsyncCallback(LengthReceived),-)
}

see: http://msdn.microsoft.com/en-us/library/system.asynccallback.aspx for correct examples

Timothy Pratley
A: 

It will help if you describe the structure of the message you are sending.

As long as you have only one BeginReceive() outstanding, it will complete and give you the next available bytes of data on the wire. If you have more than one outstanding at the same time, then all bets are off, because .net does not guarantee that the completion will be in any given order.

feroze
A: 

Could you accomplish this? I am having same problem. its been long time since you asked this question. I suppose you found a solution. can you give me some help? thanks

sawyer
A: 

As the others have said, don't use global variables here - use a class for socket state. Something like:

public class StateObject
{
    public const int DEFAULT_SIZE = 1024;           //size of receive buffer

    public byte[] buffer = new byte[DEFAULT_SIZE];  //receive buffer
    public int dataSize = 0;                        //data size to be received
    public bool dataSizeReceived = false;           //received data size?
    public StringBuilder sb = new StringBuilder();  //received data String
    public int dataRecieved = 0;

    public Socket workSocket = null;                //client socket.
    public DateTime TimeStamp;                      //timestamp of data
} //end class StateObject

Before trying to resend the message, you should verify the socket... you might have a socket exception.

You should probably have a return; after your WaitForData call in the "if" block of ReceiveComplete.

Timothy Pratley said it above, one error will be in bytesRecieved the second time through. Each time you are only measuring the bytesReceived from that EndReceive and then comparing it to messageLength. You need to keep a sum of all bytesRecieved.

And your biggest error is that in your first call to ReceiveComplete, you take account for the fact that the message might (most likely) contain more data than just the size of the message - it will probably contain half the message as well. You need to peel off the data size, and then also store the rest of the message in your message variable.

Jess