本文主要介紹了工做中經常使用的TCP/IP對應協議棧相關基礎知識,科普文。html
本博客全部文章:http://www.cnblogs.com/xuanku/p/index.htmllinux
TCP/IP網絡協議棧分爲四層, 從下至上依次是:編程
鏈路層緩存
其實在鏈路層下面還有物理層, 指的是電信號的傳輸方式, 好比常見的雙絞線網線, 光纖, 以及早期的同軸電纜等, 物理層的設計決定了電信號傳輸的帶寬, 速率, 傳輸距離, 抗干擾性等等。服務器
在鏈路層自己, 主要負責將數據跟物理層交互, 常見工做包括網卡設備的驅動, 幀同步(檢測什麼信號算是一個新幀), 衝突檢測(若是有衝突就自動重發), 數據差錯校驗等工做。cookie
鏈路層常見的有以太網
, 令牌環網
的標準。網絡
網絡層併發
網絡層的IP協議是構成Internet的基礎。該層次負責將數據發送到對應的目標地址, 網絡中有大量的路由器來負責作這個事情, 路由器每每會拆掉鏈路層和網絡層對應的數據頭部並從新封裝。IP層不負責數據傳輸的可靠性, 傳輸的過程當中數據可能會丟失, 須要由上層協議來保證這個事情。tcp
傳輸層高併發
網絡層負責的是點到點的協議, 即只到某臺主機, 傳輸層要負責端到端的協議, 即要到達某個進程。
典型的協議有TCP/UDP兩種協議, 其中TCP協議是一種面向鏈接的, 穩定可靠的協議, 會負責作數據的檢測, 分拆和從新按照順序組裝, 自動重發等。而UDP就只負責將數據送到對應進程, 幾乎沒有任何邏輯, 也就是說須要應用層本身來保證數據傳輸的可靠性。
應用層
即咱們常見的HTTP, FTP協議等。
這四層協議對應的數據包封裝以下圖:
四層協議對應的通訊過程以下圖:
以太網數據幀格式以下:
說明以下:
在網絡通訊過程當中, 源主機的應用程序只知道目的應用程序的IP地址, 並不知道對方主機的硬件地址, 因此在數據發送以前, 須要先找到目標及其的硬件地址, 這就是ARP協議所起的做用了。
每次在創建鏈接以前, 會在本地網絡廣播發送目的IP地址, 全部機器都會受到該請求, 目的機器發現該請求中的IP地址跟本身同樣, 就把本身的硬件地址返回回去, 不然忽略該請求。
通常來講, 每臺機器都維護的有一個ARP緩存表, 存儲了近期的IP地址和硬件地址的映射關係, 能夠用arp -a
命令來查看緩存表中內容。
若是目的機器和本機器不在同一個網段以內的話, 會將數據發送給網關來處理, 通常網關就是路由器, 此時網關會進行IP路由, 將ARP請求發送到目的網絡地址, 而後再依次將應答返回給該發起請求的機器。
IP協議數據包格式以下:
幾個字段解釋以下:
IP地址的一共分爲以下幾類:
在互聯網剛出來的時候, 大部分組織都申請的B類網絡地址, 致使B類地址很快就用完了, 可是A類又有不少空閒的地址, 而每一個路由器又必須掌握全部網絡的信息, 隨着C類網絡的增多, 路由器中的路由表項數也就愈來愈多了。
針對這種狀況, 後來人們發現, 絕大部份內部網絡的機器都不須要一個獨立的公網IP的, 這些機器經過一個公網IP跟外部鏈接, 在本身的網絡內部爲每臺機器申請一個私有IP, 內部再建設一個路由器, 作內網IP地址的定位便可。
私有IP的出現大大解決了IP浪費的問題, 因此咱們平常中能夠看到不少如192.168.xx這樣的IP, 這些IP都只是局域網內部IP, 不會浪費IP地址。
因而, RFC1918就規定了組建局域網的私有IP地址規範:
這些私有IP地址雖然沒有公網IP, 可是仍然能夠經過NAT等技術來跟公網進行鏈接交互。
除了私有IP以外, 還有幾種特殊的IP地址:
TCP協議數據包以下:
部分字段解釋以下:
上圖中每次鏈接線上的數字標記了這次數據包中的關鍵信息, 好比
SYN,1000(0),<mss 1460>
表明: 請求包包含SYN標記, 32位序號爲1000, 不包含數據, 帶有一個mss的選項, 其值爲1460SYN,8000(0),ACK,1001,<mss 1024>
表明: 請求包包含SYN和ACK標記, 32位序號爲8000, 不會包含數據, 32位確認序號爲1001, 一樣帶有mss選項那麼接下來咱們看TCP協議的交互過程:
創建鏈接
至此, 鏈接創建完畢, 能夠發送數據了, 該過程包含了客戶端和服務器各一次請求和應答, 服務器的請求和應答放到一個包中作了, 一共包含3次包發送, 因此該過程又被稱爲三次握手。
交換數據
這一段主要是要理解TCP交互的序號管理邏輯, 由於是全雙工協議, 即服務器和客戶端能夠同時像對方發送數據, 因此須要客戶端和服務器各維護一個序列號。若是是半雙工協議的話, 就只須要一方維護一個序號便可。
關閉鏈接
在創建鏈接的時候, 服務器的請求和應答是合併到了一個包當中。可是在關閉鏈接的過程當中, 就必須分開兩個包來, 由於客戶端關閉鏈接以後就不能再發送數據了, 可是服務器還能夠發送數據給客戶端, 直到服務器也發送FIN標記。
如上講的都是一來一回的交互, 通常狀況下可能會存在一方數據發得特別快, 另外一方數據發得特別慢, 這種時候若是不作控制, 勢必會讓慢的這方數據處理不過來從而致使丟包。
TCP協議中採用了滑動窗口協議
來解決該問題, 相似上面的mss
, 再增長一個新的選項win
, 告訴對方本身的滑動窗口大小, 對方在發送數據的時候每次發送數據就知道對方到底窗口空間還夠不夠, 若是不夠了就不發了, 從而解決了一快一慢這種問題。
以下圖:
其餘狀態都還好, 在工做中常會碰到TIME_WAIT鏈接過多的問題, 這裏把TIME_WAIT狀態單獨拿出來講一下。
TIME_WAIT是主動關閉方在收到被動關閉方發的FIN包以後處於的狀態, 這個包是主動關閉方收到的最後一個包了, 在收到這個包以後還不能直接就把鏈接給關閉了, 還得等待一段時間才能關閉, 等待時間爲2MSL。
爲何要等待一段時間呢? 主要是兩個緣由:
在收到最後一個包以後主動關閉方還得發一個ACK回去, 這個ACK可能會丟包, 若是丟包, 對方還須要從新發最後一個FIN包, 若是收到從新發過來的FIN包的時候這邊廂連接已經關閉, 則會致使連接異常終止;
不過第1點也不會形成太大的問題, 畢竟數據已經正常交互了。可是有另一點風險更高, 就是若是不等待2MSL的話, 那麼若是正好一個新連接又創建在相同的端口上, 那麼上次的FIN包可能由於網絡緣由而延時迷途的包這個時候才送達該端口, 致使下一次鏈接出現問題;
因此必定要有一個TIME_WAIT的狀態等待一段時間, 等待的MSL時間RFC上面建議是2分鐘, 可是筆者實際工做中測試每每是30秒。
可是若是你的服務是一個高併發短鏈接服務, TIME_WAIT可能會致使鏈接句柄被大量佔用, 而你又相信服務內部是一個很是穩定的網絡服務, 或者即便有兩個鏈接交互出現故障也能夠接受或者有應用層處理, 不但願有那麼多的TIME_WAIT狀態的鏈接, 通常有兩種方式:
在/etc/sysctl.conf
中加入以下內容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
而後執行/sbin/sysctl -p
生效參數。
UDP協議就簡單不少了, 基本上就只包含源地址, 目的地址, 長度, 校驗, 數據。
交互過程也再也不像TCP這樣通過很複雜的創建鏈接和關閉鏈接的過程了, 就直接每次都發送數據了, 這樣會有以下的一些問題:
因此如前面所說, UDP協議並不保證數據的可靠性, 他通常用於一些高性能的場景, 且須要應用層再作一些簡單的封裝處理。