TCP 粘包解決
TCP 粘包:
什麼是粘包現象 :
TCP粘包是指傳送方傳送的若干包資料到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾。
為什麼出現粘包現象 :
(1) 傳送方原因 我們知道,TCP預設會使用Nagle演算法。而Nagle演算法主要做兩件事:
1)只有上一個分組得到確認,才會傳送下一個分組;
2)收集多個小分組,在一個確認到來時一起傳送。所以,正是Nagle演算法造成了傳送方有可能造成粘包現象。 由於TCP協議本身的機制(面向連線的可靠地協議-三次握手機制)客戶端與伺服器會維持一個連線(Channel),資料在連線不斷開的情況下,可以持續不斷地將多個數據包發往伺服器
(2) 接收方原因
什麼時候需要處理粘包現象 :
(1) 如果傳送方傳送的多個分組本來就是同一個資料的不同部分,比如一個很大的檔案被分成多個分組傳送,這時,當然不需要處理粘包的現象;
(2) 但如果多個分組本毫不相干,甚至是並列的關係,我們就一定要處理粘包問題了。比如,我當時要接收的每個分組都是一個有固定格式的商品資訊
如何處理粘包現象 :
(1) 傳送方
對於傳送方造成的粘包現象,我們可以通過關閉Nagle演算法來解決,使用TCP_NODELAY選項來關閉Nagle演算法。
(2) 接收方
遺憾的是TCP並沒有處理接收方粘包現象的機制,我們只能在應用層進行處理。
(3) 應用層處理
應用層的處理簡單易行!並且不僅可以解決接收方造成的粘包問題,還能解決傳送方造成的粘包問題。
解決方法就是迴圈處理:應用程式在處理從快取讀來的分組時,讀完一條資料時,就應該迴圈讀下一條資料,直到所有的資料都被處理;但是如何判斷每條資料的長度呢? 解決方法:
1、關閉TCP套接字的NAGL的演算法(效率低下)
2、每傳送一次,等待對方收到才傳送下一條。(效率低下)
3、回答機制:
4、在資料之前新增特殊訊號或長度:
[ 5 ] abcefg
[13] 1224323432423
解決TCP粘包的伺服器的程式碼實現 :
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
//伺服器
int main()
{
//1建立套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket fail");
return -1;
}
socklen_t slen=sizeof(socklen_t);
if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&slen,sizeof(slen))<0)
{
perror("setsockopt fail");
return -1;
}
//2繫結 /*先填充 再繫結bind*/
struct sockaddr_in myaddr;
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family =AF_INET;
myaddr.sin_port =htons(7979);
myaddr.sin_addr.s_addr =INADDR_ANY;
if(bind(sock,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("bind fail");
return -1;
}
//3監聽
if(listen(sock,1)<0)
{
perror("listen fail");
return -1;
}
int newsock=accept(sock,NULL,NULL);
//sleep(5);//在10秒的睡眠時,客戶分三次傳送1 2 3字串。
char buf[100]="";
int ilen=0;
short num=0;
int total=0;
char* p=NULL;
while(1)
{
total=0;
p=(char*)#
bzero(buf,sizeof(buf));
//讀取長度 [5] abcefg 相當於讀取的括號中的 5
while(total<2)//1 2
{
ilen=recv(newsock,p+total,1,0);
total+=ilen;
}
//讀取字元內容 [5] abcefg 相當於讀取 abcefg 的資料
total=0;
while(total<num)
{
ilen=recv(newsock,buf+total,1,0);
total+=ilen;
}
printf("收到%d位元組 內容:%s\n",num,buf);
}
close(sock);
return 0;
}
伺服器讀取內容的效果 , 完整的將客戶端傳送過來的資料進行了讀取 :
客戶端傳送資料的程式碼 :
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
//伺服器
int main()
{
//1建立
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("fail\n");
return -1;
}
//2繫結
//3傳送連線
struct sockaddr_in myaddr;
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family =AF_INET;
myaddr.sin_port =htons(7979);
myaddr.sin_addr.s_addr =inet_addr("127.0.0.1");
if(connect(sock,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("連線失敗");
return -1;
}
//4傳送資訊
short ilen=5; //每次傳送資料前先發送資料長度 如: [5] abcefg
send(sock,"12345",5,0);
ilen=17;
send(sock,&ilen,2,0);
send(sock,"abcdefghijklmnopq",17,0);
ilen=12;
send(sock,&ilen,2,0);
send(sock,"How are you!",12,0);
sleep(5);
//3關閉
close(sock);
}
我們再來看看現象 :
結果將資料完好的讀取了出來 , 是因為一個位元組傳送 , 一個位元組讀取的所以沒有出現粘包現象 :
我們再來看看粘包的現象 , 這次傳送過來的是結構體資料 , 而伺服器需要讀取結構體的資料 .
結果 出現粘包現象 :
怎麼解決上面出現的粘包現象 , 只需要將客戶端傳送的資料 sleep(2) , 再把伺服器的 sleep(5)註釋掉 , 解決了 , 每次等傳送一個結構體就等待 , 直到傳送完畢再發送第二個結構體 , 就沒有出現粘包現象 .
結果 資料完好的讀取了出來 :