动手学习TCP:TCP连接建立与终止
TCP是一个面向连接的协议,任何一方在发送数据之前,都必须先在双方之间建立一条连接。所以,本文就主要看看TCP连接的建立和终止。
在开始介绍TCP连接之前,先来看看TCP数据包的首部,首部里面有很多重要的字段,在我们实现程序的时候需要进行设置。
TCP的首部
在OSI七层模型中,上层的数据包都会作为下层数据包的数据部分(payload)。
也就是说,当构造TCP数据包的时候,会把应用层的数据包作为TCP包的数据部分,然后加上TCP头构成TCP数据包;同样,当构造IP数据包的时候,整个TCP包就会被当作数据部分,然后加上IP头构成IP数据包。

TCP头的数据格式如下,在不包括可选字段的情况下,一般TCP头会占用20个字节。

在TCP首部中,有几个字段是需要关注一下:
- 在TCP首部中没有源和目标的IP、MAC地址(IP和MAC地址分别是网络层和链路层首部的信息),只有源和目标的端口
- Sequence Number是包的序号,网络层(IP层)的传输是不可靠的,可能产生包乱序,所以这个需要可以解决网络包乱序的问题
- Acknowledgement Number用来确认收到数据包的确认序号,为TCP的传输提供了可靠性保证
- TCP Flags包括了8个bit,通过对这些bit的设置,可以代表不同类型的TCP数据包
下面就看看TCP连接的建立和终止。
TCP连接建立
TCP连接建立的过程被称为三次握手过程:
- 连接建立发起端发送[SYN]包,该端将主动打开(active open)
- 接收端将发送[SYN, ACK]包,该端将被动打开(passive open),ACK标志表示对收到的[SYN]包的确认
- 连接建立发起端发送[ACK]包确认[SYN, ACK]包

Initial Sequence Number
连接建立过程中,一个重要的工作就是初始化Sequence Number,通信的双方在建立连接的过程中互相通知对方自己的初始Sequence Number(ISN:Initial Sequence Number)。ISN不是固定的,ISN跟时钟绑定,根据特定的间隔自增,直到超过2^32,又从0开始。
SYN全称就是Synchronize Sequence Number,通过seq序号,TCP就可以保证数据包的顺序;通过ack序号,TCP就有了可靠性。
连接建立注意点
在建立TCP连接的过程中,有以下两点需要注意一下:
- [SYN]标志的数据包会使用消耗一个序号,所以对端的确认号(ack)是当前序号(seq)加一
- 当被动打开端发送[ACK]确认包的时候,同时设置了[SYN]标志,所以TCP连接建立的过程只需要三次握手,而不是四次
TCP连接终止
TCP连接终止的过程被称为四次挥手过程,以下图为例:
- 连接终止端(client)发送[FIN, ACK] 包,关闭client到server方向的数据发送通路
- server端发送[ACK]包来确认来自client的[FIN, ACK] 包
- server端发送[FIN, ACK] 包,关闭server到client方向的数据发送通路
- client端发送[ACK]包来确认来自server的[FIN, ACK] 包,到此TCP连接关闭

连接终止注意点
在建立TCP连接的过程中,有以下两点需要注意一下:
- [FIN]标志的数据包会使用消耗一个序号,所以对端的确认号(ack)是当前序号(seq)加一
- 与建立连接时的三次 握手不同,终止连接需要四次挥手
- 因为TCP连接是全双工的,每个方向都必须单独进行关闭。当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,但是TCP连接在收到一个FIN后仍能发送数据
TCP连接实验
好了,了解了TCP连接建立和终止的基本知识后,就可以通过Pcap.Net来进行TCP连接建立和终止的实验了。
建立连接代码的基本流程如下:
- client程序使用一个初始的seq序号(100),然后生成并发送一个带[SYN]标志的TCP包
- client将期待来自服务端的[SYN, ACK]包
- 当收到[SYN, ACK]包之后,client需要生成并发送一个[ACK]包进行确认,这个[ACK]包的ack号是[SYN, ACK]包seq号加一
终止连接代码的基本流程如下:
- client程序delay 10秒钟,然后发送[FIN, ACK]包关闭client到server的通路,继续使用全局的seq号
- client将期待来自服务端的[ACK]包,以及[FIN, ACK]包
- 最后client发送[ACK]包,seq号需要加一,因为[FIN]标志的包将消耗一个序号,TCP连接终止完成
主程序如下,发送TCP连接建立和终止请求,每个请求发送后都用PacketHandler处理收到的包:
communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Synchronize, null));
PacketHandler(communicator, endPointInfo); // delay 10 secs, then client to send Fin
Thread.Sleep(); communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Fin | TcpControlBits.Acknowledgment));
PacketHandler(communicator, endPointInfo);
程序的主要逻辑在PacketHandler中,这个函数根据收到的不同TCP包的类型完成不同的逻辑,产生并发送不同类型的包。
例如,当PacketHandler接收到来自服务端的[SYN, ACK]包后,处理函数就会生成并发送一个[ACK]确认包。也就是说,PacketHandler的逻辑就是实现了TCP连接建立和终止的逻辑。
private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo)
{
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):
Utils.PacketInfoPrinter(packet);
Packet ack4SynAck = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack4SynAck);
break; case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
Utils.PacketInfoPrinter(packet);
Packet ack4FinAck = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack4FinAck);
break;
case TcpControlBits.Acknowledgment:
Utils.PacketInfoPrinter(packet);
break;
default:
Utils.PacketInfoPrinter(packet);
break;
}
}
else
{
switch (packet.Ethernet.IpV4.Tcp.ControlBits)
{
case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
Utils.PacketInfoPrinter(packet);
break;
case TcpControlBits.Synchronize:
Utils.PacketInfoPrinter(packet);
break;
case TcpControlBits.Acknowledgment:
Utils.PacketInfoPrinter(packet);
running = false;
break;
default:
Utils.PacketInfoPrinter(packet);
break;
}
}
break;
default:
throw new InvalidOperationException("The result " + result + " should never be reached here");
}
} while (running); }
在PacketHandler函数中用到了BuildTcpResponsePacket这个函数,这个函数根据收到的TCP包,来构建response包。
这个函数有下面几个注意点:
- 该函数会根据收到的包,设置response包的源和目的地址
- 该函数会接受PacketHandler传递来的TCP flags,并设置到TCP首部中
- 该函数的另一个重要部分就是会计算并设置TCP首部中的seq好ack号,这一点很重要
public static Packet BuildTcpResponsePacket(Packet packet, TcpControlBits tcpControlBits)
{
EthernetLayer ethernetHeader = new EthernetLayer
{
Source = new MacAddress(packet.Ethernet.Destination.ToString()),
Destination = new MacAddress(packet.Ethernet.Source.ToString()),
EtherType = EthernetType.None, // Will be filled automatically.
}; IpV4Layer ipHeader = new IpV4Layer
{
Source = new IpV4Address(packet.Ethernet.IpV4.Destination.ToString()),
CurrentDestination = new IpV4Address(packet.Ethernet.IpV4.Source.ToString()),
Fragmentation = IpV4Fragmentation.None,
HeaderChecksum = null, // Will be filled automatically.
Identification = ,
Options = IpV4Options.None,
Protocol = null, // Will be filled automatically.
Ttl = ,
TypeOfService = ,
}; TcpLayer tcpHeader = new TcpLayer
{
SourcePort = packet.Ethernet.IpV4.Tcp.DestinationPort,
DestinationPort = packet.Ethernet.IpV4.Tcp.SourcePort,
Checksum = null, // Will be filled automatically.
SequenceNumber = seqNum = packet.Ethernet.IpV4.Tcp.AcknowledgmentNumber,
AcknowledgmentNumber = ackNum = packet.Ethernet.IpV4.Tcp.SequenceNumber + (uint)((packet.Ethernet.IpV4.Tcp.Payload.Length > ) ? packet.Ethernet.IpV4.Tcp.Payload.Length : ),
ControlBits = tcpControlBits,
Window = windowSize,
UrgentPointer = ,
Options = TcpOptions.None,
}; PacketBuilder builder = new PacketBuilder(ethernetHeader, ipHeader, tcpHeader); return builder.Build(DateTime.Now);
}
运行效果
打开Wireshark监听"VirtualBox Host-Only Network"网卡,并设置filter为"port 8081"。
然后运行程序,通过console可以看到客户端发送的包,以及服务端返回的包,通过这些包完成了TCP连接的建立和终止。

下面是Wireshark中显示的结果,Wireshark比较友好,会显示相对seq号,所以看到的都是从0开始编号。
注意seq号和ack号的变化,[SYN]和[FIN]标志的TCP包都会消耗一个序号。

总结
本文介绍了TCP首部,通过设置TCP首部中的[SYN]标志,可以构造TCP连接建立请求包;通过设置[FIN]标志,可以构造TCP连接终止请求包。
文中使用Pcap.Net构建了一个简单的客户端,完成了向服务器建立(三次握手)和终止(四次挥手)连接的过程。
通过这个实验,一定会对TCP连接的建立和终止有一个比较直观的认识。
动手学习TCP:TCP连接建立与终止的更多相关文章
- 传输控制协议(TCP) -- 连接建立及终止过程
TCP简介 相对于不可靠.无连接的用户数据报协议(User Datagram Protocol, UDP),传输控制协议(Transmission Control Protocol, TCP)是可靠的 ...
- 动手学习TCP:总结和索引
TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...
- 动手学习TCP:TCP特殊状态
前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁. 下面就来看看TCP状态变迁过程中的几个特殊状态. SYN_RCVD 在TCP连接建立的过程中,当服务端接收到[SYN ...
- 动手学习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/IP之TCP的建立与终止
TCP协议简介 tcp/ip协议族中传输层最重要的两种协议是UDP和TCP协议,上一篇文章用很短的篇幅介绍完了UDP协议相关的内容,但相对于UDP而言的TCP协议,是种更复杂,应用更广的协议.在接下来 ...
- TCP传输连接建立与释放详解
一直以来有许多读者朋友对TCP的传输连接建立和释放过程不是很理解,而这又是几乎网络认证中必考的知识点,包括软考.CCNA\CCNP.H3CNA\H3CNE等,为此再把笔者年度巨作,广受好评的——< ...
随机推荐
- 重新想象 Windows 8.1 Store Apps (84) - 图像处理的新特性, Share Contract 的新特性
[源码下载] 重新想象 Windows 8.1 Store Apps (84) - 图像处理的新特性, Share Contract 的新特性 作者:webabcd 介绍重新想象 Windows 8. ...
- ugui 获取Text的高度,动态改变高度
项目中需要根据聊天内容的多少.显示外边框的高度.因为Text的内容是不固定的.但宽度是固定的.高度根据文字多少自增 可以通过Text的属性preferredHeight 获取文本框的高度
- python peewee.ImproperlyConfigured: MySQLdb or PyMySQL must be installed.
最近在学习Python,打算先看两个在线教程,再在github上找几个开源的项目练习一下,在学到“被解放的姜戈”时遇到django同步数据库时无法执行的错误,记录一下. 错误现象: 执行python ...
- 成熟的RosettaNet解决方案软件介绍
RosettaNet是一套B2B标准,以标准来优化供应链管理流程,它可以缩短整个供应链各个供货周期.RosettaNet 标准为电子商务标准化提供一个健壮的.非专有的解决方案,它是免费的,通过 Ros ...
- jdk1.8 J.U.C之FutureTask实现机制分析
我画了一张关于FutureTask的类图,主要包括FutureTask的几个重要的函数和字段,还有它和父类的关系. 根据上面图我们可以清晰的看出FutureTask的继承关系.FutureTask继承 ...
- Linux学习笔记(整理记录)
1.安装 (1):安装网址:http://www.jb51.net/os/78318.html 2.鸟哥的Linux命令学习 (1):显示系统目前所支持的语言:echo $LANG (2):修改语言成 ...
- angularjs作用域
作用域(scope)①是构成AngularJS应用的核心基础,在整个框架中都被广泛使用,因此了解它如何工作是非常重要的.应用的作用域是和应用的数据模型相关联的,同时作用域也是表达式执行的上下文.$sc ...
- Android开发 Failed to install *.apk on device 'emulator-5554': EOF
在运行android 程序时出现这样的错误: Failed to install homework.apk on device 'emulator-5554': EOF java.io.IOExcep ...
- WCF概念
WCF 概念 WCF是.NET Framework 上灵活通讯技术.在.NET 3.0推出之前,一个企业解决方案需要几种通讯方式.对于独立于平台的通讯,使用ASP.NET Web服务.对于比较高级的 ...
- 读取XML绑定TreeNode
<asp:TreeView ID="treeview" OnClick="TreeViewCheckBox_Click()" runat="se ...