前言
TCP/IP 協議是網絡通信的基石,TCP/IP協議不是只有 TCP 和 IP 協議,它是整個網絡通信中所有協議的簡稱。
維基百科:TCP/IP協議簇
維基百科:OSI模型
# TCP/IP 參考模型維基百科
https://zh.wikipedia.org/wiki/TCP/IP%E5%8D%8F%E8%AE%AE%E6%97%8F
# OIS 參考模型維基百科
https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B
OSI 參考模型
圖片來自 《圖解 TCP/IP 與 OSI 參考模型》 中 TCP/IP 協議分層模型
OSI 參考模型 (七層)是個理論模型,實際我們用的是 TCP/IP (四層)模型。不過我們可以通過 OSI 參考模型來學習 TCP/IP 模型。
應用層:應用程序通信細節的協議,比如常用的 HTTP。
傳輸層:主要是負責兩個節點之間數據傳輸,通信標識是 port 端口號。
網絡層:地址管理和路由選擇,在兩點之間找到一條最佳的通信路線,通信標識是 IP
數據鏈路層:負責物理層面鏈接的通信(同一個網段內)。也就是局域網中通過交換機鏈接的節點。通信標識是 mac 地址,網卡出廠自帶的標識。
物理層:將鏈路層的數據幀(字節流)轉換為電壓或光信號傳播。
網絡通信可以做什么呢?
redisson (一個操作 redis 的 JAVA 庫),就是使用的 netty 來做網絡通信連接 redis 服務的。
微服務中的服務發現和通信,就需要你熟悉網絡通信。
你要是在通信行業,那就不是了解了,你連協議的規范都得很清楚,不然路由器你都整不出來,還說什么 5G 。
作為一個 Java 后端開發,主要是開發偏應用層面的程序,離底層相對比較遠,熟練掌握即可,如果以后做通信行業的時候,你也一定會進一步學習的相關細節的。
TCP/IP 你不了解,也不會有多大問題,CRUD 還是沒有問題的。但是你了解了之后,日常開發定位和解決問題方面有很大助力,總之學習 TCP/IP 是一個重要不緊急的事情,根據自己目標和層次安排。
本文內容
- 局域網中各節點怎么通信
- 介紹 IP,ICMP,ARP 協議在網絡層的作用及路由表的作用,及網段劃分,子網掩碼、網關的作用
- 介紹交換機和路由器的作用
- 介紹 TCP/IP 三次握手和四次揮手,TCP 中通信狀態的作用,滑動窗口
- 介紹 tcp 包格式,ip 包格式,鏈路層 幀 數據格式
交換機與路由器
交換機
交換機
交換機上有多個端口(不是 port)供計算機連接,交換機會維護端口與連接這個端口的 PC 的 Mac 地址映射表。當交換機接受到數據的時候,會根據數據包的目的 Mac 地址,發送到交換機上對應的端口上,然后經過網線發送到目的 PC。
交換機連接多個電腦組成一個局域網,交換機連接交換機又可以組成一個更大的局域網。
比如 A、B 交換機各有 100 個端口,A 鏈接了 99 個PC,然后 B 交換機鏈接99 個,再將其中的一個端口 A/B 之間相互連接組成一個更大的局域網。
路由器
路由器工作在網絡層,主要用于將一個網段數據包轉發到另一個網段內。路由器上也會有個幾個 LAN 口 (Local Area Network,局域網),用于建立局域網。還會有一個 WAN(Wide Area Network,廣域網),連接運營商的網絡。
路由器也具有交換機的功能,只是 LAN 口 比較少,可以接入的電腦比較少。
當 PC 或者 手機 連接無線路由器時也會給 PC 分配一個局域網 IP,子網掩碼,網關等。
我住的地方的網絡拓撲圖如下:
當手機與電腦通信的時候,實際通過 LAN 口走局域網通信。
當手機訪問 維基百科 時,實際是通過路由器跳入到光貓網段,再通過光貓跳入到小區運營商的網絡,然后經過多個網絡節點連接到維基百科的服務器上。
只要需要有 IP 地址的設備(光貓,路由器,PC,手機)都需要有網卡,網卡出廠自帶有 Mac 地址。IP 和 Mac 地址的作用后文中會介紹。
交互機和路由器的區別
這部分內容是我自己的理解,我沒有在網上找到資料佐證,請謹慎對待
其實交換機和路由器硬件差別不大,只是硬件上的軟件決定了它能做什么。
2 層交換機上的軟件(只有數據鏈路層)可能只做解析幀,拿到 mac 地址,然后查找當前交換機的端口對應的 mac 地址,然后從對應的端口傳遞過去。
路由器(有網絡層和數據鏈路層),當路由器拿到數據包的時候,發現目的 mac 地址不是自己,就會將數據包通過 LAN 口發送出去。
當路由器接受的數據包的目的 MAC 地址是當前路由器上 MAC地址 ,路由器就會對其解包,拿到數據包 目的 IP ,然后根據 目的 IP 匹配下一跳 mac 地址,封包為新的幀數據發送出去。
TCP/IP 通信
從發送端發送數據的時候,數據經過每層的封包,經物理層傳送到接收端。接收端收到數據包,一層一層進行拆包,然后將數據數據發送給我接收端的應用層的應用程序。
通常我們說的第一層就是 物理層 ,第二層是 鏈路層 …...
數據鏈路層
源 MAC 地址 就是發送端的 MAC 地址,目標 MAC 地址不是最終的 MAC 地址,是下一跳節點的 MAC 地址。
類型 指的是這個以太網幀中的數據是何種類型的數據,比如 IPV4,IPV6。然后調用對應的接口進行處理。
數據鏈路層傳輸的幀是有大小限制的(64-1518 字節),能傳輸的數據的最大值就是最大傳輸單元,簡稱 MTU,Maximum Transmission Unit。這個值在以太網中通常是 1500。
# 查看網卡對應的 MTU
ifconfig -a
netstat -i
網絡層
網絡層主要以 IP 協議為主,也有 ICMP,ARP(在 TCPIP 模型 中,arp 屬于網絡層。在 osi 七層模型,arp 數據鏈路層。)。
DNS
IP 是網絡層通信的標識。但是 IP 不容易記憶,所以出現了 域名。
訪問 DNS 可以將域名解析為 IP 。
可以在本地配置 host ,定義域名和 IP 對應關系,這樣就不用解析了。
也可以在電腦配置 DNS 解析時訪問的 ip,這樣域名解析時就會訪問這個服務。
# 解析域名的 ip
dig www.mflyyou.cn
IP 基礎
IP 地址又可以分為 IPV4 和 IPV6,目前使用比較廣的是 IPV4 ,所以只介紹 IPV4。
IP 地址 由 32 (2 進制)位組成,32 位被 . 分為了四組。每組 8 位,十進制表示就是 xxx.xxx.xxx.xxx(xxx 取值在 0-255)。
IP 地址 由 網絡地址 (網段) 和 主機號 。
同一個網段的電腦用 2 層交互機相連,然后就可以局域網通信了。
同一個網段內,主機號不能重復,重復主機號的電腦不能上網。
為了便于區分出 IP 在那個網段,引入了子網掩碼 (netmask)。IP 地址與子網掩碼按位與計算可以得出網段,32 位 中取出網段所在的位,剩余就是主機號能取得值。
IP 中主機號全為 0 就是網段,全為 1 就是廣播地址。這兩個是不能被分配給電腦的。
IP:192.168.202.116
子網掩碼:255.255.252.0
網段為:192.168.200.0
廣播地址為:192.168.203.255
IP:192.168.201.56
子網掩碼:255.255.252.0
網段為:192.168.200.0
廣播地址為:192.168.203.255
ICMP
網絡層是不可靠傳輸,發送失敗的數據包,網絡層是不會再發一次數據包,但是會有 ICMP 包回復告訴你發包到底是什么問題。傳輸層 可以根據 ICMP 來判斷是否需要重發包。
ARP
ARP 用于查找 IP 對應的MAC 地址。
目的 IP 在路由表中查詢下一跳的 IP,在查詢這個 IP 對應的 mac 地址
查詢的這個 IP 是當前網段內的 ip,它會通過廣播地址發送給當前網段內所有主機,收到這個協議的主機會判斷是否是當前主機,是的話就會恢復當前 ip 對應的 MAC 地址。
通信過程分析
當我在瀏覽器輸入 wwww.mflyyou.cn 的時候:
1、先解析域名(DNS) www.mflyyou.cn 為 IP (目的 IP: 47.104.168.20)
2、將目的 IP 與本地路由表中的子網掩碼進行按位與,計算出網段與 Destination 匹配,看哪個匹配度更高,走哪個條目。都沒有匹配到走默認條目(0.0.0.0)
# 查看路由表
route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.31.1 0.0.0.0 UG 100 0 0 eth0
3、然后用 arp 查詢(有緩存可不查,走緩存)192.168.31.1 對應的 mac 地址
4、數據鏈路層封裝以太網幀數據包中的目的 Mac 地址址就是 192.168.31.1 對應的 Mac 地址,然后將數據幀發送到下一個節點(這也就常說的下一跳,數據包發送只是找到當前接節點的下一個節點)
5、到下一個路由器節點,路由器解包,看是發給自己的數據包(根據幀中的目的 MAC 地址與自己的 MAC 地址比較),不是就丟棄了;是的話就會解包拿到 目的 IP (47.104.168.20),然后在當前路由器上根據路由表查詢下一跳,發送給下一個節點;。。。。 直到目的服務器,或者發送的包 TTL 為 0
6、發到目的服務器的網卡上,網卡將數據復制到內核緩沖區,應用程序從緩沖區中讀取數據
IP 數據格式
IPv4 數據結構
圖來自《圖解 TCP/IP》
- 版本(Version):4 bit 構成,代表當前 IP包是哪個版本,IPv4 或者 IPv6,為 4 時表示當前是 IPv4。
- 首部長度(Internet Header Length):由 4 bit 構成,一般 20字節大小。
- 標識(Identification):用于分片重組用,值相同的屬于同一個 IP 數據包
- 標志(Flags):用于判斷是否還有分片。
- 總長度(Total Length):16 個字節,IP 數據包總的長度,最長可為 65525 字節。
- 分段偏移(Fragment Offset):表示這個包在原來 IP 包中的位置。
- 生存時間 TTL(Time To Live): IP 包在路由轉發中存活的時間,被路由轉發一次,次數減 1,為 0 時,數據包被丟棄。
- 掛載協議標識 (Protocol):記錄數據包中 Data(實際發送的數據)是什么類型的數據,1 標識 ICMP, 4 標識 IP, 6標識 TCP, 17 標識 UDP。根據這個掛載協議程序就知道調用哪些接口來進行后續的處理了。
數據鏈路層中以太網數據幀的 MTU 是 1500 字節,限定了 IP 數據包最大為 1500 字節。然后去掉 IP 包首部 20 字節,一般 IP 數據包發送的數據為 1480 字節。
當我們發送一個 3058 字節的 IP 數據包時,這顯然大于了數據鏈路層的 MTU (1500 字節)。所以網絡層會對大于鏈路層MTU 的數據包進行分片。拆分一個一個的1500 的數據包發送接收端,接收端接收到這三個包,在匯聚成一個完成的,在調用傳輸層接口。
# 會發送 3050 字節數據與 8 字節的 ICMP 首部,這個命令會總共發送 ip 數據大小 3058 字節。
ping -s 3050 www.mflyyou.cn
通過 wireshark 抓包可以看到,IP 數據包的首部長度占了 20 字節,實際每次發送數據為 1480 字節,最后一次發送了 98 字節。
從 Fragment 和 Identification 可以看到這三個包屬于同一個 IP 數據包,并且從 Fragment offset 能將這三個包合成一個完成的網絡層數據包。
傳輸層 TCP
TCP 是面向鏈接的,可靠的,全雙工協議。
面向連接就是發送之前,需要建立一個鏈接通道,數據都是在這個鏈接中發送。
TCP 協議中發送端會記錄發送的那些數據包被客戶端收到了。接收端接受數據之后,會回復一個 ACK 包(由數據格式中的控制位決定),確認應答號告訴發送端哪些數據包接收到了。
發送端發送了數據包之后,這個包會有一個重發倒計時,在這個倒計時內沒有收到接收端回復 ACK 包,就會再重發一個數據包。如果是 HTTP 請求 ,就相當于同樣的數據請求了兩次。
我們知道支付接口都要求冪等性,有一部分原因是因為這個超時重發。發送端發送了請求,接收端處理好業務之后回復的 ACK 包超時,發送端超時重發這個請求。如果不保證接口的冪等性,那么扣錢就會扣兩次。
我們要做的就是保證這個重發 n+1 次不再扣用戶的錢,一般會用一個 token 來判斷是不是重復請求,重復就不走扣款處理了,直接返回已經支付,保證接口的冪等性。或者用一個賬單流水來保證冪等性。
連接既然需要建立,那么也會有連接斷開。斷開連接需雙方協商好之后斷開連接,不能單方面關閉而不管對方。因為建立連接之后占用的計算機資源需要釋放掉。你單方面強制斷開連接釋放了資源,但是對方不知道需要斷開連接,分配的計算機資源一直占用那就是不可靠協議了。所以 TCP 有四次揮手斷開連接。
全雙工就是連接兩邊都可以主動發送接受數據,而不是輪訓訪問有沒有數據到達。
TCP 數據格式
首先我們要先了解 TCP 數據格式,才能更容易知道 TCP 的工作原理。
源端口號(Source Port)
占用 2 個字節。標識 發送端 程序的端口號,當接收端需要回復消息的時候,需要帶上這個端口號。
目的端口號(Destination Port)
占用 2 個字節。標識 接收端 程序的端口號,可以傳遞給監聽在這個端口的程序
控制位(Control Flag)
占用 6 位,不滿一個字節。標識當前 TCP 包是什么包,在通信過程中有一些特殊作用。
SYN
表示希望建立三次握手鏈接,并初始化序列號。
ACK
對收到數據包的應答確認。接收端接受數據之后,會回復 ACK 包,發送端從其上 確認應答號 知道接收端哪些數據已經接受了。
FIN
表示沒有數據發送了,希望斷開連接
PASH
接收端接收到這個數據包需要立刻傳遞給應用層,不能等待接收更多的數據包
RET
鏈接出現異常,需要強制斷開連接
URG
表示包中有需要緊急處理的數據
序列號(Sequence Number)
占用 4 個字節。TCP 三次握手的時候,發送端和接收端各自初始化(隨機的)自己的 `序列號。
我們可以這樣理解,發送端發送的數據就是一個字節數組,這個數組中每個字節都有一個 序列號。
發送端和接收端都有自己的序列號,并且不相同,在三次握手的時候自己初始化,然后告知對方。
確認應答號(Acknowledgement Number)
占用 4 個字節。確認應答號 也是指的序列號,指的是期望發送端下次發送的序列號,這個序列號(確認應答號)之前的數據已經接受處理了。
下圖是我抓包建立三次鏈接,然后我發送三次 1n 數據。
三次握手,發送端通過發送 SYN 包,發送自己的初始化序列號(893189542),然后發送的每個字節都會有一個序列號。
接收端發送 ACK 包中的 確認應答號,指明這個序列號之前的數據我已經接受了。
窗口大小(Window Size)
窗口大小適用于流控的。發送端不能一直發送消息,需要根據接收端的接受能力來調整發包的速率。
內核會為每個 TCP/IP 分配讀寫緩沖區,網卡會從寫緩沖區中把數據取走,然后發送。
TCP/IP 是可靠連接,所以它需要記錄哪些數據發送已被對方接受了(由確認應答號可以知道),接受的數據會被淘汰掉,節省內存空間。
窗口大小作用:接收端會通過 ACK 告訴發送端調整窗口大小。
當窗口中的數據全都是已發送未確認數據時,發送端不能再發送新的數據,必須等待窗口空出位置來。
當有一個數據包被確認了,發送端就可以發送新的數據包。已發送未確認數據會在超時的時候重新發包。
校驗和 (Checksum)
占用 2 個字節。校驗和 用于校驗數據包是否損壞。每個數據包都一個校驗和 ,接收端接收到數據之后,使用相同的算法對數據計算出一個 hash值,然后跟數據包的校驗和比較,不一樣說明數據在傳輸過程中損壞了,接收端會丟棄這個包,等待發送端重新發這個包。
TCP 中 MSS
鏈路層能發送的最大以太網幀為 1500 字節,MTU 為 1500。
IP 數據包能發送的最大數據 = MTU - IP 首部大小(一般 20 字節),IP 數據包超過這個 1500 字節會分片
TCP 傳輸數據以段 (Segment) 為單位。
TCP 為了避免分片,會主動將數據分片之后交給網絡層。 TCP 能傳輸的最大分段(只是數據不包括首部)稱之為 Max Segment Size,簡稱為 MSS。
MSS = MTU - IP 首部大小 - TCP 首部大小
在以太網中 TCP 的 MSS = 1500(MTU) - 20(一般 IP 首部大小) - 20(一般 TCP 首部大小)= 1460,這個值需要根據首部計算
MSS 值在三次握手時,會通過 MTU 計算的。
TCP 三次握手建立連接
圖片來自 碼出高效:Java 開發手冊
為什么是三次握手建立連接呢?很多面試官也會問。這其實是可靠連接的最少握手次數。
圖片來自 碼出高效:Java 開發手冊
這里還有個 全連接隊列和半鏈接隊列 的知識點
http://mflyyou.cn/2020/07/18/cong-linux-nei-he-li-jie-java-zhong-socket-tong-xin/#toc-heading-5
TCP 四次揮手斷開連接
圖片來自 碼出高效:Java 開發手冊
CLOSE_WAIT 是被動斷開連接的一方收到對方 FIN 包之后,回復 ACK 之后進入的狀態。進入 CLOSE_WAIT 之后不會接受數據了,進行已收數據的業務處理之后,在發送一個 ACK+FIN,進入 LASK_ACK,然后等待對方發送 ACK,超時沒有等到,會重試發送(內核可以配置重試發送次數)。當你發現服務端有大量的 CLOSE_WAIT 鏈接,服務端的代碼有問題,需要排查。
TIME_WAIT 的連接需要等待 2MSL再斷開連接。
TIME_WAIT 的連接多的話,服務端也要優化,不然這個鏈接會占用很長時間,在高并發的時候,資源連接釋放的慢,導致不能分配新的鏈接。
MSL 為 Maximum Segment Lifetime,在 centos 中 MSL 默認值為 60s,2MSL=2*60=120s
# sysctl -a | grep tcp_fin_timeout
# 推薦小于 30,也不能太小,15-30
net.ipv4.tcp_fin_timeout = 60
A 機器 TIME_WAIT 鏈接 會在 120 s 之后才能釋放。這個是為了保證 B 機器 能接收到最后一個來自 A 機器 的ACK。如果 B 機器 處于 LAST_ACK 狀態的連接沒有收到 A 機器 發來的 ACK 的話,會重試發送一個 FIN+ACK。這個 2MSL 也是為了最大限度保證 B 機器正常關閉。
三次握手 建立連接 和 四次揮手 斷開連接需要結合抓包工具自己分析一下,理解會更深刻。
網絡抓包
Wireshark 抓包分析是很厲害的,mac os 和 linux 都有命令行程序 tshark,可以在服務器用 tshark 抓包,拿到本地來分析。
抓包的時候一定要指定抓什么包,什么包都抓的話,一會你的電腦內存就飆升好多(別問我為啥知道,問就是 30g 內存都讓它吃了)。
Wireshark 有個 抓包過濾器 和 顯示過濾器。抓包的時候指定抓什么包這是 抓包過濾器 的作用,抓包之后顯示顯示那些內容那是 顯示過濾器 的作用
# -i 指定那個網卡
# -f 指定抓包過濾器
# -Y 顯示過濾器
# -w 指定抓包數據到文件,沒有 -w 輸出屏幕
# -V 顯示 TCP/IP 每層包的詳細信息,建議將抓包的文件在圖形化界面中查看,不指定 -V
tshark -i en0 -f "tcp" -Y "http"
# 抓取訪問 www.mflyyou.cn 的包
tshark -i en0 -w a.pcap -f "host www.mflyyou.cn"
# 指定抓那個協議 tcp,ip,icmp,arp,udp
tshark -i en0 -f "tcp"
# host 指定域名或者 ip
# port 指定端口
# 訪問 www.mflyyou.cn 的包,或者 icmp. ping www.baidu.com 也會被抓到
tshark -i en0 -f "host www.mflyyou.cn || icmp"
tshark -i en0 -f "port 80"
# 條件之間支持邏輯運算符 || && !
# 抓取 ssh 鏈接的包
tshark -i en0 -f "host www.mflyyou.cn && port 22"
參考資料
《圖解 TCP/IP》
linux-tcp 說明
鳥哥私房菜:基礎網絡的概念
本文由 張攀欽的博客 http://www.mflyyou.cn/ 創作。 可自由轉載、引用,但需署名作者且注明文章出處。
歡迎關注我的微信公眾號【Mflyyou】獲取持續更新。
github.com/zhangpanqin/MFlyYou 收集技術文章及我的系列文章,歡迎 Star。