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