1. QUIC
1.1 为什么要用UDP实现可靠传输?
有以下两点:
- 在弱网(2G、3G、信号不好)环境下,使用 TCP 连接的延迟很高(连接过程多、头部数据大、滑动窗口、重传、ACK、拥塞控制等原因),影响体验。使用 UDP 是很好的解决方案,既然把 UDP 作为弱网里面的 TCP 来使用,就必须保证数据传输能像 TCP样可靠。
- 在HTTP2.0协议中,解决了HTTP1.1 头部冗余、单工通信、没有优先级控制和网络层队头阻塞问题,但是HTTP2.0同样存在以下三个问题
- 传输层的队头阻塞
- TCP与TLS的握手时延迟
- 网络迁移需要重新连接
对于HTTP2.0 传输层的队头阻塞问题: HTTP/2 多个 Stream 请求都是在一条 TCP 连接上传输,这意味着多个 Stream 共用同一个 TCP 滑动窗口,那么当发生数据丢失,滑动窗口是无法往前移动的,此时就会阻塞住所有的 HTTP 请求,这属于TCP 层队头阻塞。之所以滑动窗口不会往前移动是因为,TCP的数据包必须接收对端发生的确定ACK才算接收成功,如果未接收那么数据会一直占用窗口,以至于其他在它后面的数据也无法接收成功(如果一个数据未接收确定ACK,接收端会连发三个ACK让发送端重传,这个过程中接收端只会发送重传的ACK,其他数据的ACK不会。如果其他数据的ACK发送了,说明这个数据之前的数据都接收完全)
对于TCP与TLS的握手时延迟:发出HTTP请求时,需要经过TCP三次握手和TLS四次握手,共计3RTT的时延才能发出请求数据。
对于网络迁移需要重新连接:基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的IP、目的端口)定一条 TCP 连接,那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接,而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
基于上面两个原因,我们需要一个能解决以上所有问题且安全可靠的传输协议,即采用QUIC的UDP协议。
1.2 如何使用UDP实现可靠传输?
UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。关键在于两点,从应用层角度考虑:
- 提供超时重传,能避免数据报丢失,
- 提供确认序列号,可以对数据报进行确认和排序
QUIC其实就是拥有TCP协议拥塞控制、流量控制、滑动窗口功能,能保证UDP传输过程中不会丢包并且能够对数据进行确认和排序的协议。
首先第一点,如何保证UDP传输过程中能避免数据包丢失?
重传其实很简单,但我们不能按照TCP的方式进行序列号确认来实现重传,这样会导致传输层的队头阻塞问题。因此,QUIC实现重传的机制很简单,发送端每次发送的报文都有独一无二的编号,每次发送的数据包编号都会在上一个发送数据包编号的基础上加一,并不会因为重传了数据包而和重传数据的编号一样,这样就能避免了发送重传时,TCP协议因为确认应答号的机制而导致滑动窗口停滞不前,导致队头阻塞问题,QUIC支持乱序确认,当数据包Packet N 丢失后,只要有新的已接收数据包确认,当前窗口就会继续向右滑动。因此,QUIC在重传数据时,包内的编号和重传数据的不一样,解决了HTTP2.0的队头阻塞问题,也提供了超时重传机制。
至于快速重传,QUIC其实在发送包体的时候,包内会存有上一个已发送包体的一部分数据,这样虽然降低了每个数据包容纳数据的上限,但是尽可能避免了因为数据缺失而导致重传的问题。
第二点,因为数据编号的独一无二性,重传数据包的编号和丢失数据包的编号并不一致,我们应该如何对数据报进行确认和排序?
QUIC的stream可以认为是一条HTTP请求,示意图如下:
- Stream lD:多个并发传输的 HTTP 消息,通过不同的 Stream ID 加以区别,类似于 HTTP2 的Stream lD;
- Offset :类似于 TCP 协议中的 Seq, 序号保证数据的顺序性和可靠性,。
- Length :指明了 Frame 数据的长度。
我们可以通过 Sream ID + Offset字段信息保证数据的有序性,通过比较两个数据包的 Sream ID和 Offset 是否都一致,如果是,那么两个包的数据都相同。这些数据包传输到接收端后,接收端能根据 Stream ID 与 Ofset 字段信息将 Streamx和 Stream x+y按照顺序组织起来,然后交给应用程序处理。
这样我们就通过QUIC stream流的格式解决了对数据的确认和排序问题。
总的来说,QUIC 通过单向递增的 Packet Number,配合 Stream ID与 Offset 字段信息,可以支持乱序确认而不影响数据包的正确组装,摆脱了TCP 必须按顺序确认应答 ACK 的限制,解决了 TCP 因某个数据包重传而阻塞后续所有待发送数据包的问题(队头阻塞),而且保证了UDP能拥有重传和数据确认、排序功能,使得DUP能可靠传输。
1.3 如何解决队头阻塞问题
其实在1.2就说了,QUIC 通过单向递增的 Packet Number,配合 Stream ID与 Offset 字段信息,可以支持乱序确认而不影响数据包的正确组装,摆脱了TCP 必须按顺序确认应答 ACK 的限制),解决了 TCP 因某个数据包重传而阻塞后续所有待发送数据包的问题(队头阻塞)。但我们这里详细说一下为什么TCP会发生队头阻塞。
TCP 队头阻塞的问题,其实就是接收窗口的队头阻塞问题。
接收方收到的数据范围必须在接收窗口范围内,如果收到超过接收窗口范围的数据,就会丢弃该数据,比如下图接收窗口的范围是 32 ~ 51 字节,如果收到第 52 字节以上数据都会被丢弃,
当接收窗口收到有序数据时,接收窗口才能往前滑动,然后那些已经接收并被ACK确定的有序数据就可以被应用层读取。
但是,当接收窗口收到的数据不是有序的,比如收到第 3340 字节的数据,由于第 32 字节数据没有收到, 接收窗口无法向前滑动,那么即使先收到第 3340 字节的数据,这些数据也无法被应用层读取的。只有当发送方重传了第 32 字节数据并且被接收方收到后,接收窗口才会往前滑动,然后应用层才能从内核读取第 32~40 字节的数据(在这个过程中,接收端会重复发送三次32的ACK,至于51的ACK并不会发送,如果51的ACK发送了,那么就说明51及以前的数据都完整接收了)。
导致接收窗口的队头阻塞问题,是因为 TCP 必须按序处理数据,也就是 TCP 层为了保证数据的有序性只有在处理完有序的数据后,滑动窗口才能往前滑动,否则就停留,停留「接收窗口」会使得应用层无法读取新的数据。这就是TCP队头阻塞问题。
而HTTP2.0的传输层队头阻塞是因为,HTTP/2 多个 Stream 请求都是在一条 TCP 连接上传输,这意味着多个 Stream 共用同一个 TCP 滑动窗口,那么当发生数据丢失,滑动窗口是无法往前移动的,此时就会阻塞住所有的 HTTP 请求,这属于传输层队头阻塞。
而QUIC除了通过发送单调递增序列的数据包以解决了TCP 因某个数据包重传而阻塞后续所有待发送数据包的问题,而且 QUIC 给每一个 Stream 都分配了一个独立的滑动窗口,这样使得一个连接上的多个 Stream 之间没有依赖关系,都是相互独立的,各自控制的滑动窗口,以此解决了HTTP2.0队头阻塞问题。
因此虽然QUIC 借鉴 HTTP/2 里的 Stream 的概念,在一条 QUIC 连接上可以并发发送多个 HTTP 请求 (Stream)。但是假如 Stream2 丢了一个 UDP 包,也只会影响 Stream2 的处理,不会影响其他 Stream,与 HTTP/2 不同HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。
1.4 如何解决TCP与TLS的握手时延迟
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先TCP握手(1RTT),再 TLS 握手(2RTT),所以需要 3RTT 的延迟才能传输数据,就算 Session 会话服用,也需要至少 2 个 RTT。
HTTP/3 在传输数据前虽然需要 QUIC 协议握手,这个握手过程只需要1RTT,握手的目的是为确认双方的
「连接 ID」,连接迁移就是基于连接 ID 实现的。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS1.3,因此仅需要1个 RTT 就可以「同时」完成建立连接与密钥协商。甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到0-RTT 的效果(因为不是像TCP一样是记住ip连接的,而是根据id连接的,因此第二次无需重复连接,也是解决网络迁移的方法)。
如下图右边部分,HTTP/3 当会话恢复时,有效负载数据与第一个数据包一起发送,可以做到 0-RTT:
1.5 如何解决网络迁移需要重新连接
在前面我们提到,基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的IP、目的端口)确定一条 TCP 连接。
那么当移动设备的网络从 4G 切换到 WiFi时,意味着 IP地址变化了,那么就必须要断开连接,然后重新建立连接,而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来“绑定“连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
2. TLS四层握手
HTTP 由于是明文传输,所谓的明文,就是说客户端与服务端通信的信息都是肉眼可见的,随意使用一个抓包工具都可以截获通信的内容。
所以安全上存在以下三个风险:
- 窃听风险,比如通信链路上可以获取通信内容,用户号容易没。
- 篡改风险,比如强制植入垃圾广告,视觉污染,用户眼容易瞎。
- 冒充风险,比如冒充淘宝网站,用户钱容易没。
HTTPS 在 HTTP 与 TCP 层之间加入了 TLS 协议,来解决上述的风险。
TLS 协议是如何解决 HTTP 的风险的呢?
- 信息加密:HTTP 交互信息是被加密的,第三方就无法被窃取;
- 校验机制:校验信息传输过程中是否有被第三方篡改过,如果被改过,则会有警告提示
- 身份证书:证明淘宝是真的淘宝网;
TCP在三次握手之后会进行TLS四层握手,过程如下:
2.1 TLS 第一次握手
客户端首先会发一个「Client Hello」消息,字面意思我们也能理解到,这是跟服务器「打招呼」。
消息里面有客户端使用的 TLS 版本号、支持的密码套件列表,以及生成的随机数(Client Random),这个随机数会被服务端保留,它是生成对称加密密钥的材料之一。
2.2 TLS 第二次握手
当服务端收到客户端的「Client Hello」消息后,会确认 TLS 版本号是否支持,和从密码套件列表中选择一个密码套件,以及生成随机数(Server Random)
接着,返回「Server Hello」消息,消息里面有服务器确认的 TLS 版本号,也给出了随机数(ServerRandom),然后从客户端的密码套件列表选择了一个合适的密码套件。
就前面这两个客户端和服务端相互[打招呼」的过程,客户端和服务端就已确认了 TLS 版本和使用的密码套件,而且你可能发现客户端和服务端都会各自生成一个随机数,并且还会把随机数传递给对方。那这个随机数有啥用呢?
其实这两个随机数是后续作为生成「会话密钥」的条件,所谓的会话密钥就是数据传输时,所使用的对称加密密钥。
然后,服务端为了证明自己的身份,会发送[Server Certificate」给客户端,这个消息里含有数字证书。
随后,服务端发了[Server Hello Done」消息,目的是告诉客户端,我已经把该给你的东西都给你了。
本次打招呼完毕。
2.3 客户端验证证书
在这里刹个车,客户端拿到了服务端的数字证书后,要怎么校验该数字证书是真实有效的呢?
在说校验数字证书是否可信的过程前,我们先来看看数字证书是什么,一个数字证书通常包含了:
- 公钥;
- 持有者信息;
- 证书认证机构(CA)的信息;
- CA 对这份文件的数字签名及使用的算法,
- 证书有效期:
- 还有一些其他额外信息;
那数字证书的作用,是用来认证公钥持有者的身份,以防止第三方进行冒充。说简单些,证书就是用来告诉客户端,该服务端是否是合法的,因为只有证书合法,才代表服务端身份是可信的。
我们用证书来认证公钥持有者的身份(服务端的身份),那证书又是怎么来的?又该怎么认证证书呢?
为了让服务端的公钥被大家信任,服务端的证书都是由 CA (Certificate Authority,证书认证机构)签名的,CA 就是网络世界里的公安局、公证中心,具有极高的可信度,所以由它来给各个公钥签名,信任的一方签发的证书,那必然证书也是被信任的。(比如淘宝网的证书就是验证过的,因此我们不会进入套皮的淘宝网中,如果进入了,浏览器会提示对方的证书不合法)。
之所以要签名,是因为签名的作用可以避免中间人在获取证书时对证书内容的篡改。
2.3.1 验证过程
CA 签发证书的过程,如上图左边部分:
- 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash,计算,得到一个 Hash 值;
- 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
- 最后将 Certificate signature 添加在文件证书上,形成数字证书;
客户端校验服务端的数字证书的过程,如上图右边部分:
- 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;
- 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密,Certificate Signature 内容,得到一个 Hash 值 H2;
- 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。
2.4 TLS第三次握手
客户端验证完证书后,认为可信则继续往下走。
接着,客户端就会生成一个新的随机数 (pre-master)
,用服务器的 RSA 公钥加密该随机数(该公钥是在第一次第二次握手时,服务端选择的密码套件),通过「ClientKey Exchange」消息传给服务端。
服务端收到后,用 RSA 私钥解密,得到客户端发来的随机数(pre-master)。
至此,客户端和服务端双方都共享了三个随机数,分别是 Client Random、Server Random、pre-master.
于是,双方根据已经得到的三个随机数,生成会话密钥(MasterSecret),它是对称密钥,用于对后续的HTTP 请求/响应的数据加解密。
告诉服务端开始使用加密方式发送生成完「会话密钥」后,然后客户端发一个[Change Cipher Spec」消息。
然后,客户端再发一个[Encrypted Handshake Message(Finishd)」消息,把之前所有发送的数据做个摘要,再用会话密钥(master secret)加密一下,让服务器做个验证,验证加密通信「是否可用」和[之前握手信息是否有被中途篡改过」。
可以发现,[Change Cipher Spec」之前传输的 TLS 握手数据都是明文,之后都是对称密钥加密的密文。
2.5 TLS第四次握手
服务器也是同样的操作,发「Change Cipher Spec」和[Encrypted Handshake Message」消息,如果双方都验证加密和解密没问题,那么握手正式完成。
最后,就用「会话密钥」加解密 HTTP 请求和响应了
3. TCP 和 UDP 可以同时绑定相同的端口吗?
答案:可以的。
在数据链路层中,通过 MAC 地址来寻找局域网中的主机。在网际层中,通过 IP 地址来寻找网络中互连的主机或路由器。在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。
所以,传输层的「端口号」的作用,是为了区分同一个主机上不同应用程序的数据包。
传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。
当主机收到数据包后,可以在IP包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。
因此, TCP/UDP 各自的端口号也相互独立,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突。
4. 多个 TCP 服务进程可以绑定同一个端口吗?
如果两个TCP 服务进程同时绑定的 IP 地址和端口都相同,那么执行 bind()) 时候就会出错,错误是”Addressalready in use”。
注意,如果 TCP 服务进程 A 绑定的地址是 0.0.0.0 和端口 8888,而如果 TCP 服务进程 B绑定的地址是192.168.1.100 地址(或者其他地址)和端口 8888,那么执行 bind() 时候也会出错。这是因为 0.0.0.0 地址比较特殊,代表任意地址,意味着绑定了 0.0.0.0地址,相当于把主机上的所有 IP 地址都绑定了。
但如果两个TCP服务进程绑定的是不同的ip,那么端口是可以相同的。
5. 输入URL到网页显示,期间发生了什么⭐
2.2 键入网址到网页显示,期间发生了什么? | 小林coding
6. 网络模型
对于同一台设备上的进程间通信,有很多种方式,比如有管道、消息队列、共享内存、信号等方式,而对于不同设备上的进程间通信,就需要网络通信,而设备是多样性的,所以要兼容多种多样的设备,就协商出了一套通用的网络协议。
这个网络协议是分层的,每一层都有各自的作用和职责,接下来就根据[TCP/IP 网络模型」分别对每一层进行介绍。
6.1 TCP/IP模型
6.1.1 应用层
最上层的,也是我们能直接接触到的就是应用层,我们电脑或手机使用的应用软件都是在应用层实现。那么,当两个不同设备的应用需要通信的时候,应用就把应用数据传给下一层,也就是传输层。
所以,应用层只需要专注于为用户提供应用功能,比如 HTTP、FTP、Telnet、DNS、SMTP等。
应用层是不用去关心数据是如何传输的,就类似于,我们寄快递的时候,只需要把包裹交给快递员,由他负责运输快递,我们不需要关心快递是如何被运输的。而且应用层是工作在操作系统中的用户态,传输层及以下则工作在内核态。
6.1.2 传输层
应用层的数据包会传给传输层,传输层是为应用层提供网络支持的。
在传输层会有两个传输协议,分别是 TCP和 UDP。
大部分应用使用的正是 TCP 传输层协议,比如 HTTP 应用层协议。TCP 相比 UDP 多了很多特性,比如流量控制、超时重传、拥塞控制等,这些都是为了保证数据包能可靠地传输给对方。
UDP 相对来说就很简单,简单到只负责发送数据包,不保证数据包是否能抵达对方,但它实时性相对更好,传输效率也高。当然,UDP 也可以实现可靠传输,把TCP的特性在应用层上实现就可以。
应用需要传输的数据可能会非常大,如果直接传输就不好控制,因此当传输层的数据包大小超过 MSS(TCP最大报文段长度,减去TCP报头+IP报头之外TCP能容纳的最大数据),就要将数据包分块,这样即使中途有一个分块丢失或损坏了,只需要重新发送这一个分块,而不用重新发送整个数据包。在 TCP 协议中,我们把每个分块称为一个 TCP 段。
当设备作为接收方时,传输层则要负责把数据包传给应用,但是一台设备上可能会有很多应用在接收或者传输数据,因此需要用一个编号将应用区分开来,这个编号就是端口(端口其实就是为了区分同一个主机上不同应用程序的数据包)。
比如 80 端口通常是 Web 服务器用的,22 端口通常是远程登录服务器用的。而对于浏览器(客户端)中的每个标签栏都是一个独立的进程,操作系统会为这些进程分配临时的端口号。
由于传输层的报文中会携带端口号,因此接收方可以识别出该报文是发送给哪个应用。
6.1.3 网络层
传输层可能大家刚接触的时候,会认为它负责将数据从一个设备传输到另一个设备,事实上它并不负责。
实际场景中的网络环节是错综复杂的,中间有各种各样的线路和分叉路口,如果一个设备的数据要传输给另一个设备,就需要在各种各样的路径和节点进行选择,而传输层的设计理念是简单、高效、专注,如果传输层还负责这一块功能就有点违背设计原则了。
也就是说,我们不希望传输层协议处理太多的事情,只需要服务好应用即可,让其作为应用间数据传输的媒介,帮助实现应用到应用的通信,而实际的传输功能就交给下一层,也就是网络层。
网络层最常使用的是 IP 协议,IP协议会将传输层的报文作为数据部分,再加上 IP 包头组装成 IP 报文,如果 IP 报文大小超过 MTU(以太网中一般为 1500 字节)就会再次进行分片,得到个即将发送到网络的 IP 报文。
网络层负责将数据从一个设备传输到另一个设备,世界上那么多设备,又该如何找到对方呢?因此,网络层需要有区分设备的编号。
我们一般用 IP 地址给设备进行编号,对于IPv4 协议,IP 地址共 32 位,分成了四段(比如192.168.100.1),每段是8位。只有一个单纯的iP地址虽然做到了区分设备,但是寻址起来就特别麻烦,全世界那么多台设备,难道一个一个去匹配?这显然不科学。
注意,IP 地址并不是根据主机台数来配置的,而是以网卡。像服务器、路由器等设备都是有2个以上的网卡,也就是它们会有2 个以上的 IP 地址。
因此,需要将 IP 地址分成两种意义:
- 一个是网络号,负责标识该 IP 地址是属于哪个「子网」的;
- 一个是主机号,负责标识同一「子网」下的不同主机;
路由器寻址工作中,就是要找到目标地址的子网,找到后进而把数据包转发给对应的网络内。
怎么分的呢?这需要配合子网掩码才能算出IP 地址 的网络号和主机号。
举个例子,比如 10.100.122.0/24,后面的 /24 表示就是 255.255.255.0 子网掩码,255.255.255.0 二进制是[11111111-11111111-11111111-00000000」,大家数数一共多少个1?不用数了,是 24个1,为了简化子网掩码的表示,用/24代替255.255.255.0。
知道了子网掩码,该怎么计算出网络地址和主机地址呢?
将 10.100.122.2和 255.255.255.0 进行按位与运算,就可以得到网络号,如下图:
将 255.255.255.0 取反后与IP地址进行进行按位与运算,就可以得到主机号。
因此一个IP中,**/24 表示前 24 位是网络号,剩余的 8 位是主机号**:
那为什么要分离网络号和主机号?
因为两台计算机要通讯,首先要判断是否处于同一个广播域内,即网络号是否相同。如果网络地址相同,表明接受方在本网络上,那么可以把数据包直接发送到目标主机。
那么在寻址的过程中,先匹配到相同的网络号(表示要找到同一个子网),才会去找对应的主机。
路由
除了寻址能力, IP 协议还有另一个重要的能力就是路由。实际场景中,两台设备并不是用一条网线连接起来的,而是通过很多网关、路由器、交换机等众多网络设备连接起来的,那么就会形成很多条网络的路径,因此当数据包到达一个网络节点,就需要通过路由算法决定下一步走哪条路径。
路由器寻址工作中,就是要找到目标地址的子网,找到后进而把数据包转发给对应的网络内。
所以,IP 协议的寻址作用是告诉我们去往下一个目的地该朝哪个方向走,路由则是根据「下一个目的地」选择路径。寻址更像在导航,路由更像在操作方向盘。
至此,网络包的报文如下图
6.1.4 网络接口层
生成了IP 头部之后,接下来要交给网络接口层(Link Layer)在IP 头部的前面加上 MAC 头部,并封装成数据帧(Data frame)发送到网络上。
IP 头部中的接收方 IP 地址表示网络包的目的地,通过这个地址我们就可以判断要将包发到哪里,但在以太网的世界中,这个思路是行不通的。
什么是以太网呢?电脑上的以太网接口,Wi-Fi接口,以太网交换机、路由器上的千兆,万兆以太网口还有网线,它们都是以太网的组成部分。以太网就是一种在「局域网」内,把附近的设备连接起来,使它们之间可以进行通讯的技术。
以太网在判断网络包目的地时和 IP的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而 MAC 头部就是干这个用的,所以,在以太网进行通讯要用到 MAC 地址。
MAC头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息。
那么MAC 发送方和接收方如何确认?
发送方的 MAC 地址获取就比较简单了,MAC 地址是在网卡生产时写入到 ROM 里的,只要将这个值读取出来写入到 MAC 头部就可以了。
接收方的 MAC 地址就有点复杂了,只要告诉以太网对方的 MAC的地址,以太网就会帮我们把包发送过去,那么很显然这里应该填写对方的 MAC 地址。
所以先得搞清楚应该把包发给谁,这个只要查一下路由表就知道了(局域网内)。在路由表中找到相匹配的条目,然后把包发给 Gateway 列中的 IP 地址就可以了。
举个例子:
在 Linux 操作系统,我们可以使用 route-n
命令查看当前系统的路由表。
根据上面的路由表,我们假设 Web 服务器的目标地址是 192.168.10.200。
- 首先先和第一条目的子网掩码( Genmask )进行 与运算,得到结果为 192.168.10.0 ,但是第一个条目的 Destination 是192.168.3.0,两者不一致所以匹配失败。
- 再与第二条目的子网掩码进行 与运算,得到的结果为 192.168.10.0,与第二条目的Destination 192.168.10.0 匹配成功,所以将使用eth1 网卡的 |P 地址作为 IP 包头的源地址。
根据这个IP地址,我们可以通过ARP协议获取路由器的MAC地址:
ARP 协议会在以太网中以广播的形式,对以太网所有的设备喊出:“这个IP 地址是谁的?请把你的 MAC地址告诉我”。
然后就会有人回答:“这个 IP 地址是我的,我的 MAC 地址是 xXXX”。
如果对方和自己处于同一个子网中,那么通过上面的操作就可以得到对方的 MAC 地址。然后,我们将这个 MAC 地址写入 MAC 头部,MAC 头部就完成了。
所以说,网络接口层主要为网络层提供「链路级别」传输的服务,负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标识网络上的设备。
至此,网络包的报文如下:
6.1.5 每一层的封装格式
帧头用于存放源/目的的MAC地址以及协议类型(IP),帧尾负责生成校验码,以防数据传输过程中发送丢包、篡改风险。
网络接口层的传输单位是帧(frame),IP 层的传输单位是包(packet),TCP 层的传输单位是段(segment),HTTP 的传输单位则是消息或报文(message)。但这些名词并没有什么本质的区分,可以统称为数据包。
6.1.6 网络通信总结
总而言之,MAC其实就是用于以太网(局域网)下不同设备通信的设备编号;而IP则是用于在不同局域网下设备之间通信的设备编号。
IP之所以能找到其他局域网是因为,ip分为网络号和主机号,如果是局域网下通信,那么两个设备之间的网络号是相同的,直接通过mac通信即可。但是网络号如果不同,那么数据就会被发送到默认网关(路由器),路由器维护了路由表,路由器根据路由表决定转发路径:
- 如果目标网络直接相连(如路由器直连的 LAN1 和 LAN2),直接转发;
- 若目标网络非直连,通过其他路由器(下一跳)转发,直到到达目标网络。
- 路由器最多能支持255跳,每跳一次,IP协议中的TTL就减一,当TTL=0时就将该数据包丢弃。TTL默认值是64,但是可以手动设为最大值255,因为TTL只占据8位。
然后在目标网络中通过ARP协议(ARP只能获取局域网内机器的MAC)对以太网内的所有设备进行广播,找出目标ip的MAC地址,然后在以太网下进行通信。
6.2 OSI网络模型
为了使得多种设备能通过网络相互通信,和为了解决各种不同设备在网络互联中的兼容性问题,国际标准化组织制定了开放式系统互联通信参考模型(Open System Interconnection Reference Model),也就是OSI 网络模型,该模型主要有7层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。
OSI网络模型介绍:
- 应用层,负责给应用程序提供统一的接口;
- 表示层,负责把数据转换成兼容另一个系统能识别的格式;
- 会话层,负责建立、管理和终止表示层实体之间的通信会话
- 传输层,负责端到端的数据传输,
- 网络层,负责数据的路由、转发、分片;
- 数据链路层,负责数据的封帧和差错检测,以及 MAC 寻址,物理层,负责在物理网络中传输数据帧;
TCP/IP 网络模型共有 4 层,分别是应用层、传输层、网络层和网络接口层,层负责的职能如下:
- 应用层,负责向用户提供一组应用程序,比如 HTTP、DNS、FTP 等:
- 传输层,负责端到端的通信,比如 TCP、UDP 等;
- 网络层,负责网络包的封装、分片、路由、转发,比如IP、ICMP 等;
- 网络接口层,负责网络包在物理网络中的传输,比如网络包的封帧、 MAC 寻址、差错检测,以及通过网卡传输网络帧等:
6.3 五层网络模型
五层网络体系结构分别为:应用层、运输层、网络层、数据链路层、物理层。各层功能分别如下:
- 应用层(Application Laver):与直接为用户的应用进程提供服务,是操作系统中的用户态(应用层是工作在操作系统中的用户态,传输层及以下则工作在内核态。),常见的有支持万维网应用的HTTP协议、支持电子邮件的SMTP协议,支持文件传送的FTP协议等等。
- 传输层(Transport Laver):负责向两个主机中进程之间的通信提供服务,是端(端口)到端的通信。传输层有两个传输协议TCP/UDP。
- 网络层(Network Layer):负责数据的路由和转发。它选择最佳路径将数据从源主机传输到目标主机,并使用逻辑地址(如IP地址)来标识主机和网络。
- 数据链路层(Data Link Laver):在直连网络中传输数据帧。它提供错误检测和纠正的功能,并负责数据的帧同步、地址寻址和流量控制。在这一层级上,通常会使用MAC地址来标识网络设备。
- 物理层(Phvsical Laver):负责物理传输媒介的传输。这包括电缆、光纤、无线信号等。该层级定义了传输数据位的形式、电压级别、传输速率等特性。
7. ping原理⭐
ping 是基于 ICMP协议工作的,所以要明白 ping 的工作,首先我们先来熟悉 ICMP 协议。
主要的功能包括:确认 IP 包是否成功送达目标地址、报告发送过程中 IP 包被废弃的原因和改善网ICMP络设置等。
在IP通信中如果某个 IP包因为某种原因未能达到目标地址,那么这个具体的原因将由 ICMP 负责通在
知。
如上图例子,主机 A 向主机8发送了数据包,由于某种原因,途中的路由器 2未能发现主机 B的存在,这时,路由器 2就会向主机 A发送一个 CMP目标不可达数据包,说明发往主机 8 的包未能成功。
ICMP 的这种通知消息会使用 IP进行发送。
因此,从路由器 2返回的 ICMP 包会按照往常的路由控制先经过路由器1再转发给主机 A。收到该ICMP 包的主机 A则分解 ICMP 的首部和数据域以后得知具体发生问题的原因。
ICMP 报文是封装在IP 包里面(协议号是1,如果是TCP报文传输,这里的协议是TCP),它工作在网络层,是IP协议的助手
ICMP 包头的类型字段,大致可以分为两大类:
- 一类是用于诊断的查询消息,也就是查询报文类型
- 另一类是通知出错原因的错误消息,也就是差错报文类型
ping 原理?
ping 其实就是查询报文类型的使用。
接下来,我们重点来看 ping的发送和接收过程。同个子网下的主机 A 和 主机 B,主机 A 执行 ping主机B后,我们来看看其间发送了什么?
ping 命令执行的时候,源主机首先会构建一个ICMP 回送请求消息数据包。
ICMP 数据包内包含多个字段,最重要的是两个:
- 第一个是类型,对于回送请求消息而言该字段为8。
- 另外一个是序号,主要用于区分连续 ping 的时候发出的多个数据包。
每发出一个请求数据包,序号会自动加1。为了能够计算往返时间RTT,它会在报文的数据部分插入发送时间。
然后,由 ICMP 协议将这个数据包连同地址 192.168.1.2 一起交给 IP 层。IP 层将以 192.168.1.2 作为目的地址,本机IP 地址作为源地址,协议字段设置为1表示是 ICMP协议,再加上一些其他控制信息,构建一个 IP数据包。
接下来,需要加入 MAc头。如果在本地 ARP 映射表中査找出 IP 地址 192.168.1.2 所对应的 MAC 地址则可以直接使用;如果没有,则需要发送 ARP协议査询 MAC 地址,获得 MAC 地址后,由数据链路层构建一个数据帧,目的地址是 IP 层传过来的 MAC 地址,源地址则是本机的 MAC 地址;还要附加上一些控制信息,依据以太网的介质访问规则,将它们传送出去。
主机B 收到这个数据帧后,先检査它的目的 MAC地址,并和本机的 MAC 地址对比,如符合,则接收,否则就丢弃。
接收后检査该数据帧,将IP 数据包从帧中提取出来,交给本机的 IP 层。同样,IP 层检查后,将有用的信息提取后交给 ICMP 协议。
主机, B会构建一个 ICMP 回送响应消息数据包,回送响应数据包的类型字段为 0,序号为接收到的请求数据包中的序号,然后再发送出去给主机 A。
在规定的时候间内,源主机如果没有接到 ICMP 的应答包,则说明目标主机不可达;如果接收到了 ICMP回送响应消息,则说明目标主机可达。
当然这只是最简单的,同一个局域网里面的情况。如果跨网段的话,还会涉及网关的转发、路由器的转发等等。
但是对于 ICMP 的头来讲,是没什么影响的。会影响的是根据目标 IP 地址,选择路由的下一跳,还有每经过一个路由器到达一个新的局域网,需要换 MAC 头里面的 MAC 地址。
说了这么多,可以看出 **ping 这个程序是使用了 ICMP 里面的 ECHO REQUEST(类型为8) 和 ECHOREPLY (类型为 0)**。
8. 既然有了HTTP为什么还需要RPC?⭐
其实 RPC 和 HTTP 本身就不是一个东西。RPC 是一个概念泛指调用远程的函数,HTTP 是一个协议,想实现 RPC 也可以基于 HTTP 协议。
而一般语境中的 RPC 可以理解为 gRPC 这种 RPC 框架,它对比起纯 HTTP协议传输数据来说,这类框架会对编解码和网络层有特殊的优化,从而有更高的效率。
所以哪怕 HTTP/2 或 HTTP/3 完全普及(性能上碾压),也不会取代RPC,因为它本质上就是个泛指调用远程函数的概念,很多框架依旧可以基于高性能的 HTTP/2 或 3 去实现新的 RPC 框架。
TCP 是传输层的协议,而基于 TCP 造出来的 HTTP 和各类 RPC 框架,它们都只是定义了不同消息格式的
应用层协议而已。
- HTTP 协议(Hyper Text Transfer Protocol),又叫做超文本传输协议。我们用的比较多,平时上网在浏览器上敲个网址就能访问网页,这里用到的就是 HTTP 协议。
- 而 RPC(Remote Procedure Call),又叫做远程过程调用。它本身并不是一个具体的协议,而是一种调用方式。
而且虽然大部分 RPC 协议底层使用 TCP,但实际上它们不一定非得使用 TCP,改用 UDP 或者 HTTP,其实也可以做到类似的功能。
那么有了rpc,为什么还需要HTTP?
其实, TCP是70年代出来的协议,而 HTTP是 90 年代才开始流行的。而直接使用裸 TCP 会有问题,可想而知,这中间这么多年有多少自定义的协议,而这里面就有80年代出来的RPC。所以我们该问的不是既然有 HTTP 协议为什么要有 RPC,而是为什么有 RPC 还要有 HTTP 协议。
现在电脑上装的各种联网软件,比如 xx管家,x卫士,它们都作为客户端(Client)需要跟服务端(Server)建立连接收发消息,此时都会用到应用层协议,在这种 Client/Server(C/S)架构下,它们可以使用自家造的 RPC 协议,因为它只管连自己公司的服务器就 ok 了。
但有个软件不同,浏览器(Browser),不管是 Chrome 还是 IE,它们不仅要能访问自家公司的服务器(Server),还需要访问其他公司的网站服务器,因此它们需要有个统一的标准,不然大家没法交流。于是,HTTP 就是那个时代用于统一 Browser/Server(B/S)的协议。
也就是说在多年以前,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,比如某度云盘,既要支持网页版,还要支持手机端和 PC 端,如果通信协议都用 HTTP 的话,那服务器只用同一套就够了。而 RPC 就开始退居幕后,一般用于公司内部集群里,各个微服务之间的通讯。
那这么说的话,都用 HTTP 得了,还用什么 RPC?
HTTP和RPC的区别⭐
1) 服务发现
首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道IP 地址和端口。这个找到服务对应的 IP 端口的过程,其实就是服务发现。
在 HTTP 中,你知道服务的域名,就可以通过 DNS 服务去解析得到它背后的 IP 地址,默认 80 端口。
而 RPC的话,就有些区别,一般会有专门的中间服务去保存服务名和IP信息,比如 ZooKeeper,甚至是 Redis。想要访问某个服务,就去这些中间服务去获得IP 和端口信息。由于 DNS 也是服务发现的一种,所以也有基于 DNS 去做服务发现的组件,比如CoreDNS。
可以看出服务发现这一块,两者是有些区别,但不太能分高低。
2)底层连接形式
以主流的 HTTP/1.1协议为例,其默认在建立底层 TCP 连接之后会一直保持这个连接(Keep Alive),之后的请求和响应都会复用这条连接。
而 RPC 协议,也跟 HTTP 类似,也是通过建立 TCP 长链接进行数据交互,但不同的地方在于,RPC 协议一般还会再建个连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。
3)传输的内容
基于 TCP 传输的消息,说到底,无非都是消息头 Header 和消息体 Body。
Header 是用于标记一些特殊信息,其中最重要的是消息体长度。
Body 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串(TCP传输是基于字节流传输的),毕竟计算机只认识这玩意。所以TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01串,这样的方案现在也有很多现成的,比如 Json, Protobuf。
这个将结构体转为二进制数组的过程就叫序列化,反过来将二进制数组复原成结构体的过程叫反序列化。
对于主流的 HTTP/1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 Json 来序列化结构体数据。
可以看到这里面的内容非常多的冗余,显得非常啰嗦。最明显的,像 Header 里的那些信息,其实如果我们约定好头部的第几位是 Content-Type,就不需要每次都真的把”Content-Type”这个字段都传过来,类似的情况其实在 body 的 Json 结构里也特别明显。
而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。
当然上面说的 HTTP,其实特指的是现在主流使用的 HTTP/1.1,HTTP/2 在前者的基础上做了很多改进,所以性能可能比很多 RPC协议还要好,甚至连 gRPC 底层都直接用的 HTTP/2 。
4)应用场景不同
5)概念不同
那么问题又来了,为什么既然有了 HTTP/2,还要有 RPC 协议?
这个是由于 HTTP/2 是 2015 年出来的。那时候很多公司内部的 RPC协议都已经跑了好些年了,基于历史原因,一般也没必要去换了。
总结:
- 纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义消息边界。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。
- RPC 本质上不算是协议,而是一种调用方式,而像 qRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC有很多种实现方式,不一定非得基于 TCP 协议。
- 从发展历史来说,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。
- RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP/1.1 性能要更好,所以大部分公司内部都还在使用 RPC。
- HTTP/2.0 在 HTTP/1.1的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。
其实 RPC 和 HTTP 本身就不是一个东西。RPC 是一个概念泛指调用远程的函数,HTTP 是一个协议,想实现 RPC 也可以基于 HTTP 协议。
而一般语境中的 RPC 可以理解为 gRPC 这种 RPC 框架,它对比起纯 HTTP协议传输数据来说,这类框架会对编解码和网络层有特殊的优化,从而有更高的效率。
所以哪怕 HTTP/2 或 HTTP/3 完全普及(性能上碾压),也不会取代RPC,因为它本质上就是个泛指调用远程函数的概念,很多框架依旧可以基于高性能的 HTTP/2 或 3 去实现新的 RPC 框架。
9. HTTP状态码
10. get和post接口的区别
1、用途:get 是从服务器上获取数据,post 是向服务器传送数据。
2、参数传递方式:get 的请求参数会附加在 URL 后面,以键值对的形式出现,各参数之间用 &
符号分隔,浏览器堆get的参数长度有一定限制。post 是通过HTTP post 机制,将请求参数放在 HTTP 请求体中,不会在 URL 中显示。这样可以传递更大量、更复杂的数据,且数据相对更安全
3、数据长度限制:get 传输的数据量较小,URL 的长度是有限制的,不同浏览器和服务器对 URL 长度的限制不同,一般来说限制在 2048 个字符左右。post理论上对请求体的数据长度没有限制,可以传递大量的数据。不过,服务器可能会对请求体的大小进行限制,以防止恶意攻击或资源耗尽。
4、缓存:对于get方式,通常会被浏览器缓存。如果请求的 URL 相同,浏览器可能会直接从缓存中获取数据,而不是再次向服务器发送请求。这在一定程度上可以提高性能,但也可能导致获取到的数据不是最新的。post默认情况下不会被缓存,每次请求都会向服务器发送新的数据。。
5、安全性:get 安全性低,由于参数直接暴露在 URL 中,所以不太安全。如果包含敏感信息(如密码、银行卡号等),可能会被他人轻易获取。此外,GET
请求还可能被浏览器历史记录、服务器日志等记录下来。。post 安全性高,参数在请求体中,相对更安全,适合传递敏感信息。但这并不意味着 POST
就绝对安全,在实际应用中,还需要结合加密等其他安全措施来保障数据安全。(安全的含义仅仅是指是否修改信息)
6、幂等性:幂等性是指一个操作无论执行多少次,其结果都是相同的。Get是幂等的请求方法,POST通常不是幂等的(多次执行相同的 POST
请求可能会导致服务器上的数据发生多次改变,比如多次提交表单可能会创建多条重复的记录。)
11. HTTP
11.1 HTTP的概念
HTTP 的名字「超文本协议传输」,它可以拆成三个部分:
- 超文本
- 传输
- 协议
协议
HTTP 是一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范(两个以上的参与者),以及相关的各种控制和错误处理方式(行为约定和规范)
传输
所谓的「传输」,很好理解,就是把一堆东西从 A 点搬到 B点,或者从 B点 搬到 A 点。别轻视了这个简单的动作,它至少包含两项重要的信息。
HTTP 协议是一个双向协议。
我们在上网冲浪时,浏览器是请求方 A,百度网站就是应答方 B。双方约定用 HTTP 协议来通信,于是浏览器把请求数据发送给网站,网站再把一些数据返回给浏览器,最后由浏览器渲染在屏幕,就可以看到图片、视频了。
数据虽然是在 A和 B 之间传输,但允许中间有中转或接力。
就好像第一排的同学想传递纸条给最后一排的同学,那么传递的过程中就需要经过好多个同学(中间人),这样的传输方式就从「A< — > B」 ,变成了 「A <-> N <-> M <-> B」。
而在 HTTP 里,需要中间人遵从 HTTP 协议,只要不打扰基本的数据传输,就可以添加任意额外的东西。
针对传输,我们可以进一步理解了 HTTP.
HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。
超文本
我们先来理解「文本」,在互联网早期的时候只是简单的字符文字,但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等,在 HTTP 眼里这些都算作「文本」。
再来理解「超文本」,它就是超越了普通文本的文本,它是文字、图片、视频等的混合体,最关键有超链接,能从一个超文本跳转到另外一个超文本。
总结
HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的[约定和规范」
11.2 HTTP1.1
HTTP1.1的优点
简单
HTTP 基本的报文格式就是 header + body,头部信息也是 key-value易于理解,降简单文本的形式,低了学习和使用的门槛。灵活和易于扩展
HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充。
同时 HTTP 由于是工作在应用层( osI 第七层),则它下层可以随意变化,比如:
HTTPS 就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层,但HTTPS握手先要经过TCP三次,然后还有TLS四次握手。
HTTP/1.1 和 HTTP/2.0 传输协议使用的是 TCP 协议,而到了 HTTP/3.0 传输协议改用了 UDP 协议,HTTPS中的QUIC包含了TLS协议,因此握手过程比HTTPS和HTTP2更快。
- 应用广泛和跨平合
互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应用遍地开花,同时天然具有跨平台的优越性。
HTTP的缺点
- 无状态,通过cookie解决,但会增加请求头大小
- 明文传输,通过HTTPS的TLS层解决
- 不安全
- 通信使用明文(不加密),内容可能会被窃听。比如,账号信息容易泄漏,那你号没了。
- 不验证通信方的身份,因此有可能遭遇伪装。比如,访问假的淘宝、拼多多,那你钱没了
- 无法证明报文的完整性,所以有可能已遭篡改。比如,网页上植入垃圾广告,视觉污染,眼没了,
性能
- 长连接
- 管道网络传输,即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。但是服务器必须按照接收请求的顺序发送对这些管道化请求的响应。如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。
- 队头阻塞,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。
- 冗长的头部,并且未经压缩。
- 没有请求优先级控制。
- 单工通信。
前两点是HTTP1.1相比HTTP1.0的改进,后四点是HTTP1.1的性能缺点。
如何优化HTTP1.1
HTTP1.1最大的性能问题是HTTP1.1的高延迟:
- 延迟难以下降,虽然现在网络的「带宽」相比以前变多了,但是延迟降到一定幅度后,就很难再下降了,说白了就是到达了延迟的下限;
- 并发连接有限,谷歌浏览器最大并发连接数是6个,而且每一个连接都要经过 TCP 和 TLS 握手耗时以及 TCP 慢启动过程给流量带来的影响;
- 队头阻塞问题,同一连接只能在完成一个 HTTP 事务(请求和响应)后,才能处理下一个事务;
- HTTP 头部巨大日重复,由于 HTTP 协议是无状态的,每一个请求都得携带 HTTP 头部,特别是对于有携带 Cookie 的头部,而 Cookie 的大小通常很大;
- 不支持服务器推送消息,因此当客户端需要获取通知时,只能通过定时器不断地拉取消息,这无疑浪费大量了带宽和服务器资源。
HTTP2.0就是将HTTP1.1的所有性能问题一一突破解决,这里不说HTTP2.0,讲一下HTTP1.1如何进行优化:
注意,优化不是升级,对于HTTP1.1单工、头部巨大、队头阻塞这些问题,我们没办法进行优化,除非升级协议。
第一个思路是,通过缓存技术来避免发送 HTTP 请求。客户端收到第一个请求的响应后,可以将其缓存在本地磁盘,下次请求的时候,如果缓存没过期,就直接读取本地缓存的响应数据。如果缓存过期,客户端发送请求的时候带上响应数据的摘要,服务器比对后发现资源没有变化,就发出不带包体的 304 响应,告诉客户端缓存的响应仍然有效。
第二个思路是,减少 HTTP 请求的次数,有以下的方法:
1.将原本由客户端处理的重定向请求(请求的资源换到了其他目录下,需要重定向),交给代理服务器处理,这样可以减少重定向请求的次数:
2.将多个小资源合并成一个大资源再传输,能够减少 HTTP 请求次数以及 头部的重复传输,再来减少TCP 连接数量,进而省去 TCP 握手和慢启动的网络消耗:
3.按需访问资源,达到延迟请求,也就减少了同一时间的 HTTP 请求次数。只访问当前用户看得到/用得到的资源,当客户往下滑动,再访问接下来的资源,以此达到延迟请求。
第三思路是,通过压缩响应资源,降低传输资源的大小,从而提高传输效率,所以应当选择更优秀的压缩算法。
不管怎么优化 HTTP/1.1协议都是有限的,不然也不会出现 HTTP/2 和 HTTP/3 协议,后续我们再来介绍HTTP/2 和 HTTP/3 协议。
11.3 HTTP2.0
我们在上面介绍了如何对HTTP1.1优化的手段,尽管对 HTTP/1.1 协议的优化手段如此之多,但是效果还是不尽人意,因为这些手段都是对 HTTP/1.1 协议的“外部”做优化,而一些关键的地方是没办法优化的,比如请求-响应模型、头部巨大且重复、、并发连接耗时、服务器不能主动推送等,要改变这些必须重新设计 HTTP 协议,于是 HTTP/2 就出来了!
兼容HTTP1.1
那么,HTTP/2 是怎么做的呢?
第一点,HTTP/2 没有在 URI里引入新的协议名,仍然用 [http://」表示明文协议,用 「https://」表示加密协议,于是只需要浏览器和服务器在背后自动升级协议,这样可以让用户意识不到协议的升级,很好的实现了协议的平滑升级。
第二点,只在应用层做了改变,还是基于 TCP 协议传输,应用层方面为了保持功能上的兼容,HTTP/2 把HTTP 分解成了「语义」和「语法」两个部分,「语义」层不做改动,与 HTTP/1.1 完全一致,比如请求方法、状态码、头字段等规则保留不变。
但是,HTTP/2 在「语法」层面做了很多改造,基本改变了 HTTP 报文的传输格式。
头部压缩
HTTP/1.1 报文中 Header 部分存在的问题:
- 含很多固定的字段,比如 Cookie、User Agent、Accept等,这些字段加起来也高达几百字节甚至上千字节,所以有必要压缩;
- 大量的请求和响应的报文里有很多字段值都是重复的,这样会使得大量带宽被这些冗余的数据占用了所以有必须要避免重复性;
- 字段是 ASCI 编码的,虽然易于人类观察,但效率低,所以有必要改成二进制编码
HTTP/2 对 Header 部分做了大改造,把以上的问题都解决了。
HTTP/2 没使用常见的 gzip 压缩方式来压缩头部,而是开发了 HPACK 算法,HPACK 算法主要包含三个组成部分:
- 静态字典;
- 动态字典;
- Huffman 编码(压缩算法);
客户端和服务器两端都会建立和维护「字典」用长度较小的索引号表示重复的字符串,再用 Huffman编码压缩数据,可达到 50%~90% 的高压缩率。
二进制帧
HTTP/2 厉害的地方在于将 HTTP/1 的文本格式(json)改成二进制格式传输数据,极大提高了 HTTP 传输效率,而且二进制数据使用位运算能高效解析。
HTTP1.1响应和HTTP2.0的区别如下图
HTTP/2 把响应报文划分成了两类帧(Frame),图中的 HEADERS(首部)和 DATA(消息负载)是帧的类型,也就是说一条 HTTP 响应,划分成了两类帧来传输,并且采用二进制来编码。
帧头(Frame Header)很小,只有9个字节,帧开头的前3个字节表示帧数据(Frame Playload)的长度。帧长度后面的一个字节是表示帧的类型,HTTP/2 总共定义了 10 种类型的帧,一般分为数据帧和控制帧两类,如下表格:
并发传输
我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。
而 HTTP/2 就很牛逼了,通过 Stream 这个设计,多个 Stream 复用一条 TCP 连接,达到并发的效果,解决了 HTTP/1.1 队头阻塞的问题,提高了 HTTP 传输的吞吐量。
- 1 个 TCP 连接包含一个或者多个 Stream,Stream 是 HTTP/2 并发的关键技术
- Stream 里可以包含1个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成;
- Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体);
因此,我们可以得出个结论:多个 Stream 跑在一条 TCP 连接,同一个 HTTP 请求与响应是跑在同一个Stream 中,HTTP 消息可以由多个 Frame 构成,一个 Frame 可以由多个 TCP 报文构成。
在 HTTP/2 连接上,不同 Stream 的帧是可以乱序发送的(因此可以并发不同的 Stream),因为每个帧的头部会携带 Stream ID 信息,所以接收端可以通过 Stream ID 有序组装成 HTTP 消息,而同一Stream内部的帧必须是严格有序的。
同一个连接中的 Stream ID 是不能复用的,只能顺序递增,所以当 StreamID 耗尽时,需要发一个控制帧GOAWAY ,用来关闭 TCP 连接。
双工通信
HTTP/1.1不支持服务器主动推送资源给客户端,都是由客户端向服务器发起请求后,才能获取到服务器响应的资源。比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返。
那 HTTP/2 的推送是怎么实现的?
客户端发起的请求,必须使用的是奇数号 Stream,服务器主动的推送,使用的是偶数号 Stream。服务器在推送资源时,会通过 PUSH PROMISE 帧传输 HTTP 头部,并通过帧中的 Promised stream Ip 字段告知客户端,接下来会在哪个偶数号 Stream 中发送包体。
如上图,在 Stream1中通知客户端 CSS 资源即将到来,然后在 Stream 2 中发送 CSS 资源,注意 Stream
1 和 2 是可以并发的。
为什么能够双向通信?
其实还是基于请求响应模式,只不过HTTP1.1中是客户端请求一次,然后服务端返回一次,是一一对应的(服务器不能主动推送资源,如果要实现实时双工,那么就使用websocket);而HTTP2.0中客户端可以在一条TCP连接中连续请求多次,然后服务器可以将数据在返回的时候返回很多;并且服务端可以在客户端主动申请资源之前将数据推送给客户端,只需要在stream1响应客户端第一个请求的同时,在stream1的PUSH_PROMISE
帧中通过帧中的Promised Stream ID字段告知客户端,接下来在哪个偶数号stream中发送包体,比如stream2或者stream4中。
- HTTP 2.0 实现双向通信的原理:HTTP 2.0 引入了二进制分帧层,它将每个请求和响应分解为更小的帧(frame),并对这些帧进行二进制编码。在这种架构下,客户端和服务器可以同时发送和接收多个帧,这些帧可以属于不同的请求和响应流,实现了双向的、并发的通信。
- 多路复用:HTTP 2.0 的多路复用允许在一个 TCP 连接上同时发送多个请求和响应的帧,这些帧可以交错传输,并且可以根据优先级进行处理。客户端和服务器可以同时在这个连接上发送和接收数据,不再像 HTTP 1.0 那样需要等待一个请求响应完成后才能进行下一个。例如,浏览器可以在同一个 TCP 连接上同时请求 HTML 页面、图片、CSS 和 JavaScript 文件,服务器也可以同时发送这些资源的响应,提高了传输效率。
- 服务器推送:HTTP 2.0 新增的服务器推送功能允许服务器在客户端发送请求之前,主动将一些客户端可能需要的资源(例如 HTML 页面中引用的 CSS、JavaScript 文件等)推送给客户端。这也是双向通信的一种体现,服务器不再仅仅是被动地响应客户端的请求,还可以主动向客户端发送数据,提前将资源发送到客户端缓存中,减少客户端后续的请求次数,提高页面加载速度 。
11.4 HTTPS
HTTP1.1和HTTPS的区别
- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
- 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
HTTPS是如何解决HTTP的风险的
HTTP 由于是明文传输,所以安全上存在以下三个风险:
- 窃听风险,比如通信链路上可以获取通信内容,用户号容易没
- 篡改风险,比如强制入垃圾广告,视觉污染,用户眼容易瞎。
- 冒充风险,比如冒充淘主网站,用户钱容易没。
解决方法:
- 混合加密(非对称加密和对称加密)的方式实现信息的机密性,解决了窃听的风险
- 摘要算法(其实就是哈希算法)的方式来实现完整性,它能够为数据生成独一无二的「指纹」,指纹用于校验数据的完整性,解决了篡改的风险。
- 将服务器公钥放入到数字证书中,解决了冒充的风险
HTTPS如何保证应用数据完整性
HTTP1.1无法保证应用数据的完整性,那么HTTPS是如何保证的?
TLS 在实现上分为握手协议和记录协议两层:
- TLS 握手协议就是TLS 四次握手的过程,负责协商加密算法和生成对称密钥,后续用此密钥来保护应用程序数据(即 HTTP 数据);
- TLS 记录协议负责保护应用程序数据并验证其完整性和来源,所以对 HTTP 数据加密是使用记录协议;
TLS 记录协议主要负责消息(HTTP 数据)的压缩,加密及数据的认证,过程如下图:
- 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩
- 接下来,经过压缩的片段会被加上消息认证码(MAC值,这个是通过哈希算法生成的),这是为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。
- 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密码进行加密。
- 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。
记录协议完成后,最终的报文数据将传递到传输协议层进行传输。
HTTPS一定安全吗?
有个问题的场景是这样的:客户端通过浏览器向服务端发起 HTTPS 请求时,被「假基站!转发到了一个[中间人服务器」,于是客户端是和「中间人服务器」完成了 TLS 握手,然后这个「中间人服务器」再与真正的服务端完成 TLS 握手。
从客户端的角度看,其实并不知道网络中存在中间人服务器这个角色。那么中间人就可以解开浏览器发起的 HTTPS 请求里的数据,也可以解开服务端响应给浏览器的 HTTPS 响应数据。相当于,中间人能够“偷看”浏览器与服务端之间的 HTTPS 请求和响应的数据。
但是要发生这种场景是有前提的,前提是用户点击接受了中间人服务器的证书。
中间人服务器与客户端在 TLS 握手过程中,实际上发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器(客户端)识别出是非法的,于是就会提醒用户该证书存在问题。
抓包工具为什么能截取HTTPS数据?
抓包工具本质上还是中间人,只不过要满足以下两点:
- 中间人要作为客户端与真实服务端建立连接这一步不会有问题,因为服务端不会校验客户端的身份。
- 中间人要作为服务端与真实客户端建立连接,这里会有客户端信任服务端的问题,也就是服务端必须有对应域名(真实服务端)的私钥
中间人要拿到私钥只能通过如下方式:
- 去网站服务端拿到私钥,
- 去CA处拿域名签发私钥,
- 自己签发证书,切要被浏览器信任;
不用解释,抓包工具只能使用第三种方式取得中间人的身份。
使用抓包工具进行 HTTPS 抓包的时候,需要在客户端安装 Fiddler 的根证书,这里实际上起认证中心(CA)的作用。
抓包工具能够抓包的关键是客户端会往系统受信任的根证书列表中导入抓包工具生成的证书,而这个证书会被浏览器信任,也就是抓包工具给自己创建了一个认证中心 CA,客户端拿着中间人签发的证书去中间人自己的 CA 去认证,当然认为这个证书是有效的。
如何避免被中间人抓取数据
我们要保证自己电脑的安全,不要被病毒乘虚而入,而且也不要点击任何证书非法的网站,这样 HTTPS数据就不会被中间人截取到了,
当然,我们还可以通过 HTTPS 双向认证来避免这种问题。般我们的 HTTPS 是单向认证,客户端只会验证了服务端的身份,但是服务端并不会验证客户端的身份
如果用了双向认证方式,不仅客户端会验证服务端的身份,而且服务端也会验证客户端的身份。服务端旦验证到请求自己的客户端为不可信任的,服务端就拒绝继续通信,客户端如果发现服务端为不可信任的,那么也中止通信。
12. 没有 “listen” 能否建立 TCP 连接
如果从建立 TCP 连接的基本三次握手过程来看,“listen”函数并不是建立 TCP 连接的必需步骤。因为三次握手主要是客户端和服务器之间通过交换带有 SYN、ACK 等标志的报文段来完成连接建立,没有涉及“isten“这个操作。
但是在实际的网络编程场景中,如果是服务器主动去建立连接(这比较少见通常是客户端发起连接请求),不需要“isten”函数就可以进行三次握手建立连接,或者客户端自己连接自己,又或者两个客户端同时向对方发送连接请求建立连接。
listen 是为了创建半连接队列和全连接队列,三次握手的过程中会在这两个队列中暂存连接信息,所以形成连接的前提是得有个地方存储连接信息,方便握手的时候能根据IP和端口找到对应的socket。
因此服务器必须调用listen创建队列,而客户端不需要,因为内核有一个全局哈希表,可以用于存放socket连接的信息,客户端在调用connect方法时,最后会将自己的连接信息放入到这个全局哈希表中,然后将信息发出,当服务器主动向客户端建立连接或者客户端自连接时,根据IP和端口的信息通过哈希算法映射在全局哈希表中找出对应的socket信息。
服务器必须要listen,因为握手过程中需要把连接对应的socket存放至相应队列中,而全局哈希只能用于存放自身的,不能存放其他连接的。
而如果是服务器等待客户端连接,就必须使用“isten”函数来设置套接字头监听状态,否则服务器无法正确接收客户端的连接请求,连接也就无法正常建立。
所以,没有“listen”在某些特殊情况下(如服务器主动连接)可以建立 TCP 连接,但在常规的服务器等待客户端连接的情况下不能正确建立 TCP 连接。
13. 没有‘accept’能否建立连接
accpet 系统调用并不参与 TCP 三次握手过程,它只是负责从 TCP 全连接队列取出一个已经建立连接的socket,用户层通过 accpet 系统调用拿到了已经建立连接的 socket,就可以对该 socket 进行读写操作
全连接队列是链表,半连接队列是哈希表。
全连接队列本质是个链表,因为也是线性结构,说它是个队列也没毛病。它里面放的都是已经建立完成的连接,这些连接正等待被取走。而服务端取走连接的过程中,并不关心具体是哪个连接只要是个连接就行,所以直接从队列头取就行了。这个过程算法复杂度为 0(1)。
而半连接队列却不太一样,因为队列里的都是不完整的连接,嗷等待着第三次握手的到来。那么现在有个第三次握手来了,则需要从队列里把相应IP端口的连接取出,如果半连接队列还是个链表,那我们就需要依次遍历,才能拿到我们想要的那个连接,算法复杂度就是O(n)。
因此我们需要将半连接队列设计成哈希表,这样查找半连接队列的算法复杂度就算O(1)了
14. 已建立连接的TCP,收到SYN会发生什么
1.客户端的 SYN 报文里的端口号与历史连接不相同
如果客户端恢复后发送的 SYN 报文中的源端口号跟上一次连接的源端口号不一样,此时服务端会认为是新的连接要建立,于是就会通过三次握手来建立新的连接。
那旧连接里处于 Established 状态的服务端最后会怎么样呢?
如果服务端发送了数据包给客户端(心跳机制),由于客户端的连接已经被关闭了,此时客户的内核就会回 RST 报文服务端收到后就会释放连接,
如果服务端一直没有发送数据包给客户端,在超过一段时间后,TCP 保活机制就会启动,检测到客户端没有存活后,接着服务端就会释放掉该连接。
如果Keep-alive字段没有启动,那么该连接会一直连接不会断开
2.客户端的 SYN 报文里的端口号与历史连接相同
如果客户端恢复后,发送的 SYN 报文中的源端口号跟上一次连接的源端口号一样,也就是处于Established 状态的服务端收到了这个 SYN 报文。
处于 Established 状态的服务端,如果收到了客户端的 SYN 报文(注意此时的 SYN 报文其实是乱序的,因为 SYN 报文的初始化序列号其实是一个随机数),会回复一个携带了正确序列号和确认号的 ACK 报文(根据服务端当前已建立连接的状态和期望的序列号来设置的,而不是根据这个意外的 SYN 报文来设置的),这个 ACK 被称之为 Challenge ACK。
接着,客户端收到这个 Challenge ACK,发现确认号(acknum)并不是自己期望收到的,于是就会回RST 报文,服务端收到后,就会释放掉该连接。(在正常的 TCP 连接建立过程中,客户端发送 SYN 报文后,期望收到的 ACK 报文中的确认号应该是基于自己发送的 SYN 报文中的序列号来设置的。但由于这个 Challenge ACK 是服务端根据已建立连接的状态回复的,所以确认号和客户端期望的不一致。)
15. TCP连接,一端断电和进程崩溃有什么区别
如果「客户端进程崩溃」客户端的进程在发生崩溃的时候,内核会发送 FIN 报文,与服务端进行四次挥手。
但是,**[客户端主机宕机**」,那么是不会发生四次挥手的,具体后续会发生什么?还要看服务端会不会发送数据?
- 如果服务端会发送数据(心跳),由于客户端已经不存在,收不到数据报文的响应报文,服务端的数据报文会超时重传,当重传总间隔时长达到一定阈值后,会断开 TCP 连接:
- 如果服务端一直不会发送数据,再看服务端有没有开启 TCP keepalive 机制?
- 如果有开启,服务端在一段时间没有进行数据交互时,会触发 TCP keepalive 机制,探测对方是否存在,如果探测到对方已经消亡,则会断开自身的TCP 连接;
- 如果没有开启,服务端的 TCP 连接会一直存在,并且一直保持在 ESTABLISHED 状态。
16. TCP协议的缺陷
其实在介绍QUIC协议时已经提到过了TCP协议的缺陷:
- 升级 TCP 的工作很困难
- TCP 建立连接的延迟
- TCP 存在队头阻塞问题
- 网络迁移需要重新建立 TCP 连接
后面三点在QUIC协议中介绍过了,这里说以下第一点:
TCP 协议是诞生在 1973 年,至今 TCP 协议依然还在实现更多的新特性。
但是 TCP 协议是在内核中实现的,应用程序只能使用不能修改,如果要想升级 TCP 协议,那么只能升级内核。
而升级内核这个工作是很麻烦的事情,麻烦的事情不是说升级内核这个操作很麻烦,而是由于内核升级涉及到底层软件和运行库的更新,我们的服务程序就需要回归测试是否兼容新的内核版本,所以服务器的内核升级也比较保守和缓慢。
很多 TCP 协议的新特性,都是需要客户端和服务端同时支持才能生效的,比如 TCP Fast Open 这个特性虽然在2013 年就被提出了,但是 Windows 很多系统版本依然不支持它,这是因为 PC 端的系统升级滞后很严重,windows Xp 现在还有大量用户在使用,尽管它已经存在快 20 年。
所以,即使 TCP 有比较好的特性更新,也很难快速推广,用户往往要几年或者十年才能体验到。
17. 客户端的端口可以重复使用吗
客户端在执行 connect 函数的时候,会在内核里随机选择一个端口,然后向服务端发起 SYN 报文,然后与服务端进行三次握手。
所以,客户端的端口选择的发生在 connect 函数,内核在选择端口的时候,会从net.ipv4.ip_1ocal_port_range
这个内核参数指定的范围来选取一个端口作为客户端端口。
那问题来了,上面客户端已经用了 64992 端口,那么还可以继续使用该端口发起连接吗?
TCP 连接是由四元组(源IP地址,源端口,目的IP地址,目的端口)唯一确认的,那么只要四元组中其中一个元素发生了变化,那么就表示不同的 TCP 连接的。所以如果客户端已使用端口64992 与服务端 A建立了连接,那么客户端要与服务端 B建立连接,还是可以使用端口 64992 的,因为内核是通过四元祖信息来定位一个 TCP 连接的,并不会因为客户端的端口号相同,而导致连接冲突的问题。
18. 多个客户端可以bind同一个端口吗
bind 函数虽然常用于服务端网络编程中,但是它也是用于客户端的。
前面我们知道,客户端是在调用 connect 函数的时候,由内核随机选取一个端口作为连接的端口。
而如果我们想自己指定连接的端口,就可以用 bind 函数来实现:客户端先通过 bind 函数绑定一个端口然后调用 connect 函数就会跳过端口选择的过程了,转而使用 bind 时确定的端口。
针对这个问题:多个客户端可以 bind 同一个端口吗?
要看多个客户端绑定的 IP+PORT 是否都相同,如果都是相同的,那么在执行 bind0 时候就会出错,错误是”Address already in use”.
如果一个绑定在 192.168.1.100:6666,一个绑定在 192.168.1.200:6666,因为IP 不相同,所以执行 bind0的时候,能正常绑定。
所以,如果多个客户端同时绑定的 IP 地址和端口都是相同的,那么执行 bind0 时候就会出错,错误是”Address already in use”。
一般而言,客户端不建议使用 bind 函数,应该交由 connect 函数来选择端口会比较好,因为客户端的端口通常都没什么意义。
19. 客户端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?
要看客户端是否都是与同一个服务器(目标地址和目标端口一样)建立连接。
如果客户端都是与同一个服务器(目标地址和目标端口一样)建立连接,那么如果客户端 TIME WAIT 状态的连接过多,当端口资源被耗尽,就无法与这个服务器再建立连接了。即使在这种状态下,还是可以与其他服务器建立连接的,只要客户端连接的服务器不是同一个,那么端口是重复使用的(只要四元组中有一个不同,就是一个全新的TCP连接)。
20. SYN报文在什么情况下会被丢弃
SYN 报文被丢弃的两种场景:
- 开启 tcp_tw_recycle 参数,并且在 NAT 环境下,造成 SYN 报文被丢弃
- TCP 两个队列满了(半连接队列和全连接队列),造成 SYN 报文被丢弃
21. UDP
客户端要发起一次请求,仅仅需要两个步骤(socket和sendto),不需要connect,而服务器端也仅仅需要三个步骤即可接收到来自客户端的消息(socket、bind、recvfrom),不需要listen。
UDP 是无连接协议,无需像 TCP 那样通过三次握手建立连接。服务器只需调用
bind
绑定 IP 和端口,即可直接接收客户端发送的数据报。**无需
listen
或accept
**:TCP 中listen
和accept
用于监听连接请求并创建新的连接套接字,但 UDP 中完全不需要这些步骤。客户端通过
sendto
或sendmsg
向服务器的 IP 和端口发送 UDP 数据报。服务器的 UDP 套接字在已绑定端口的情况下,内核会将数据报直接投递到该套接字的接收缓冲区。
服务器需调用
recvfrom
或recvmsg
主动从缓冲区中读取数据,否则数据会被丢弃(UDP 不保证可靠性)。服务器未调用
recvfrom
的后果
- 若服务器仅调用bind绑定端口,但未调用
recvfrom
,则:
- 数据报会被内核接收并存储到套接字的接收缓冲区。
- 若缓冲区已满,后续数据会被丢弃。
- 应用层无法感知数据的到达,因为没有调用
recvfrom
读取。
UDP 是无连接协议,不像 TCP 那样需要建立连接以及(不需要)接收客户端的套接字来维持连接状态,只需要接收客户端发送的数据报。
UDP 套接字是无连接协议,必须使用 sendto 函数发送数据,必须使用 recvfrom 函数接收数据,发送时需指明目的地址。sendto 函数与 send 功能基本相同, recvfrom 与 recv 功能基本相同,只不过 sendto 函数和 recvfrom 函数参数中都带有对方地址信息,这两个函数是专门为 UDP 协议提供的。