① tcp是個流協議,它存在粘包問題
tcp是乙個基於位元組流的傳輸服務,"流"意味著tcp所傳輸的資料是沒有邊界的。這不同於udp提供基於訊息的傳輸服務,其傳輸的資料是有邊界的。tcp的傳送方無法保證對等方每次接收到的是乙個完整的資料報。主機a向主機b傳送兩個資料報,主機b的接收情況可能是
產生粘包問題的原因有以下幾個:
這些情況都會導致乙個完整的應用層資料被分割成多次傳送,導致接收對等方不是按完整資料報的方式來接收資料。
② 粘包的問題的解決思路
粘包問題的最本質原因在與接收對等方無法分辨訊息與訊息之間的邊界在哪。我們通過使用某種方案給出邊界,例如:
③ 粘包解決方案一:使用定長包
這裡需要封裝兩個函式:
ssize_t readn(int fd, void *buf, size_t count)
ssize_t writen(int fd, void *buf, size_t count)
這兩個函式的引數列表和返回值與read
、write
一致。它們的作用的讀取/寫入count個位元組後再返回。其實現如下:
ssize_t readn(int fd, void *buf, size_t count)
return -1;
}if(readbytes == 0)//讀到了eof
left -= readbytes;
ptr += readbytes ;
}return count ;}/*
writen 函式
寫入count位元組的資料
*/ssize_t writen(int fd, void *buf, size_t count)
else if(writebytes == 0)
continue;
left -= writebytes;
ptr += writebytes;
}return count;
}有了這兩個函式之後,我們就可以使用定長包來傳送資料了,我抽取其關鍵**來講訴:
char readbuf[512];
readn(conn,readbuf,sizeof(readbuf)); //每次讀取512個位元組
同理的,寫入的時候也寫入512個位元組
char writebuf[512];
fgets(writebuf,sizeof(writebuf),stdin);
writen(conn,writebuf,sizeof(writebuf);
每個訊息都以固定的512位元組(或其他數字,看你的應用層的緩衝區大小)來傳送,以此區分每乙個資訊,這便是以固定長度解決粘包問題的思路。定長包解決方案的缺點在於會導致增加網路的負擔,無論每次傳送的有效資料是多大,都得按照定長的資料長度進行傳送。
④ 粘包解決方案二:使用結構體,顯式說明資料部分的長度
在這個方案中,我們需要定義乙個『struct packet』包結構,結構中指明資料部分的長度,用四個位元組來表示。傳送端的對等方接收報文時,先讀取前四個位元組,獲取資料的長度,由長度來進行資料的讀取。定義乙個結構體
struct packet
讀寫過程如下所示,這裡抽取關鍵**進行說明:
//傳送資料過程
struct packet writebuf;
memset(&writebuf,0,sizeof(writebuf));
while(fgets(writebuf.data,sizeof(writebuf.data),stdin)!=null)
下面是讀取資料的過程,先讀取msglen欄位,該欄位指示了有效資料data的長度。依據該字段再讀出data。
memset(&readbuf,0,sizeof(readbuf));
int ret = readn(conn,&readbuf.msglen,4); //先讀取四個位元組,確定後續資料的長度
if(ret == -1)
else if(ret == 0)
int databytes = ntohl(readbuf.msglen); //位元組序的轉換
int readbytes = readn(conn,readbuf.data,databytes); //讀取出後續的資料
if(readbytes == 0)
if(readbytes<0)
⑤ 粘包解決方案三:按行讀取
ftp協議採用/r/n來識別乙個訊息的邊界,我們在這裡實現乙個按行讀取的功能,該功能能夠按/n來識別訊息的邊界。這裡介紹乙個函式:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
與read函式相比,recv函式的區別在於兩點:
recv函式只能夠用於套介面io。
recv函式含有flags引數,可以指定一些選項。
recv函式的flags引數常用的選項是:
msg_oob 接收帶外資料,即通過緊急指標傳送的資料
msg_peek 從緩衝區中讀取資料,但並不從緩衝區中清除所讀資料
為了實現按行讀取,我們需要使用recv函式的msg_peek選項。peek的意思是"偷看",我們可以理解為窺視,看看socket的緩衝區內是否有某種內容,而清除緩衝區。
/** 封裝了recv函式
返回值說明:-1 讀取出錯
*/ssize_t read_peek(int sockfd,void *buf ,size_t len)
}下面是按行讀取的**:
/**讀取一行內容
* 返回值說明:
== 0 :對端關閉
== -1 : 讀取錯誤
其他:一行的位元組數,包含\n
* **/
ssize_t readline(int sockfd ,void * buf ,size_t maxline)
}//如果嗅探到沒有\n
//那麼先將這一段沒有\n的讀取出來
ret = readn(sockfd , pbuf , nread);
if(ret != nread)
exit(exit_failure);
pbuf += nread ;
left -= nread ;
count += nread;
}return -1;
}
Socket程式設計 TCP粘包問題及解決方案
tcp是個流協議,它存在粘包問題 tcp是乙個基於位元組流的傳輸服務,流 意味著tcp所傳輸的資料是沒有邊界的。這不同於udp提供基於訊息的傳輸服務,其傳輸的資料是有邊界的。tcp的傳送方無法保證對等方每次接收到的是乙個完整的資料報。主機a向主機b傳送兩個資料報,主機b的接收情況可能是 產生粘包問題...
Socket粘包問題
這兩天看csdn有一些關於socket粘包,socket緩衝區設定的問題,發現自己不是很清楚,所以查資料了解記錄一下 一兩個簡單概念長連線與短連線 1.長連線 client方與server方先建立通訊連線,連線建立後不斷開,然後再進行報文傳送和接收。2.短連線 client方與server每進行一次...
Socket粘包問題
這兩天看csdn有一些關於socket粘包,socket緩衝區設定的問題,發現自己不是很清楚,所以查資料了解記錄一下 一兩個簡單概念長連線與短連線 1.長連線 client方與server方先建立通訊連線,連線建立後不斷開,然後再進行報文傳送和接收。2.短連線 client方與server每進行一次...