使用wireshark分析tcp

今天使用wireshark來分析一下tcp的一些原理。首先我們建立一個tcp服務器。

const net = require('net');
net.createServer().listen(11111);

再建立一個tcp客戶端。

const net = require('net');
net.connect({port: 11111, host: '192.168.8.226'})

我們逐個情況分析。
1 不啓動服務器,啓動客戶端。
我們看看這種情況下tcp的表現。先看看總覽。

我們看到tcp首先發了一個syn包。

因爲服務器沒有啓動,所以客戶端沒有收到sync+ack包,然後重傳了兩次。最後報錯。

2 啓動服務器,啓動客戶端。
我們看看一次完整的tcp握手是怎樣的。

首先客戶端發送seq等於0的sync包,然後服務器返回一個sync+ack的tcp包。並且確認的序號是1。即1之前的序列號已經收到了。最後客戶端再發送一個ack包。這時候seq等於1,說明握手是佔據序號的。ack也等於1。客戶端告訴服務器1之前的序列已經收到。這就完成了三次握手。那麼三次握手意味着什麼呢?又是怎麼實現的呢?三次握手的本質其實就是在兩端記錄了一些上下文。比如服務器端記錄了我和哪個ip端口建立了連接。那麼下次收到這個客戶端的時候包的時候,服務器就會從這個表裏找,是否有記錄,有的話說明已經建立了連接,是個合法的請求。否則發送重置包給客戶端(我們可以使用c語言構造一個tcp報文)。

3 客戶端掛了(或者服務器掛了)
我們看一下,如果客戶端直接掛機了,tcp是怎麼處理的。

我們看到tcp會給服務器發一個重置包。

4 客戶端(或服務器)正常關閉連接
我們先改一下客戶端代碼

const net = require('net');
const socket = net.connect({port: 11111, host: '192.168.8.226'});
socket.on('connect', (client) => {
    socket.destroy()
})

上面的代碼使得客戶端完成三次握手後立刻開始四次握手關閉連接。我們看看tcp的表現。

我們只看後面四行(四次揮手)。首先客戶端發送了一個fin包,但是我們發現seq是等於1,說明fin包是不消耗序列號的。同理,服務器首先返回了一個ack包。然後再發送一個fin包,等到客戶端返回ack。客戶端最後一次發送ack的時候,需要等到2msl。該ip和端口可以重用,除非設置了端口複用。

5 兩端一起關閉
我們把服務器代碼也改一下。

const net = require('net');
net.createServer((socket) => {
	socket.destroy();
}).listen(11111);

服務器完成三次握手的時候,在回調裏立刻發送fin包。那麼這時候tcp會怎樣處理呢?因爲三次握手中,第三次握手是由客戶端發送的,客戶端發送第三次握手的時候,就進入了完成連接狀態(established)。而這時候服務器還沒有收到第三次握手的數據包。所以客戶端會先發送fin包。那麼問題就來了。客戶端發送的fin包和第三次握手的ack包,哪個先到服務端,影響了後續的流程。下面就是這兩種情況。
第三次握手的ack先到
fin包先到
6 keep-alive
tcp默認情況下是不會自動斷開的,需要調用方去控制。不過tcp還是做了一些優化,就是如果隔了一段時間,沒有數據傳輸,那麼tcp就會發送探測包,如果還沒有數據傳輸或者沒有收到探測包的ack,則每隔一段時間再次發送探測包(這兩個一段時間的值,在window下貌似是一樣的,linux下可以不一樣)。發到一定的次數還沒有任何動靜,那麼就發重置包給對端斷開連接。

我們看到三次握手後,我們沒有傳輸數據,tcp就會一直髮送探測包。

總結:今天就到這裏,tcp非常複雜,本文列舉了一些例子,分析一些tcp的某些原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章