1.概述
OSI七層模型是萬能的國際標準化組織(ISO)提出的一個試圖使各種計算機在世界范圍內互連的理想標準
#從分層上來區分:
物理層:透明的傳輸比特流
數據鏈路層:建立邏輯鏈接、進行硬件地址尋址、差錯效驗等功能
網絡層:進行邏輯地址尋址,實現不同網絡之間的路徑選擇
傳輸層:定義傳輸數據的協議端口號,以及流控和差錯效驗
會話層:建立、管理、終止會話。(在五層模型中已經合并到了應用層)對應主機進程,指本地主機與遠程主機正在進行的會話
表示層:數據的表示、安全、壓縮。(在五層模型中已經合并到了應用層)
應用層:網絡服務于最終用戶的一個接口
七層模型 | 五層模型 | 四層模型 |
---|---|---|
應用層 | ||
表示層 | 應用層 | 應用層 |
會話層 | ||
傳輸層 | 傳輸層 | 傳輸層 |
網絡層 | 網絡層 | 網絡層 |
數據鏈路層 | 數據鏈路層 | 鏈接層/實體層 |
物理層 | 物理層 |
1.2 TCP/IP協議簇
#TCP
TCP是面向連接的一種傳輸控制協議。
TCP通過三次握手建立連接, 通過四次揮手斷開連接。
TCP連接之后,客戶端和服務器可以互相發送和接收消息,
在客戶端或者服務器沒有主動斷開之前,連接一直存在,故稱為長連接。
特點:連接有耗時,傳輸數據無大小限制,準確可靠,先發先至。
#UDP
UDP是無連接的用戶數據報協議。
所謂的無連接就是在傳輸數據之前不需要交換信息,
沒有握手建立連接的過程,只需要直接將對應的數據發送到指定的地址和端口就行。
特點: 不穩定,速度快,可廣播,一般數據包限定64KB之內,先發未必先至。
#HTTP
>> HTTP/1.0是基于TCP協議的應用,請求時需建立TCP連接,而且請求包中需要包含
請求方法,URI,協議版本等信息,請求結束后斷開連接,完成一次請求/響應操作。故稱為短連接。
>> HTTP/1.1中的keep-alive所保持的長連接則是為了優化每次HTTP請求中TCP連接三次握手的麻煩和資源開銷,
只建立一次TCP連接,多次的在這個通道上完成請求/響應操作。
值得一提的是,服務器無法主動給客戶端推送消息。
#WebSocket
WebSocket也是一種協議,并且也是基于TCP協議的。
具體流程是WebSocket通過HTTP先發送一個標記了 Upgrade 的請求,
服務端解析后開始建立TCP連接,省去了HTTP長連接每次請求都要上傳header的冗余,
可以理解為WebSocket是HTTP的優化,但WebSocket不僅僅在Web應用程序上得到支持。
#HTTP、WebSocket與TCP的關系
HTTP通信過程屬于“你推一下,我走一下”的方式,客戶端不發請求則服務器永遠無法發送數據給客戶端,
WebSocket則在進行第一次HTTP請求之后,其他全部采用TCP通道進行雙向通訊。
所以,HTTP和WebSocket雖都是基于TCP協議,但是兩者屬于完全不同的兩種通訊方式。
1.x TCP協議和UDP協議的區別
1.TCP協議面向連接,UDP協議面向非連接 (有無鏈接)
2.TCP協議傳輸速度慢,UDP協議傳輸速度快 (傳輸速度)
3.TCP協議保證數據順序,UDP協議不保證 (數據的有序性,在IP層時,數據包會變得無序)
4.TCP協議保證數據正確性,UDP協議可能丟包 (TCP保證數據的可靠性)
5.TCP協議對系統資源要求多,UDP協議要求少 (TCP和UDP占用的資源)
2.相關協議
2.1 WebSocket協議
2.1.1概述
WebSocket是一種在單個TCP連接上進行全雙工通信的協議。即允許服務端主動向客戶端推送數據。
WebSocket通信協議于2011年被IETF定為標準RFC 6455,并由RFC7936補充規范。
WebSocket API也被W3C定為標準, 是HTML5新增的協議。
WebSocket API中,瀏覽器和服務器只需要完成一次握手,
兩者之間就直接可以創建持久性的連接,并進行雙向數據傳輸。
HTTP 有 1.1 和 1.0 之說,也就是所謂的 keep-alive ,把多個 HTTP 請求合并為一個,
但是 Websocket 其實是一個新協議,跟 HTTP 協議基本沒有關系,
只是為了兼容現有瀏覽器,所以在握手階段使用了 HTTP 。
2.1.1 為什么傳統的HTTP協議不能做到WebSocket實現的功能
這是因為HTTP協議是一個請求-響應協議,請求必須先由瀏覽器發給服務器,
服務器才能響應這個請求,再把數據發送給瀏覽器。
換句話說,瀏覽器不主動請求,服務器是沒法主動發數據給瀏覽器的。
這樣一來,要在瀏覽器中搞一個實時聊天,或者在線多人游戲的話就只能借助Flash這些插件。
也有人說,HTTP協議其實也能實現啊,比如用輪詢或者Comet。
#ajax輪詢
ajax輪詢的原理非常簡單,讓瀏覽器隔個幾秒就發送一次請求,詢問服務器是否有新信息。
這個機制的缺點一是實時性不夠,二是頻繁的請求會給服務器帶來極大的壓力。
#Comet
本質上也是輪詢,但是在沒有消息的情況下,服務器先拖一段時間,等到有消息了再回復。
這個機制暫時地解決了實時性問題,但是它帶來了新的問題:
以多線程模式運行的服務器會讓大部分線程大部分時間都處于掛起狀態,極大地浪費服務器資源。
另外,一個HTTP連接在長時間沒有數據傳輸的情況下,鏈路上的任何一個網關都可能關閉這個連接,
而網關是我們不可控的,這就要求Comet連接必須定期發一些ping數據表示連接“正常工作”。
以上兩種機制都治標不治本,所以,HTML5推出了WebSocket標準,
讓瀏覽器和服務器之間可以建立無限制的全雙工通信,任何一方都可以主動發消息給對方。
WebSocket對客戶端和服務端的要求
#瀏覽器
要支持WebSocket通信,瀏覽器得支持這個協議,這樣才能發出ws://xxx的請求。
目前,支持WebSocket的主流瀏覽器如下:
>> Chrome
>> Firefox
>> IE >= 10
>> Sarafi >= 6
>> Android >= 4.4
>> iOS >= 8
#服務器
由于WebSocket是一個協議,服務器具體怎么實現,取決于所用編程語言和框架本身。
1) Node.js
本身支持的協議包括TCP協議和HTTP協議,要支持WebSocket協議,
需要對Node.js提供的HTTPServer做額外的開發。
2) java
3) python
2.1.2 WebSocket的握手通信過程
>> WebSocket并不是全新的協議,而是利用了HTTP協議來建立連接。
>> WebSocket 是獨立的、創建在 TCP 上的協議。
>> Websocket 通過HTTP/1.1 協議的101狀態碼進行握手。
>> 為了創建Websocket連接,需要通過瀏覽器發出請求,之后服務器進行回應,這個過程通常稱為握手(handshaking)。
1) 首先,WebSocket連接必須由瀏覽器發起,因為請求協議是一個標準的HTTP請求,格式如下:
"GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13"
#該請求和普通的HTTP請求有幾點不同:
>> GET請求的地址不是類似/path/,而是以ws://開頭的地址;
>> 請求頭Upgrade: websocket和Connection: Upgrade表示這個連接將要被轉換為WebSocket連接;
>> Sec-WebSocket-Key: 是瀏覽器經過Base64加密后的密鑰,用來和response里面的Sec-WebSocket-Accept進行比對驗證
>> Sec-WebSocket-Extensions是對WebSocket的協議擴展
>> Sec-WebSocket-Version指定了WebSocket的協議版本。
>> 請求返回101狀態碼, 如果不是101狀態碼,表示握手升級的過程失敗了
101是Switching Protocols,表示服務器已經理解了客戶端的請求,
并將通過Upgrade 消息頭通知客戶端采用不同的協議來完成這個請求。
在發送這個響應后的空檔,將http升級到webSocket。
2) 隨后,服務器如果接受該請求,就會返回如下響應:
"HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string"
該響應代碼101表示本次連接的HTTP協議即將被更改,
更改后的協議就是Upgrade: websocket指定的WebSocket協議。
版本號和子協議規定了雙方能理解的數據格式,以及是否支持壓縮等等。
如果僅使用WebSocket的API,就不需要關心這些。
現在,一個WebSocket連接就建立成功,瀏覽器和服務器就可以隨時主動發送消息給對方。
消息有兩種,一種是文本,一種是二進制數據。
通常,我們可以發送JSON格式的文本,這樣,在瀏覽器處理起來就十分容易。
#為什么WebSocket連接可以實現全雙工通信而HTTP連接不行呢?
實際上HTTP協議是建立在TCP協議之上的,TCP協議本身就實現了全雙工通信,
但是HTTP協議的請求-應答機制限制了全雙工通信。
WebSocket連接建立以后,其實只是簡單規定了一下:
接下來,咱們通信就不使用HTTP協議了,直接互相發數據吧。
#安全的WebSocket連接機制和HTTPS類似:
首先,瀏覽器用wss://xxx創建WebSocket連接時,會先通過HTTPS創建安全的連接,
然后,該HTTPS連接升級為WebSocket連接,底層通信走的仍然是安全的SSL/TLS協議。
2.1.3 WebSocket 的其他特點
>> 建立在 TCP 協議之上,服務器端的實現比較容易。
>> 與 HTTP 協議有著良好的兼容性。
默認端口也是80和443,并且握手階段采用 HTTP 協議,
因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
>> 數據格式比較輕量,性能開銷小,通信高效。
>> 可以發送文本,也可以發送二進制數據。
>> 沒有同源限制,客戶端可以與任意服務器通信。
>> 協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。
2.1.4 WebSocket適用場景
追求數據的實時更新的應用, 例如:
>> 股票交易數據
>> 數據貨幣交易數據等
>> 消息傳遞應用程序
>> 在線多人游戲
>> 聊天室
>> 現場體育更新
>> 社交媒體
參考資料
http://www.websocket.org/index.html
https://segmentfault.com/a/1190000012709475
https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096
https://www.cnblogs.com/nnngu/p/9347635.html
https://www.cnblogs.com/bianzy/p/5822426.html
https://www.cnblogs.com/jingmoxukong/p/7755643.html (nginx代理WebSocket)
https://segmentfault.com/a/1190000012948613
http://www.lxweimin.com/p/42260a2575f8
2.2 RPC
http://www.lxweimin.com/p/45cbc2252c11 (參考資源)
2.3 IP協議
http://www.lxweimin.com/p/9e63767c04e1
2.3.1 IP理論部分
IP數據包由IP頭部和數據負載兩部分組成。
IP數據包長度不固定,其中頭部長度不固定,負載長度也不固定。
1.版本:占4位,表示IP協議的版本。如果是IPv4,則取值為0100,如果是IPv6,則取值為0110
2.IP包頭長度:
占4位,從0000-1111,也就是最小為0,最大為15。
實際上,IP包頭的長度至少為20字節,最大為60字節。
IP包頭長度的值 * 4就是ip包頭所占的字節數。
舉例來說,IP包頭長度的值是0110,值為6,那么IP包頭的長度就是6 * 4 = 24。
固定區域20字節,可選區域4字節。
3.服務類型:占8位,包含優先級、標志位等,實際中很少使用,不做介紹。
4.IP包總長度:占16位,表示該IP包的總長度。即IP包頭長度+IP包數據長度。
5.標識:占16位,在數據包分段重組時,標識表示包的序列號。
當數據包比較大時,會分成多個IP包發送,每個IP包到達目的地的時間是不確定的,此時就需要根據標識進行重組。
標識符與下面的標志、偏移量結合使用。
6.標志:占3位。從左至右依次是MF、DF、未使用字段。
MF = 1,表示后面還有分段數據包,MF = 0表示后面沒有分段數據包,也就是最后一個。
DF = 1,表示該數據包不能被分段,DF = 0表示數據包可以被分段。
7.偏移量:占13位。表示該數據段在上層初始數據報文中的偏移量。和標識符、標志位結合使用。
8.生存時間:占8位。
生存時間由操作系統初始化,每經過一次轉發,生存時間減1,如果生存時間為0,則該包丟棄。
生存時間是為了防止數據包在網絡中無休止發送,占用網絡資源。
9.協議:占8位。常用的UDP的值是17,TCP的值是6。
10.首部校驗和:占16位。對IP數據包首部校驗得到的值,校驗和有具體的算法。
11.源IP地址:占32位
12.目的IP地址:占32位。
上面的內容共占20個字節,這些部分是固定的,每一個IP包的頭部都包含這些,所以IP包頭的長度至少20字節。
下面的可選部分包含安全處理機制、記錄路徑、時間戳等信息,長度為0-40字節。
再下面就是IP包的數據部分。IP包的數據包含TCP包、UDP包兩種。
#注意
下圖中的位指的是bit,上文中說包的長度是單位是字節,1字節=8位。
2.3.2 IP抓包實踐
2.4 TCP協議
http://www.lxweimin.com/p/e41a329ef353 (握手等過程參考這里)
2.4.1 TCP理論部分
TCP包的格式和IP包格式類似,同樣由頭部和數據兩部分組成。
TCP包的長度不固定,其中頭部長度不固定,數據部分長度不固定。
由IP部分的內容可知,TCP包實際上就是IP包的數據部分。
#TCP頭部
1.源端口:占16位。
2.目的端口:占16位。
3.序號:sequence number,占32位。表示從發送端向接收端發送的字節流編號。
4.確認號:Acknowledgement number,占32位。表示接收端所期望接收到的下一序號。
5.數據偏移:
占4位。數據偏移其實就是TCP包的頭部長度,理論取值從0000-1111,最小為0,最大為15。
同IP頭部一樣,TCP包的頭部長度至少為20字節,最多為60字節,且計算方式和IP頭部長度的計算方式也一樣。
如果數據偏移的值為6,那么TCP包的頭部長度為6 * 4 = 24。
6.保留值:占6位。目前沒用,為了之后新功能所保留。
7.6位標志位。分別介紹:
>> URG:緊急標志位,為1說明緊急指針有效。
>> ACK:確認標志位,為1說明確認序號有效。
>> PSH:值為1,表示需要將數據立刻發送給應用程序。
>> RST:值為1時,表示需要重連。
>> SYN:握手時使用。值為1,表示連接請求報文。
>> FIN:值為1時,需要斷開連接。
8.窗口大小:占16位,表示接收端的窗口大小,用于控制網絡流量速率。
9.校驗和:占16位,和IP包頭部中的校驗和作用一致。
10.緊急指針:占16位,和上面提到的URG字段結合使用。
11.可選部分,包含窗口擴大選項、時間戳等。最小為0,最大為40字節。
TCP包固定頭部20字節,可選部分在0-40字節之間。
剩下的就是TCP包的數據部分。
2.4.2 TCP抓包實踐
2.4.3 TCP 三次握手 & 四次揮手
TCP 標志位
1、SYN(synchronous建立聯機)
2、ACK(acknowledgement 確認)
3、PSH(push傳送)
4、FIN(finish結束)
5、RST(reset重置)
6、URG(urgent緊急)
Sequence number(順序號碼)
Acknowledge number(確認號碼)
TCP狀態
LISTEN - 偵聽來自遠方TCP端口的連接請求;
SYN-SENT -在發送連接請求后等待匹配的連接請求;
SYN-RECEIVED - 在收到和發送一個連接請求后等待對連接請求的確認;
ESTABLISHED- 代表一個打開的連接,數據可以傳送給用戶;
FIN-WAIT-1 - 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認;
FIN-WAIT-2 - 從遠程TCP等待連接中斷請求;
CLOSE-WAIT - 等待從本地用戶發來的連接中斷請求;
CLOSING -等待遠程TCP對連接中斷的確認;
LAST-ACK - 等待原來發向遠程TCP的連接中斷請求的確認;
TIME-WAIT -等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認;
CLOSED - 沒有任何連接狀態.
建立連接協議(三次握手)
>> 第一次握手:
客戶端發送syn包(syn=x)的數據包到服務器,并進入SYN_SEND狀態,等待服務器確認;
>> 第二次握手:
服務器收到syn包,必須確認客戶的SYN(ack=x+1),
同時自己也發送一個SYN包(syn=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
>> 第三次握手:
客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),
此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
握手過程中傳送的包里不包含數據,三次握手完畢后,客戶端與服務器才正式開始傳送數據。
理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP連接都將被一直保持下去。
連接終止協議(四次握手)
client和server釋放資源
由于TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。
這個原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。
收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。
首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
TCP的連接的拆除需要發送四個包,因此稱為四次揮手(four-way handshake)。
客戶端或服務器均可主動發起揮手動作,在socket編程中,任何一方執行close()操作即可產生揮手操作。
TCP協議的連接是全雙工連接,一個TCP連接存在雙向的讀寫通道。
簡單說來是“先關讀,后關寫”,一共需要四個階段。
以客戶機發起關閉連接為例:
1.服務器讀通道關閉
2.客戶機寫通道關閉
3.客戶機讀通道關閉
4.服務器寫通道關閉
#詳細過程:
第一階段:
客戶機發送完數據之后,向服務器發送一個FIN數據段,序列號為x+2;
1.服務器收到FIN(x+2)后,返回確認段ACK,序列號為x+3,關閉服務器讀通道;
2.客戶機收到ACK(i+1)后,關閉客戶機寫通道;
(此時,客戶機仍能通過讀通道讀取服務器的數據,服務器仍能通過寫通道寫數據)
第二階段:
服務器發送完數據之后,向客戶機發送一個FIN數據段,序列號為y+1;
3.客戶機收到FIN(j)后,返回確認段ACK,序列號為y+2,關閉客戶機讀通道;
4.服務器收到ACK(y+2)后,關閉服務器寫通道。
這是標準的TCP關閉兩個階段,服務器和客戶機都可以發起關閉,完全對稱。
https://blog.csdn.net/yangyangye/article/details/38851271 (詳解)
https://blog.csdn.net/zzhongcy/article/details/21992471
2.4.4 一些問題
1.為什么建立連接協議是三次握手,而關閉連接卻是四次握手呢?
#建立連接時
因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求后,
它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個報文里來發送。
#斷開連接時
但關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;
但未必你所有的數據都全部發送給對方了,所以你可能未必會馬上會關閉SOCKET,
也即你可能還需要發送一些數據給對方之后,再發送FIN報文給對方來表示你同意現在可以關閉連接了,
所以它這里的ACK報文和FIN報文多數情況下都是分開發送的。
2. 為什么不能用兩次握手進行連接?
3次握手完成兩個重要的功能,既要雙方做好發送數據的準備工作(雙方都知道彼此已準備好),
也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被發送和確認。
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。
作為例子,考慮計算機S和C之間的通信,假定C給S發送一個連接請求分組,
S收到了這個分組,并發送了確認應答分組。
按照兩次握手的協定,S認為連接已經成功地建立了,可以開始發送數據分組。
可是,C在 S的應答分組在傳輸中被丟失的情況下,將不知道S是否已準備好,
不知道S建立什么樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。
在這種情況下,C認為連接還未建立成功,將忽略S發來的任何數據分組,只等待連接確認應答分組。
而S在發出的數據分組超時后,重復發送同樣的數據分組。這樣就形成了死鎖。
就好比打電話, A打給B, A要告訴B ,我打給你了, B要回應給A聽,OK,連接成功了;
然后B也要得到A的確認,才能開始正式通話。
3.為什么TIME_WAIT狀態還需要等2MSL后才能返回到CLOSED狀態?
什么是2MSL?
MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用《TCP/IP詳解》中的話:
“它(MSL)是任何報文段被丟棄前在網絡內的最長時間。”
那么,2MSL也就是這個時間的2倍,當TCP連接完成四個報文段的交換時,
主動關閉的一方將繼續等待一定時間(2-4分鐘),即使兩端的應用程序結束。
1)為什么需要這個2MSL呢?
>> 雖然雙方都同意關閉連接了,而且握手的4個報文也都協調和發送完畢,
按理可以直接回到CLOSED狀態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);
但是因為我們必須要假想網絡是不可靠的,你無法保證你最后發送的ACK報文會一定被對方收到,
因此對方處于LAST_ACK狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,
所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。
>> 報文可能會被混淆,意思是說,其他時候的連接可能會被當作本次的連接。
直接引用《The TCP/IP Guide》的說法:
The second is to provide a “buffering period” between the end of this connection and any subsequent ones.
If not for this period, it is possible that packets from different connections could be mixed, creating confusion.
2)TIME_WAIT狀態存在的理由:
>> 可靠地實現TCP全雙工連接的終止
在進行關閉連接四路握手協議時,最后的ACK是由主動關閉端發出的,如果這個最終的ACK丟失,
服務器將重發最終的FIN,因此客戶端必須維護狀態信息允許它重發最終的ACK。
如果不維持這個狀態信息,那么客戶端將響應RST分節,
服務器將此分節解釋成一個錯誤(在java中會拋出connection reset的SocketException)。
因而,要實現TCP全雙工連接的正常終止,必須處理終止序列四個分節中任何一個分節的丟失情況,
主動關閉 的客戶端必須維持狀態信息進入TIME_WAIT狀態。
>> 允許老的重復分節在網絡中消逝
TCP分節可能由于路由器異常而“迷途”,在迷途期間,TCP發送端可能因確認超時而重發這個分節,
迷途的分節在路由器修復后也會被送到最終目的地,這個原來的迷途分節就稱為lost duplicate。
在關閉一個TCP連接后,馬上又重新建立起一個相同的IP地址和端口之間的TCP連接,
后一個連接被稱為前一個連接的化身 (incarnation),那么有可能出現這種情況,
前一個連接的迷途重復分組在前一個連接終止后出現,從而被誤解成從屬于新的化身。
為了避免這個情 況,TCP不允許處于TIME_WAIT狀態的連接啟動一個新的化身,
因為TIME_WAIT狀態持續2MSL,就可以保證當成功建立一個TCP連接的時候,
來自連接先前化身的重復分組已經在網絡中消逝。
>> 對第二點的補充說明.
防止lost duplicate對后續新建正常鏈接的傳輸造成破壞。
lost duplicate在實際的網絡中非常常見,經常是由于路由器產生故障,路徑無法收斂,
導致一個packet在路由器A,B,C之間做類似死循環的跳轉。
IP頭部有個TTL,限制了一個包在網絡中的最大跳數,因此這個包有兩種命運:
要么最后TTL變為0,在網絡中消失;
要么TTL在變為0之前路由器路徑收斂,它憑借剩余的TTL跳數終于到達目的地。
但非??上У氖荰CP通過超時重傳機制在早些時候發送了一個跟它一模一樣的包,
并先于它達到了目的地,因此它的命運也就注定被TCP協議棧拋棄。
另外一個概念叫做incarnation connection,指跟上次的socket pair一摸一樣的新連接,
叫做incarnation of previous connection。
lost duplicate加上incarnation connection,則會對我們的傳輸造成致命的錯誤。
大家都知道TCP是流式的,所有包到達的順序是不一致的,依靠序列號由TCP協議棧做順序的拼接;
假設一個incarnation connection這時收到的seq=1000,
來了一個lost duplicate為seq=1000, len=1000,
則tcp認為這個lost duplicate合法,并存放入了receive buffer,導致傳輸出現錯誤。
通過一個2MSL TIME_WAIT狀態,確保所有的lost duplicate都會消失掉,避免對新連接造成錯誤。
3)該狀態為什么設計在主動關閉這一方:
(1)發最后ack的是主動關閉一方
(2)只要有一方保持TIME_WAIT狀態,就能起到避免incarnation connection在2MSL內的重新建立,不需要兩方都有
4)對服務器的影響
當某個連接的一端處于TIME_WAIT狀態時,該連接將不能再被使用。
事實上,對于我們比較有現實意義的是,這個端口將不能再被使用。
某個端口處于TIME_WAIT狀態(其實應該是這個連接)時,
這意味著這個TCP連接并沒有斷開(完全斷開),那么,如果你bind這個端口,就會失敗。
對于服務器而言,如果服務器突然crash掉了,那么它將無法在2MSL內重新啟動,因為bind會失敗。
解決這個問題的一個方法就是設置socket的SO_REUSEADDR選項。
這個選項意味著你可以重用一個地址。
5)如何正確對待2MSL TIME_WAIT
RFC要求socket pair在處于TIME_WAIT時,不能再起一個incarnation connection。
但絕大部分TCP實現,強加了更為嚴格的限制。
在2MSL等待期間,socket中使用的本地端口在默認情況下不能再被使用。
若A 10.234.5.5:1234和B 10.55.55.60:6666建立了連接,A主動關閉,
那么在A端只要port為1234,無論對方的port和ip是什么,都不允許再起服務。
顯而易見這是比RFC更為嚴格的限制,RFC僅僅是要求socket pair不一致,
而實現當中只要這個port處于TIME_WAIT,就不允許起連接。
這個限制對主動打開方來說是無所謂的,因為一般用的是臨時端口;
但對于被動打開方,一般是server,就悲劇了,因為server一般是熟知端口。
比如http,一般端口是80,不可能允許這個服務在2MSL內不能起來。
解決方案是給服務器的socket設置SO_REUSEADDR選項,
這樣的話就算熟知端口處于TIME_WAIT狀態,在這個端口上依舊可以將服務啟動。
當然,雖然有了SO_REUSEADDR選項,但sockt pair這個限制依舊存在。
比如上面的例子,A通過SO_REUSEADDR選項依舊在1234端口上起了監聽,
但這時我們若是從B通過6666端口去連它,TCP協議會告訴我們連接失敗,原因為Address already in use.
2.4.5 tcp長連接和短連接
HTTP協議是基于請求/響應模式的,因此只要服務端給了響應,本次HTTP連接就結束了,
或者更準確的說,是本次HTTP請求就結束了,根本沒有長連接/短連接這一說。
之所以說HTTP分為長連接和短連接,其實本質上是說的TCP連接。
TCP連接是一個雙向的通道,它是可以保持一段時間不關閉的,
因此TCP連接才有真正的長連接和短連接這一說。
HTTP協議說到底是應用層的協議,而TCP才是真正的傳輸層協議,只有負責傳輸的這一層才需要建立連接。
TCP在真正的讀寫操作之前,server與client之間必須建立一個連接,
當讀寫操作完成后,雙方不再需要這個連接時它們可以釋放這個連接,
連接的建立通過三次握手,釋放則需要四次握手,
所以說每個連接的建立都是需要資源消耗和時間消耗的。
TCP短連接
模擬一種TCP短連接的情況:
1.client 向 server 發起連接請求
2.server 接到請求,雙方建立連接
3.client 向 server 發送消息
4.server 回應 client
5.一次讀寫完成,此時雙方任何一個都可以發起 close 操作
在步驟5中,一般都是 client 先發起 close 操作。當然也不排除有特殊的情況。
從上面的描述看,短連接一般只會在 client/server 間傳遞一次讀寫操作!
#短連接的操作步驟是
建立連接——數據傳輸——關閉連接...建立連接——數據傳輸——關閉連接
TCP長連接
再模擬一種長連接的情況:
1.client 向 server 發起連接
2.server 接到請求,雙方建立連接
3.client 向 server 發送消息
4.server 回應 client
5.一次讀寫完成,連接不關閉
6.后續讀寫操作...
7.長時間操作之后client發起關閉請求
#長連接的操作步驟是
建立連接——數據傳輸...(保持連接)...數據傳輸——關閉連接
TCP長/短連接的優點和缺點
#長連接
#優點:
可以省去較多的TCP建立和關閉的操作,減少浪費,節約時間。
對于頻繁請求資源的客戶來說,較適用長連接。
#缺點:
client與server之間的連接如果一直不關閉的話,會存在一個問題,
隨著客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要采取一些策略,
如關閉一些長時間沒有讀寫事件發生的連接,這樣可以避免一些惡意連接導致server端服務受損;
如果條件再允許就可以以客戶端機器為顆粒度,限制每個客戶端的最大長連接數,
這樣可以完全避免某個蛋疼的客戶端連累后端服務。
#短連接
#優點:
對于服務器來說管理較為簡單,存在的連接都是有用的連接,不需要額外的控制手段。
#缺點:
但如果客戶請求頻繁,將在TCP的建立和關閉操作上浪費時間和帶寬。
TCP長/短連接的應用場景
#長連接
多用于操作頻繁,點對點的通訊,而且連接數不能太多情況。
每個TCP連接都需要三次握手,這需要時間,如果每個操作都是先連接,
再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,
再次處理時直接發送數據包就OK了,不用建立TCP連接。
例如:數據庫的連接用長連接,如果用短連接頻繁的通信會造成socket錯誤,
而且頻繁的socket 創建也是對資源的浪費。
#短鏈接
像WEB網站的http服務一般都用短鏈接,因為長連接對于服務端來說會耗費一定的資源,
而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,
如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。
所以并發量大,但每個用戶無需頻繁操作情況下需用短連好。
2.5 UDP協議
2.5.1 UDP基本理論
UDP數據傳輸的特點
#面向無連接
UDP 不需要與 TCP一樣在發送數據前進行三次握手建立連接,UDP想發數據就直接發送了;
并且UDP只是數據報文的搬運工,不會對數據報文進行任何拆分和拼接操作。
#不可靠
首先不可靠性體現在無連接上,通信都不需要建立連接,想發就發,這樣的情況肯定不可靠的;
并且收到什么數據就傳遞什么數據,也不會備份數據,發送數據也不會關心對方是否已經正確接收到數據;
再者網絡環境時好時壞,但是 UDP 因為沒有擁塞控制,一直會以恒定的速度發送數據;
即使網絡條件不好,也不會對發送速率進行調整,這樣實現的弊端就是在網絡條件不好的情況下可能會導致丟包,
但是優點也很明顯,在某些實時性要求高的場景(比如直播、電話會議等)就需要使用 UDP 而不是 TCP;
#單播、多播、廣播功能
由于 UDP 不會建立連接,因此它可以給任何人傳遞數據,
不止支持一對一的傳輸方式,同樣支持一對多、多對多、多對一的方式;
#UDP是面向報文的
發送方的UDP對應用程序交下來的報文,在添加首部后就向下交付IP層.
UDP對應用層交下來的報文,既不合并,也不拆分,而是保留這些報文的邊界;
#頭部開銷小,傳輸數據高效
UDP 的頭部開銷小,只有八字節,在傳輸數據報文時是比較高效的.在某些實時性要求高的場景,例如直播、電話會議、媒體傳輸等場景經常使用 UDP協議;
相對TCP來說,UDP是無序、無狀態的,因此UDP包相對TCP包來說要簡單一些。
UDP包實際上也是IP包的數據部分。
UDP包由頭部和數據兩部分組成,頭部長度固定,數據部分長度不固定。
1.UDP頭部的長度為固定8字節。
>> 源端口:占16位
>> 目的端口:占16位
>> UDP長度:包含頭部和數據包總長度,注意和TCP包、IP包的區別
>> 校驗和:占16位,同IP包、TCP包校驗和。
2.UDP的數據部分。
#注意
UDP頭部中UDP長度,這個地方和TCP、IP包有明顯的區別。
因為UDP包頭部長度是固定8個字節,所以該部分的取值最小為1000。
2.5.2 UDP抓包實踐
wireshark
2.6 HTTP協議
http://www.lxweimin.com/p/e41a329ef353 (參考這里)
http://www.lxweimin.com/p/7c01759c28dd (https加密傳輸參考這里)
2.6.1 HTTP基本理論
2.6.2 HTTP抓包實踐
2.7 DNS協議
2.7.1 DNS基本理論
DNS包屬于UDP包,實際上,DNS包是UDP包的數據部分。
DNS包同樣分為頭部和數據部分,頭部長度固定,數據部分長度不固定。
#DNS數據包結構
1.TranscationID:
會話標識,占2個字節。DNS請求報文和DNS應到報文的TranscationID是相同的。
2.Flags:
占2個字節,包含多個標志位:
2.1 QR:
占1位。0查詢報文,1響應報文
2.2 Opcode:
占4位。0標準查詢,1反向查詢,2服務器狀態查詢,3~15保留未用
2.3 AA:
占1位。為1,表示授權回答
2.4 TC:
占1位。為1,表示報文被截斷
2.5 RD:
占1位。0,表示客戶端期望域名解析服務器采用迭代的方式解析;1表示客戶端期望域名解析服務器采用遞歸的方式解析
2.6 RA:占1位。
>> 0,表示域名解析服務器采用迭代的方式解析;
>> 1表示域名服務器采用遞歸的方式解析
2.7 Zero:占3位。全0,保留位。
2.8 Rcode:占4位。響應碼.
>> 0表示無差錯;
>> 1表示查詢格式錯誤;
>> 2表示服務器失效;
>> 3表示域名錯誤;
>> 4表示查詢沒有被執行;
>> 5表示查詢被拒絕。
>> 6~15保留。
3.Questions:占2個字節,表示查詢問題區域的數量
4.Answers RRS:占2個字節,表示回答區域的數量
5.Authority RRs:占2個字節,表示授權區域的數量
6.Additional RRs:占2個字節,表示附加區域的數量
7.Quries:問題區域,長度不固定,可以有多個。
7.1 Name區域:就是查詢的域名,如www.baidu.com,Name區域也有固定的格式。
由于域名的長度不固定,所以name區域長度不固定。
7.2 Type: 查詢類型,占2個字節。DNS有多種查詢類型:
查詢類型 助記符 功能
1 A 獲得IPv4地址
28 AAAA 獲得IPv6地址
15 MX 郵件服務器
2 NS 指定域名服務器
5 CNAME 將域名指向另一個域名時
7.3 Class,查詢類,通常為1,表示Internet數據。
8.Answers:回答區域,長度不固定。
回答區域的格式和授權區域、附加區域的格式是類似的,因此,只介紹回答區域的格式。
8.1 Name:查詢的域名,同問題區域的域名。
但是格式和問題區域的域名不同,為了減小包大小,當包中出現重復域名的時候,回答區域的域名部分使用偏移量來表示。
當使用偏移量來表示時,占2個字節。不使用偏移量時,長度不固定(和域名本身長度有關)。
使用偏移量來表示時,首字節固定為C0,用于識別,后面字節用于表示偏移量。
通過上面的介紹可知,DNS包的頭部固定占12字節,頭部之后就是查詢問題區域,
查詢問題區域的第一部分就是域名。因此,常見的偏移量是C00C。
8.2 Type:同請求部分
8.3 Class:同請求部分
8.4 Time To Live:生存時間,占4個字節。實際上是客戶端緩存的時間。
通常情況,域名所對應的IP不會經常改變。
因此,為了提高網絡傳輸效率,可以將結果緩存,下次訪問時跳過域名解析。
8.5 數據長度:占2個字節,下文數據部分的長度。
8.6 數據:不定長。
9.Authoritative:同回答區域。
10.Additional:同回答區域。
2.7.2 DNS抓包實踐
3.網絡通信過程
使用集線器組成一個網絡
1)當有多臺電腦需要組成一個網時,那么可以通過集線器(Hub)將其鏈接在一起
2)一般情況下集線器的接口較少
3)集線器有個缺點,它以廣播的方式進行發送任何數據,
即如果集線器接收到來自A電腦的數據本來是想轉發給B電腦,
如果此時它還連接著另外兩臺電腦C、D,
那么它會把這個數據給每個電腦都發送一份,因此會導致網絡擁堵
使用交換機組成一個網絡
1)克服了集線器以廣播發送數據的缺點,當需要廣播的時候發送廣播,當需要單播的時候又能夠以單播的方式進行發送
2)它已經替代了之前的集線器
3)企業中就是用交換機來完成多臺電腦設備的鏈接成網絡的
使用路由器連接多個網絡(組包, 拆包過程)
數據傳遞的過程, 是mac地址的交換與轉發, 自始至終, 源ip和目的ip是固定的
路由器一般是默認網關, 具有數據轉發能力的設備
當數據傳遞時, 若一臺設備發送給另一臺設備時:
會先執行arp -a命令, 查看緩存中是否有另一個設備的mac地址(鏈路層),
若沒有, 則通過默認網關進行廣播, 廣播中有一類mac地址(FF:FF:FF:FF:FF:FF), 所有設備都能收到, 待鏈路層認證通過后, 進入ip層驗證ip,
若ip認證通過, 則返回該設備的mac,
若ip認證不通過, 丟掉包, 不響應
通信過程
1)在瀏覽器中輸入一個網址時,需要將它先解析出ip地址來(DNS解析, 各電腦都有DNS地址:DNS服務器由國家管控)
2)當得到ip地址之后,瀏覽器以tcp的方式3次握手鏈接服務器
3)以tcp的方式發送http協議的請求數據 給 服務器
4)服務器tcp的方式回應http協議的應答數據 給瀏覽器
1)MAC地址:在設備與設備之間數據通信時用來標記收發雙方(網卡的序列號)
2)IP地址:在邏輯上標記一臺電腦,用來指引數據包的收發方向(相當于電腦的序列號)
3)網絡掩碼:用來區分ip地址的網絡號和主機號
4)默認網關:當需要發送的數據包的目的ip不在本網段內時,就會發送給默認的一臺電腦,稱為網關
5)集線器:已過時,用來連接多態電腦,缺點:每次收發數據都進行廣播,網絡會變的擁堵
6)交換機:集線器的升級版,有學習功能知道需要發送給哪臺設備,根據需要進行單播、廣播
7)路由器:連接多個不同的網段,讓他們之間可以進行收發數據,每次收到數據后,ip不變,但是MAC地址會變化
8)DNS:用來解析出IP(類似電話簿)
9)http服務器:提供瀏覽器能夠訪問到的數據
10)網關: 具有轉發數據能力的設備
(如192.168.1.2要發數據給192.168.2.2, 但二者不可直接通信,
可通過路由器A(有兩塊網卡, ip分別是192.168.1.3, 192.168.2.3),
ip192.168.1.2先轉給192.168.1.3, 然后192.168.1.3轉發給192.168.2.3, 然后192.168.2.3最終轉給192.168.2.2)
4. 常見問題
4.1 TCP/IP協議簇粘包拆包問題
http://www.lxweimin.com/p/209576915459
參考資源
http://www.lxweimin.com/p/9e63767c04e1 (各種協議包結構)
http://www.lxweimin.com/p/29868fb82890 (TCP三次握手四次揮手)