了解TCP、UDP、TLS

本文最后更新于:2023年3月19日 晚上

本文转自:https://coffe1891.gitbook.io/frontend-hard-mode-interview/1/1.5.2
了解一点底层网络通信原理,对日常工作有很大的帮助,减少与后端工程师的“日常沟通摩擦”,增强共鸣和互信与理解。而在面试过程中,很多公司会考察前端工程师的知识广度,TCP/IP、TCP、UDP、TLS 是被高频问到的。
TCP/IP 协议是一个协议集,里面包括很多协议的,TCP、UDP、TLS 等只是其中的协议。
TCP/IP 协议集包括应用层传输层网络层网络访问层
应用层包括:

  • 超文本传输协议(HTTP):万维网的基本协议;

  • 文件传输(TFTP 简单文件传输协议);

  • 远程登录(Telnet),提供远程访问其它主机功能,它允许用户登录;

  • internet 主机,并在这台主机上执行命令;

  • 网络管理(SNMP 简单网络管理协议),该协议提供了监控网络设备的方法,以及配置管理,统计信息收集,性能管理及安全管理等;

  • 域名系统(DNS),该系统用于在 internet 中将域名及其公共广播的网络节点转换成 IP 地址。

传输层包括:

  • TLS,也即 SSL(Secure Sockets Layer,安全套接字层)协议,后来 IETF 在标准化 SSL 协议时,将其改名为 Transport Layer Security(TLS,传输层安全)。

网络层包括:

  • Internet 协议(IP)

  • Internet 控制信息协议(ICMP)

  • 地址解析协议(ARP)

  • 反向地址解析协议(RARP)** **

网络访问层:

  • 网络访问层又称作主机到网络层(host-to-network)。网络访问层的功能包括 IP 地址与物理地址硬件的映射,以及将 IP 封装成帧。基于不同硬件类型的网络接口,网络访问层定义了和物理介质的连接。

TCP

术语 含义
SYN 请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为 1。
ACK 确认号是否有效,一般置为 1。
FIN 希望断开连接。
URG 紧急指针是否有效。为 1,表示某一位需要被优先处理。
PSH 提示接收端应用程序立即从 TCP 缓冲区把数据读走。
RST 对方要求重新建立连接,复位。

三次握手


如上图,三次握手分别是:

  1. SYN客户端选择一个随机序列号 x,并发送一个 SYN 数据包,其中可能还包括其他 TCP 标志和选项。

  2. SYN ACK服务器给 x 加 1,并选择自己的一个随机序列号 y,追加自己的标志和选项,然后返回响应。

  3. ACK客户端给 x 和 y 加 1 并发送握手期间的最后一个 ACK 数据包。

握手对延迟的影响

三次握手完成后,客户端与服务器之间就可以通信了。客户端可以在发送 ACK 分组之后立即发送数据,而服务器必须等接收到 ACK 分组之后才能发送数据。这个启动通信的过程适用于所有 TCP 连接,因此对所有使用 TCP 的应用具有非常大的性能影响,因为每次传输应用数据之前,都必须经历一次完整的往返。
举个例子,如果客户端在纽约,服务器在伦敦,要通过光纤启动一次新的 TCP 连接,光握手至少就要花 56 ms:向伦敦发送分组需要 28 ms,响应发回纽约又要 28 ms。在此,连接的带宽对时间没有影响,延迟完全取决于客户端和服务器之间的往返时间,这其中主要是纽约到伦敦之间的传输时间。 三次握手带来的延迟使得每创建一个新 TCP 连接都要付出很大代价。而这也决定了提高 TCP 应用性能的关键,在于想办法重用连接。
重用 TCP 连接有一个方案叫“TCP Fast Open”(TFO),Linux 3.7 及之后的内核已经在客户端和服务器中支持 TFO,具体内容可以查阅IETF 规范

四次挥手

跟着动画来学习 TCP 三次握手和四次挥手

**常见面试题 **

1.为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 Client 端,”你发的 FIN 报文我收到了”。只有等到我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。

2.为什么 TIME_WAIT 状态需要经过 2MSL(最大报文段生存时间)才能返回到 CLOSE 状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入 CLOSE 状态了,但是我们必须假象网络是不可靠的,有可以最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文。在 Client 发送出最后的 ACK 回复,但该 ACK 可能丢失。Server 如果没有收到 ACK,将不断重复发送 FIN 片段。所以 Client 不能立即关闭,它必须确认 Server 接收到了该 ACK。Client 会在发送出 ACK 之后进入到 TIME_WAIT 状态。Client 会设置一个计时器,等待 2MSL 的时间。如果在该时间内再次收到 FIN,那么 Client 会重发 ACK 并再次等待 2MSL。所谓的 2MSL 是两倍的 MSL(Maximum Segment Lifetime)。MSL 指一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。

3.为什么不能用两次握手进行连接?

答:3 次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机 S 和 C 之间的通信,假定 C 给 S 发送一个连接请求分组,S 收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S 认为连接已经成功地建立了,可以开始发送数据分组。可是,C 在 S 的应答分组在传输中被丢失的情况下,将不知道 S 是否已准备好,不知道 S 建立什么样的序列号,C 甚至怀疑 S 是否收到自己的连接请求分组。在这种情况下,C 认为连接还未建立成功,将忽略 S 发来的任何数据分 组,只等待连接确认应答分组。而 S 在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

4.如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP 还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 秒钟发送一次。若一连发送 10 个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

UDP

1980 年 8 月,UDP(User Datagram Protocol,用户数据报协议)被 John Postel 加入了核心网络协议套件。UDP 的主要功能和亮点并不在于它引入了什么特性,而在于它忽略的那些特性。UDP 经常被称为无(Null)协议,RFC 768 描述了其运作机制,全文完全可以写在一张餐巾纸上。

数据报

数据报(Datagram)为一个完整、独立的数据实体,携带着从源节点到目的地节点的足够信息,对这些节点间之前的数据交换和传输网络没有任何依赖。 数据报(datagram)和数据包(packet)是两个经常被人混用的词,实际上它们还是有区别的。数据包可以用来指代任何格式化的数据块,而数据报则通常只用来描述那些通过不可靠的服务传输的数据包,既不保证送达,也不发送失败通知。正因为如此,很多场合下人们都把 UDP 中 User(用户)的 U,改成 Unreliable(不可靠)的 U,于是 UDP 就成了“不可靠数据报协议”(Unreliable Datagram Protocol)。这也是为什么把 UDP 数据包称为数据报更为恰当的原因。

UDP 的“无协议”是怎么回事呢?

要理解为什么 UDP 被人称作“无协议”,必须从作为 TCP 和 UDP 下一层的 IP 协议说起。
IP 层的主要任务就是按照地址从源主机向目标主机发送数据报(Datagram)。为此,消息会被封装在一个 IP 分组内(如下图),其中载明了源地址和目标地址,以及其他一些路由参数。

注意,数据报这个词暗示了一个重要的信息:IP 层不保证消息可靠的交付,也不发送失败通知,实际上是把底层网络的不可靠性直接暴露给了上一层。

如果某个路由节点因为网络拥塞、负载过高或其他原因而删除了 IP 分组,那么在必要的情况下,IP 的上一层协议要负责检测、恢复和重发数据。

IPV4 首部
而 UDP 协议会用自己的分组结构(如下图)封装用户消息,它只增加了 4 个字段:源端口、目标端口、分组长度和校验和。这样,当 IP 把分组送达目标主机时,该主机能够拆开 UDP 分组,根据目标端口找到目标应用程序,然后再把消息发送过去。

UDP 首部
事实上,UDP 数据报中的源端口和校验和字段都是可选的。IP 分组的首部也有校验和,应用程序可以忽略 UDP 校验和。也就是说,所有错误检测和错误纠正工作都可以委托给上层的应用程序。说到底,UDP 仅仅是在 IP 层之上通过嵌入应用程序的源端口和目标端口,提供了一个“应用程序多路复用”机制。明白了这一点,就可以总结一下 UDP 的无服务是怎么回事了:

  1. 不保证消息交付不确认,不重传,无超时。_ _

  2. 不保证交付顺序不设置包序号,不重排,不会发生队首阻塞。

  3. 不跟踪连接状态不必建立连接或重启状态机。

  4. 不需要拥塞控制不内置客户端或网络反馈机制。

应用场合

TCP 是一个面向字节流的协议,能够以多个分组形式发送应用程序消息,且对分组中的消息范围没有任何明确限制。因此,TCP 连接的两端存在一个连接状态,每个分组都有序号,丢失还要重发,并且要按顺序交付。相对来说,UDP 数据报有明确的限制:数据报必须封装在 IP 分组中,应用程序必须读取完整的消息。换句话说,数据报不能分片。
UDP 是一个简单、无状态的协议,适合作为其他上层应用协议的辅助。实际上,这个协议的所有决定都需要由上层的应用程序作出。
有些应用程序可能并不需要可靠的交付或者不需要按顺序交付。比如,每个分组都是独立的消息,那么按顺序交付就没有任何必要。而且,如果每个消息都会覆盖之前的消息,那么可靠交付同样也没有必要了。可惜的是,TCP 不支持这种情况,所有分组必须按顺序交付。
无需按序交付数据或能够处理分组丢失的应用程序,以及对延迟或抖动要求很高的应用程序,最好选择 UDP 等协议。

TLS 1.2

Netscape(网景公司) 在 1994 年时提出了 SSL 协议的原始规范, TLS 协议也经过了很多次版本的更新。目前低版本的 TLS (例如:SSL 3.0/TLS 1.0 等)存在许多严重漏洞。另外根据 Nist(美国国家标准与技术研究院)所说,现在没有补丁或修复程序能够充分修复低版本 TLS 的漏洞,尽快升级到高版本的 TLS 是最好的方法。
目前行业正处于 TLS 1.2 取代 TLS 1/1.1 的过渡时期,将来会有越来越多的互联网安全企业启用 TLS 1.2。它引入了 SHA-256 哈希算法,摒弃了 SHA-1,对增强数据完整性有着显著优势。

TLS 的四次握手

客户端与服务器在通过 TLS 交换数据之前,必须协商建立加密信道。协商内容包括 TLS 版本、加密套件,必要时还会验证证书。然而,协商过程的每一步都需要一个分组在客户端和服务器之间往返一次(如下图,绿色部分代表 TLS 的四次握手),因而所有 TLS 连接启动时都要经历一定的延迟。

详细解说如下

1.客户端发出请求(ClientHello)

首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做 ClientHello 请求。在这一步,客户端主要向服务器提供以下信息:

  • 支持的协议版本,比如 TLS 1.0 版。

  • 一个客户端生成的随机数,稍后用于生成“对话密钥”。

  • 支持的加密方法,比如 RSA 公钥加密。

  • 支持的压缩方法。

2.服务器回应(SeverHello)

  • 确认使用的加密通信协议版本,比如 TLS 1.0 版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。

  • 确认使用的加密方法,比如 RSA 公钥加密,返回加密公钥。

  • 服务器证书。

3.客户端回应

  • 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。

  • 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。

  • 使用约定好的 HASH 计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。

4.服务器

  • 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证 HASH 是否与浏览器发来的一致。

  • 使用密码加密一段握手消息内容为”Finished”,发送给浏览器。

TLS 1.3

2018 年 8 月份,IETF 正式宣布 TLS 1.3 规范真正落地了,标准规范(Standards Track)定义在 rfc8446,这里概要性的了解下 TLS 1.3 协议的特性,并解释各个组织对于该版本的支持。
TLS 1.3 版本从 2014 年开始开发,到 2018 年 8 月份历经了四年,可见是非常大的一个工程,一共有 28 个草案。
作为 TLS1.3 协议最重要、最著名的实现,OpenSSL 也发布了 OpenSSL 1.1.1 版本,该版本全面支持 TLS 1.3,是一个长期支持版本(LTS),将会有 5 年的支持,该版本兼容 1.1.0 版本,OpenSSL 官方建议尽快从 1.1.0 版本升级到 1.1.1 版本。
另外 Facebook 开源了一个 TLS 1.3 协议实现软件 Fizz,仅仅支持 TLS 1.3 版本,不用考虑老的 TLS 版本,会让代码简洁不少。
另外一个比较流行的 TLS 协议实现就是 NSS,其 3.39 版本也全面支持 TLS 1.3(rfc8446)协议了。
作为世界上最流行的 Web 服务器,Nginx 从 1.13.0 版本开始支持 TLS 1.3 协议,但真正支持还是依赖于其引用的协议实现(比如 OpenSSL、NSS)。
说完服务器支持 TLS 1.3,接下去说下浏览器对于该版本的支持。不管是 Chrome 还是 Firefox 都可以手动配置支持 TLS 1.3(当然是 draf 草案)。目前两大浏览器厂商宣布:

  • chrome70(桌面版)开始,将默认开启支持 TLS 1.3(rfc8446),潜台词 70 以前的版本可以手动启用 TLS 1.3(draf)。

  • firefox63 版本(201810 月),将默认开启支持 TLS 1.3(rfc8446)。

国外的一些云服务厂商也全面支持 TLS 1.3 协议了,比如 CloudFlare 从 2016 年就启用 TLS 1.3 支持了,KeyCDN 也已经全面支持了,国内在这方面还差了不少。
解密 HTTPS 流量的 Wireshark,从 2.6.3 版本开始,Wireshark 也将支持 TLS 1.3(rfc8446)。
另外一个著名的 HTTP 协议调试工具 Curl,从 7.52.0 版本开始也已经支持 TLS 1.3 协议,但真正支持还是依赖于其引用的协议实现(比如 OpenSSL、NSS)。

TLS 1.3 协议做了那些改变?

  • 性能提升,主要是减少了握手次数,甚至可以做到 0-RTT,了解 TLS 协议的同学都知道,TLS 握手延迟是 TLS 性能最大的杀手。

  • 安全性提升,比如说仅仅支持 AEAD 密码套件,废除了 AES-CBC 密码套件(使用不当会存在安全问题);整个握手协议也使用签名保证握手消息的完整性(在 TLS 1.2 协议使用 MAC 算法验证握手消息),同时握手协议也是加密的(在 TLS 1.2 协议中,握手消息是明文的)。

  • 协议设计的全方位改革,和 TLS 1.2 协议完全是不兼容的,可以说是一次大手术,完全不同的设计理念,比如说仅仅只支持 5 种密码套件,进一步保障了安全性。

如果你想详细了解 TLS 1.3 协议,这篇文章也被开源中国翻译了,有兴趣可以看看。

我们是否可以全面拥抱 TLS 1.3?

对于服务提供者来说,现在可以支持 TLS 1.3 了,但不能废弃其他的 TLS 版本(比如 TLS 1.2),原因在于:
1)很多老的客户端(比如浏览器)版本可能比较低,根本不支持较新的一些密码套件,所以旧的 TLS 版本存活时间是比较长的,想想现在 TLS 1.1 版本还不能强制下线,TLS 1.2 从 2016 年开始启用,到现在已经 12 年了,某些网站还没有支持 TLS 1.2,可见从兼容性的角度看,TLS 老版本仍然会存在很长的生命周期。最近几年 HTTPS 应用越来越普及了,但并不是所有人(包括技术层面)对它还并不是特别了解,就我预估 TLS 1.2 协议至少还会存活十年以上。
2)TLS1.3 是个新版本,认知度和可信任度还比较欠缺,CloudFlare 和 Firefox 在 2017 年统计过,仅有 5%的用户支持 TLS 1.3(当然是草案),所以各个服务全面支持 TLS 1.3 还有很大的时间,比如服务器可能不会轻易升级 OpenSSL,拿我们公司来说,很多服务器 OpenSSL 版本还是 OpenSSL 1.0.1e。另外很多服务都会使用 OpenSSL,轻易是不敢升级的。后续估计各个 Linux 发行版会发布 OpenSSL 1.1.1 的 APT 或 RPM 包。
3)TLS1.3 版本是完全不兼容老版本的,HTTPS 协议是 TLS 协议最大的是使用者,其本质上还是 HTTP 协议,而 HTTP 应用非常的灵活,在互联网中,有很多的代理服务器或网关,他们为了各种各样的目的,都会透明解析 TLS 协议,CloudFlare 称之为 「middleboxes」,TLS 1.2 版本已经有 12 年历史了,这些代理服务器和网关也逐步能够解析 TLS 协议了,但 TLS1.3 版本的来临,其解析规则全部失效,那么对于用户来说,他们可能就无法使用 TLS1.3 协议了,这也是 TLS1.3 协议非常重要的一个问题。
总结来说,TLS 1.3 的普及还需要很长时间。