傳輸層
是整個網絡體系結構中的關鍵層次之一,主要負責向兩個主機中進程之間的通信提供服務。由於一個主機同時運行多個進程,因此運輸層具有有複用和分用功能。傳輸層在終端用戶之間提供透明的數據傳輸,向上層提供可靠的數據傳輸服務。傳輸層在給定的鏈路上通過流量控制、分段/重組和差錯控制來保證數據傳輸的可靠性。傳輸層的一些協議是面向鏈接的,這就意味着傳輸層能保持對分段的跟蹤,並且重傳那些失敗的分段。
傳輸層負責端對端的傳輸,不論是TCP協議還是UDP協議都是和端口有着聯繫
端口號
端口的劃分:uint16_t (0~2^16)
- 知名端口
http:80
https:443
ssh服務:22
ftp服務:21
Telnet服務:23
- 非知名端口
mysql:3306
oracle:1521
知名端口的查看
cat /etc/services
傳輸層UDP協議
- 特點
無連接,不可靠,面向數據報
無連接
:知道端口的ip和端口號就可以直接進行傳輸,不需要建立連接
不可靠
:沒有確認應答和重傳機制。如果數據在傳輸的過程中發生丟失,UDP協議也不會給應用層返回任何錯誤的信息
面向數據報
:對於信息只能整條發送與整條接收,不會存在兩條數據並存在緩衝區中。也就不存在粘包問題
- UDP協議的報頭
源端口(2字節)+ 目的端口(2字節)+ 數據長度(最大可表示的字節數2^16 – 65536)+ 校驗和
網絡抓包
- 抓包的命令
sudo tcpdump -i any port 19999 -s 0 -w 1.dat
然後在Windows下的wireshark
中打開後:
可以看到只有數據發送的過程,沒有連接
數據長度
uint16_t:最大的數值是65536,也就是說UDP協議單次傳輸的數據最大長度時65536個字節
UDP協議在傳輸數據的時候,不會出現粘包
問題的,因爲他的收發緩衝區一次只能存在一條數據
- 一次傳輸的數據小於65536時,選擇直接傳送就可以了
- 當一次傳輸的數據大於65536時,就需要在應用層進行拆分,向數據分爲小於65536的幾部分,然後按照順序依次發送給對端即可
當應用層一次需要傳輸的數據大於2^16時,我們就需要在應用層進行分片傳輸。就是說在應用層的時候,就將大於2 ^16的數據進行拆分,分多次使用UDP協議進行傳輸
因爲UDP協議的特性是面向數據報,數據都是整條整條的傳輸,所以在拆分後認爲應用層傳輸的每一條數據都是一個完整的UDP數據報
所以說,對於接受端接受UDP數據的進程而言,從協議棧中的傳輸層中的UDP接受的數據可能並不是一個完整的數據。爲了解決數據傳輸不完整的問題,數據發送雙方需要在應用層的時候就定製自定義協議,標識着應用層數據的長度和判斷數據是否完整
校驗和
因爲UDP協議在傳輸數據的時候可能存在丟失的情況,所以就用校驗和來判斷UDP數據在傳輸的過程中是否發生損壞
- 發生損壞,直接丟棄數據報,不會傳遞給應用層
- 沒有損壞,在應用層調用
recvfrom
的時候,將數據報提交給應用層
校驗和的計算
將UDP的數據報分成多個16位的數據,除了檢驗和不進行相加外,其他數據進行加運算
僞頭部(源IP + 目的IP + UDP的協議號(17) + 數據長度)+ UDP頭部(源端口 + 目的端口 + 數據長度) + 所有需要發送的數據
在計算的時候,因爲都是16位的數據進行相加,所有很容易出現溢出的情況,這個時候就需要進行回捲
。
因爲兩個16位的數據進行相加,最多隻能溢出一位,就變成了17位數據。那麼回捲就是把這個17位的數據分成最高位 + 低16位
兩部分,再把這兩部分進行相加,這就是新的結果
在計算校驗和的時候,就是把所有加起來的結果進行反碼運算,最後這個反碼運算的結果就是16位的校驗和。
所以說,校驗和 和 其他所有數據相加的結果就是 FFFF
UDP緩衝區
- 緩衝區中對應應用層的數據,都是整條發送與整條接收的
- 對於發送而言,應用層先使用
sendto
接口將數據提交到傳輸層的UDP發送緩衝區中,然後打上UDP協議的報頭,就可以直接交給網絡層進行下一步傳輸了 - 對於接收而言,應用層使用
recvfrom
接口將數據從傳輸層的接收緩衝區中拷貝到應用層,UDP接收緩衝區不保證數據的有序到達,也不保證可靠性; - 當接收緩衝區滿的時候,從網卡中接收的UDP數據報就會被丟棄,然後內核會返回一個
EMSGSIZE
錯誤
UDP的應用
DNS,域名解析協議。將域名轉換爲IP地址的時候,使用UDP協議
TCP協議
- 特點
面向連接,可靠的傳輸,面向字節流
面向連接
:使用TCP協議進行通信的雙方在通信之前必須建立連接,然後纔可以進行通信。TCP連接是一個全雙工的,也就是通信雙方可以互相進行發送和接收數據。(三次握手)
可靠的傳輸
:TCP協議有一個確認應答與超時重傳的機制,當數據在傳輸的過程中丟失的話,對端就不會進行確認,等到一個時間段後發送端就會重新發送這個數據。
面向字節流
:發送端與接收端進行讀寫數據之間沒有任何數量關係,發送端多次待發送的數據可以一起放在發送緩衝區中,接收端可以一下全部接受
- TCP協議段格式
源/目的端口號
:表示數據從哪裏來,到哪裏去
32位序號/32位確認號
:確認應答機制
4位TCP報頭長度
:表示TCP的頭部有多少個32位數據(四個字節),所以TCP頭部的最大長度時 15 * 4 = 60
6個標誌位
- URG:緊急指針是否有效
- ACK:確認號是否有效
- PSH:提示接收端應用程序立刻從TCP緩衝區把數據讀走
- RST:對方要求重新建立連接,攜帶RST標識的稱爲復位報文段
- SYN:請求建立連接,攜帶SYN標識的稱爲同步報文段
- FIN:通知對方,這個端口要關閉了,稱攜帶FIN標識的稱爲結束報文
16位窗口大小
:兩個字節數據,窗口表示的範圍0~2^16
16爲校驗和
:檢驗數據是否傳輸完成
16位緊急指針
:和URG標誌位一起來使用,如果URG標誌位爲1,則緊急指針指向的數據有效
40字節頭部選項
:MSS–》最大報文段長度
網絡抓包
TCP連接的通信雙方,在通信之前進行三次握手的請求連接,通信完畢之後進行四次揮手關閉連接
三次握手
有一個概念就是客戶端與服務端是相對的,而不是絕對的。我們認爲的時候,率先發起連接的一方稱爲客戶端,被動連接的一方稱爲服務端。
三次握手的前提就是連接方與被動連接方都完成了前期的準備工作
接下來就是三次握手的過程,這個過程就是一個確認應答的階段
- 客戶端率先發起連接請求,向服務端發送SYN報文
- 服務端在收到SYN報文後,向客戶端回覆ACK + SYN報文。其中,ACK報文是告訴客戶端:服務端收到了你發送的信息,至於是發送的哪一個請求,就是後面跟着的SYN報文
- 客戶端收到了服務端發送的確認收到報文的請求,並回復服務端自己收到的請求,發送一個ACK報文
四次揮手
通信雙方通信,那麼肯定就有通信結束的時候。又因爲TCP協議是面向連接的,所以在結束的時候不能一句話不說,悄悄就走了,這讓對方情何以堪?
四次揮手,肯定也是四個階段,因爲可能是服務端先給客戶端發起斷開請求,也可能是客戶端先給服務端發起斷開請求。
所以發起請求的一方稱爲主動斷開連接的一方,另一方就是對端,也就是被動斷開連接的一方
- 主動斷開連接的一方,率先給對端發送一個FIN報文,表示自己不想聊了,要退出了
- 對端在收到FIN報文後,向主動斷開連接的一方發送ACK報文,表名自己收到了你想要退出的消息
- 緊接着對端向主動斷開連接的一方也發送一個FIN報文,既然你不想聊了,我也不想聊了,表示自己也要退出了
- 主動斷開連接方先收到對端的ACK報文,這個時候還不能退出,因爲不確定對端是否還有話要說。
然後收到了對端的FIN報文,知道了對端也要退出,就給對端發送一個ACK確認的報文,告訴對端,我知道你退出了
到了這裏,其實四次揮手還不算完,爲什麼呢?這個時候主動斷開連接方還需要考慮,自己發送給對端的ACK報文,對方有沒有收到?
MSL,最大報文段生存時間,指的是發送方認爲TCP報文在網絡中最大的生存時間,一般是60s
- 查看MSL的命令
cat /proc/sys/net/ipv4/tcp_fin_timeout
等待兩個MSL,就是爲了防止發送方給對端發送的ACK確認退出報文,在途中因爲各種原因丟失的情況。
這個時候給了發送方一個重新發送的機會,假設ACK報文發生了丟失
- 第一個MSL,這時對端因爲還沒有收到發送方的ACK報文,自己還處於LASK_ACK的狀態,於是就再次給發送方發送一個FIN退出的報文,告訴自己要走了
- 第二個MSL,這時發送方又收到了對方的FIN報文,知道自己剛纔那個ACK報文丟失了,就再給對方發一個ACK報文,好讓對方退出
這個時候,如果第二次的ACK報文沒有發生丟失,對端在收到ACK報文後,就進入了CLOSED狀態。發送方在第二個MSL結束後就會進入CLOSED狀態。
而如果又發生了丟失。。。。。這時發送方和對端因爲2MSL已經結束了,就進入了CLOSED狀態
2MSL == 丟失ACK的MSL + 重傳FIN的MSL
由2MSL引起的地址複用問題
如果一個TCP協議的程序,他的主動斷開連接的一方是服務端,那麼在服務端斷開連接之後,立刻重新連接的時候,就會發現該端口處於bind error: Address already in use
的狀態,如下所示:
問題的根源:
就是因爲我們直接退出了服務端,使得服務端所佔有的端口沒有變成CLOSE
狀態,而是TIME_WAIT
的狀態
我們的服務端率先斷開了連接,在連接斷開的時候,就有一個TIME_WAIT
的狀態,他需要等待2MSL的時間。
這個行爲是傳輸層TCP的行爲,即使我們的應用程序已經退出掉了,但是內核中對應的19999端口還是被佔用的
同理,當我們的客戶端端口後,客戶端的進程也屬於一個TIME_WAIT
的狀態,但是進程號是隨機的。。。我們服務端的端口號是固定的,所以客戶端先斷開連接後影響比較小。
解決地址複用的函數接口
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
- sockfd:將要被設置的套接字
- level:指定套接字的層次,他的取值有三種
SOL_SOCKET:通用套接字選項 --》地址複用
IPPROTO_TCP:TCP選項
IPPROTO_IP:IP選項
- optname:在level中要完成的任務
SOL_SOCKET -->
SO_REUSERADDR:允許重用本地地址和端口
SO_RECVBUF:獲取接收緩衝區的大小
IPPROTO_TCP -->
TCP_MAXSEG:獲取TCP最大數據段的大小
IPPROTO_IP -->
IP_TTL:獲取最大字節數,也就是最大生存空間
- optval:需要完成的任務,地址複用–》傳入1。傳入的是一個 變量的地址
int i = 1;
&i;
- len:optval的長度
然後在創建套接字的同時,加上地址複用的函數