在實際編寫網絡通信代碼過程中,上層業務如何解析和使用收到的數據包?這是我們每個人都會遇到的問題。
其實這個問題也可以回答常用的面試題:如何解決數據的丟包、粘包、包不完整的問題。
首先,因為tcp協議是可靠的,所以不存在丟包問題,也不存在包順序錯亂問題(udp會存在這個問題,這個時候需要自己使用序號之類的機制保證了,這裡只討論tcp)。一般的做法是先收取一個固定大小的包頭信息,接著根據包頭裡面指定的包體大小來收取包體大小(這裡“收取”既可以從socket上收取,也可以在已經收取的數據緩衝區裡面拿取)。舉個例子:
看上面幾個協議,拿登錄請求來說,每次可以先收取一個包頭的大小,即sizeof(msg),然後根據msg.packagesize的大小再收取包體的大小sizeof(msg_login_req) - sizeof(msg),這樣就能保證一個包完整了,如果包頭或包體大小不夠,則說明數據不完整,繼續等待更多的數據的到來。
因為tcp協議是流協議,對方發送10個字節給你,你可能先收到5個字節,再收到5個字節;或者先收到2個字節,再收到8個字節;或者先收到1個字節,再收到9個字節;或者先收到1個字節,再收到7個字節,再收到2個字節。總之,你可能以這10個字節的任意組合方式收取到。所以,一般在正式的項目中的做法是,先檢測socket上是否有數據,有的話就收一下(至於收完不收完,上文已經說了區別),收好之後,在收到的字節中先檢測夠不夠一個包頭大小,不夠下次收數據後再檢測;如果夠的話,再看看夠不夠包頭中指定的包體大小,不夠下次再處理;如果夠的話,則取出一個包的大小,解包並交給上層業務邏輯。注意,這個時候還要繼續檢測是否夠下一個包頭和包體,如此循環下去,直到不夠一個包頭或者包體大小。這種情況很常見,尤其對於那些對端連續發數據包的情況下。
閱讀更多 一個程序員的奮鬥史 的文章