上一篇https://blog.csdn.net/Small_entreprene/article/details/148143494?fromshare=blogdetail&sharetype=blogdetail&sharerId=148143494&sharerefer=PC&sharesource=Small_entreprene&sharefrom=from_link
上文學習了傳輸層的協議之一UDP,接下來我們來好好學習一下傳輸層另外一個更加重要的協議 --- TCP協議:
TCP協議
TCP協議段格式?
tcphdr
struct tcphdr {__be16 source; // 16位源端口號__be16 dest; // 16位目的端口號__be32 seq; // 32位序列號__be32 ack_seq; // 32位確認號
#if defined(__LITTLE_ENDIAN_BITFIELD)__u16 res1:4, // 保留位doff:4, // TCP頭長度,單位為4字節fin:1, // FIN標志syn:1, // SYN標志rst:1, // RST標志psh:1, // PSH標志ack:1, // ACK標志urg:1, // URG標志ece:1, // ECE標志(用于擁塞控制)cwr:1; // CWR標志(用于擁塞控制)
#elif defined(__BIG_ENDIAN_BITFIELD)__u16 doff:4,res1:4,cwr:1,ece:1,urg:1,ack:1,psh:1,rst:1,syn:1,fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif__be16 window; // 16位滑動窗口大小__sum16 check; // 16位校驗和__be16 urg_ptr; // 16位緊急指針
};
標準問題?
TCP是如何實現將報頭和有效載荷進行分離的?
前提說明:TCP設置為面向字節流,主要是為了其可靠性這個TCP的特性!!!
TCP協議的標準的報頭的長度是20個字節,也就是一般而言,TCP報頭也是一個定長的報頭!也就是將來來一個TCP報文,我們只需要進行data指針位置的加20個字節,就可以提取出來了。不過TCP協議當中還有一個選項字段,可以為0字節,也可以有字節,這個后面我們自會理解!所以一個TCP報頭的完整長度是20個字節加上選項字段,這個選項字段正常是為0字節的。因為有不定長的選項字段存在,我們該如何保證報文的報頭和有效載荷進行分離呢?所以在標準報頭20字節當中,存在一個叫做數據偏移的字段,也叫做4位首部長度,就是4個比特位,當成無符號來算,就是【0000,1111】總共是【0,15】個數據范圍,TCP協議規定【0,15】的數據范圍的單位是4字節,也就是說TCP報頭的大小是【0,15*4】>>>【0,60】字節,但是對于TCP來說,其報頭的標準長度最少都是20個字節,所以TCP的報頭長度應該是x*4=20字節,也就是4位首部長度的范圍應該是【0101,1111】,即【20,60】字節!
又因為TCP的報頭的基本單位是4個字節,所以后面的TCP報頭是肯定可以整除4個字節的【20,24,28,32,......,60】!所以將來選項的基本單位也是4字節的!!!
所以報頭和有效載荷是如何進行分離的呢?就是我們首先無腦讀取報文的前20個字節,然后提取4為首部長度,直接*4,然后減去20,剩下的就是選項的所占字節大小,最后剩下的就是有效載荷了!?
我們在談UDP協議的報文格式的時候,UDP有其對應的報頭長度8字節定長,其中報頭中還有整個UDP報文的長度的,但是為什么TCP協議格式中為什么沒有報文大小,只有報頭大小呢??沒有對應的數據部分的長度?其實從這里我們就可以窺探出一個認知了:
在操作系統內部,操作系統可以知道UDP報文的邊界的,知道UDP報文從哪里開始,從哪里結束,可以知道一個完整的UDP報文!因為UDP是面向數據報的!不過TCP是面向字節流的,在操作系統內部,發送了許多TCP數據段,他也區分不清楚是不是一個完整的報文,所以TCP不能夠來表示一個報文完整的長度!
所以對于TCP協議來說,不需要,也不能設置一個叫做報文長度的字段,因為將來來的TCP報文是不是一個完整報文我操作系統吃不準!只能是將報頭拿走,剩下的數據交給接收緩沖區當中,由用戶上層自行去分析這么多個按序拼接好的有效載荷!
可是就不會說數據和下一個報頭黏在一堆的情況嘛???
這種情況是不可能存在的,因為每收到一個報文,都是一個獨立的sk_buff!!!
但是TCP是面向字節流的,報文的有效載荷在接收緩沖區當中會黏在一起!!!如果UDP有接收緩沖區,我們可以理解為數據報是由一個一個的隊列維護的,這樣就不會產生和字節流的黏在一起的問題了。?
TCP如何將自己的有效載荷交付給上一層?
當 TCP 數據段到達時,內核會根據目的端口號找到對應的套接字(socket),并將數據傳遞給該套接字關聯的應用程序。這個過程是通過內核的協議棧和套接字管理機制實現的,確保數據能夠準確地到達正確的應用程序。
可靠性的本質
確認應答(ACK)機制
給個實際的例子,兩名同學在相距100m的操場上:
小B:吃了嗎?
小S:吃了!
其實如果小S沒有回復的話,那么小S可以看成是沒有收到小B給小S發送的消息的!
但是小B回復了,也沒辦法確定說小N聽到了,所以:
小S:好!
但是又這么保證說小N聽到了呢?😥😥😥😥😥😥😥😥😥😥😥😥
所以這個世界上,在長距離傳輸的時候,根本就不存在100%可靠的協議!所以:
正確理解可靠性:
- 具有應答,可以保證對歷史消息的可靠性,是100%的:
- 通信中,最新的報文,永遠沒有應答,最新可靠性無法保證!
TCP一般的通信過程(暫時這么說,后面會更加正確理解)(最樸素的認識)
在TCP當中,如果要保證可靠性,處于核心地位的可靠性,叫做確認應答機制!
就是在客戶端和服務端,客戶端向服務端發送消息,硬性規定說服務端要給客戶端應答,這就是確定了客戶端向服務端的可靠性;服務端向客戶端發送消息,客戶端響應服務端,給服務端做應答,這就保證說服務端到客戶端的可靠性的保證:
不過這種模式是一個一個串行的走的,效率太低下了,所以我們就有了TCP傳遞信息時,更加通用的過程:
TCP傳遞信息時,更加通用的過程:
我們可以一次性發送多個報文,這就相當于是并行的由客戶端向服務端發報文信息,這樣的效率是明顯提高了,但是一系列問題也就隨之而來:
假設一次性發送過來的這么多報文數據當中,有其中一兩條沒有發送到服務端,服務端就沒辦法做應答,造成客戶端沒法接收到對應的應答!哪一個呢,客戶端怎么知道!
所以為了能夠更加細致的確認應答,TCP協議報頭中就引入了32位序號和32位確認序號!
我們來好好說說:
假設客戶端需要向服務端發送一系列數據包,每個數據包包含一個報文。為了提高效率,客戶端選擇一次性發送多個報文,而不是逐個發送。
客戶端發送報文:
客戶端(C)一次性發送多個報文(黃色實線箭頭)到服務端(S)。這些報文可能包含不同的數據,如請求、命令或數據更新。
服務端接收報文:
服務端(S)接收到這些報文后,需要確認每個報文是否成功接收。服務端通過發送確認應答(綠色虛線箭頭)來告知客戶端哪些報文已經成功接收。
確認應答的作用:
確認應答(綠色虛線箭頭)包含一個確認序號,表示服務端期望接收的下一個字節的序號。例如,如果服務端成功接收了序號為1到10的報文,它會發送一個確認序號為11的應答。
客戶端收到確認應答后,知道序號為1到10的報文已經被服務端成功接收,可以繼續發送后續報文。(指定報文序號之前的所用信息,已經全部收到!!!---這個一條很重要的規定!!!)
處理丟失的報文:
如果服務端發現某個報文丟失(例如,序號為5的報文沒有收到),它不會發送確認序號為6的應答,而是繼續發送確認序號為5的應答,告知客戶端需要重傳序號為5的報文。
客戶端收到這個重復的確認應答后,知道序號為5的報文丟失,需要重新發送該報文。
那么為什么會有兩個序號,一個序號一個確認序號呢?
說人話:其實雙方發送的是一個TCP報文,應答正常來說是TCP報頭,總不能說客戶端向服務器發送后,服務端只做應答,也就是報頭,所以有捎帶應答=報頭+有效載荷,所以需要兩個序號來,不然這么區分
詳細說明:
在TCP協議中,數據傳輸的可靠性是通過序號和確認序號來保證的。這兩個序號在TCP報文段中扮演著不同的角色:
1.?序號是用來標識TCP報文段中的數據字節流的位置。目的是為了確保數據的順序性,允許接收方正確地重新組裝從發送方接收到的數據片段。是標識本報文段的數據的第一個字節的序號。
2.?確認序號是用來指示接收方期望從發送方接收的下一個字節的序號。目的是為了告知發送方哪些數據已經被成功接收,從而觸發發送方發送更多的數據或重傳丟失的數據。如果ACK標志位被設置,此字段值加1即是下一個期望接收的字節序號。
TCP報文由TCP報頭和可能的有效載荷(數據)組成。TCP報頭包含了序號和確認序號等重要信息。
捎帶應答(Piggybacking)
在TCP中,捎帶應答是一種優化技術,它允許接收方在發送數據(即有數據要發送給發送方)時,將對先前接收數據的應答包含在同一個報文中。這樣可以減少單獨發送應答報文的次數,提高網絡效率。(報頭:確認應答+有效載荷:數據)(不是單單的只有報頭,即只有單純的應答!)
客戶端發送數據:客戶端發送一個TCP報文,序號為100,包含數據字節101到105。
服務端接收并發送數據:服務端接收到數據后,發送一個TCP報文作為應答,其中包含:
-
確認序號:106(表示已成功接收到序號為105的數據,并期望接收下一個序號為106的數據)。
-
有效載荷:如果服務端也有數據要發送給客戶端,這些數據將作為有效載荷包含在同一個報文中。
為什么需要兩個序號來區分?
-
序號:用于標識發送的數據字節流,確保接收方可以正確地重新組裝數據。
-
確認序號:用于告知發送方哪些數據已經被成功接收,觸發發送方的后續動作(如發送更多數據或重傳丟失的數據)。
通過使用兩個序號,TCP協議能夠確保數據的可靠傳輸,同時通過捎帶應答優化網絡通信效率。這種設計允許TCP在保證數據完整性和順序性的同時,也能夠有效利用網絡帶寬。
好了,對應TCP,數據從發送端的發送緩沖區"拷貝"到對端的接收緩沖區中,這就有一問題,上面也說過了,在對端接收緩沖區如果滿了的話,那么就會導致發送的報文被丟棄,這已經不是可不可靠性的問題了,因為可以通過超時重傳等等的機制來保證,但是這里帶來的問題不就是浪費,是一種低效的嗎,如果滿了的話,還一次性又發來一大推,然后又丟了???這不就很低效,浪費嗎???所以發送端是需要知道對端的接收緩沖區的當前接收能力!!!是由對端接收緩沖區中剩余空間的大小所決定的!
其實,我們上面也說了,發送端發送消息,對端原則上是需要給發送端做應答的,不管是捎帶還是普通應答,而只要對端給應答,那么不就是雙方互相在進行報頭/報文的傳輸嗎!雙方就需要知道對應對端的接收緩沖區的剩余空間大小!然而TCP協議中有一個關鍵字段 --- 16位窗口大小!
這個16位窗口大小的內容就是接收緩沖區剩余空間的大小,是自己的!!!不是對端的接收緩沖區的剩余空間的大小!!!(我們所構建的報文一定是給對方的,當然將自己的剩余緩沖區空間大小告訴對方!)
所以盡管可以一次性并行的給對端發送報文,但是也是有上限的,這個上限取決于對端接收緩沖區剩余空間的大小!
我們將這種按照對端接收緩沖區的接受能力來動態調整我們發送速度的機制稱為 --- 流量控制!?(多發多,少發少,合理控制的機制)(考慮了可靠性,一定程度上防止了丟包,但是流量控制更加考慮的是效率問題)
TCP協議當中還有一個校驗和,這個我們不用關心:
TCP協議中的校驗和是一個重要的機制,用于確保數據在傳輸過程中的完整性和準確性。以下是關于TCP校驗和的簡單介紹:
-
檢測數據完整性:TCP校驗和的主要功能是檢測數據在傳輸過程中是否發生了錯誤。由于網絡傳輸可能會受到干擾(如電磁干擾、硬件故障等),數據可能會被損壞。通過校驗和機制,接收端可以驗證數據是否與發送端發送的數據一致。
-
提高可靠性:TCP是一個面向連接的可靠協議,校驗和是其可靠性機制的重要組成部分。如果接收端發現校驗和不匹配,可以要求發送端重新發送數據,從而確保數據的正確性。
還有一個16位緊急指針,需要了解這個,我們就需要將剩下的標志位談清楚!
TCP協議報頭中有一個保留位(6位),也就是還沒想好該怎么用!知道我們想使用再用!除了保留的6位,后面還有6個標志位!
協議的本質就是結構體,tcphdr結構體當中含有條件編譯,是對與位段的,這也說明了內核已經可以自己進行大小端的區分了,其中位段是一種數據結構,通常用于將一個字節(8位)或多個字節劃分為多個字段,每個字段可以表示不同的信息。位段的設計允許在有限的空間內存儲多個標志或狀態信息。這是我們C語言所學習的,現在知道了吧!位段主要應用在網絡通信中!其實所謂的標志位本質就是報頭中的比特位!!!
我們先來說說為什么需要有標志位???
我們先簡單談談TCP的三次握手,后面會談到TCP的三次握手的詳細過程:
在這個世界上,我們正常通信之前有許多準備工作,有的是沒有準備工作的,比如發送郵件,這種直接發送的我們稱之為面向數據報,但是如果要打電話進行雙方間的通信的話,需要先撥通電話,撥通之后,對方需要將電話接起來,這樣的話,電話就建立好通信了。
TCP也是如此,TCP在正常通信之前需要進行三次握手:你可以做我女朋友嗎(建立連接的請求)---可以呀,什么時候---就現在!連接關系建立本質就是達成共識!往后就是正常通信了!
這樣客戶端在某一時刻向接收端發送報文的類型是不一樣的!那么可以有多個客戶端同時向服務端發送各種各樣的報文數據,這樣就會導致:接收方收到的TCP報文,一定會存在不同的類型,針對不同的報文類型,接收方要有對應的不同的做法!
雙方傳輸的至少是含有報頭的,所以報頭當中一定存在標志報文類型的字段!
在TCP協議中,6個標志位(也稱為控制位)是TCP頭部的重要組成部分,用于控制TCP連接的建立、維護和終止等操作。以下是這6個標志位的詳細介紹:
1.?SYN(Synchronize Sequence Numbers)
作用:用于建立連接。當SYN標志位被設置為1時,表示這是一個連接請求或連接接受報文。
應用場景:
-
在TCP的三次握手過程中,第一次握手(客戶端向服務器發送連接請求)和第二次握手(服務器接受連接請求并回復)都會設置SYN標志位。
-
客戶端發送SYN報文時,表示請求建立連接;服務器收到SYN報文后,也會發送一個帶有SYN標志位的報文作為響應。
2.?ACK(Acknowledgment)
作用:確認號(Acknowledgment Number)字段是否有效。如果ACK標志位被設置為1,則表示確認號字段是有效的,接收方通過確認號來確認已收到的數據。表示該報文是一個應答報文!
應用場景:
-
在TCP通信中,除了第一次握手的SYN報文外,幾乎所有的TCP報文都會設置ACK標志位。
-
例如,服務器在收到客戶端的SYN報文后,會發送一個帶有ACK標志位的SYN-ACK報文,表示確認收到了客戶端的連接請求。
三次握手建立之前,不能正常通信,前兩次握手不能發送像"Hello World"的數據,就是三次握手沒有完成,也就是只能互發報頭了!
那么客戶端給服務端發送消息之前不是要進行流量控制嗎?那么第一次發送報文的時候不是還不清楚對端的接收緩沖區的剩余空間大小嗎???注意!我們第一次發數據是第一次給對端服務器發報文嗎???并不是報文!!!而是說雙方在通信之前就已經完成三次握手了,三次握手期間已經發送過報頭了!!!絕對不會出現數據溢出,丟棄的問題了!!!
3.?FIN(Finish)
作用:用于終止連接。當FIN標志位被設置為1時,表示發送方已經完成數據發送,希望關閉連接。
和三次握手類似,客戶端給服務端說:我不想玩了,想要斷開連接;
服務端:好呀;
服務端:我也要跟你斷開連接;
客戶端:好呀。
為什么是4次,主要是TCP是全雙工的,一方關閉就是只關閉一個通信信道,如果服務端有的還沒發完,客戶端發完了,那就客戶段信道關了,等服務端發完了,也就雙發一起實現了完全關閉了!
應用場景:
-
在TCP的四次揮手過程中,發送方(客戶端或服務器)在完成數據傳輸后,會發送一個帶有FIN標志位的報文,通知對方自己已經沒有數據要發送了。
-
對方收到FIN報文后,會發送一個ACK報文作為確認,然后自己也會發送一個FIN報文,最終完成連接的關閉。
4.?PSH(Push)
作用:表示接收方應盡快將數據推送給上層應用。當PSH標志位被設置為1時,接收方會立即將數據傳遞給上層應用,而不是等待緩沖區填滿后再傳遞。
應用場景:
-
通常用于實時性要求較高的數據傳輸,例如在HTTP協議中,當服務器向客戶端發送響應數據時,可能會設置PSH標志位,以便客戶端盡快處理這些數據。
對端上層應用層很忙,接收緩沖區的所剩空間越來越少,直至為0,那么發送端就只能不發了,但是不發總不能就傻傻的等著吧?!還有如果接收端讀走了部分緩沖區的數據,那么發送端又怎么知道?!(我們后面會說到:一旦數據讀走了,窗口更新了,這個接收端/服務端,就會主動向客戶端發送報文,還有一種方式時客戶端會定期像服務端發送一個不攜帶數據的報文,一發數據就需要ACK,那么客戶端也就清楚現在服務端的接收緩沖區的狀態了)
PSH就是叫接收方應盡快將數據推送給上層應用!!!是一種催促的機制!!!注意:我們舉得例子比較極端,準確來說:當PSH標志位被設置為1時,接收方會立即將數據傳遞給上層應用,而不是等待緩沖區填滿后再傳遞。
什么是叫對端操作系統趕快讀???緩沖區沒有數據的時候讀取會阻塞,其實有數據的時候也可能會阻塞,就是我們后面會談到的低水位線和高水位線的問題,舉例就是需要滿50個字節才會觸發read的條件,我們后面提到IO時間就緒的概念的時候詳談!所以PSH的作用就是讓對端的read/...條件就緒!?
5.?RST(Reset)
作用:用于重置連接。當RST標志位被設置為1時,表示當前連接出現了問題,需要強制斷開連接。
應用場景:
-
如果接收方收到一個錯誤的報文(例如,報文中的序號或確認號不正確),或者接收方沒有建立對應的TCP連接,它會發送一個帶有RST標志位的報文,通知對方終止連接。
-
例如,當客戶端嘗試連接一個不存在的端口時,服務器會發送一個RST報文,拒絕連接。
我們知道TCP建立連接的三次握手的第三次握手是沒有應答的,也就是前兩次握手盡管丟包了也不怕,因為有應答,能知道,其實客戶端在最后一次握手的時候,將第三次握手的ACK發送出去的時候,就已經是客戶端三次握手成功了,但是如果在第三次握手的時候丟包了的話,客戶端是不知道的,雙方只是站在自己的立場,三次握手是看自己有沒有發送和收到的(發-收-發;收-發-收)雙發三次握手完成是有時間差的:
如果第三次的ACK丟了的話,客戶端認為建立成功,但是服務端認為連接失敗了,導致連接是否成功的判定不一致,?那么客戶端接下來就極有可能向服務器發送數據!!!可是服務器說不是三次握手還沒有好嗎?于是服務器就會向客戶端發送一個RST置為1的報文,表示當前連接出現了問題,需要強制斷開連接。
上面這個例子也是比較極端的,其實在通信的過程中,連接出現任何問題,都可以進行重置!?
6.?URG(Urgent)
作用:表示報文中有緊急數據。當URG標志位被設置為1時,表示TCP報文中的數據是緊急數據,接收方應優先處理這些數據。
應用場景:
-
緊急數據通常用于控制信息,例如在Telnet協議中,用戶可以通過發送緊急數據來中斷當前的操作。
-
當URG標志位被設置時,TCP會使用緊急指針(Urgent Pointer)來指示當前報文的有效載荷中緊急數據的起始位置。
接受緩沖區可以看成是字節流式的接收隊列還有因為序號按序到達的機制,那么我們如果有數據想要優先讀取并處理的話,直接是行不通的!在計算機網絡當中,什么情況是需要被優先處理的呢?
有一種情形:就是上傳資料到百度網盤,大小大概是一個G的文件,就會像服務端發送大量的報文,對端緩存區也是收到了該文件的部分的報文,上層會進行備份保存在指定文件當中,但是上傳到一半的時候,發現傳錯了,要取消上傳,盡管該文件是一個報文來完成,也是需要死板的到對端接收緩沖區,等待前面的報文處理了,才到該報文,現在就是表示取消上傳的報文想要優先被處理,因為前面部分報文是屬于同一文件的,上層還需要將前面的報文處理了,但是處理這些報文已經是沒有意義的了,所以對端接收緩沖區一旦接收到了含有緊急數據的報文就直接優先處理了,就像我們的取消還有暫停等等!!!
URG是比較不常用的,URG標志位通常是要與TCP報文協議當中的16位緊急指針相配合,如果URG標志位被置為0,表示該16位緊急指針是無效的,為1就是有效了,下面我們來看看16位緊急指針的源碼:
__be16 urg_ptr; // 16位緊急指針
緊急數據并不屬于正常數據,他是帶外數據!
URG只是代表緊急指針有無效,本質還是需要看16位緊急指針!
緊急指針來指示當前報文的有效載荷中緊急數據的起始位置,本質就是偏移量!緊急數據只有一個字節!也就是因為一個字節,所以他往往可以用來設置狀態碼!
暫停,取消,繼續上傳不就可以使用0,1,2來表示嘛!不同的狀態碼來表示不同的控制命令!
標志位的組合
TCP標志位可以組合使用,以實現不同的功能。例如:
-
SYN-ACK:SYN和ACK標志位同時被設置,表示接受連接請求并確認。
-
FIN-ACK:FIN和ACK標志位同時被設置,表示完成數據傳輸并確認對方的FIN報文。
TCP的6個標志位是TCP協議的核心控制機制,通過這些標志位,TCP能夠實現連接的建立、數據傳輸、連接終止以及錯誤處理等功能。這些標志位在TCP的三次握手和四次揮手過程中起著關鍵作用,確保了TCP協議的可靠性和高效性。
至此,我們TCP包頭協議的主要字段我們就談完了,剩下的字段我們后續會在利用到的時候談起!
🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔更多精彩請看下文!!!
🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔