C#Socket.BeginReceive / EndReceive

以什么顺序调用Socket.BeginReceive / EndReceive函数?

例如,我调用BeginReceive两次,一次获取消息长度,第二次调用消息本身。 现在的情况是这样的,对于我发送的每条消息,我开始等待它的完成(实际上确认发送的消息,我等待动作在收到确认后完成),所以我用每个BeginSend调用BeginReceive ,但是每个BeginReceive的回调,我检查我是否收到长度或消息。 如果我收到消息并完全收到消息,那么我会调用另一个BeginReceive来接收完成的操作。 现在这是事情不同步的地方。 因为我的一个接收回调是接收字节,它将其解释为消息的长度,实际上它是消息本身。

现在我该如何解决?

编辑:这是一个C#.NET问题:)

这是代码,基本上它太大了,对不起

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. BeginReceive用于消息长度
  2. EndReceive完成#1
  3. 邮件正文的BeginReceive
  4. EndReceive完成#3

例如,不使用你可以拥有的回调:

 var sync = socket.BeginReceive(....); sync.AsyncWaitHandle.WaitOne(); var res = socket.EndReceive(sync); sync = socket.BeginReceive(....); sync.AsyncWaitHandle.WaitOne(); var res2 = socket.EndReceive(sync); 

但是,你最好只使用Receive

我想你可能会发现为两个不同的接收使用单独的处理程序更容易:

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

最终将所有关联放在一个状态对象中并从BeginReceive传递它(通过IAsyncResult.AsyncState完成委托访问)可以使事情变得更容易,但确实从命令式代码的线性思维转变并完全采用事件驱动的方法。


2012年附录

.NET 4.5版本

通过C#5中的异步支持,有一个新选项。 这使用编译器从内联代码生成手动延续(单独的回调方法)和闭包(状态)。 但是有两件事要解决:

  1. 虽然System.Net.Sockets.Socket有各种…Async方法,但这些方法适用于基于事件的异步模式,而不是C#5 await使用的基于Task的模式。 解决方案:使用TaskFactory.FromAsyncBegin… End…对获取单个Task

  2. TaskFactory.FromAsync仅支持向Begin…传递最多三个附加参数(除了回调和状态)。 解决方案:一个带有零个额外参数的lambda具有正确的签名,而C#将为我们提供正确的闭包来传递参数。

因此(并且更充分地实现了Message是另一种类型,它处理从以某个固定数量的字节编码的长度的初始发送转换然后将内容字节转换为内容缓冲区的长度):

 private async Task 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); } 

也许你想要做的就是把你的回调链接起来:

伪代码:

 // 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),-) } 

请参阅: http : //msdn.microsoft.com/en-us/library/system.asynccallback.aspx以获取正确的示例

通常,BeginXXX方法表示异步操作,您似乎希望以同步方式执行此操作。

如果你确实想要一个同步客户端/服务器,这可能有助于http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

如果您描述要发送的消息的结构,它将有所帮助。

只要你只有一个BeginReceive()未完成,它就会完成并在线上为你提供下一个可用的数据字节。 如果您同时有多个未结,则所有投注均已关闭,因为.net不保证完成将按任何给定顺序完成。

正如其他人所说,不要在这里使用全局变量 – 使用类作为套接字状态。 就像是:

 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 

在尝试重新发送消息之前,您应该validation套接字…您可能有套接字exception。

你应该有回报; 在WaitCoData调用ReceiveComplete的“if”块之后。

Timothy Pratley上面说过,一个错误将以字节为单位第二次通过。 每次只测量从EndReceive获取的bytesReceived,然后将它与messageLength进行比较。 你需要保留所有bytesRecieved的总和。

您最大的错误是,在您第一次调用ReceiveComplete时,您会考虑到这样的事实:消息可能(很可能)包含的数据多于消息的大小 – 它可能也包含一半消息。 您需要剥离数据大小,然后将消息的其余部分存储在消息变量中。