默认
打赏 发表评论 30
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
网络编程懒人入门(三):快速理解TCP协议一篇就够
阅读(318412) | 评论(30 收藏26 淘帖1 4
微信扫一扫关注!

原作者:阮一峰(ruanyifeng.com),本文由即时通讯网重新整理发布,感谢原作者的无私分享。


1、前言


本系列文章的前两篇《网络编程懒人入门(一):快速理解网络通信协议(上篇)》、《网络编程懒人入门(二):快速理解网络通信协议(下篇)》快速介绍了网络基本通信协议及理论基础,建议开始阅读本文前先读完此2篇文章。

TCP 是互联网的核心协议之一,鉴于它的重要性,本文将单独介绍它的基础知识,希望能加深您对TCP协议的理解。

老规矩,为了让文字尽量通俗易懂、不浪费你的脑细胞,本文尽量点到为止,不对理论进行深入挖掘,如需深入理论细节,请参见下方参考资料中有关TCP协议的详细介绍和学习文章。

群神镇楼:
网络编程懒人入门(三):快速理解TCP协议一篇就够_72539919.jpg

2、系列文章


本文是系列文章中的第3篇,本系列文章的大纲如下:


本站的《脑残式网络编程入门》也适合入门学习,本系列大纲如下:


如果您觉得本系列文章过于基础,您可直接阅读《不为人知的网络编程》系列文章,该系列目录如下:


关于移动端网络特性及优化手段的总结性文章请见:


3、参考资料


TCP/IP详解 - 第11章·UDP:用户数据报协议
TCP/IP详解 - 第17章·TCP:传输控制协议
TCP/IP详解 - 第18章·TCP连接的建立与终止
TCP/IP详解 - 第21章·TCP的超时与重传
通俗易懂-深入理解TCP协议(上):理论基础
通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理
理论经典:TCP协议的3次握手与4次挥手过程详解
理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程
计算机网络通讯协议关系图(中文珍藏版)
高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少
高性能网络编程(二):上一个10年,著名的C10K并发连接问题
高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了
高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索
简述传输层协议TCP和UDP的区别
为什么QQ用的是UDP协议而不是TCP协议?
移动端即时通讯协议选择:UDP还是TCP?

4、TCP 协议的作用


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

网络编程懒人入门(三):快速理解TCP协议一篇就够_1.png
▲ TCP 是以太网协议和 IP 协议的上层协议,也是应用层协议的下层协议

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_2.jpg
▲ 以太网协议解决了局域网的点对点通信

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_3.png
▲ IP 协议可以连接多个局域网

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_4.jpg
▲ 路由器就是基于 IP 协议。局域网之间要靠路由器连接

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_5.jpg
▲ 本机的路由表注明了不同 IP 目的地的数据包,要发送到哪一个网口(interface)

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

5、TCP 数据包的大小


以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中, 1500 字节是负载(payload),22字节是头信息(head)。IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。

网络编程懒人入门(三):快速理解TCP协议一篇就够_6.jpg
▲ IP 数据包在以太网数据包里面,TCP 数据包在 IP 数据包里面

TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度

网络编程懒人入门(三):快速理解TCP协议一篇就够_7.jpg
▲ 以太网数据包的负载是1500字节,TCP 数据包的负载在1400字节左右

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


一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_8.jpg
▲ 当前包的编号是45943,下一个数据包的编号是46183,由此可知,这个包的负载是240字节

7、TCP 数据包的组装


收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。对于应用程序来说,不用关心数据通信的细节。除非线路异常,收到的总是完整的数据。应用程序需要的数据放在 TCP 数据包里面,有自己的格式(比如 HTTP 协议)。

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

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_9.jpg

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

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

8、慢启动和 ACK


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

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

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

Linux 内核里面设定了(常量TCP_INIT_CWND),刚开始通信的时候,发送方一次性发送10个数据包,即"发送窗口"的大小为10。然后停下来,等待接收方的确认,再继续发送。默认情况下,接收方每收到两个 TCP 数据包,就要发送一个确认消息。"确认"的英语是 acknowledgement,所以这个确认消息就简称 ACK。

ACK 携带两个信息:

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

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_10-14.36.jpg
▲ 每个 ACK 都带有下一个数据包的编号,以及接收窗口的剩余容量,双方都会发送 ACK

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_11.jpg

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

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

9、数据包的遗失处理


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

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

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

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

网络编程懒人入门(三):快速理解TCP协议一篇就够_12.jpg
▲ Host B 没有收到100号数据包,会连续发出相同的 ACK,触发 Host A 重发100号数据包

(原文链接:点此进入,有改动)

附录:更多网络编程资料


技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)
UDP中一个包的大小最大能多大?
Java新一代网络编程模型AIO原理及Linux系统AIO介绍
NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
P2P技术详解(一):NAT详解——详细原理、P2P简介
P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
P2P技术详解(三):P2P技术之STUN、TURN、ICE详解
通俗易懂:快速理解P2P技术中的NAT穿透原理
>> 更多同类文章 ……

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:网络编程懒人入门(二):快速理解网络通信协议(下篇)下一篇:有什么工具配合学习《TCP/IP详解 卷1:协议》吗?

本帖已收录至以下技术专辑

推荐方案
评论 30
牛比牛比 我还是改行吧
签名: 该会员没有填写今日想说内容.
引用:BKBK 发表于 2017-10-12 16:29
牛比牛比 我还是改行吧

大神镇楼,史上粪量最重的文章。。。
看了这篇,对协议的理解清晰了很多,特别是各个协议的作用!
引用:TeochewZhang 发表于 2017-10-12 22:18
看了这篇,对协议的理解清晰了很多,特别是各个协议的作用!

关于数据包丢失处理有一个疑问:
通过重复的ack,HOSTA能确认第二个包丢失,但如果判断第3、4、5个包是否丢失?如果无法判断,那么HOSTA就需要重新发送3~5包,但实际HOSTB在之前已经接收过,重复发送会导致带宽浪费和网络实时性能下降。望题主大神解答
本文是系列文章中的第1篇,是第三篇
引用:huangshazsw 发表于 2018-07-19 10:00
本文是系列文章中的第1篇,是第三篇

很细心,多谢纠错。已修正!
请教一下,最后一个图,如果收到了重发的100号数据包,HostB会回复一个ack 157还是120?
引用:will 发表于 2018-03-31 12:30
关于数据包丢失处理有一个疑问:
通过重复的ack,HOSTA能确认第二个包丢失,但如果判断第3、4、5个包是否 ...

上面也知道TCP是基于请求问答的,比如客户端已经收到3,4,5号包,客户端本地肯定会有记录,但是他迟迟没有收到2号包,他当然要不断地请求服务器,请求重发第二个包,如果第二个包客户端接收到了,那客户端就查看本地的数据包是否完整,查看还有没有分片没有到达,如果有 他就会ACK请求下一个分片,也就是6号包,如果完整那就ACK 接收完成. (TCP报头的32位序号,和32位确认序号具体好像就是做的这些事情)

评分

1

查看评分

慢启动和ACK 窗口
签名: 大家早上好,鸿蒙系统
群神的图好高清啊!!!
签名: 什么鬼
引用:AndyRon 发表于 2018-11-24 16:25
群神的图好高清啊!!!

应该每日来膜拜一下他们
彷佛发现了网络通信的新大陆
签名: 哈哈哈
谢谢楼主的分享
懒人一时爽,一直懒人一直爽
签名: DotNet NB
多谢大佬的教程
那个慢启动的第二个图不是很理解...能解释一下吗?
为啥第一次响应Server回复的length是200byte,但是第二次Client发送的ack是201??? 那第二次Client发送的的length = 50要怎么理解???
还是说length是和ack、seq无关...应该也有关系吧?不然第一次的发送为啥Server能得出ack为101

引用:Rayman 发表于 2020-12-01 17:43
那个慢启动的第二个图不是很理解...能解释一下吗?
为啥第一次响应Server回复的length是200byte,但是第二 ...

哦哦哦~知道为啥了,Client和Server是双向的...也就是说seq也可以作为Server的某个序列..
清晰,受教了
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部