Socket.BeginReceive / EndReceive函数的调用顺序是什么?
例如,我调用 BeginReceive 两次,一次获取消息长度,第二次调用消息本身。现在的情况是这样的,对于我发送的每条消息,我开始等待它的完成(实际上确认发送的消息,我等待收到确认后的动作完成),所以我调用 BeginReceive 每个 BeginSend ,但在每个 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);
}
}
答案 0 :(得分:19)
时间顺序应为:
BeginReceive
了解邮件长度EndReceive
完成#1 BeginReceive
EndReceive
完成#3 E.g。不使用你可以拥有的回调:
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年附录:
通过C#5中的异步支持,有一个新选项。这使用编译器从内联代码生成手动延续(单独的回调方法)和闭包(状态)。但是,有两件事可以解决:
虽然System.Net.Sockets.Socket
有各种…Async
方法,但这些方法适用于基于事件的异步模式,而不是C#5 Task
使用的基于await
的模式。解决方案:使用TaskFactory.FromAsync
从Task<T>
Begin…
对中获取一个End…
。
TaskFactory.FromAsync
仅支持向Begin…
传递最多三个附加参数(除了回调和状态)。解决方案:一个带有零个附加参数的lambda具有正确的签名,C#将为我们提供正确的闭包来传递参数。
因此(并且更充分地实现了Message
是另一种类型,它处理从以某个固定数量的字节编码的长度的初始发送转换为内容字节到内容缓冲区的长度的转换: / p>
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);
}
答案 1 :(得分:5)
也许你想要做的就是连结你的回调:
伪代码:
// 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了解正确的示例
答案 2 :(得分:1)
通常,BeginXXX方法表示异步操作,您似乎希望以同步方式执行此操作。
如果您确实需要同步客户端/服务器,这可能有助于http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html
答案 3 :(得分:1)
如果您描述要发送的邮件的结构,将会有所帮助。
只要您只有一个BeginReceive()未完成,它就会完成并在线上提供下一个可用的数据字节。如果您同时有多个未结,那么所有投注都将被取消,因为.net不保证完成将按任何给定的顺序进行。
答案 4 :(得分:0)
正如其他人所说,不要在这里使用全局变量 - 使用类作为套接字状态。类似的东西:
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
在尝试重新发送消息之前,您应该验证套接字...您可能有套接字异常。
你应该有回报;在WaitCoData调用ReceiveComplete的“if”块之后。
Timothy Pratley上面说过,一个错误将以字节为单位第二次通过。每次只测量从EndReceive获取的bytesReceived,然后将它与messageLength进行比较。你需要保留所有bytesRecieved的总和。
您最大的错误是,在您第一次调用ReceiveComplete时,您会考虑到这样的事实:消息可能(很可能)包含的数据多于消息的大小 - 它可能包含一半消息。您需要剥离数据大小,然后将消息的其余部分存储在消息变量中。