我們都知道,TCP協議是面向流的。面向流是指無保護訊息邊界的,如果發送端連續發送資料,接收端有可能在一次接收動作中會接收兩個或者更多的資料包。
那什麼是保護訊息邊界呢?就是指傳輸協議把資料當做一條獨立的訊息在網上傳輸,接收端只能接收獨立的訊息。也就是說存在保護訊息邊界,接收端一次只能接收發送端發出的一個資料包。
舉個例子來說,連續發送三個資料包,大小分別是1k,2k,4k,這三個資料包都已經到達了接收端的緩衝區中,如果使用UDP協議,不管我們使用多大的接收緩衝區去接收資料,則必須有三次接收動作,才能把所有資料包接受完。而使用TCP協議,只要把接收資料的緩衝區大小設定在7kb以上,就能夠一次把所有的資料包接收下來,即只需要有一次接收動作。
這樣問題就來了,由於TCP協議是流傳輸的,它把資料當作一串資料流,所以他不知道訊息的邊界,即獨立的訊息之間是如何被分隔開的。這便會造成訊息的混淆,也就是說不能夠保證一個Send方法發出的資料被一個Recive方法讀取。在講解緩衝區的時候,我們已經講過Recive方法是從系統緩衝區上讀取資料的,所以只要資料緩衝區的容量足夠大,該方法不單單接收第一個包的資料,可能是所有的資料。
例如,有兩台網路上的電腦,客戶機發送的訊息是:第一次發送abcde,第二次發送12345,伺服器方接收到的可能是abcde12345,即一次性收完;也可能是第一次接收到abc,第二次接收到de123,第三次接收到45.
針對這個問題,一般有3種解決方案:
(1)發送固定長度的訊息
(2)把訊息的尺寸與訊息一塊發送
(3)使用特殊標記來區分訊息間隔
下面我們主要分析下前兩種方法:
1、發送固定長度的訊息
這種方法的好處是他非常容易,而且只要指定好訊息的長度,沒有遺漏未未發的資料,我們重寫了一個SendMessage方法。代碼如下:
private static int SendMessage(Socket s, byte[] msg)
{
int offset = 0;
int size = msg.Length;
int dataleft = size;
while (dataleft > 0)
{
int sent = s.Send(msg, offset, SocketFlags.None);
offset += sent;
dataleft -= sent;
}
return offset;
}
簡要分析一下這個函數:形參s是進行通訊的通訊端,msg即待發送的位元組數組。該方法使用while迴圈檢查是否還有資料未發送,尤其當發送一個很龐大的資料包,在不能一次性發完的情況下作用比較明顯。特別的,用sent來記錄實際發送的資料量,和recv是異曲同工的作用,最後返回傳送的實際資料總數。
有sentMessage函數後,還要根據指定的訊息長度來設計一個新的Recive方法。代碼如下:
private byte[] ReciveMessage(Socket s, int size)
{
int offset = 0;
int recv;
int dataleft = size;
byte[] msg = new byte[size];
while (dataleft > 0)
{
//接收訊息
recv = s.Receive(msg, offset, dataleft, 0);
if (recv == 0)
{
break;
}
offset += recv;
dataleft -= recv;
}
return msg;
}
以上這種做法比較適合於訊息長度不是很長的情況。
2、訊息長度與訊息一同發送
我們可以這樣做:通過使用訊息的整形數值來表示訊息的實際大小,所以要把整形數轉換為位元組類型。下面是發送變長訊息的SendMessage方法。具體代碼如下:
private static int SendMessage(Socket s, byte[] msg)
{
int offset = 0;
int sent;
int size = msg.Length;
int dataleft = size;
byte[] msgsize = new byte[2];
//將訊息尺寸從整形轉換成可以發送的位元組型
msgsize = BitConverter.GetBytes(size);
//發送訊息的長度資訊
sent = s.Send(size);
while (dataleft > 0)
{
sent = s.Send(msg, offset, dataleft, SocketFlags.None);
//設定位移量
offset += sent;
dataleft -= sent;
}
return offset;
}
下面是接收變長訊息的ReciveVarMessage方法。代碼如下:
private byte[] ReciveVarMessage(Socket s)
{
int offset = 0;
int recv;
byte[] msgsize = new byte[2];
//將位元組數組的訊息長度資訊轉換為整形
int size = BitConverter.ToInt16(msgsize);
int dataleft = size;
byte[] msg = new byte[size];
//接收2個位元組大小的長度資訊
recv = s.Receive(msgsize, 0, 2, 0);
while (dataleft > 0)
{
//接收資料
recv = s.Receive(msg, offset, dataleft, 0);
if (recv == 0)
{
break;
}
offset += recv;
dataleft -= recv;
}
return msg;
}
參考資料:http://aksnzhy.javaeye.com/blog/789049