自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

動(dòng)手學(xué)習(xí)TCP系列之客戶端狀態(tài)變遷

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
上一篇文章中介紹了TCP連接的建立和終止。通過(guò)實(shí)際操作了解到,在TCP協(xié)議工作過(guò)程中,客戶端和服務(wù)端都會(huì)接收或者發(fā)送特定標(biāo)志的TCP數(shù)據(jù)包,然后進(jìn)入不同的狀態(tài)。

上一篇文章中介紹了TCP連接的建立和終止。

通過(guò)實(shí)際操作了解到,在TCP協(xié)議工作過(guò)程中,客戶端和服務(wù)端都會(huì)接收或者發(fā)送特定標(biāo)志的TCP數(shù)據(jù)包,然后進(jìn)入不同的狀態(tài)。

也就是說(shuō),TCP協(xié)議就是一個(gè)包含多種狀態(tài)轉(zhuǎn)換的狀態(tài)機(jī),下面介紹一下TCP狀態(tài)機(jī)。

TCP狀態(tài)機(jī)

網(wǎng)絡(luò)上的傳輸是沒(méi)有連接的,包括TCP也是一樣的。TCP所謂的"連接",其實(shí)是在通訊的雙方維護(hù)一個(gè)"連接狀態(tài)",讓它看上去好像有連接一樣。

所以,了解TCP狀態(tài)機(jī),以及TCP的狀態(tài)變遷是非常重要的。

TCP 協(xié)議的操作可以使用一個(gè)具有 11 種狀態(tài)的有限狀態(tài)機(jī)來(lái)表示(看下圖),圖中的矩形表示狀態(tài),箭頭表示狀態(tài)之間的轉(zhuǎn)換。

客戶端的狀態(tài)變遷用紅實(shí)線,服務(wù)器端的狀態(tài)變遷用藍(lán)實(shí)線

圖中紅實(shí)線表示客戶端正常的狀態(tài)變遷

圖中藍(lán)實(shí)線表示服務(wù)端正常的狀態(tài)變遷

2. 虛線用于不常見的序列,如復(fù)位、同時(shí)打開、同時(shí)關(guān)閉等等

 

根據(jù)上面的狀態(tài)變遷圖,可以看到在TCP狀態(tài)機(jī)的全部11種狀態(tài)中:

客戶端特有的狀態(tài):SYN_SENT、FIN_WAIT_1、FIN_WAIT_2、CLOSING、TIME_WAIT 。

服務(wù)端特有的狀態(tài):LISTEN、SYN_RCVD、CLOSE_WAIT、LAST_ACK 。

共有的狀態(tài):CLOSED、ESTABLISHED 。

下面就主要來(lái)看看客戶端的狀態(tài)變遷。

客戶端狀態(tài)變遷

根據(jù)狀態(tài)變遷圖,客戶端的正常狀態(tài)變遷流程如下:

CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED

具體的將狀態(tài)跟TCP包關(guān)聯(lián)起來(lái)就如下表所示,根據(jù)這張表,我們就可以構(gòu)建客戶端正常狀態(tài)變遷的狀態(tài)機(jī)了:

From State To State Recv Packet Send Packet
CLOSED SYN_SENT -[SYN]
SYN_SENT ESTABLISHED [SYN, ACK] [ACK]
ESTABLISHED FIN_WAIT_1 - [FIN, ACK]
FIN_WAIT_1 FIN_WAIT_2 [ACK] -
FIN_WAIT_2 TIME_WAIT [FIN, ACK] [ACK]
TIME_WAIT CLOSED - -

客戶端狀態(tài)變遷實(shí)驗(yàn)

有了上面的客戶端狀態(tài)變遷表之后,我們就清楚客戶端會(huì)接受或發(fā)送什么類型的包,然后進(jìn)入什么特定的狀態(tài)了。

下面就可以通過(guò)Pcap.Net來(lái)模擬一些這個(gè)狀態(tài)變遷過(guò)程了。

代碼實(shí)現(xiàn)

首先在代碼中定義了一個(gè)枚舉類型,列出了TCP狀態(tài)機(jī)的所有11中狀態(tài)。

public enum TCPStatus
{
CLOSED,
LISTENING,
SYN_RECEIVED,
SYN_SEND,
ESTABLISHED,
CLOSE_WAIT,
LAST_ACK,
FIN_WAIT_1,
FIN_WAIT_2,
TIME_WAIT,
CLOSING,
NULL,
}

主程序開始之前,會(huì)將TCP狀態(tài)機(jī)的初始狀態(tài)設(shè)置為"CLOSED":
private static TCPStatus tcpStatus = TCPStatus.CLOSED;

主程序跟上一次TCP連接的實(shí)驗(yàn)類似,只是加入了TCP狀態(tài)變遷的過(guò)程。

例如,當(dāng)客戶端發(fā)送過(guò)[SYN]數(shù)據(jù)包之后,根據(jù)上面總結(jié)的客戶端TCP狀態(tài)變遷表,將“tcpStatus”設(shè)置為“SYN_SEND”。

bool clientToSendFin = true;
communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Synchronize, null));
tcpStatus = TCPStatus.SYN_SEND;
PacketHandler(communicator, endPointInfo, clientToSendFin);
if (clientToSendFin)
{
Thread.Sleep(10000);
communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Fin | TcpControlBits.Acknowledgment));
tcpStatus = TCPStatus.FIN_WAIT_1;
PacketHandler(communicator, endPointInfo);
}

注意,代碼中有一點(diǎn)特殊的就是 bool clientToSendFin = true 這個(gè)標(biāo)志:

正常情況下客戶端在完成請(qǐng)求之后,會(huì)發(fā)送[FIN]包來(lái)請(qǐng)求終止TCP連接

但是很多應(yīng)用服務(wù)器為了提高TCP連接的利用效率,會(huì)在TCP連接長(zhǎng)時(shí)間空閑的情況下,會(huì)主動(dòng)向客戶端發(fā)送[FIN]包。

例如,我通過(guò)nodejs實(shí)現(xiàn)了一個(gè)http server進(jìn)行測(cè)試,在TCP連接空閑3分鐘之后,服務(wù)端會(huì)發(fā)送[FIN]終止連接

這次實(shí)驗(yàn)中的"PacketHandler"也跟上次有所不同,在TCP包的接收或發(fā)送的過(guò)程中,都加入了TCP狀態(tài)變遷的邏輯。

結(jié)合這前面的狀態(tài)變遷表,這段代碼就非常容易理解了。

#p#

private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo, bool clientToSendFin = true)
{
Packet packet = null;
bool running = true;
do
{
PacketCommunicatorReceiveResult result = communicator.ReceivePacket(out packet);
switch (result)
{
case PacketCommunicatorReceiveResult.Timeout:
// Timeout elapsed
continue;
case PacketCommunicatorReceiveResult.Ok:
bool isRecvedPacket = (packet.Ethernet.IpV4.Destination.ToString() == endPointInfo.SourceIp) ? true : false;
if (isRecvedPacket)
{
switch (packet.Ethernet.IpV4.Tcp.ControlBits)
{
case (TcpControlBits.Synchronize | TcpControlBits.Acknowledgment):
if (tcpStatus == TCPStatus.SYN_SEND)
{
Utils.PacketInfoPrinter(packet);
Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack);
tcpStatus = TCPStatus.ESTABLISHED;
}
break;
case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
if (tcpStatus == TCPStatus.FIN_WAIT_2)
{
Utils.PacketInfoPrinter(packet);
Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack);
tcpStatus = TCPStatus.TIME_WAIT;
}
else if (tcpStatus == TCPStatus.ESTABLISHED)
{
Utils.PacketInfoPrinter(packet);
Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack);
tcpStatus = TCPStatus.CLOSE_WAIT;
}
break;
case TcpControlBits.Acknowledgment:
if (tcpStatus == TCPStatus.FIN_WAIT_1)
{
tcpStatus = TCPStatus.FIN_WAIT_2;
Utils.PacketInfoPrinter(packet, tcpStatus);
}
else if (tcpStatus == TCPStatus.LAST_ACK)
{
tcpStatus = TCPStatus.CLOSED;
Utils.PacketInfoPrinter(packet, tcpStatus);
running = false;
}
break;
default:
Utils.PacketInfoPrinter(packet);
break;
}
}
else
{
switch (packet.Ethernet.IpV4.Tcp.ControlBits)
{
case TcpControlBits.Synchronize:
if (tcpStatus == TCPStatus.SYN_SEND)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
break;
case TcpControlBits.Acknowledgment:
if (tcpStatus == TCPStatus.ESTABLISHED)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
if (clientToSendFin)
running = false;
}
else if (tcpStatus == TCPStatus.TIME_WAIT)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
running = false;
}
else if (tcpStatus == TCPStatus.CLOSE_WAIT)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
Packet fin = Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Fin | TcpControlBits.Acknowledgment);
communicator.SendPacket(fin);
tcpStatus = TCPStatus.LAST_ACK;
}
break;
case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
if (tcpStatus == TCPStatus.FIN_WAIT_1)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
else if (tcpStatus == TCPStatus.LAST_ACK)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
break;
default:
Utils.PacketInfoPrinter(packet);
break;
}
}
break;
default:
throw new InvalidOperationException("The result " + result + " should never be reached here");
}
} while (running);
}

運(yùn)行效果

下面,將"clientToSendFin"設(shè)置為"true",看看正常情況下客戶端的狀態(tài)變遷。

打開Wireshark監(jiān)聽"VirtualBox Host-Only Network"網(wǎng)卡,并設(shè)置filter為"port 8081"。

運(yùn)行程序,通過(guò)console可以看到客戶端和服務(wù)端之間的包,以及客戶端的狀態(tài)變遷。

 

下面是Wireshark抓到的包,這七個(gè)數(shù)據(jù)包就表示了TCP連接的建立和終止過(guò)程。

 

總結(jié)

本文介紹了TCP狀態(tài)變遷圖,根據(jù)客戶端的狀態(tài)變遷過(guò)程,得到了客戶端的狀態(tài)變遷表。

然后使用Pcap.Net,基于客戶端的狀態(tài)變遷表,構(gòu)建了一個(gè)簡(jiǎn)單的客戶端,展示了客戶端狀態(tài)變遷的過(guò)程。

通過(guò)這個(gè)實(shí)驗(yàn),一定能夠?qū)CP客戶端的狀態(tài)變遷有個(gè)深刻的印象。

責(zé)任編輯:何妍 來(lái)源: 博客園
相關(guān)推薦

2015-10-12 08:33:06

TCP網(wǎng)絡(luò)協(xié)議服務(wù)端

2015-10-13 15:09:31

2015-10-08 14:03:01

TCP網(wǎng)絡(luò)協(xié)議

2015-10-09 13:15:03

TCP網(wǎng)絡(luò)協(xié)議

2015-10-14 09:44:55

TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)傳輸

2015-10-15 09:38:48

TCP網(wǎng)絡(luò)協(xié)議定時(shí)器

2009-12-21 09:05:22

2022-04-01 08:31:11

RabbitMQ客戶端Channel

2022-03-29 08:31:18

RabbitMQMQ客戶端

2010-06-28 10:11:00

桌面虛擬化虛擬化MED-V

2020-03-31 20:23:46

C語(yǔ)言TCP服務(wù)器

2022-04-07 08:30:57

AMQP協(xié)議RabbitMQ客戶端源碼

2012-01-13 10:29:37

ibmdw

2018-12-18 10:47:37

2019-08-28 15:19:15

PythonTCP服務(wù)器

2022-04-20 08:32:09

RabbitMQ流控制

2010-06-01 14:11:11

TortoiseSVN

2011-08-17 10:10:59

2021-09-22 15:46:29

虛擬桌面瘦客戶端胖客戶端

2009-12-21 15:53:56

WCF獲取客戶端IP
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)