TCP无保护消息边界的解决

3870阅读 0评论2015-12-22 星闪夜空
分类:LINUX

      我们都知道,TCP协议是面向流的。面向流是指无保护消息边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中会接收两个或者更多的数据包。

      那什么是保护消息边界呢?就是指传输协议把数据当做一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。举个例子来说,连续发送三个数据包,大小分别是1k,2k,4k,这三个数据包都已经到达了接收端的缓冲区中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,则必须有三次接收动作,才能把所有数据包接受完。而使用TCP协议,只要把接收数据的缓冲区大小设置在7kb以上,就能够一次把所有的数据包接收下来,即只需要有一次接收动作。

      这样问题就来了,由于TCP协议是流传输的,它把数据当作一串数据流,所以他不知道消息的边界,即独立的消息之间是如何被分隔开的。这便会造成消息的混 淆,也就是说不能够保证一个Send方法发出的数据被一个Recive方法读取。在讲解缓冲区的时候,我们已经讲过Recive方法是从系统缓冲区上读取数据的,所以只要数据缓冲区的容量足够大,该方法不单单接收第一个包的数据,可能是所有的数据。

      例如,有两台网络上的计算机,客户机发送的消息是:第一次发送abcde,第二次发送12345,服务器方接收到的可能是abcde12345,即一次性收完;也可能是第一次接收到abc,第二次接收到de123,第三次接收到45.

      针对这个问题,一般有3种解决方案:

      (1)发送固定长度的消息

      (2)把消息的尺寸与消息一块发送

      (3)使用特殊标记来区分消息间隔

     

下面我们主要分析下前两种方法:

1、发送固定长度的消息
     这种方法的好处是它非常容易,而且只要指定好消息的长度,没有遗漏未发的数据,我们重写了一个SendMessage方法。代码如下:

点击(此处)折叠或打开

  1. private static int SendMessage(Socket s, byte[] msg)
  2. {
  3.     int offset = 0;
  4.     int size = msg.Length;
  5.     int dataleft = size;

  6.     while (dataleft > 0)
  7.     {
  8.         int sent = s.Send(msg, offset, SocketFlags.None);
  9.         offset += sent;
  10.         dataleft -= sent;
  11.     }

  12.     return offset;
  13. }

      简要分析一下这个函数:形参s是进行通信的套接字,msg即待发送的字节数组。该方法使用while循环检查是否还有数据未发送,尤其当发送一个很庞大的数据包,在不能一次性发完的情况下作用比较明显。特别的,用sent来记录实际发送的数据量,和recv是异曲同工的作用,最后返回发送的实际数据总数。

      有sentMessage函数后,还要根据指定的消息长度来设计一个新的Recive方法。代码如下:

点击(此处)折叠或打开

  1. private byte[] ReciveMessage(Socket s, int size)
  2. {
  3.     int offset = 0;
  4.     int recv;
  5.     int dataleft = size;
  6.     byte[] msg = new byte[size];

  7.     while (dataleft > 0)
  8.     {
  9.         //接收消息
  10.         recv = s.Receive(msg, offset, dataleft, 0);
  11.         if (recv == 0)
  12.             break;
  13.         offset += recv;
  14.         dataleft -= recv;
  15.      }

  16.      return msg;
  17. }


     以上这种做法比较适合于消息长度不是很长的情况。

2、消息长度与消息一同发送

     我们可以这样做:通过使用消息的整形数值来表示消息的实际大小,所以要把整形数转换为字节类型。下面是发送变长消息的SendMessage方法。具体代码如下:

点击(此处)折叠或打开

  1. private static int SendMessage(Socket s, byte[] msg)
  2. {
  3.     int offset = 0;
  4.     int sent;
  5.     int size = msg.Length;
  6.     int dataleft = size;
  7.     byte[] msgsize = new byte[2];

  8.     //将消息尺寸从整形转换成可以发送的字节型
  9.     msgsize = BitConverter.GetBytes(size);

  10.     //发送消息的长度信息
  11.     sent = s.Send(msgsize);

  12.     while (dataleft > 0)
  13.     {
  14.         sent = s.Send(msg, offset, dataleft, SocketFlags.None);

  15.         //设置偏移量
  16.         offset += sent;
  17.         dataleft -= sent;
  18.     }

  19.     return offset;
  20. }
     下面是接收变长消息的ReciveVarMessage方法。代码如下:

点击(此处)折叠或打开

  1. private byte[] ReciveVarMessage(Socket s)
  2. {
  3.     int offset = 0;
  4.     int recv;
  5.     byte[] msgsize = new byte[2];

  6.     //接收2个字节大小的长度信息
  7.     recv = s.Receive(msgsize, 0, 2, 0);

  8.     //将字节数组的消息长度信息转换为整形
  9.     int size = BitConverter.ToInt16(msgsize);
  10.     int dataleft = size;
  11.     byte[] msg = new byte[size];

  12.     while (dataleft > 0)
  13.     {
  14.         //接收数据
  15.         recv = s.Receive(msg, offset, dataleft, 0);
  16.         if (recv == 0)
  17.             break;

  18.         offset += recv;
  19.         dataleft -= recv;
  20.     }

  21.     return msg;
  22. }

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/xinshi9608/archive/2010/12/31/6109511.aspx
上一篇:kernel 3.10内核源码分析--slab原理及相关代码
下一篇:Linux网络编程:原始套接字的魔力【上】