TCP 协议简介

作者: 阮一峰

日期: 2017年6月 8日

TCP 是互联网核心协议之一,本文介绍它的基础知识。

一、TCP 协议的作用

互联网由一整套协议构成。TCP 只是其中的一层,有着自己的分工。

(图片说明:TCP 是以太网协议和 IP 协议的上层协议,也是应用层协议的下层协议。)

最底层的以太网协议(Ethernet)规定了电子信号如何组成数据包(packet),解决了子网内部的点对点通信。

(图片说明:以太网协议解决了局域网的点对点通信。)

但是,以太网协议不能解决多个局域网如何互通,这由 IP 协议解决。

(图片说明:IP 协议可以连接多个局域网。)

IP 协议定义了一套自己的地址规则,称为 IP 地址。它实现了路由功能,允许某个局域网的 A 主机,向另一个局域网的 B 主机发送消息。

(图片说明:路由器就是基于 IP 协议。局域网之间要靠路由器连接。)

路由的原理很简单。市场上所有的路由器,背后都有很多网口,要接入多根网线。路由器内部有一张路由表,规定了 A 段 IP 地址走出口一,B 段地址走出口二,......通过这套"指路牌",实现了数据包的转发。

(图片说明:本机的路由表注明了不同 IP 目的地的数据包,要发送到哪一个网口(interface)。)

IP 协议只是一个地址协议,并不保证数据包的完整。如果路由器丢包(比如缓存满了,新进来的数据包就会丢失),就需要发现丢了哪一个包,以及如何重新发送这个包。这就要依靠 TCP 协议。

简单说,TCP 协议的作用是,保证数据通信的完整性和可靠性,防止丢包。

二、TCP 数据包的大小

以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中, 1500 字节是负载(payload),22字节是头信息(head)。

IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。

(图片说明:IP 数据包在以太网数据包里面,TCP 数据包在 IP 数据包里面。)

TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。

因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。

(图片说明:以太网数据包的负载是1500字节,TCP 数据包的负载在1400字节左右。)

三、TCP 数据包的编号(SEQ)

一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。

发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。

第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。

(图片说明:当前包的编号是45943,下一个数据包的编号是46183,由此可知,这个包的负载是240字节。)

四、TCP 数据包的组装

收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。

对于应用程序来说,不用关心数据通信的细节。除非线路异常,收到的总是完整的数据。应用程序需要的数据放在 TCP 数据包里面,有自己的格式(比如 HTTP 协议)。

TCP 并没有提供任何机制,表示原始文件的大小,这由应用层的协议来规定。比如,HTTP 协议就有一个头信息Content-Length,表示信息体的大小。对于操作系统来说,就是持续地接收 TCP 数据包,将它们按照顺序组装好,一个包都不少。

操作系统不会去处理 TCP 数据包里面的数据。一旦组装好 TCP 数据包,就把它们转交给应用程序。TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。

(图片说明:系统根据 TCP 数据包里面的端口,将组装好的数据转交给相应的应用程序。上图中,21端口是 FTP 服务器,25端口是 SMTP 服务,80端口是 Web 服务器。)

应用程序收到组装好的原始数据,以浏览器为例,就会根据 HTTP 协议的Content-Length字段正确读出一段段的数据。这也意味着,一次 TCP 通信可以包括多个 HTTP 通信。

五、慢启动和 ACK

服务器发送数据包,当然越快越好,最好一次性全发出去。但是,发得太快,就有可能丢包。带宽小、路由器过热、缓存溢出等许多因素都会导致丢包。线路不好的话,发得越快,丢得越多。

最理想的状态是,在线路允许的情况下,达到最高速率。但是我们怎么知道,对方线路的理想速率是多少呢?答案就是慢慢试。

TCP 协议为了做到效率与可靠性的统一,设计了一个慢启动(slow start)机制。开始的时候,发送得较慢,然后根据丢包的情况,调整速率:如果不丢包,就加快发送速度;如果丢包,就降低发送速度。

Linux 内核里面设定了(常量TCP_INIT_CWND),刚开始通信的时候,发送方一次性发送10个数据包,即"发送窗口"的大小为10。然后停下来,等待接收方的确认,再继续发送。

默认情况下,接收方每收到两个 TCP 数据包,就要发送一个确认消息。"确认"的英语是 acknowledgement,所以这个确认消息就简称 ACK。

ACK 携带两个信息。

  • 期待要收到下一个数据包的编号
  • 接收方的接收窗口的剩余容量

发送方有了这两个信息,再加上自己已经发出的数据包的最新编号,就会推测出接收方大概的接收速度,从而降低或增加发送速率。这被称为"发送窗口",这个窗口的大小是可变的。

(图片说明:每个 ACK 都带有下一个数据包的编号,以及接收窗口的剩余容量。双方都会发送 ACK。)

注意,由于 TCP 通信是双向的,所以双方都需要发送 ACK。两方的窗口大小,很可能是不一样的。而且 ACK 只是很简单的几个字段,通常与数据合并在一个数据包里面发送。

(图片说明:上图一共4次通信。第一次通信,A 主机发给B 主机的数据包编号是1,长度是100字节,因此第二次通信 B 主机的 ACK 编号是 1 + 100 = 101,第三次通信 A 主机的数据包编号也是 101。同理,第二次通信 B 主机发给 A 主机的数据包编号是1,长度是200字节,因此第三次通信 A 主机的 ACK 是201,第四次通信 B 主机的数据包编号也是201。)

即使对于带宽很大、线路很好的连接,TCP 也总是从10个数据包开始慢慢试,过了一段时间以后,才达到最高的传输速率。这就是 TCP 的慢启动。

六、数据包的遗失处理

TCP 协议可以保证数据通信的完整性,这是怎么做到的?

前面说过,每一个数据包都带有下一个数据包的编号。如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。

举例来说,现在收到了4号包,但是没有收到5号包。ACK 就会记录,期待收到5号包。过了一段时间,5号包收到了,那么下一轮 ACK 会更新编号。如果5号包还是没收到,但是收到了6号包或7号包,那么 ACK 里面的编号不会变化,总是显示5号包。这会导致大量重复内容的 ACK。

如果发送方发现收到三个连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢包,即5号包遗失了,从而再次发送这个包。通过这种机制,TCP 保证了不会有数据包丢失。

(图片说明:Host B 没有收到100号数据包,会连续发出相同的 ACK,触发 Host A 重发100号数据包。)

七、参考链接

(完)

留言(55条)

您好,跟随您的博文很久了。我有一个长久以来的技术困惑,想求助一下:就是骨干网(两个直接相连的路由器之间)第二层(数据链路层)通行协议都用啥,主流的是啥?

你可能会问,为什么一个以太网数据包只能有1500字节负载,不能设计得更大一些吗?
这是因为这些协议是上个世纪设计的,必须考虑到那时的硬件状况。如果数据包太大,就会占用更多的缓存。如果缓存溢出,就会大量丢包,降低整个网络的通信效率。

//这个在根本上还真不是受制于缓存的限制(局域网这个真不是什么大问题)。而是在物理层的信号设计要求下,传输距离标准要求下, 协议上设计使用的时钟同步相关。

包过大,也意味着,在传输距离增加时,同一个信号包的传输时长变长,而时钟同步信号,必须是在固定的时隙中发出的,为了保证这一点,必须限制MTU的大小。

这也是本质上,不同协议的MTU值有非常大的差异。
以下可参考:
FDDI协议:4352字节
以太网(Ethernet)协议:1500字节
PPPoE(ADSL)协议:1492字节
X.25协议(Dial Up/Modem):576字节
Point-to-Point:4470字节

就服阮老师通俗易懂的介绍。我觉得楼上说得很有道理啊。

比我们大学老师讲的要好,经济学家,历史学家,艺术家 都来学计算机技术,让我对这个行业感到很有信息,希望能够看到未来的计算机技术为人类服务的那一天!

1522字节(15KB )

1522字节 == 15KB ??

慢开始与拥塞避免


快重传与快回复

因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。

其中如何理解“TCP数据包”和“一个HTTP请求”之间的联系?建立了一个TCP链接之后,发送的一个数据包中包含一个HTTP请求?亦或多个数据包承载一个HTTP请求?

另外:
期待要收到下一个数据包的编号
接收方的接收窗口的剩余容量

中的“接受窗口的剩余容量”是指下次再发数据包过来,大小就是接收窗口指定大小,这么理解吗?

> 这也意味着,一次 TCP 通信可以包括多个 HTTP 通信。

阮老师,这句话是不是写错了啊,应该是一次 HTTP 通信可以包括多个 TCP 通信吧

@vdust.leo:谢谢指出,这一段已经删除了。

@zyg:谢谢指出,已经改正。

@张小张:一次 TCP 通信包含多次 HTTP 通信,反之不成立。如果 HTTP 通信到一半, TCP 断了,就只能重新开始了。

@wongxp:可以看那张图,接受窗口就是还剩下的空闲的接受容量。

"图片说明:当前包的编号是45943,下一个数据包的编号是46183,由此可知,这个包的负载是240字节。"

这句话没看明白,序号和字节数是匹配的吗?

三年网络技术与通讯专业,不如阮老师三分钟的讲解。

讲解的很清晰

建议将802.3和Ethernet II分开来讲。

请教个问题,接收方怎么知道自己接收的哪个数据包是发送方发送的第一个包?

阮老师,
1、如何理解"一次 TCP 通信",它和"一个 TCP 数据包"是一个意思吗?
2、“HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度”,这个意思是http/2以前,一个http请求是通过多个tcp包发送的吗?
3、如果2成立,那在http/2以前,如何理解"根据 HTTP 协议的Content-Length字段正确读出一段段的数据。这也意味着,一次 TCP 通信可以包括多个 HTTP 通信",或者说这句话的"HTTP协议"就是指http/2 协议?http/2以前是一个http请求由多个tcp包发送,http/2之后,是一个tcp包发送多个http请求?

很赞

您好,最近在学习如何使用OAuth2,以及OLTU,搜遍百度大都是转载您写的OAuth2的文章,其道理讲得很透彻,但是具体如何应用到

您好,最近在学习如何使用OAuth2,以及OLTU,搜遍百度大都是转载您写的OAuth2的文章,其道理讲得很透彻,但是具体如何应用到自己项目中一直没找到合适的教程,想请教您OLTU具体集成以及使用是否有相关的文章或者Demo? 十分感谢

阮老师,你好。第一次看你的网站,就被深深吸引住了。我是一个毕业一年的IT工作者。一直在找寻未来的方向。知道今天看到你的网站,我很羡慕,也很憧憬,想问下您在哪座城市,我可以跟着您学习吗?

高手都是只言片语把高深的技术讲的通俗易懂,佩服

看阮老师的文章真是茅塞顿开啊

实在是受益匪浅

引用fromdtor的发言:

您好,跟随您的博文很久了。我有一个长久以来的技术困惑,想求助一下:就是骨干网(两个直接相连的路由器之间)第二层(数据链路层)通行协议都用啥,主流的是啥?

貌似这篇文章有讲:

https://zh.wikipedia.org/wiki/%E5%85%89%E7%BA%A4%E5%88%86%E5%B8%83%E5%BC%8F%E6%95%B0%E6%8D%AE%E6%8E%A5%E5%8F%A3

引用南阁的发言:

请教个问题,接收方怎么知道自己接收的哪个数据包是发送方发送的第一个包?

套接字机制,32+16位,32位ip通过网络层协议交付到主机,16位端口号通过多路复用和分解来交付到进程。即定位到特定主机上的特定进程。

今天突然想到之前好像看到tcp/ip方面详尽的介绍,就是忘了是哪里看到的了,最后突然想起来是在阮老师这里看到的。还是需要多学习啊。

请问为什么localhost 都是127.0.0.1?

阮老师,按前文提到的接收窗口由客服端告知服务端,那后面的图里client和server标注是不是反了呢,接受窗口就是还剩下的空闲的接受容量,而图中window却是server端发给client的,期待您的回复

阮老师,第三节中,TCP 数据包的编号(SEQ),一个TCP负载最大值约1400字节,为何下面的例子是100字节一个包发送,而不是按1400字节一个包发送呢。是不是因为MSS在TCP层就对数据包进行了分段。

@memory:

确实应该像你说的,负载1400字节比较合理。这里是为了举例,如何推算下一个包的编号,我就用100字节作为例子,现在想起来不够严谨。

可怕的理解能力和表达能力!

竟然有种想把老师的文章都看一遍的冲动。。。。

既然 tcp 基于 ip 协议定位数据包来源, 那我在同一个局域网内的多个客户端(一个公网ip)同时访问下载一个外网的文件, 这时候 tcp 的分包是怎么区分不同的客户端 分别发送不同的 ack, 因为大家公网 ip 都一样

引用 音風的发言:

既然 tcp 基于 ip 协议定位数据包来源, 那我在同一个局域网内的多个客户端(一个公网ip)同时访问下载一个外网的文件, 这时候 tcp 的分包是怎么区分不同的客户端 分别发送不同的 ack, 因为大家公网 ip 都一样

这里涉及到NAT(网络地址转换),局域网中的主机全都用Mac地址编号,发送的数据包会被加上该主机在这个局域网中的IP地址,即NATIP。然后公网返回局域网的数据包会通过NAT检查接收的主机Mac地址,TCP看到的是两个正常的公网IP在互相发包,所有的IP转换问题交给NAT来做。

阮老师写的深入浅出,通俗易懂。请阮老师推荐下TCP/IP入门的书籍吧?谢谢。目前正在学习的一个资料"TCP/IP Illustrated, Volume1"

深入浅出,通俗易懂,谢谢阮老师!

很奇怪,既然发送数据包的一方是根据接收方发来的ACK来确定发送这一次的数据包和ACK,但是既然5号包接收方没有收到,也就是ACK没有变,接收方又怎么会接收到发送方在未得到需要发5号的ACK时发的6号或7号包呢?

引用wujie的发言:

很奇怪,既然发送数据包的一方是根据接收方发来的ACK来确定发送这一次的数据包和ACK,但是既然5号包接收方没有收到,也就是ACK没有变,接收方又怎么会接收到发送方在未得到需要发5号的ACK时发的6号或7号包呢?

因为6号或7号包可以根据5号包的编号和包大小推算出6号的编号(6号编号=5号编号+5号包大小)。
那为什么发送方没受到5号的ACK,就可以发送6号、7号呢?
因为前面说过了,一次会发送多个包。比如第一次就是发10个包,后面根据丢包情况再调整发包个数,带宽等条件好,每次发的包就会变多(>10), 如果丢包严重,每次发的包就会变少

请问重发了100之后,120,135,141这些包还需要重发吗?

根据RFC5681,如果MSS > 2190 bytes,则N = 2;如果MSS = MSS >= 1095 bytes,则N = 3;一篇Google的论文《An Argument for Increasing TCP’s Initial Congestion Window》建议把cwnd 初始化成了 10个MSS。Linux 3.0后采用了这篇论文的建议。

有连个疑问:
1、TCP flag(URG,ACK,PSH,RST,FIN)在回话中是一直存在且默认参数为0?
1、TCP速度是有最大上限速度(ISN 4ms更新一次,TCP包大小1480,所以,TCP的上限速度≈2.5*1^5* 1448?
至今未搞明白。特此疑问。

有一个问题要请教啊,一个tcp的负载是1400字节,发送一个10M的数据包,需要将数据包分成7100个包左右,这样的话是要建7100次tcp连接吗?还是一次连接进行发送7100包?如果一次发送7100个包,每个包都要层层包裹的吗?

阮老师,其中有一段难以理解,ACK_num 发送的是接收端期望发回的数据编号,按道理来说,就应该发送next_sequence number,为什么上面的配图中,next_sequence number 和Acknowledge_number 不一致呢?数据发送的时候不是连续发送的吗?还是我搞混了ACK_num 和Acknowlege_number的概念?

以太网数据包packet感觉换做frame更恰当一些,packet一般用表示作网络层的数据包

10MB = 10 * 1024 * 1024 = 10485760 Bytes

10485760 // 1400 = 7489

多亿不是7100 是将近7500

最近才发现阮老师的这个网站,相见恨晚啊

阮老师你好,“一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。”这句话中,7100这个计算结果貌似有点偏差,1MB = 1024KB = 1024 * 1024B,10MB / 1400B = 10485760B / 1400B = 7490,应该为7490。感谢阮老师的辛勤付出,受益匪浅!!!

乾颐堂教主16年视频,讲Python课时,提到了线程与进程的理解时引用了你的文章,于是我就发现并收藏了你的网站,哈哈!

之前看过秦柯教主讲的《TCP-IP》2017版的视频,再来看这篇文章,大概瞄了下,基本90%的知识点都能看得懂,但让我说或者写出来,就不行了......

本来一知半解,看了您的讲解更清晰了。

引用 音風的发言:

既然 tcp 基于 ip 协议定位数据包来源, 那我在同一个局域网内的多个客户端(一个公网ip)同时访问下载一个外网的文件, 这时候 tcp 的分包是怎么区分不同的客户端 分别发送不同的 ack, 因为大家公网 ip 都一样

每个局域网通过路由到公网时 端口不一样。 服务端 收到的请求端口可以区分

阮老师写的清晰明了,我大概懂了

问一下,在linux系统中,一个http请求有多大,单机最大能承载多少http请求,有什么方法计算吗?

@Trinyoung:

是一次链接 发送7100个包 每个包都是tcp/ip包

引用南阁的发言:

请教个问题,接收方怎么知道自己接收的哪个数据包是发送方发送的第一个包?

通过 TCP 包中的 SYN 标志位判断。

如果 SYN = 1,其 SEQ 表示的就是第一个包的初始序列号。

如果 SYN = 0,其 SEQ 表示的是当前包在整个 stream 中的相对位置。

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接