TCP網路編程,從socket到消息包,發送接收都是bit,傳輸中兩端怎麼知道哪些bit組成一個協議?

client發送給server的消息,按tcp協議的定義,是bit流形式的,那麼我們該怎樣告訴server如何獲取正確的bit,然後把這些數據拼成一個所謂的包呢?

根據1樓的回答,
也就是說,我需要指定一個數據長度len,後面發送數據,接收方先取出長度信息,然後根據該長度取相應的數據,然後將數據填充進事先定義的消息包結構體中嗎?


這大概是每一個初學網路編程的人都會遇到的問題。

TCP 提供位元組流方式的全雙工數據傳遞,本身並沒有「消息包」的概念。實際應用中,有多種辦法可以基於 TCP 實現一個個「消息包」的傳送。

第一種,可以用特定的分隔符(delimiter)分割每條「消息」。必須保證每條消息的內容不包含該分隔符。SMTP 即是用 CrLf 作為分隔符。

另一種辦法是指定每條消息的長度。比如,每次在發送消息之前,發送 4 個位元組的無符號整數來告知後面的消息的長度。

參考資料:Winsock Programmer』s FAQ: How to Use TCP Effectively


如上圖所示:應用層的一段數據,經過邏輯封裝,TCP封裝,IP封裝,幀封裝,然後從網卡出去,接收端則是逆過程,TCP及以下的封裝,一般不需要我們自己動手,僅考慮應用層的封裝即可。

其中對於應用層的封裝來說,有兩種方法
1、「用戶數據」就是你實際要發送的數據,比如一個字元串:"asdfgh"
Appl首部就是你要設計一個自己的包結構,比如在Appl首部裡面你填一個4個位元組的int,表示後面內容的長度
那麼當你發送"asdfgh"的時候,Appl首部裡面就是這個字元串的長度6,接收端先接受4個位元組,得到長度,再按照長度接受內容

2、沒有Appl首部,而是在用戶數據末端加上「結束標誌」,比如http協議就是這麼乾的,用

表示一段內容結束(當然你也可以選擇其他的特定字元作為結束標誌)。
那麼當你發送"asdfgh"的時候,其實就是發送"asdfgh

",接收端直接接受數據,遇到

表示一段數據結束


發送方需要先告訴伺服器包的長度,然後再發送包的內容。接收方也需要做對應的操作。


很簡單的問題tcp socket是面向流的,如果你的程序需要數據包作為原料,那自然需要將面向流的數據轉換成面向數據包的數據,就是封個包唄,基本上最簡單的方法就是+包頭(本數據包長度)+包尾巴(校驗,但是在TCP這種傳輸層能保證數據送達的協議,這個包尾非必需,當然,前提條件是發送方別發送錯包(亂序或者其他什麼的)),接收數據的時候校驗一下收到的數據是否是一個完整包,是的話觸發一個事件丟給上層去處理,然後清下buff就好了


嚴格來說,題主問題的描述是有問題的。看下圖TCP/IP協議棧(Application是應用層、Transport是傳輸層、Internet是網路(互聯)層、Link是網路介面層)

圖1:TCP/IP協議棧示意圖


一般意義上的TCP/UDP網路編程(利用socket收發數據)是工作在Transport層,而我們在自己的程序中交給或者接收的數據其實就只有Application層的DATA,比如我們調用socket.send(DATA)。我們在Application層的DATA除非我們自己制定格式(協議,比如HTTP/FTP),否則就是一個二進位的數據而已。我們在程序中進行socket編程只能工作在Application這一層,這一層的數據單位粒度其實只能控制到位元組(Byte),所謂的bit其實只有Link這一層才有資格操作到。而TCP/IP的Link層包含OSI模型的物理層(Physical)和數據鏈路層(Data-Link),bit流真正只能在OSI的物理層操作,這一層就是網卡、網線之間的數據傳輸。(數據單位在Application為報文、在Transport為段、在Internet為數據報、在Link為幀)

圖2:OSI協議棧示意圖

我們從Application層把DATA交給Transport層時(參考linux系統API的send(socket, DATA, len)),可以看到數據交給Transport時一定需要告訴Transport層DATA有多長。然後Transport層會給這個DATA加上一個「頭」形成一個段,這個頭裡面會記錄這個DATA由誰發出,發給誰(進程埠,並不會唯一標識收發者,Internet層進一步處理後就會唯一標識收發者),從段的何處開始等等信息。同樣的道理一直把DATA加工傳遞到Link層(DATA的長度一直有被存儲著),然後利用數字通信的原理將數據粒度為位元組(Byte)的數據編碼為bit流發送出去,比如乙太網利用曼徹斯特編碼。接收方則將這個加工過程反過來則可以得到有效的DATA了。


上述過程只是理論情況下,實際上在工程應用中,DATA都會有特定的格式(協議),在這個特定的格式中都會描述DATA有多長,比如HTTP就有Content-Length欄位。這是因為如果Application層在非常短時間內發送多次很小的DATA,Transport層會進行「黏包」(將多個DATA組合為一個大的DATA)再交給Internet層,然後接收方就只會收到一個大的DATA,如果沒有特定的格式,接收方就會無法區分出多個小的DATA。黏包是操作系統為了提升性能採取的一種實現手段。


其實很簡單,tcp分包~然後谷歌下tcp粘包~眾人均說偽命題,但是通過它確實能找到解決方案。三種策略(不是原創):
1.自定義協議的分界符,比如回車換行。
2.第一個欄位給出長度,然後是數據。讀的時候先拿到長度,然後讀取那麼多就好了。如果出差錯了,重啟完事。
3.固定長度。

分包你需要buffer保存收到的數據,然後根據上面的規則在buffer裡面操作,數據不夠繼續等待buffer數據足夠。

推薦看下protobuf,感受下。還有陳碩大大的muduo。聽起來玄乎,其實很簡單。
騰訊也有自己的協議工具,很好用。最近看了好幾個內部框架,tcp包完整性檢查介面都需要用戶實現的,但如果用自定義協議工具之後,所有檢查代碼似乎都一樣了,用起來挺自由的。


TCP發的包不光是單純的數據信息 在不同協議層包會被貼上不同的信息內容

TCP/IP模型及包結構_rookie_新浪博客

可以看看這個blog應該可以解釋你的疑惑。

真要有興趣就去CSDN或者github上找個tcp/ip的源碼 自己比著寫一遍吧


推薦閱讀:

HTTP協議里的請求頭有什麼用?
乙太網的mac協議和基於點對點的ppp協議的區別和使用場景是什麼
在瀏覽器地址欄輸入一個URL後回車,背後會進行哪些技術步驟?
TCP快速重傳為什麼是三次冗餘ack,這個三次是怎麼定下來的?
OSI模型中,一個協議應該屬於哪一層是以什麼為標準劃分的?

TAG:伺服器 | C | 網路編程 | TCPIP |