tcp 協議的功能
- 面向連接
- 可靠傳輸(錯誤確認和重傳)
- 擁塞控制
- 流量控制 (發送方和傳送方的傳輸速率協調)
tcp 報文格式
標志位的講解
SYN:當本字段為1時,表示這是一個連接請求或者連接接受報文。
ACK:確認標志,當本字段為1時,表示請求已收到。
PSH:tcp模塊的發送方自己決定何時發送數據包,并且使PSH值設置為1。
數據包到達接收端以后,接收端不對其進行隊列處理,而盡可能將其轉發給應用程序(觸發應用程序調用read()函數的返回)。
FIN: 當本字段為1時,表示此報文段的數據已經發送完畢,要求釋放連接。
RST: 如果要向主機發送數據包以建立連接,并且沒有這樣的服務等待在遠程主機上回答,則主
機將自動拒絕請求,然后發送回復RST標志置1。這表示遠程主機已重置連接。
窗口字段
WIN: 接收窗口。用來告知發送端自己所能接收的數據量。
序號字段
含義
tcp連接 字節流之中每一個字節都有一個編號,本字段的值指的是本報文段所發送數據部分第一個字節的序號。
確認號
含義
下一個報文段數據部分的第一個字節的序號,序號為ack-1以及以前的字節都被收到。
tcp 報文類型
PUSH 報文
PUSH 報文 定義(判斷規則)
tcp報文 的 PUSH 標志 取值為1
PUSH 報文 作用
發送方 使用 該報文 通知 接收方 將所收到的數據全部提交給接收進程。
復位報文(RST 報文)
復位報文的定義
tcp 報文 的 RST 標志 取值為1
復位報文的作用
復位報文用來 異常終止 一個連接。發送復位報文的一端,會丟棄發送緩沖區和接收緩沖區之中的數據。
發送FIN 報文,是正常終止 一個連接。
FIN 報文
FIN 報文的定義
tcp 報文的 FIN 標志位為 1, 表示為FIN 報文。
FIN 報文的作用
FIN 報文 表示一方需要終止連接。
Linux 手動 發送 tcp 報文的命令行工具
hping3
tcp 連接
tcp連接的屬性
- 目的ip
- 目的端口
- 本地ip
- 本地端口
- 連接狀態
tcp連接狀態有哪些
- CLOSED:連接處于關閉狀態
- LISTEN :監聽來自遠方的建立連接請求。
- SYN_SENT :客戶端 發送 請求建立連接報文 后轉換為此狀態(客戶端SYN_SENT狀態)
- STN_RECV:服務端 收到 客戶端發送的 請求建立連接報文后轉換為此狀態。
- ESTABLISHED :連接建立
- CLOSE_WAIT:處于ESTABLISHED狀態的服務端 收到 客戶端發送的FIN 包,變換為此狀態。
- FIN_WAIT_1: 處于ESTABLISHED狀態,主動發送FIN 報文的一端,狀態進
FIN_WAIT_1。 - FIN_WAIT_2:主動關閉端接到ACK后,狀態進入了FIN-WAIT-2。
- LAST_ACK:處于CLOSE_WAIT狀態的被動關閉端一段時間后,它所對應的應用程序,調用CLOSE關閉連接,進入LAST-ACK 狀態。
- TIME_WAIT:在主動關閉端接收到FIN后,發送ACK包,并進入TIME-WAIT狀態。
Linux 上 tcp連接的唯一性標志
目的ip 目的端口 本地ip 本地端口 合在一起作為tcp連接的唯一性標志
Linux 上 一個tcp連接的實質
自己的理解:Linux 上建立一個tcp連接,會使用一個socket來存儲tcp連接的信息,socket 在Linux 上以文件表示。但是并不表示,出現socket文件,就是建立了tcp連接。因為 調用 socket創建函數,也可以創建socket。
Linux 上 一個tcp連接會占據哪些資源
1:連接跟蹤表之中的連接數 (如果tcp 啟用連接跟蹤)
2:內存
3:文件描述符(Linux上所能打開的最大文件數是有限的,一個進程所能打開的文件數也是有限的)
Linux 上 tcp連接數是否是有限制的?
是的。受限于 連接跟蹤表 內存 Linux 上所能打開的文件的文件數
tcp 三次握手建立連接
過程
問題
tcp連接 第一次握手失敗 會發生什么(即發送了SYN報文,但是沒有收到ACK 確認報文)
自己對于第一次握手成功 和 失敗 的理解:
第一次握手成功 是在指定的時間內收到另一端發送的ACK報文。
第一次握手失敗 是在指定的時間內沒有收到另一端發送的ACK報文。
第一次握手失敗會按照重試的次數,每次等待指定的時間。
默認重試次數會6次,第一次等待的時間為 2^0 =1秒 第二次為 2^1 = 2秒,加上重試6次,一共會重試7次。總時間為127秒,如果重試6次,還是不能建立連接,那么連接建立失敗。
內核參數 net.ipv4.tcp_syn_retries 用來表示 SYN 請求建立連接 的 重發次數。
第一次SYN報文 用以判斷 是否超時的時間標準
1秒
tcp連接 第二次握手失敗 會有什么結果?
自己對于 tcp連接第二次握手失敗的理解為,客戶端未收到服務端發送的 SYN+ACK 報文。
那么客戶端會認為自己發送的SYN 報文丟失,會進行SYN 報文重傳。
tcp連接 第三次握手失敗 會有什么結果?
自己對于第三次握手失敗的理解為:服務端未收到客戶端發送的AC確認報文。
服務端會不斷重新發送SYN+ACK報文。
重試次數由net.ipv4.tcp_synack_retries 內核參數控制。
net.ipv4.tcp_synack_retries 參數含義:參數表示服務器 重發 SYN+ACK報文的重試次數。重試時間間隔 也是 第一次等待的時間為 2^0 =1秒 第二次為 2^1 = 2秒。
Linux tcp 連接隊列
- tcp 半連接隊列
- tcp 全連接隊列
tcp 半連接隊列
tcp 半連接隊列之中元素
半連接隊列之中保存 SYN_RECV 狀態的連接。
tcp 半連接隊列 入隊
服務端接收到第一次握手 SYN 報文,在半連接隊列之中保存 SYN_RECV 狀態的連接。
tcp 半連接隊列 出隊
服務端接收到 客戶端 發送的 ACK 確認報文,將對應的連接移出,放入到全連接隊列之中。
tcp 半連接隊列最大長度
獲取 tcp 半連接隊列最大長度 設置值
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
修改 tcp 半連接隊列最大長度
修改 /proc/sys/net/ipv4/tcp_max_syn_backlog 文件之中的值,注意這是臨時生效的。
持久化的修改 需要修改此內核參數,并固定下來
查看當前 tcp 半連接隊列之中元素的個數
netstat -antp | grep SYN_RECV | wc -l
tcp半連接隊列溢出處理
服務端 將 客戶端發送的多余的 SYN請求建立連接報文 丟棄。
tcp半連接隊列溢出監控
執行以下命令,如果有信息輸出 表示tcp 半連接 存在溢出
netstat -s | grep 'SYNs to LISTEN'
tcp 全連接隊列
tcp 全連接隊列之中的元素
處于 ESTABLISHED狀態 的tcp連接
tcp全連接隊列入隊
服務端 收到 客戶端 發送的 ACK確認報文,將連接從半連接隊列移至全連接隊列
tcp 全連接隊列出隊
應用層調用 accept()方法,從全連接隊列移出連接。
tcp 全連接隊列 最大長度
獲得tcp全連接隊列最大長度
計算公式為:min(backlog, /proc/sys/net/core/somaxconn)
backlog 是應用層 設置socket 監聽的時候,傳入的參數。
/proc/sys/net/core/somaxconn 是內核參數
設置tcp全連接隊列最大長度
min(backlog, /proc/sys/net/core/somaxconn)。 要么設置backlog 要么設置內核參數
tcp 全連接隊列溢出處理策略
與 內核參數 net.ipv4.tcp_abort_on_overflow 的取值有關。
取值為 0:如果全連接隊列滿了,那么 server 扔掉 client 發過來的 ack ;
取值為 1:如果全連接隊列滿了,server 發送一個 reset 包給 client,表示廢掉這個握手過程和這個連接;
tcp 全連接隊列 監控
查看當前tcp全連接隊列之中的連接個數
使用 ss -lnt 來獲得 tcp全連接隊列的長度。
輸出信息
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:9090 0.0.0.0:*
列名解析:
Recv-Q列 表示的是處于監聽狀態的socket的全連接隊列之中的連接數目。
Send-Q列 表示的是處于監聽狀態的socket的全連接隊列的最大長度。
查看 當前tcp 全連接隊列是否發生溢出
執行以下命令,如果有信息輸出 表示 tcp全連接 存在溢出。
netstat -s | grep overflow
Linux syn_cookie 機制
syn_cookie 機制的產生原因
針對可能產生的 SYN 洪水攻擊,所以提出了 syn_cookie 機制。
syn_cookie 的原理
syn_cookie 機制,會修改服務端的三次握手機制,當服務端接收到客戶端發送的SYN報文時,不再服務端分配一個專門的數據區,而是根據這個 SYN 包計算出一個 cookie 值。這個 cookie作為將要返回的 SYN+ACK 包的初始序列號。當客戶端返回一個 ACK 包時,根據包頭信息計算 cookie,與返回的確認序列號(初始序列號 + 1)進行對比,如果相同,則是一個正常連接,然后,分配資源,建立連接。
查看 syn_cookie 機制 是否開啟
檢查內核參數 net.ipv4.tcp_syncookies
參數取值:
0:表示關閉這個功能
1:表示僅當 SYN 半連接隊列放不下時,再啟用它。
2:表示無條件開啟功能。
tcp協議 數據傳輸
tcp 協議如何實現安全傳輸
- 1:如何保證不丟包
超時重傳 - 2:如何保證 不會重復發送數據包
確認號 - 3:如何保證 接收端 不會重復接收數據包
序列號 - 4:如何解決接收端接收的數據包亂序的問題
序列號 + 確認號 - 5:如何保證收到的數據包沒有差錯
tcp報文具有校驗和字段,來驗證數據在傳輸過程之中沒有變化。
MSS
MSS是什么
MSS(Maximum Segment Size,最大報文長度),是TCP協議定義的一個選項,
MSS選項用于在TCP連接建立時,收發雙方協商通信時每一個報文段所能承載的最大數據長度。
MSS 的初始值 = MTU - IP報文段的頭(20字節) - tcp報文段的頭(20字節)
在三次握手建立連接的同時,也是相互之間協商MSS大小的時機。
MSS的作用
當發送緩沖區之中的數據超過MSS 的大小時,將數據按照MSS的大小進行分割。例如,當應用程序一次性向緩沖區寫入大量的數據的時候。
問題
1: 是不是只有當 tcp 發送緩沖區之中的數據量 大于 MSS 值的時候,tcp模塊才會將緩沖區之中的數據通過網卡發送出去?
不是。自己通過java 程序驗證,在socket的 OutputStream 之中寫入3個字節,另一端仍然是收到了數據。
RTT
RTT 的定義
tcp協議之中,一個 數據包發送時刻 與 接收到確認數據包時刻之間 的 差值。
注意:RTT 指標也可以作為判斷網絡擁塞程度的標志。
RTT 的監控
執行 ss -ti 可以查看rtt指標的信息狀況
RTO
RTO 的定義
RTO: Retransmission Time Out, 超時重傳時間RTO。
RTO也就是tcp在發送一個數據包之后,會啟動一個重傳定時器,而RTO就是這個定時器的重傳時間。
一般來說RTO_MIN的值應該比RTT大,否則就會出現無謂的超時重傳了。
超時重傳
超時重傳(RTO)的時間
TCP超時時間初始值是根據RTT時間計算得到,隨著重傳次數增加重傳時間間隔也增加。
超時重傳次數
Linux 內核會有兩個參數 tcp_retries1 tcp_retries2 用以計算超時重傳的總時間。每次重傳時會判斷 當前時間 距離 第一次發送數據包的時間 是否大于 超時重傳的總時間,如果大于,那么就不會重傳了,小于的話,會進行重傳。
過了超時重傳次數,還是失敗,最終的結果是什么?
放棄重傳,發送端會關閉tcp連接,不會進入 TIME_WAIT狀態。
快速重傳
快速重傳的定義
當發送端接收到三個重復的ACK確認報文的時候,觸發數據包的重傳。
快速重傳所針對的問題
數據包超時重傳 所等待的時間太長,影響吞吐量。
快速重傳的監控
netstat -s | grep retran
輸出信息:
segments retransmitted 重傳的報文數
fast retransmits 快速重傳數
問題
1: 有時候 Linux 內核抓包發現,即使只是收到一次 重復的ACK, 但是也會觸發超時重傳,這是什么原因?
因為 SACK 機制,發送端接收到 SACK 確認報文后,可以了解服務端到底缺少哪一個報文,Linux 內核也有內部的判斷邏輯,如果 缺失的數據量 > net.ipv4.tcp_reordering * MSS,那么會觸發快速重傳,是有一個閾值的。
ACK 確認
SACK 機制
SACK 解決的問題
SACK 解決快速重傳時 需要重傳哪幾個數據包的問題,或者說針對所丟失的數據包,進行重傳,而不是一股腦全部重傳
SACK 的實現原理
SACK報文之中,除了有ACK 字段,還有其他字段,表示接收端接收到的數據包序列,兩者一比,就可以知道缺失的數據包序列是哪些。
Linux 下 查看 SACK 機制是否開啟
內核參數 net.ipv4.tcp_sack 的值 表示是否開啟 1 為開啟 0 為關閉
導致數據包重傳的原因
- 1:發送端所發送的數據包在網絡之中丟失,觸發發送端的超時重傳。
- 2:發送端的數據包到達接收端的時候出現亂序,觸發發送端快速重傳。
導致 數據包亂序的原因
- 1: 外部網絡的問題
- 2:本機所發送數據包 出現亂序
tcp 數據包重傳監控
netstat -s | grep retran
輸出信息:
segments retransmitted 重傳的報文數
fast retransmits 快速重傳數
Linux 下 命令行工具 用于測試網絡狀況 對 數據包重傳 的影響
iperf3 -c 服務端地址
輸出的信息之中 有一列 Retr,表示重傳的數據包數。
tcp 流量控制
tcp 流量控制產生的原因
發送端 和 接收端 的收發速率可能不一致,需要進行平衡。
tcp 流量控制的作用
使發送方發送的數據不要太快,讓接收方來得及接收數據。(進行端到端之間的控制)
tcp 流量控制的實現
使用滑動窗口機制來實現 tcp 流量控制
擁塞控制
擁塞控制的作用
自己的理解:避免發送方的數據充滿這個網絡。當網絡發生擁塞的時候,調整發送方的數據發送行為。
擁塞窗口
擁塞窗口的定義
擁塞窗口 cwnd 是發送方維護的一個的狀態變量,它會根據網絡的擁塞程度動態變化的。
網絡擁塞程度高 cwnd 越小。
初始化擁塞窗口 的值
有Linux 內核代碼定義。自己搜索得知是10
轉換到字節的大小 需要 ?? MSS
Linux 下 如何查看一個tcp 連接當前的擁塞窗口大小
ss -nti
命令的輸出信息 有 cwnd ,cwnd * MSS 是擁塞窗口的大小(單位 字節)
擁塞窗口大小的意義(如何看待擁塞窗口大小 這個指標)
看到擁塞窗口這個指標,可以用來判斷網絡的擁塞程度。
當然導致網絡擁塞的原因 還要探索 是否是 網絡層 或者網卡隊列擁塞導致的,還是外部的網絡擁塞導致的。
擁塞窗口大小 與 發送窗口大小的關系
發送窗口的大小= min(另一端接收窗口的大小,cwnd * MSS)
滑動窗口機制
滑動窗口機制的實現
發送端和接收端都有兩個窗口,一個發送窗口,一個接收窗口。
發送端發送窗口的大小 取決于 接收端在tcp報文之中所發送的接收端的接收窗口的大小。
發送端 和 接收端 接收窗口的大小 會受到 tcp連接的接收緩沖區剩余空間大小的影響。
如果發送端 接收到 ACK確認報文之中 接收端的接收窗口大小為0,那么 發送端停止發送數據,同時發送端會啟動一個堅持定時器,等待一定時間后發送探測報文,并將堅持計時器的等待時間加倍并復位,直到收到對方的回復為止(等待時間到達一定的限制后,不會加倍了,保持不變)。
keep alive 計時器 (保活計時器)
keep alive 計時器產生的原因
在長時間沒有交互的過程之中,交互雙方都有可能出現 死機 掉電 異常重啟 等意外。
當意外發生的時候,tcp連接還沒來得及釋放,另一端并不知道這個情況,另一端會一直維護這個連接,長時間的積累會導致非常多的半打開連接,造成系統資源的消耗和浪費。
注意:
用來檢測連接是否可用,這個是重點。
keep alive 計時器的工作原理
在一定時間內 服務端沒有收到客戶端發送的數據,服務端就向客戶端按照一定的時間間隔多次發送探測報文,如果多次發送的探測報文都沒有收到回復,則終止連接。如果收到,keep alive 計時器復位。
keep alive 計時器的相關參數
內核參數
net.ipv4.tcp_keepalive_time : keep alive 計時器等待的時間長度,單位是秒
net.ipv4.tcp_keepalive_probes : keep alive 計時器 發送探測報文的次數
net.ipv4.tcp_keepalive_intvl: keep alive 計時器發送探測報文的時間間隔 單位是秒
tcp keep alive 計時器是否開啟
KeepAlive默認不是開啟的,需要在應用層設置 SO_KEEPALIVE, 才可以生效。
linux的內核參數會包含 tcp keep-alive 的相關參數
粘包
粘包的定義
接收端一次讀取可能讀取客戶端發送的多個數據包的內容。客戶端所發送的多個數據包就像粘在了一起,成為了一個數據包。
(應用程序調用 一次socket的read()方法,返回的數據可能包含多個數據包的數據)
粘包的產生原因
tcp接收緩存區之中是字節流,沒有明顯的用以判斷數據包分隔的符號。
半包
半包的定義
接收端一次讀取,讀取的數據內容不到完整的一個數據包。
(應用程序調用 一次socket的read()方法,返回的數據只是一個完整數據包的一小部分)
半包的產生原因
tcp接收緩存區之中是字節流,沒有明顯的用以判斷數據包分隔的符號。
tcp 四次揮手斷開連接
問題
1: 四次揮手之中,如果第一次發送端的FIN 報文丟失了,如何進行處理
無論是第一次發送的FIN報文在網絡之中被丟棄,還是超過時間沒有收到ACK 報文,都會被發送端認為是報文丟失,會觸發發送端的超時重傳機制。
FIN 報文的重傳次數 由 內核參數 /proc/sys/net/ipv4/tcp_orphan_retries 決定,如果取值為0,默認的重傳次數為8 (外部搜索得知,內核代碼之中的寫法)
2:四次揮手之中,如果第二次揮手,所發送的ACK 確認報文丟失了,結果會如何?
無論是因為 ACK 報文在網絡上傳輸時被丟棄,還是因為網絡延時,沒有按時到達,
都會觸發 發送端 超時重傳 FIN 報文。
3:四次揮手之中,第三次揮手的 FIN報文 丟失了,結果會如何?
觸發發送端超時重傳。
4:四次揮手之中,第四次揮手的 ACK報文 丟失了,結果會如何?
觸發第三次揮手的FIN報文 超時重傳。
5: 如果第三次揮手的FIN 報文一直不發送,處于FIN_WAIT_2狀態的那一端會如何?
由于另一端一直不發送 FIN報文,那么接收端會處于 FIN_WAIT_2狀態,
接收端的FIN_WAIT_2狀態會存在一定的時間,如果在這個時間內,仍然沒有收到FIN報文,那么接收端不會轉入 TIME_WAITING狀態就會直接關閉。發送端的套接字還是會處于 CLOSE_WAIT狀態,不會變化。
接收端FIN_WAIT_2存在時間由內核參數 net.ipv4.tcp_fin_timeout 決定, 單位是秒
6:為什么 tcp 主動發起連接的一端,會在TIME_WAIT狀態等待 2 * MSL的時間?
可以讓tcp 重發最后的ACK,防止ACK確認報文丟失而帶來的問題。
7:tcp連接 在 TIME_WAIT狀態等待 2 * MSL的時間,帶來的影響
意味著發起關閉的客戶端的 套接字(ip+端口)在 2 * MSL 內無法復用。
實際的例子,寫java代碼,客戶端socket綁定本地端口,執行一次連接,
然后重新執行,會拋出端口已綁定的異常。
8: 限制服務器 處于 TIME_WAIT 狀態的連接數 參數
/proc/sys/net/ipv4/tcp_max_tw_buckets
TIME_WAIT 狀態的連接數 超出限制 則內核會報錯。
Linux tcp 信息 查看工具
- netstat
- ss
tcp監控
1:查看當前 Linux 上的 tcp 連接數(已建立連接)
netstat -ant | grep ESTABLISHED | wc -l
2:查看當前 Linux 上 各進程的 tcp連接數(已建立連接)
netstat -antp | grep ESTABLISHED | awk '{print $7}' | awk -F '|' '{count[$1]++;} END {for(i in count) {print i,count[i]}}'
3: 查看當前 Linux 上 tcp連接信息
ss -nti
tcp 全局監控指標
- 1:數據包重傳數
- 2:所接受的數據包亂序數。
實驗
tcp 三次連接 重傳機制實驗
目的: 驗證 SYN 重傳機制。
步驟:
1:在iptables之中增加 對 127.0.0.1 5000端口 TCP協議 SYN數據包的丟棄規則。
iptables -t filter -I INPUT -p tcp -s 127.0.0.1 --dport 5000 --syn -j DROP
2: 使用tcpdump 對本地回環網卡接口 進行抓包,查看報文的數據
tcpdump -n -i lo -Ss0 src 127.0.0.1 and dst 127.0.0.1 and port 5000
3:設置 SYN報文重傳次數為1
echo 1 > /proc/sys/net/ipv4/tcp_syn_retries
4:新開一個終端,使用telnet建立連接, 記錄命令的執行耗時
date '+ %F %T'; telnet 127.0.0.1 5000; date '+ %F %T';
5:驗證兩個SYN 報文之間的時間間隔是否為1秒,telnet 程序的耗時是否為3秒
tcp SYN+ACK 重傳的時間間隔探索實驗
目的:查看第一次 SYN+ACK 的超時的時間
步驟:
1:終端1 nc -l 8090 建立服務端
nc -l 8090
2: 終端2 iptbales 增加丟失 SYN包的規則
iptables -t filter -I INPUT -p tcp --tcp-flags ACK ACK --dport 8090 -j DROP
3: 終端2 tcpdump 抓包
tcpdump -n -i lo 'port 8090'
4: 終端3 nc 127.0.0.1 8090 建立連接
nc 127.0.0.1 8090
5: 查看終端2 上 SYN+ACK 報文的輸出時間間隔
tcp 數據包 超時重傳機制 超過限制會如何處理
目的:查看 tcp 數據包 超時重傳機制 超過限制次數的處理結果
步驟:
1:調整內核參數 net.ipv4.tcp_retries1 ipv4.tcp_retries2 的值
echo 2 > /proc/sys/net/ipv4/tcp_retries2
1: 在終端1 建立服務端
nc -l 8090
2: 在終端2 建立客戶端
nc 127.0.0.1 8090
3: 在終端3 增加iptables 規則 丟棄 客戶端發送的數據包
iptables -t filter -I INPUT -p tcp --tcp-flags PSH PSH --dport 8090 -j DROP
4:在終端3 網卡抓包
tcpdump -n -i lo 'port 8090'
5:在終端2 的客戶端發送數據
6: 觀察 終端的狀況
發現 終端2 的客戶端斷開了。
執行 ss -nt | grep 8090 ,另一端的連接也斷開了。
問題
1: PUSH 報文 是由 用戶進程控制發出 還是由操作系統控制發出?
操作系統
2: 內核的tcp模塊 什么時候將發送緩沖區之中的數據發送出去?
這個內核的發送機制 留待以后探索
3:內核的tcp模塊 什么時候將接收緩沖區之中的數據 發送給應用程序。
這個內核的接收機制 留待以后探索。