调用Socket.BeginReceive / EndReceive函数的顺序是什么?
例如,我调用 BeginReceive 两次,一次获取消息长度,第二次调用消息本身 . 现在的情况是这样的,对于我发送的每条消息,我开始等待它的完成(实际上确认发送的消息,我等待接收到确认后的动作完成),所以我用 BeginReceive 调用 BeginReceive ,但是每个 BeginReceive 's callback, I check if I' m接收长度或消息 . 如果我收到消息并完全收到消息,那么我再拨打另一个 BeginReceive 来接收完成的动作 . 现在这是事情不同步的地方 . 因为我的一个接收回调是接收字节,它将其解释为消息的长度,实际上它是消息本身 .
现在我该如何解决?
EDIT: 这是一个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);
}
}
5 回答
时间顺序应该是:
消息长度为
BeginReceive
EndReceive
完成#1消息正文
BeginReceive
EndReceive
完成#3例如 . 不使用你可以拥有的回调:
但是,你最好只使用
Receive
!我想你可能会发现为两个不同的接收器使用单独的处理程序更容易:
最终将所有关联放在一个状态对象中并从BeginReceive传递它(通过
IAsyncResult.AsyncState
完成委托访问)可以使事情变得更容易,但确实从命令式代码的线性思维转变并完全采用事件驱动的方法 .2012年附录:
.NET 4.5版本
通过C#5中的异步支持,有一个新选项 . 这使用编译器从内联代码生成手动延续(单独的回调方法)和闭包(状态) . 但是有两件事要解决:
虽然
System.Net.Sockets.Socket
有各种…Async
方法,但它们用于基于事件的异步模式,而不是C#5的await
使用的基于Task
的模式 . 解决方案:使用TaskFactory.FromAsync
从Begin…
End…
对中获取单个Task<T>
.TaskFactory.FromAsync
仅支持将最多三个附加参数(除了回调和状态)传递给Begin…
. 解决方案:一个带有零个额外参数的lambda具有正确的签名,而C#将为我们提供正确的闭包来传递参数 .因此(并且更充分地实现了
Message
是另一种类型,它处理从以某个固定数量的字节编码的长度的初始发送转换然后将内容字节转换为内容缓冲区的长度):也许你想要做的就是连接你的回调:
伪代码:
请参阅: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不保证完成将按任何给定顺序完成 .
正如其他人所说,不要在这里使用全局变量 - 使用类作为套接字状态 . 就像是:
在尝试重新发送消息之前,您应该验证套接字...您可能有套接字异常 .
你可能应该有回报;在WaitCoData调用ReceiveComplete的“if”块之后 .
Timothy Pratley在上面说过,一个错误将以字节为单位第二次通过 . 每次只测量从EndReceive获取的bytesReceived,然后将它与messageLength进行比较 . 你需要保留所有bytesRecieved的总和 .
您最大的错误是,在您第一次调用ReceiveComplete时,您会考虑到这样的事实:消息可能(很可能)包含的数据多于消息的大小 - 它可能也包含一半消息 . 您需要剥离数据大小,然后将消息的其余部分存储在消息变量中 .