C# Socket.BeginReceive/EndReceive

旧街凉风 提交于 2019-11-27 12:56:05

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.


2012 Addendum:

.NET 4.5 Version

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:

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

  2. 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);
}

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

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

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!