使用Socket遇到的问题--Problems with Tcp Messages
使用Tcp时注意:Tcp不提供Message boundaries,即不区分信息间的边界。
如果Client的2个Send(),一个发送200bytes,一个发送100个bytes,那么Server收到了300个Bytes放到了TcpBuffer,怎样区分这300个Bytes呢?怎样保证需要的2个Receive()可以收到所需要的信息呢?
Receive() 方法是从Tcp Buffer中读取数据,不是从Network直接读取数据。当从Network收到新的Tcp Packet时,Tcp Package顺序的放入Tcp Buffer中,当调用Receive()时,Receive()会读取Tcp Buffer中有的,可以装满Receive()的接收数组的信息.
2个要注意的问题:
1.不要在同一台计算机上测试Client程序和Server程序,因为同实际环境会有差别
2.假设Tcp会像我们在程序中定义的那样发送数据
//
如果要解决Tcp发送信息没有边界的问题,需要使用一些技巧,使远程计算机可以分辨信息边界:
1.一直使用固定大小的信息
2.每次先发送信息的实际大小
3.使用定界符
以上3种方法都可以使远程系统能够分辨信息边界,每种方法都有自己的优缺点。
对于方法1,这种方法最简单,却有最耗费网络资源;另一个优点是,如果收到多条信息,那么可以清楚地根据收到信息的字节数来分辨每条信息。
使用这种方法发送固定长度信息时,必须保证所有信息使用Send()发送完毕,不能假设Tcp会把信息完整的发送。依赖于Tcp Buffer的大小和要发送信息的大小,Send()有可能不会将所有信息发送出去的。Send()的返回值是实际发送到Tcp Buffer的字节数,根据这个返回的字节数,可以判断是否完整的发送了信息。可以使用一个简单的循环,来确保所有信息正确发送。 代码如下:
//
{
int total = 0;
int size = data.Length;
int dataleft = size;
int sent;
while (total < size)
{
sent = s.Send(data, total, dataleft, SocketFlags.None);
total += sent;
dataleft -= sent;
}
return total;
}
对于Receive(),同样,要确保收到预定义长度的字节,同样使用循环处理,代码如下:
//
{
int total = 0;
int dataleft = size;
byte[] data = new byte[size];
int recv;
while(total < size)
{
recv = s.Receive(data, total, dataleft, 0);
if (recv == 0)
{
data = Encoding.ASCII.GetBytes("exit ");
break;
}
total += recv;
dataleft -= recv;
byte[] ReceiveData(Socket s, int size)
{
int total = 0;
int dataleft = size;
byte[] data = new byte[size];
int recv;
while(total < size)
{
recv = s.Receive(data, total, dataleft, 0);
if (recv == 0)
{
data = Encoding.ASCII.GetBytes("exit ");
break;
}
total += recv;
dataleft -= recv;
}
return data;
}
使用固定长度数组发送,接收信息,对于比较小的信息,需要做填充处理,比较费资源。
好的解决办法是,发送不定长度的字节,但是处理Tcp边界是个问题,比较好的解决办法是,先用小的数组发送信息长度,然后发送信息,这就是方法2。
如果在信息头上加上4个字节的Integer数据标示发送信息长度,那么可以发送最大65K的数据。
注意Integer数据,转换为数组时使用byte[] bs=BitConvert.GetBytes(someinteger),收到后还原使用:int Len=BitConverter.ToInt32(bs,0);如果Server和Client是相同类型的机器,如intel based的windows系统,那么不会有问题,如果不是,那么可能会有问题,是Int类型数据在转换为字节数组用网络传输,转换的方式不同的缘故,即高位优先和低位优先的问题。
对于第3种方法,使用定界符:就是在每条信息的末尾加入特殊的字符,标记信息结束。当接收方收到数据后,需要每个字符的检查,找到定界符后,标示整条信息,后续的数据,又从新开始检查,这对于收到大的信息,检查比较耗费资源,而且定界符的定义必须明确,要保证传送内容中没有定界字符才可以。对于发送文本信息,C# 定义了一些类可以简单处理这种操作,如StreamReader的ReadLine()方法。
参考:
Sybex C# Network Programming