C# 在C语言中,通过套接字发送数据是一个非常奇怪的问题#
我对这篇冗长的文章表示歉意。我已经尽可能地将其缩小,同时仍在传达问题 好吧,这让我发疯了。我有一个客户端和一个服务器程序,都是C语言。服务器通过Socket.Send()向客户端发送数据。客户端通过Socket.BeginReceive和Socket.Receive接收数据。我的伪协议如下:服务器发送一个两字节(short)值,指示实际数据的长度,后跟实际数据。客户机异步读取前两个“是”,将字节转换为短字节,并立即同步从套接字读取这么多字节 现在,每隔几秒钟,这个循环就可以正常工作,但是当我提高速度时,事情会变得很奇怪。当客户端试图读取两字节长度时,它似乎会随机读取实际数据。然后,它尝试将这两个任意字节转换为一个短字节,这将导致一个完全不正确的值,从而导致崩溃。下面的代码来自我的程序,但经过了裁剪,只显示了重要的行 用于发送数据的服务器端方法:C# 在C语言中,通过套接字发送数据是一个非常奇怪的问题#,c#,arrays,sockets,byte,C#,Arrays,Sockets,Byte,我对这篇冗长的文章表示歉意。我已经尽可能地将其缩小,同时仍在传达问题 好吧,这让我发疯了。我有一个客户端和一个服务器程序,都是C语言。服务器通过Socket.Send()向客户端发送数据。客户端通过Socket.BeginReceive和Socket.Receive接收数据。我的伪协议如下:服务器发送一个两字节(short)值,指示实际数据的长度,后跟实际数据。客户机异步读取前两个“是”,将字节转换为短字节,并立即同步从套接字读取这么多字节 现在,每隔几秒钟,这个循环就可以正常工作,但是当我提高
private static object myLock = new object();
private static bool sendData(Socket sock, String prefix, byte[] data)
{
lock(myLock){
try
{
// prefix is always a 4-bytes string
// encoder is an ASCIIEncoding object
byte[] prefixBytes = encoder.GetBytes(prefix);
short length = (short)(prefixBytes.Length + data.Length);
sock.Send(BitConverter.GetBytes(length));
sock.Send(prefixBytes);
sock.Send(data);
return true;
}
catch(Exception e){/*blah blah blah*/}
}
}
用于接收数据的客户端方法:
private static object myLock = new object();
private void receiveData(IAsyncResult result)
{
lock(myLock){
byte[] buffer = new byte[1024];
Socket sock = result.AsyncState as Socket;
try
{
sock.EndReceive(result);
short n = BitConverter.ToInt16(smallBuffer, 0);
// smallBuffer is a 2-byte array
// Receive n bytes
sock.Receive(buffer, n, SocketFlags.None);
// Determine the prefix. encoder is an ASCIIEncoding object
String prefix = encoder.GetString(buffer, 0, 4);
// Code to process the data goes here
sock.BeginReceive(smallBuffer, 0, 2, SocketFlags.None, receiveData, sock);
}
catch(Exception e){/*blah blah blah*/}
}
}
可靠地重新创建问题的服务器端代码:
byte[] b = new byte[1020]; // arbitrary length
for (int i = 0; i < b.Length; i++)
b[i] = 7; // arbitrary value of 7
while (true)
{
sendData(socket, "PRFX", b);
// socket is a Socket connected to a client running the same code as above
// "PRFX" is an arbitrary 4-character string that will be sent
}
byte[]b=新字节[1020];//任意长度
for(int i=0;i
查看上面的代码,可以确定服务器将永远发送数字1024,即总数据的长度,包括前缀,作为一个短字符(0x400),后跟ASCII二进制中的“PRFX”,后跟一组7(0x07)。客户端将永远读取前两个字节(0x400),将其解释为1024,将该值存储为n,然后从流中读取1024个“是”
这确实是它在前40次迭代中所做的,但是,客户机会自发地读取前两个“是”,并将其解释为1799次,而不是1024次!十六进制中的1799是0x0707,这是两个连续的7!!!这是数据,不是长度!那两个字节怎么了?无论我在字节数组中输入什么值,都会发生这种情况,我只选择了7,因为很容易看到与1799的相关性
如果你还在阅读这一点,我为你的奉献精神鼓掌
一些重要的意见:
- 减少b的长度将增加问题发生前的迭代次数,但不会阻止问题的发生
- 在循环的每次迭代之间增加一个显著的延迟可以防止问题的发生
- 在同一主机上同时使用客户端和服务器并通过环回地址连接时,不会发生这种情况
谢谢。我猜
smallBuffer
是在receive方法之外声明的,正在从多个线程重复使用。换句话说,这不是线程安全的
编写好的套接字代码很难。由于您同时编写客户端和服务器,您可能希望了解一下(WCF),它可以让您以更少的麻烦发送和接收整个对象。我怀疑您是在假设整个消息在对
receiveData
的一次调用中传递。一般来说,情况并非如此。碎片、延迟等最终可能意味着数据以运球的形式到达,因此您可能会在只有900字节准备就绪时调用receiveData
函数。您的呼叫sock.Receive(buffer,n,SocketFlags.None)
表示“将数据放入缓冲区,最多为n
字节”-您可能会收到较少的数据,而实际读取的字节数由receive
返回
这就解释了为什么减少b
、增加更多延迟或使用同一主机似乎可以“修复”问题——使用这些方法,整个消息一次性到达的可能性大大增加(越小的b
,数据越小;添加延迟意味着管道中的总体数据越少;本地地址没有网络)
若要查看这是否确实是问题所在,请记录
Receive
的返回值。如果我的诊断正确,返回值有时会小于1024。若要解决此问题,您需要等待直到本地网络堆栈的缓冲区包含您的所有数据(不理想),或者只在数据到达时接收数据,将其存储在本地,直到整个消息准备就绪,然后再进行处理。使用套接字时,必须预料套接字传输的字节数可能比预期的少。必须循环.receive方法以获取剩余的字节数
通过套接字发送字节时也是如此。必须检查发送了多少字节,并在发送过程中循环,直到发送完所有字节
这种行为是由于网络层将消息拆分为多个数据包造成的。如果您的消息很短,则您不太可能遇到这种情况。但您应该始终为此编写代码
如果每个缓冲区有更多字节,您很可能会看到发送者的消息被分成多个数据包。每个读取操作都会收到一个数据包,这只是消息的一部分。但是小缓冲区也可以被拆分。我觉得这里的主要问题是没有检查EndReceive的结果,这是一个数字读取的字节数。如果套接字打开,则可以是任何内容
>0
和
您没有检查函数的返回值。套接字将返回最多“n”个字节,但只返回可用的字节。机会
sock.Receive(buffer, n, SocketFlags.None);