动手学习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(10000);

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 = 123,

Options = IpV4Options.None,

Protocol = null, // Will be filled automatically.

Ttl = 100,

TypeOfService = 0,

};

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 > 0) ? packet.Ethernet.IpV4.Tcp.Payload.Length : 1),

ControlBits = tcpControlBits,

Window = windowSize,

UrgentPointer = 0,

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连接的建立和终止有一个比较直观的认识。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354