tcpip協(xié)議使用"流式"(套接字)進(jìn)行數(shù)據(jù)的傳輸,就是說它保證數(shù)據(jù)的可達(dá)以及數(shù)據(jù)抵達(dá)的順序,但并不保證數(shù)據(jù)是否在你接收的時候就到達(dá),特別是為了提高效率,充分利用帶寬,底層會使用緩存技術(shù),具體的說就是使用Nagle算法將小的數(shù)據(jù)包放到一起發(fā)送,但是這樣也帶來一個使用上的問題——黏包,黏包就是說一次將多個數(shù)據(jù)包發(fā)送出去,導(dǎo)致接收方不能進(jìn)行正常的解析,示意圖如下:
發(fā)生黏包一般有兩種原因,一種是發(fā)送方進(jìn)行了不該緩沖的緩沖,比如上圖中,收發(fā)雙方協(xié)議好按照一定的規(guī)則進(jìn)行編寫/解析報文,但是由于Nagle算法,可能出現(xiàn)發(fā)送方一次發(fā)送了1.5個數(shù)據(jù)包,而接收方只解析了前面的1個包,后面的0.5個由于數(shù)據(jù)不完整而解析失敗,造成數(shù)據(jù)的丟失或錯位,很可能會影響之后所有的數(shù)據(jù)解析工作。由于發(fā)送方導(dǎo)致的黏包問題可以使用setsockopt()來解決
int enable=1; setsockopt(sockfd,IPROTO_TCP,TCP_NODELAY,(void*)&enable,sizeof(enable))
這條指令可以禁止發(fā)送方使用Nagle算法,一組數(shù)據(jù)被寫入就會立即被發(fā)出,不需要等待mtu被填滿。
此外,接收方處理不當(dāng)也可能導(dǎo)致黏包問題,如果發(fā)送方將4個包發(fā)送到接收方的緩沖區(qū),但是由于頻繁的存取,可能有一次只取了2.5個包,就會導(dǎo)致黏包問題。接收方的黏包問題可以使用recv(sockfd,buf,sizeof(buf),MSG_WAITALL)來解決,MSG_WAITALL可以強(qiáng)制接收方收到sizeof(buf)那么多的數(shù)據(jù)才返回,而buf的大小可以是收發(fā)雙方約定好的大小。
發(fā)送一次發(fā)送這么多,接收一次接收這么多,就可以避免黏包問題。
上述方法可以解決帶有解析需求的黏包問題,對于不需要解析的需求,比如文件傳輸,發(fā)送方需要發(fā)送100kB的文件,接收方其實只關(guān)心最終接收到100KB沒有,至于中間的某次發(fā)送方發(fā)了100byte而接收方收到20byte并不會影響文件的傳輸,對于這樣的需求,一種更好的方案是發(fā)送方在發(fā)送文件數(shù)據(jù)之前先將文件的大小告知給接收方,接收方準(zhǔn)備好后一直讀取數(shù)據(jù),知道接收到文件的大小那么多的數(shù)據(jù)就自行終止寫文件。這樣就免去了不必要的解析,是否黏包已經(jīng)不影響功能了。具體實施可以參考這個迷你云存儲。