动手学习TCP:客户端状态变迁
上一篇文章中介绍了TCP连接的建立和终止。
通过实际操作了解到,在TCP协议工作过程中,客户端和服务端都会接收或者发送特定标志的TCP数据包,然后进入不同的状态。
也就是说,TCP协议就是一个包含多种状态转换的状态机,下面介绍一下TCP状态机。
TCP状态机
网络上的传输是没有连接的,包括TCP也是一样的。TCP所谓的"连接",其实是在通讯的双方维护一个"连接状态",让它看上去好像有连接一样。
所以,了解TCP状态机,以及TCP的状态变迁是非常重要的。
TCP 协议的操作可以使用一个具有 11 种状态的有限状态机来表示(看下图),图中的矩形表示状态,箭头表示状态之间的转换。
- 客户端的状态变迁用红实线,服务器端的状态变迁用蓝实线
- 图中红实线表示客户端正常的状态变迁
- 图中蓝实线表示服务端正常的状态变迁
2. 虚线用于不常见的序列,如复位、同时打开、同时关闭等等

根据上面的状态变迁图,可以看到在TCP状态机的全部11种状态中:
- 客户端特有的状态:SYN_SENT、FIN_WAIT_1、FIN_WAIT_2、CLOSING、TIME_WAIT 。
- 服务端特有的状态:LISTEN、SYN_RCVD、CLOSE_WAIT、LAST_ACK 。
- 共有的状态:CLOSED、ESTABLISHED 。
下面就主要来看看客户端的状态变迁。
客户端状态变迁
根据状态变迁图,客户端的正常状态变迁流程如下:
CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
具体的将状态跟TCP包关联起来就如下表所示,根据这张表,我们就可以构建客户端正常状态变迁的状态机了:
|
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 |
- |
- |
客户端状态变迁实验
有了上面的客户端状态变迁表之后,我们就清楚客户端会接受或发送什么类型的包,然后进入什么特定的状态了。
下面就可以通过Pcap.Net来模拟一些这个状态变迁过程了。
代码实现
首先在代码中定义了一个枚举类型,列出了TCP状态机的所有11中状态。
public enum TCPStatus
{
CLOSED,
LISTENING,
SYN_RECEIVED,
SYN_SEND,
ESTABLISHED,
CLOSE_WAIT,
LAST_ACK,
FIN_WAIT_1,
FIN_WAIT_2,
TIME_WAIT,
CLOSING,
NULL,
}
主程序开始之前,会将TCP状态机的初始状态设置为"CLOSED":
private static TCPStatus tcpStatus = TCPStatus.CLOSED;
主程序跟上一次TCP连接的实验类似,只是加入了TCP状态变迁的过程。
例如,当客户端发送过[SYN]数据包之后,根据上面总结的客户端TCP状态变迁表,将“tcpStatus”设置为“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();
communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Fin | TcpControlBits.Acknowledgment));
tcpStatus = TCPStatus.FIN_WAIT_1;
PacketHandler(communicator, endPointInfo);
}
注意,代码中有一点特殊的就是 bool clientToSendFin = true 这个标志:
- 正常情况下客户端在完成请求之后,会发送[FIN]包来请求终止TCP连接
- 但是很多应用服务器为了提高TCP连接的利用效率,会在TCP连接长时间空闲的情况下,会主动向客户端发送[FIN]包。
- 例如,我通过nodejs实现了一个http server进行测试,在TCP连接空闲3分钟之后,服务端会发送[FIN]终止连接
这次实验中的"PacketHandler"也跟上次有所不同,在TCP包的接收或发送的过程中,都加入了TCP状态变迁的逻辑。
结合这前面的状态变迁表,这段代码就非常容易理解了。
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); }
运行效果
下面,将"clientToSendFin"设置为"true",看看正常情况下客户端的状态变迁。
打开Wireshark监听"VirtualBox Host-Only Network"网卡,并设置filter为"port 8081"。
运行程序,通过console可以看到客户端和服务端之间的包,以及客户端的状态变迁。

下面是Wireshark抓到的包,这七个数据包就表示了TCP连接的建立和终止过程。

总结
本文介绍了TCP状态变迁图,根据客户端的状态变迁过程,得到了客户端的状态变迁表。
然后使用Pcap.Net,基于客户端的状态变迁表,构建了一个简单的客户端,展示了客户端状态变迁的过程。
通过这个实验,一定能够对TCP客户端的状态变迁有个深刻的印象。
动手学习TCP:客户端状态变迁的更多相关文章
- 动手学习TCP:TCP特殊状态
前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁. 下面就来看看TCP状态变迁过程中的几个特殊状态. SYN_RCVD 在TCP连接建立的过程中,当服务端接收到[SYN ...
- 动手学习TCP:总结和索引
TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...
- 动手学习TCP:数据传输
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...
- 动手学习TCP:数据传输(转)
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...
- 动手学习TCP:4种定时器
上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...
- 动手学习TCP:服务端状态变迁
上一篇文章介绍了TCP状态机,并且通过实验了解了TCP客户端正常的状态变迁过程. 那么,本篇文章就一起看看TCP服务端的正常状态变迁过程 服务端状态变迁 根据上一篇文章中的TCP状态变迁图,可以得到服 ...
- 动手学习TCP: 环境搭建
前一段时间通过Wireshark抓包,定位了一个客户端和服务器之间数据传输的问题.最近就抽空看了看<TCP/IP详解 卷1>中关于TCP的部分,书中用了很多例子展示了TCP/IP协议中的一 ...
- 动手学习TCP:TCP连接建立与终止
TCP是一个面向连接的协议,任何一方在发送数据之前,都必须先在双方之间建立一条连接.所以,本文就主要看看TCP连接的建立和终止. 在开始介绍TCP连接之前,先来看看TCP数据包的首部,首部里面有很多重 ...
- TCP之11种状态变迁
1. TCP 之11种状态变迁 TCP 为一个连接定义了 11 种状态,并且 TCP 规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态.如,当某个应用进程在 CLOSED 状 ...
随机推荐
- csharp: Speech
Speech SDK 5.1https://www.microsoft.com/en-us/download/details.aspx?id=10121 detects mobile devices ...
- PHP(1)——学习之前做点啥准备
工欲善其事必先利其器,当然是先准备工具咯.首先硬件条件就是双核CPU以及8G内存的电脑一台,操作系统环境:windows(64bit)7+.Mac OSX 10.10+.Linux 64bit.软件环 ...
- 【poj 3080】Blue Jeans(字符串--KMP+暴力枚举+剪枝)
题意:求n个串的字典序最小的最长公共子串. 解法:枚举第一个串的子串,与剩下的n-1个串KMP匹配,判断是否有这样的公共子串.从大长度开始枚举,找到了就break挺快的.而且KMP的作用就是匹配子串, ...
- HTTP路由
HTTP路由 HTTP路由(译者注:Play的路径映射机制)组件负责将HTTP请求交给对应的action(一个控制器Controller的公共静态方法)处理. 对于MVC框架来说,一个HTTP请求可以 ...
- django配置fcgi参数解释
manage.py runfcgi minspare=50 maxspare=200 maxchildren=1000 maxrequests=99999 host=127.0.0.1 port=80 ...
- CSS中的三种基本的定位机制
CSS 定位机制 CSS 有三种基本的定位机制:普通流.浮动和绝对定位. 一.普通流 除非专门指定,否则所有框都在普通流中定位.普通流中元素框的位置由元素在(X)HTML中的位置决定.块级元素从上到下 ...
- c# Json Dictionary序列化和反序列化
说明:Dictionary对象本身不支持序列化和反序列化,需要定义一个继承自Dictionary, IXmlSerializable类的自定义类来实现该功能.感觉完全可以把这样的类封装到C#库中,很具 ...
- 区别CALL SCREEN/SET SCREEN/LEAVE TO SCREEN
分类: 1,CALL SCREEN XXXX将在Screen调用栈(CALL STACK)上面添加一层调用(进栈),调用XXXX的PBO和PAI,如果XXXX的Next Screen不为0,那么将继续 ...
- R语言学习笔记:小试R环境
买了三本R语言的书,同时使用来学习R语言,粗略翻下来感觉第一本最好: <R语言编程艺术>The Art of R Programming <R语言初学者使用>A Beginne ...
- 查看一个软件ipa包的内容
1.打开itunes 下载你要用的app 把ipa文件拉到桌面上 然后进行压缩