内容主要来自于拉勾教育《计算机网络通关29讲》
01 | 漫游互联网:什么是蜂窝移动网络
1.1 网络的组成
由无数个节点构成一个个小型网络,再再由小型网络组成重型网络,再组成大型网络,最后组成完整的网络世界。
1.1.1 公司内网
公司网络从本地网络服务提供商(Internet Service Provider, ISP)接入,然后内部再分成一个个子网。
网络中的线路也被称作通信链路(Communication Link),用于传输网络信号。有的网络节点会同时接入多条链路,数据传输到这些节点时需要选择方向,因此这些节点需要进行交换(Switch)
数据发生交换时,会从一条链路进入交换设备,然后缓存下来,再转发到另一条链路,如下图所示:
网络中数据是以分组或者包(Packet)的形式传输,因此这种数据切换链路技术也称为封包交换技术(Packet Switch)
网络中常见的具有交换能力的设备是路由器(Router)和**链路层交换机(Link-Layer Switch)**。
局域网络之间,可以通过路由器、交换机进行连接,从而构成一个更大的局域网。
1.1.2 移动网络
前面提到的网络传输中需要的通信链路是一个抽象概念。无线信号的发送接收器可以构成通信链路,蓝牙信道也可以构成通信链路。
移动网络中,无线信号构成通信链路。通信的核心被称作蜂窝塔(Cellular Tower),也称作基站(BaseStation)。每个蜂窝塔只覆盖一个六边形的范围,这种六边形的结构可以让信号无死角覆盖。如果是圆,圆与圆之间会有间隙。
一个蜂窝网络构造大体如下图:
网络提供商将网络供给处于蜂窝网络边缘的路由器,路由器连接蜂窝塔,再通过蜂窝塔(基站)提供给处于六边形地区中的设备。
将网络提供给一个子网的行为,称为网络提供(Network Provider)
对一个子网连接提供商的网络,称为网络接入(Network Access)
实际上随着蜂窝网格中设备越来越多,基站覆盖的网络常常是重叠的。这样设计的好处是,当一个基站过载、出现故障、不稳定时,可以切换到另一个基站的网络。
另一方面,在一定范围内的区域,离用户较近的地方还可以部署服务器,帮助用户完成计算。这相当于计算资源的下沉,称为边缘计算。相比中心化的计算,边缘计算延迟低、链路短,能够将更好的体验带给距离边缘计算集群最近的节点。从而让用户享受到更优质、延迟更低、算力更强的服务。
1.1.3 家庭网络
家用网络现在已经发展成一种网格状的连接。一方面家用网络会通过路由器接入本地 ISP 提供的网络服务。另一方面,一些设备,比如电脑、笔记本、手机、冰箱等都可以直接和路由器连接。路由器也承担了链路层网关的作用,作为家用电器之间信息的交换设备。
一些家用电器承担了蓝牙设备的控制器,如智能音箱。上图的智能音箱把家用网络带向了一个网格状,有的设备会同时连接路由器(WI-FI)和智能音箱,这样手机和音箱都可以直接控制这些设备。
1.1.4 整体关系
最顶部的全球或国家大型的 ISP 之间联网,构成了网络的主干。然后区域性的 ISP 承接主干网络,在这个基础之上再向家庭和公司提供接入服务。移动蜂窝网络因为部署复杂,往往也是由大型 ISP 直接提供。
1.2 数据的传输
为了传递数据,在网络中有几个特别重要的抽象。最终提供服务或者享受服务的设备,称为终端(Terminal),或者端系统(End System),有时候简单称为主机(Host)。比如说:电脑、手机、冰箱、汽车等,我们都可以看作是一个主机(Host)。
网络传输分成两类,一类是端到端(Host-to-Host)的能力,由 TCP/IP 协议群提供。还有一类是广播的能力,是一对多、多对多的能力,可以看作是端到端(Host-to-Host)能力的延伸。
网络基础设施往往不能一次性传输太大的数据量,因此通常会将数据分片传输。比如传输一个 MP3,我们会将 MP3 内容切分成很多个组,每个组也称作一个封包,英文都是 Packet。这样,如果一个封包损坏,只需要重发损坏的封包,而不需要重发所有数据。
另一方面,网络中两点间的路径非常多,如果一条路径阻塞了,部分封包可以考虑走其他路径。发送端将数据拆分成封包(Packet),封包在网络中遇到岔路,由交换器和路由器节点决定走向,接收端再组包。下图对封包交换技术的一个演示。
1.3 总结
互联网是一个非常庞大的结构,从整体来看,互联网是一个立体的、庞大的网状结构。但是如果将它放大、再放大,将镜头拉近,在微观层面,我们会看到一个个网络、一台台设备,还会看到大量的封包在交换、有设备在不断地改变封包的走向、损坏的封包被重发、一个个光电信号被转化和传输。
02 | 传输层协议 TCP:TCP 为什么握手是 3 次、挥手是 4 次?
TCP 和 UDP 是今天应用最广泛的传输层协议,拥有最核心的垄断地位。今天互联网的整个传输层,几乎都是基于这两个协议打造的。无论是应用开发、框架设计选型、做底层和优化,还是定位线上问题,只要碰到网络,就逃不开 TCP 协议相关的知识。
TCP(Transport Control Protocol)是一个传输层协议,提供 Host-To-Host 数据的可靠传输,支持全双工,是一个连接导向的协议。
2.1 主机到主机(Host-To-Host)如何通信?
TCP 提供的是 Host-To-Host 传输,一台主机通过 TCP 发送数据给另一台主机。这里的主机(Host)是一个抽象的概念,可以是手机、平板、手表等。收发数据的设备都是主机,所以双方是平等的。
TCP 协议往上是应用到应用(Application-To-Application)的协议。什么是应用到应用的协议呢?比如用微信发信息给张三,微信客户端、微信聊天服务都是应用。微信有自己的聊天协议,微信的聊天协议是应用到应用的协议;如果微信的聊天协议想要工作,就需要一个主机到主机的协议帮助它实现通信。
TCP 上层的应用层协议要使用 TCP 能力的时候,需要告知 TCP 是哪个应用——这就是端口号。端口号用于区分应用。
TCP 要实现主机到主机通信,就需要知道主机的网络地址(IP 地址),但是 TCP 不负责实际地址到地址(Address-To-Address)的传输,因此 TCP 协议把 IP 地址给底层的网络层处理。
网络层(Netowrk Layer),提供地址到地址的通信,IP 协议就在这一层工作。网络层解决地址到地址的通信,但是不负责信号在具体两个设备间传递。因此,网络层会调用下方的链路层在两个相邻设备间传递信息。当信号在两个设备间传递的时候,物理层封装最底层的物理设备、传输介质等,由最下方的物理层提供最底层的传输能力。
2.2 什么是连接和会话?
连接是通信双方的一个约定,目标是让两个在通信的程序之间产生一个默契,保证两个程序都在线,而且尽快地响应对方的请求,这就是连接(Connection)。
设计上,连接是一种传输数据的行为。传输之前,建立一个连接。具体来说,数据收发双方的内存中都建立一个用于维护数据传输状态的对象,比如双方 IP 和端口是多少?现在发送了多少数据了?状态健康吗?传输速度如何?等。所以,连接是网络行为状态的记录。
和连接关联的还有一个名词,叫作会话(Session),会话是应用的行为。比如微信里你要和张三聊天,你们创建一个聊天窗口,这个就是会话。你开始传输数据,你和微信服务器间建立一个连接。如果你们聊一段时间,各自休息了,约定先不要关微信,1 个小时后再回来。那么连接会断开,因为聊天窗口没关,所以会话还在。
有些系统设计中,会话会自动重连(也就是重新创建连接),或者帮助创建连接。 此外,会话也负责在多次连接中保存状态,比如 HTTP Session 在多次 HTTP 请求(连接)间保持状态(如用户信息)。
会话是应用层的概念,连接是传输层的概念。
2.3 单工/双工问题
在任何一个时刻,如果数据只能单向发送,就是单工,所以单工需要至少一条线路。如果在某个时刻数据可以向一个方向传输,也可以向另一个方向反方向传输,而且交替进行,叫作半双工;半双工需要至少 1 条线路。最后,如果任何时刻数据都可以双向收发,这就是全双工,全双工需要大于 1 条线路。当然这里的线路,是一个抽象概念,你可以并发地处理信号,达到模拟双工的目的。
TCP 是一个双工协议,数据任何时候都可以双向传输。这就意味着客户端和服务端可以平等地发送、接收信息。正因为如此,客户端和服务端在 TCP 协议中有一个平等的名词——Host(主机)。
2.4 什么是可靠性?
可靠性指数据保证无损传输。如果发送方按照顺序发送,然后数据无序地在网络间传递,就必须有一种算法在接收方将数据恢复原有的顺序。另外,如果发送方同时要把消息发送给多个接收方,这种情况叫作多播,可靠性要求每个接收方都无损收到相同的副本。多播情况还有强可靠性,就是如果有一个消息到达任何一个接收者,那么所有接受者都必须收到这个消息。
2.5 TCP的握手和挥手
TCP 是一个连接导向的协议,设计有建立连接(握手)和断开连接(挥手)的过程。TCP 没有设计会话(Session),因为会话通常是一个应用的行为。
TCP 协议有这样几个基本操作:
- 如果一个 Host 主动向另一个 Host 发起连接,称为 SYN(Synchronization),请求同步;
- 如果一个 Host 主动断开请求,称为 FIN(Finish),请求完成;
- 如果一个 Host 给另一个 Host 发送数据,称为 PSH(Push),数据推送。
以上 3 种情况,接收方收到数据后,都需要给发送方一个 ACK(Acknowledgement)响应。请求/响应的模型是可靠性的要求,如果一个请求没有响应,发送方可能会认为需要重发这个请求。
2.6 建立连接的过程(三次握手)
因为要保持连接和可靠性约束,TCP 协议要保证每一条发出的数据必须给返回,返回数据叫作 ACK(也就是响应)。
按照这个思路,建立连接需要 3 次握手:
- 客户端发消息给服务端(SYN)
- 服务端准备好进行连接
- 服务端针对客户端的 SYN 给一个 ACK
服务端还没有确定客户端是否准备好了,所以还要继续确认。比如步骤 3 之后,服务端马上给客户端发送数据,这个时候客户端可能还没有准备好接收数据。因此还需要增加一个过程。 - 服务端发送一个 SYN 给客户端
- 客户端准备就绪
- 客户端给服务端发送一个 ACK
步骤1、3、4、6 是传输数据,算是握手,3、4合并成 SYN-ACK 响应。
从上面的例子中,可以进一步思考 SYN、ACK、PSH 这些常见的标识位(Flag)在传输中如何表示。
一种思路是为 TCP 协议增加协议头。在协议头中取多个位(bit),其中 SYN、ACK、PSH 都占有 1 个位。比如 SYN 位,1 表示 SYN 开启,0 表示关闭。因此,SYN-ACK 就是 SYN 位和 ACK 位都置 1。这种设计,我们也称为标识(Flag)。标识位是放在 TCP 头部的。
2.7 断开连接的过程(4次挥手)
- 客户端要求断开连接,发送一个断开的请求,这个叫作(FIN)。
- 服务端收到请求,然后给客户端一个 ACK,作为 FIN 的响应。
- 这里需要思考一个问题,可不可以像握手那样马上传 FIN 回去?
这个时候服务端不能马上传 FIN,因为断开连接要处理的问题比较多,比如说服务端可能还有发送出去的消息没有得到 ACK;也有可能服务端自己有资源要释放。因此断开连接不能像握手那样操作——将两条消息合并。所以,服务端经过一个等待,确定可以关闭连接了,再发一条 FIN 给客户端。 - 客户端收到服务端的 FIN,同时客户端也可能有自己的事情需要处理完,比如客户端有发送给服务端没有收到 ACK 的请求,客户端自己处理完成后,再给服务端发送一个 ACK。
2.8 总结
在学习 3 次握手、4 次挥手时,一定要理解为什么这么设计,而不是死记硬背。
- TCP 提供连接(Connection),让双方的传输更加稳定、安全。
- TCP 没有直接提供会话,因为应用对会话的需求多种多样,比如聊天程序会话在保持双方的聊天记录,电商程序会话在保持购物车、订单一致,所以会话通常在 TCP 连接上进一步封装,在应用层提供。
- TCP 是一个面向连接的协议(Connection -oriented Protocol),说的就是 TCP 协议参与的双方(Host)在收发数据之前会先建立连接。后面我们还会学习 UDP 协议,UDP 是一个面向报文(Datagram-oriented)的协议——协议双方不需要建立连接,直接传送报文(数据)。
- 连接需要消耗更多的资源。比如说,在传输数据前,必须先协商建立连接。因此,不是每种场景都应该用连接导向的协议。像视频播放的场景,如果使用连接导向的协议,服务端每向客户端推送一帧视频,客户端都要给服务端一次响应,这是不合理的。
TCP 为什么是 3 次握手,4 次挥手?
TCP 是一个双工协议,为了让双方都保证,建立连接的时候,连接双方都需要向对方发送 SYC(同步请求)和 ACK(响应)。
握手阶段双方都没有烦琐的工作,因此一方向另一方发起同步(SYN)之后,另一方可以将自己的 ACK 和 SYN 打包作为一条消息回复,因此是 3 次握手——需要 3 次数据传输。
到了挥手阶段,双方都可能有未完成的工作。收到挥手请求的一方,必须马上响应(ACK),表示接收到了挥手请求。最后等所有工作结束,再发送请求中断连接(FIN),因此是 4 次挥手。
一台内存在 8G 左右的服务器,可以同时维护多少个连接?
- tcp连接数上限其实受限于机器的内存,以8G内存为例,假设一个tcp连接需要占用的最小内存是8k(发送接收缓存各4k,当然还要考虑socket描述符),那么最大连接数为:810241024/8=1048576个,即约100万个tcp长连接。不过这只是理论数值,并未考虑实际业务。
03 | TCP 的封包格式:TCP 为什么要粘包和拆包?
3.1 TCP的拆包和粘包
TCP 是一个传输层协议。TCP 发送数据的时候,往往不会将数据一次性发送,像下图这样:
而是将数据拆分成很多个部分,然后再逐个发送。像下图这样:
TCP 为什么不一次发送完所有的数据?
这里有很多原因,比如为了稳定性,一次发送的数据越多,出错的概率越大。再比如说为了效率,网络中有时候存在着并行的路径,拆分数据包就能更好地利用这些并行的路径。再有,比如发送和接收数据的时候,都存在着缓冲区。如下图所示:
缓冲区是在内存中开辟的一块区域,目的是缓冲。因为大量的应用频繁地通过网卡收发数据,这个时候,网卡只能一个一个处理应用的请求。当网卡忙不过来的时候,数据就需要排队,也就是将数据放入缓冲区。如果每个应用都随意发送很大的数据,可能导致其他应用实时性遭到破坏。
还有一些原因,比如内存的最小分配单位是页表,如果数据的大小超过一个页表,可能会存在页面置换问题,造成性能的损失。
总之,方方面面的原因:在传输层封包不能太大。这种限制,往往是以缓冲区大小为单位的。也就是 TCP 协议,会将数据拆分成不超过缓冲区大小的一个个部分。每个部分有一个独特的名词,叫作 TCP 段(TCP Segment)。在接收数据时,一个个 TCP 段又被重组成原来的数据。
像这样,数据经过拆分,然后传输,然后在目的地重组,俗称拆包。所以拆包是将数据拆分成多个 TCP 段传输。那么粘包是什么呢?有时候,如果发往一个目的地的多个数据太小了,为了防止多次发送占用资源,TCP 协议有可能将它们合并成一个 TCP 段发送,在目的地再还原成多个数据,这个过程俗称粘包。所以粘包是将多个数据合并成一个 TCP 段发送。
3.2 TCP Segment
下图是一个 TCP 段的格式:
TCP 协议就是依靠每一个 TCP 段工作的,所以每认识一个 TCP 的能力,几乎都会找到在 TCP Segment 中与之对应的字段。
- Source Port/Destination Port 描述的是发送端口号和目标端口号,代表发送数据的应用程序和接收数据的应用程序。比如 80 代表 HTTP 服务,22 是 SSH 服务
- Sequence Number 和 Achnowledgment Number 是保证可靠性的两个关键。
- Data Offset 是一个偏移量。这个量存在的原因是 TCP Header 部分的长度是可变的,因此需要一个数值来描述数据从哪个字节开始。
- Reserved 是很多协议设计会保留的一个区域,用于日后扩展能力。
- URG/ACK/PSH/RST/SYN/FIN 是几个标志位,用于描述 TCP 段的行为。也就是一个 TCP 封包到底是做什么用的。(标志位可以混合使用)
- URG 代表这是一个紧急数据,比如远程操作的时候,用户按下了 Ctrl+C,要求终止程序,这种请求需要紧急处理。
- ACK 代表响应,我们在“02 | 传输层协议 TCP:TCP 为什么握手是 3 次、挥手是 4 次?”讲到过,所有的消息都必须有 ACK,这是 TCP 协议确保稳定性的一环。
- PSH 代表数据推送,也就是在传输数据的意思。
- SYN 同步请求,也就是申请握手。
- FIN 终止请求,也就是挥手。
- Window 是 TCP 保证稳定性并进行流量控制的工具
- Checksum 是校验和,用于校验 TCP 段有没有损坏。
- Urgent Pointer 指向最后一个紧急数据的序号(Sequence Number)。它存在的原因是:有时候紧急数据是连续的很多个段,所以需要提前告诉接收方进行准备。
- Options 中存储了一些可选字段,比如 MSS(Maxiumun Segment Size)。
- Padding 存在的意义是因为 Options 的长度不固定,需要 Pading 进行对齐。
3.2.1 Sequence Number 和 Acknowledgement Number
在 TCP 协议的设计当中,数据被拆分成很多个部分,部分增加了协议头。合并成为一个 TCP 段,进行传输。这个过程,我们俗称拆包。这些 TCP 段经过复杂的网络结构,由底层的 IP 协议,负责传输到目的地,然后再进行重组。
稳定性要求数据无损地传输,也就是说拆包获得数据,又需要恢复到原来的样子。而在复杂的网络环境当中,即便所有的段是顺序发出的,也不能保证它们顺序到达,因此,发出的每一个 TCP 段都需要有序号。这个序号,就是 Sequence Number(Seq)。
发送数据的时候,为每一个 TCP 段分配一个自增的 Sequence Number。接收数据的时候,虽然得到的是乱序的 TCP 段,但是可以通过 Seq 获得完整的顺序。
接收方如果要回复发送方,也需要这个 Seq。而网络的两个终端,去同步一个自增的序号是非常困难的。因为任何两个网络主体间,时间都不能做到完全同步,又没有公共的存储空间,无法共享数据,更别说实现一个分布式的自增序号了。此时需要用到发送的字节数和接收的字节数。
重新定义一下 Seq(如上图所示),对于任何一个接收方,如果知道了发送者发送某个 TCP 段时,已经发送了多少字节的数据,那么就可以确定发送者发送数据的顺序。
但是这里有一个问题。如果接收方也向发送者发送了数据请求(或者说双方在对话),接收方就不知道发送者发送的数据到底对应哪一条自己发送的数据了。
因此我们还需要另一个数据,就是每个 TCP 段发送时,发送方已经接收了多少数据。用 Acknowledgement Number 表示,下面简写为 ACK。
下图中,终端发送了三条数据,并且接收到四条数据,通过观察,根据接收到的数据中的 Seq 和 ACK,将发送和接收的数据进行排序。
上图中,发送方发送了 100 字节的数据,而接收到的(Seq = 0 和 Seq =100)的两个封包,都是针对发送方(Seq = 0)这个封包的。发送 100 个字节,所以接收到的 ACK 刚好是 100。说明(Seq= 0 和 Seq= 100)这两个封包是针对接收到第 100 个字节数据后,发送回来的。这样就确定了整体的顺序。(感觉这段有问题)
注意,无论 Seq 还是 ACK,都是针对“对方”而言的。是对方发送的数据和对方接收到的数据。
3.2.2 MSS(Maxiumun Segment Size)
这个可选项控制了 TCP 段的大小,它是一个协商字段(Negotiate)。协议是双方都要遵循的标准,因此配置往往不能由单方决定,需要双方协商。
TCP 段的大小(MSS)涉及发送、接收缓冲区的大小设置,双方实际发送接收封包的大小,对拆包和粘包的过程有指导作用,因此需要双方去协商。
如果这个字段设置得非常大,就会带来一些影响。
首先对方可能会拒绝,作为服务的提供方,可能不会愿意接收太大的 TCP 段。因为大的 TCP 段,会降低性能,比如内存使用的性能。
其次,支持 TCP 协议工作的 IP 协议,工作效率会下降。TCP 协议不肯拆包,IP 协议就需要拆出大量的包。那么 IP 协议为什么需要拆包呢?这是因为在网络中,每次能够传输的数据不可能太大,这受限于具体的网络传输设备,也就是物理特性。但是 IP 协议,拆分太多的封包并没有意义。因为可能会导致属于同个 TCP 段的封包被不同的网络路线传输,这会加大延迟。同时,拆包,还需要消耗硬件和计算资源。
那是不是 MSS 越小越好呢?MSS 太小的情况下,会浪费传输资源(降低吞吐量)。因为数据被拆分之后,每一份数据都要增加一个头部。如果 MSS 太小,那头部的数据占比会上升,这让吞吐量成为一个灾难。所以在使用的过程当中,MSS 的配置,往往都是一个折中的方案。而根据 Unix 的哲学,不要去猜想什么样的方案是最合理的,而是要尝试去用实验证明它,一切都要用实验依据说话。
3.3 总结
TCP 协议是如何恢复数据的顺序的,TCP 拆包和粘包的作用是什么?
TCP 拆包的作用是将任务拆分处理,降低整体任务出错的概率,以及减小底层网络处理的压力。拆包过程需要保证数据经过网络的传输,又能恢复到原始的顺序。这中间,需要数学提供保证顺序的理论依据。TCP 利用(发送字节数、接收字节数)的唯一性来确定封包之间的顺序关系。
粘包是为了防止数据量过小,导致大量的传输,而将多个 TCP 段合并成一个发送。
04 | TCP 的稳定性:滑动窗口和流速控制
TCP 利用发送字节数和接收字节数,这个二元组的唯一性保证顺序。
TCP 作为一个传输层协议,最核心的能力是传输。传输需要保证可靠性,还需要控制流速,这两个核心能力均由滑动窗口提供。而滑动窗口中解决的问题,是今后的工作中可以长期使用的,比如设计一个分布式的 RPC 框架、实现一个消息队列或者分布式的文件系统等。
1. 请求/响应模型
TCP 中每个发送的请求都需要响应。如果一个请求没有收到响应,发送方就会认为这次发送出现了故障,会触发重发。
大体的模型,和下图很像。但是如果完全和下图一样,每一个请求收到响应之后,再发送下一个请求,吞吐量会很低,浪费带宽。带宽没有用满,意味着可以同时发送更多的请求,接收更多的响应。
一种改进的方式,就是让发送方有请求就发送出去,而不是等待响应。通过这样的处理方式,发送的数据连在了一起,响应的数据也连在了一起,吞吐量就提升了。
如果同时发送的数据非常多,比如成百上千个 TCP 段都需要发送,这个时候带宽可能会不足,像下图这样。
2. 排队
这种情况下,通常我们会考虑排队(Queuing)机制。
考虑上图这个模型,在 TCP 层实现一个队列,新元素从队列的一端(左侧)排队准备发送,从队列的右侧发送离开。可以思考一下,这个模型有什么问题?
这样做需要多个队列,要将未发送的数据从队列中取出,加入发送中的队列。然后再将发送中的数据,收到 ACK 的部分取出,放入已接收的队列。而发送中的封包,何时收到 ACK 是一件不确定的事情,这样使用队列似乎也有一定的问题。
3. 滑动窗口
在上面的模型当中,之所以觉得算法不好设计,是因为用错了数据结构。这里应该用一种叫作滑动窗口的数据结构去实现。
- 深绿色代表已经收到 ACK 的段
- 浅绿色代表发送了,但是没有收到 ACK 的段
- 白色代表没有发送的段
- 紫色代表暂时不能发送的段
下面重新设计一下不同类型封包的顺序,将已发送的数据放到最左边,发送中的数据放到中间,未发送的数据放到右边。假设我们最多同时发送 5 个封包,也就是窗口大小 = 5。窗口中的数据被同时发送出去,然后等待 ACK。如果一个封包 ACK 到达,将它标记为已接收(深绿色)。
如下图所示,有两个封包的 ACK 到达,因此标记为绿色。
这个时候滑动窗口可以向右滑动,如下图所示:
4. 重传
如果发送过程中,部分数据没能收到 ACK 会怎样呢?这就可能发生重传。
如果发生下图这样的情况,段 4 迟迟没有收到 ACK。
这个时候滑动窗口只能右移一个位置,如下图所示:
在这个过程中,如果后来段 4 重传成功(接收到 ACK),那么窗口就会继续右移。如果段 4 发送失败,还是没能收到 ACK,那么接收方也会抛弃段 5、段 6、段 7。这样从段 4 开始之后的数据都需要重发。
5. 快速重传
在 TCP 协议中,如果接收方想丢弃某个段,可以选择不发 ACK。发送端超时后,会重发这个 TCP 段。而有时候,接收方希望催促发送方尽快补发某个 TCP 段,这个时候可以使用快速重传能力。
例如段 1、段 2、段 4 到了,但是段 3 没有到。 接收方可以发送多次段 3 的 ACK。如果发送方收到多个段 3 的 ACK,就会重发段 3。这个机制称为快速重传。这和超时重发不同,是一种催促的机制。
为了不让发送方误以为段 3 已经收到了,在快速重传的情况下,接收方即便收到发来的段 4,依然会发段 3 的 ACK(不发段 4 的 ACK),直到发送方把段 3 重传。
实际操作中,每个 TCP 段的大小不同,限制数量会让接收方的缓冲区不好操作,因此实际操作中窗口大小单位是字节数。
6. 流速控制
发送、接收窗口的大小可以用来控制 TCP 协议的流速。窗口越大,同时可以发送、接收的数据就越多,支持的吞吐量也就越大。当然,窗口越大,如果数据发生错误,损失也就越大,因为需要重传越多的数据。
举个例子:我们用 RTT 表示 Round Trip Time,就是消息一去一回的时间。
假设 RTT = 1ms,带宽是 1mb/s。如果窗口大小为 1kb,那么 1ms 可以发送一个 1kb 的数据(含 TCP 头),1s 就可以发送 1mb 的数据,刚好可以将带宽用满。如果 RTT 再慢一些,比如 RTT = 10ms,那么这样的设计就只能用完 1/10 的带宽。 当然你可以提高窗口大小提高吞吐量,但是实际的模型会比这个复杂,因为还存在重传、快速重传、丢包等因素。
而实际操作中,也不可以真的把带宽用完,所以最终我们会使用折中的方案,在延迟、丢包率、吞吐量中进行选择,毕竟鱼和熊掌不可兼得。
7. 总结
为了提高传输速率,TCP 协议选择将多个段同时发送,为了让这些段不至于被接收方拒绝服务,在发送前,双方要协商好发送的速率。但是我们不可能完全确定网速,所以协商的方式,就变成确定窗口大小。
有了窗口,发送方利用滑动窗口算法发送消息;接收方构造缓冲区接收消息,并给发送方 ACK。滑动窗口的实现只需要数组和少量的指针即可,是一个非常高效的算法。像这种算法,简单又实用,比如求一个数组中最大的连续 k 项和,就可以使用滑动窗口算法。如果你对这个问题感兴趣,不妨用你最熟悉的语言尝试解决一下。
滑动窗口和流速控制是怎么回事?
滑动窗口是 TCP 协议控制可靠性的核心。发送方将数据拆包,变成多个分组。然后将数据放入一个拥有滑动窗口的数组,依次发出,仍然遵循先入先出(FIFO)的顺序,但是窗口中的分组会一次性发送。窗口中序号最小的分组如果收到 ACK,窗口就会发生滑动;如果最小序号的分组长时间没有收到 ACK,就会触发整个窗口的数据重新发送。
另一方面,在多次传输中,网络的平均延迟往往是相对固定的,这样 TCP 协议可以通过双方协商窗口大小控制流速。补充下,上面我们说的分组和 TCP 段是一个意思。
05 | UDP 协议:TCP协议和 UDP 协议的优势和劣势
TCP 最核心的价值是提供了可靠性,而 UDP 最核心的价值是灵活,你几乎可以用它来做任何事情。例如:HTTP 协议 1.1 和 2.0 基于 TCP,HTTP 3.0 开始用 UDP。
UDP 在数据传输、网络控制、音视频、Web 技术中,都有很重要的地位。
1. UDP 协议
UDP(User Datagram Protocol),目标是在传输层提供直接发送报文(Datagram)的能力。Datagram 是数据传输的最小单位。UDP 协议不会帮助拆分数据,它的目标只有一个,就是发送报文。
为什么不直接调用 IP 协议呢? 如果裸发数据,IP 协议不香吗?
传输层协议在承接上方应用层的调用,需要提供应用到应用的通信——因此要附上端口号。每个端口,代表不同的应用。传输层下层的 IP 协议,承接传输层的调用,将数据从主机传输到主机。IP 层不能区分应用,导致哪怕是在 IP 协议上进行简单封装,也需要单独一个协议。这就构成了 UDP 协议的市场空间。
2. UDP 的封包格式
UDP 的设计目标就是在允许用户直接发送报文的情况下,最大限度地简化应用的设计。下图是 UDP 的报文格式。
- Source Port 是源端口号。因为 UDP 协议的特性(不需要 ACK),因此这个字段是可以省略的。但有时候对于防火墙、代理来说,Source Port 有很重要的意义,它们需要用这个字段行过滤和路由。
- Destination Port 是目标端口号(这个字段不可以省略)。
- Length 是消息体长度。
- Checksum 是校验和,作用是检查封包是否出错。
- Data octets 就是一个字节一个字节的数据,Octet 是 8 位。
校验数据在传输过程中有没有丢失、损坏是一个普遍需求。
一种最简单的校验和就是:
1 | checksum=(a+b+c+d) ^ 0xff |
如果发送方用上述方式计算出 Checksum,并将 a,b,c,d 和 Checksum 一起发送给接收方,接收方就可以用同样的算法再计算一遍,这样就可以确定数据有没有发生损坏(变化)。当然 Checksum 的做法,只适用于数据发生少量变化的情况。如果数据发生较大的变动,校验和也可能发生碰撞。
UDP 的可靠性保证仅仅就是 Checksum 一种。如果一个数据封包 Datagram 发生了数据损坏,UDP 可以通过 Checksum 纠错或者修复。 但是 UDP 没有提供再多的任何机制,比如 ACK、顺序保证以及流控等。
3. UDP 与 TCP 的区别
目的差异
TCP 协议的核心目标是提供可靠的网络传输,而 UDP 的目标是在提供报文交换能力基础上尽可能地简化协议轻装上阵。可靠性差异
TCP 核心是要在保证可靠性提供更好的服务。TCP 会有握手的过程,需要建立连接,保证双方同时在线。而且TCP 有时间窗口持续收集无序的数据,直到这一批数据都可以合理地排序组成连续的结果。
UDP 并不具备以上这些特性,它只管发送数据封包,而且 UDP 不需要 ACK,这意味着消息发送出去成功与否 UDP 是不管的。
连接 vs 无连接
TCP 是一个面向连接的协议(Connection-oriented Protocol),传输数据必须先建立连接。 UDP 是一个无连接协议(Connection-less Protocol),数据随时都可以发送,只提供发送封包(Datagram)的能力。流控技术(Flow Control)
TCP 使用了流控技术来确保发送方不会因为一次发送过多的数据包而使接收方不堪重负。TCP 在发送缓冲区中存储数据,并在接收缓冲区中接收数据。当应用程序准备就绪时,它将从接收缓冲区读取数据。如果接收缓冲区已满,接收方将无法处理更多数据,并将其丢弃。UDP 没有提供类似的能力。传输速度
UDP 协议简化,封包小,没有连接、可靠性检查等,因此单纯从传输速度上讲,UDP 更快。场景差异
TCP 每个数据封包都需要确认,因此天然不适应高速数据传输场景,比如观看视频(流媒体应用)、网络游戏(TCP 有延迟)等。具体来说,如果网络游戏用 TCP,每个封包都需要确认,可能会造成一定的延迟;再比如音、视频传输天生就允许一定的丢包率;Ping 和 DNSLookup,这类型的操作只需要一次简单的请求/返回,不需要建立连接,用 UDP 就足够了。
近些年有一个趋势,TCP/UDP 的边界逐渐变得模糊,UDP 应用越来越多。比如传输文件,如果考虑希望文件无损到达,可以用 TCP。如果考虑希望传输足够块,就可能会用 UDP。再比如 HTTP 协议,如果考虑请求/返回的可靠性,用 TCP 比较合适。但是像 HTTP 3.0 这类应用层协议,从功能性上思考,暂时没有找到太多的优化点,但是想要把网络优化到极致,就会用 UDP 作为底层技术,然后在 UDP 基础上解决可靠性。
所以理论上,任何一个用 TCP 协议构造的成熟应用层协议,都可以用 UDP 重构。这就好比,本来用一个工具可以解决所有问题,但是如果某一类问题体量非常大,就会专门为这类问题创造工具。因此,UDP 非常适合需要定制工具的场景。
把场景分成三类,TCP 应用场景、UDP 应用场景、模糊地带(TCP、UDP 都可以考虑)
- 第一类:TCP 场景
- 远程控制(SSH)
- File Transfer Protocol(FTP)
- 邮件(SMTP、IMAP)等
- 点对点文件传出(微信等)
- 第二类:UDP 场景
- 网络游戏
- 音视频传输
- DNS
- Ping
- 直播
- 第三类:模糊地带
- HTTP(目前以 TCP 为主)
- 文件传输
UDP 不提供可靠性,不代表我们不能解决可靠性。UDP 的核心价值是灵活、轻量,构造了最小版本的传输层协议。在这个之上,还可以实现连接(Connection),实现会话(Session),实现可靠性(Reliability)
4. 总结
TCP 最核心的价值就是提供封装好的一套解决可靠性的优秀方案。解决可靠性是非常复杂的,要考虑非常多的因素。TCP 帮助我们在确保吞吐量、延迟、丢包率的基础上,保证可靠性。TCP 的成功在于它给人们提供了很多现成、好用的能力。
UDP 则不同,UDP 提供了最小版的实现,只支持 Checksum。UDP 最核心的价值是灵活、轻量、传输速度快。考虑到不同应用的特性,如果不使用一个大而全的方案,为自己的应用特性量身定做,可能会做得更好。比如网络游戏中游戏客户端不断向服务端发送玩家的位置,如果某一次消息丢失了,只要这个消息不影响最终的游戏结果,就可以只看下一个消息。不同应用有不同的特性,需要的可靠性级别不一样,这就是越来越多的应用开始使用 UDP 的原因之一。
TCP 协议和 UDP 协议根本不存在什么优势和劣势,只不过是场景不同,选择不同而已。最后还有一个非常重要的考虑因素就是成本,如果没有足够专业的团队解决网络问题,TCP 无疑会是更好的选择。