计网.md 32 KB

什么是TCP

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

img

  • 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。

TCP如何保证可靠

  • 重传机制
    • 超时重传
    • 快速重传
    • SACK-- 可以将已收到的数据的信息发送给「发送方」
    • D-SACK -- 使用了 SACK 来告诉「发送方」有哪些数据被重复接收了
  • 滑动窗口
  • 流量控制 -- TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。
  • 拥塞控制

tcp拥塞控制的几个策略

  1. 慢启动:在连接刚建立时,TCP发送方会以较小的拥塞窗口(cwnd)开始发送数据,并根据每次接收到的确认(ACK)增加cwnd(拥塞窗口)的大小,从而逐渐增加发送速率,达到网络带宽的最佳利用。
  2. 拥塞避免:当 cwnd >= ssthresh (阈值)时,就会使用「拥塞避免算法」,TCP发送方会切换到拥塞避免模式,此时每次接收到一个ACK时,cwnd的大小只会增加一个MSS(最大报文段长度),而不是每次都加倍。这样可以避免发送过多的数据导致网络拥塞。
  3. 拥塞发生:当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
    1. 快速重传:当TCP发送方连续收到3个重复的ACK时,TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthreshcwnd 变化如下:
      • cwnd = cwnd/2 ,也就是设置为原来的一半;
      • ssthresh = cwnd;
      • 进入快速恢复算法
    2. 超时重传:如果在发送数据时未能及时接收到ACK,ssthresh 设为 cwnd/2cwnd 重置为 1
  4. 快速恢复:进入快速恢复之前,cwndssthresh 已被更新了,然后,进入快速恢复算法如下:
    1. 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
    2. 重传丢失的数据包;
    3. 如果再收到重复的 ACK,那么 cwnd 增加 1;
    4. 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值(这里是快速重传时候的ssthresh ),原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

TCP和UDP区别

连接 服务对象

可靠性 拥塞控制、流量控制

头部长度不同 分片不同

  1. 连接
  • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
  • UDP 是不需要连接,即刻传输数据。
  1. 服务对象
  • TCP 是一对一的两点服务,即一条连接只有两个端点。
  • UDP 支持一对一、一对多、多对多的交互通信
  1. 可靠性
  • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
  • UDP 是尽最大努力交付,不保证可靠交付数据。
  1. 拥塞控制、流量控制
  • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
  • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
  1. 头部长度不同
  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
  • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
  1. 分片不同
  • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
  • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

两个队列

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;服务端收到第一次握手后,会将sock加入到这个队列中,队列内的sock都处于SYN_RECV 状态。
  • 全连接队列,也称 accept 队列;在服务端收到第三次握手后,会将半连接队列的sock取出,放到全连接队列中。队列里的sock都处于 ESTABLISHED状态。这里面的连接,就等着服务端执行accept()后被取出了。

socket函数常见方法

image-20230416213623643

服务端方法:

  • socket,新建一个socket对象

    • 如果没有调用bind方法会,客户端对服务端发起了连接建立,服务端会回 RST 报文
  • bind,将 socket 绑定在指定的 IP 地址和端口;

    • 服务端如果只 bind 了 IP 地址和端口,而没有调用 listen 的话,然后客户端对服务端发起了连接建立,服务端会回 RST 报文。
  • listen,进行监听,服务器端处于listen状态

    • 每一个socket执行listen时,内核都会自动创建一个半连接队列和全连接队列。
  • accept,等待客户端连接;accept 成功返回是在三次握手成功之后

    • accept方法只是为了从全连接队列中拿出一条连接,本身跟三次握手几乎毫无关系

客户端

  • connect,向服务端的地址和端口发起连接请求;connect 成功返回是在第二次握手
    • 客户端调用connect函数主动构建连接

TCP三次握手

为什么需要三次握手

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)

在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费

  • 三次握手才可以同步双方的初始序列号

TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素。一来一回,才能确保双方的初始序列号能被可靠的同步。

  • 三次握手才可以避免资源浪费

如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

需要注意的是第三次握手是可以携带数据的,前两次握手是不可以携带数据的

image-20230509151944605

第一次握手丢失了,会发生什么?

  • 客户端会重传 SYN 报文,也就是第一次握手就会触发「超时重传」机制,重传 SYN 报文

第二次握手丢失了,会发生什么?

  • 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries内核参数决定;(由于没有收到ACK)
  • 服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。(由于没有收到ACK)

第三次握手丢失了,会发生什么?

  • 服务端会重传 SYN-ACK 报文,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文

既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?

那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传,经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。

TCP的ACK报文会重传吗

TCP协议中的ACK(Acknowledgment)报文通常不会重传。

ACK报文是TCP用于确认接收到数据的报文,用于确认对方发送的数据已经成功接收。在TCP的可靠传输机制中,发送方发送数据后,会等待接收方发送ACK报文进行确认。如果发送方在一定的超时时间内没有接收到ACK报文,则会认为数据没有成功送达,并进行重传(这里是对应报文不是ACK报文)。

在TCP协议中,数据的重传是由发送方负责的,而ACK报文的重传通常不会由接收方触发。一般情况下,接收方只需要发送ACK报文来确认已接收到数据,而不会因为没有接收到ACK报文而进行重传。接收方只有在需要通知发送方数据丢失或者需要进行流量控制等特殊情况下,才会发送特定的控制报文,如SACK(Selective Acknowledgment)报文或者窗口更新报文。

什么是 SYN 攻击?如何避免 SYN 攻击?

我们都知道 TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。

避免 SYN 攻击方式,可以有以下四种方法:

  • 调大 内核处理速度
  • 增大 TCP 半连接队列;
  • 开启 tcp_syncookies;可以将全连接队列利用起来
  • 减少 SYN+ACK 重传次数

TCP四次挥手

  • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
  • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手

这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。

第一次挥手丢失了,会发生什么?

  • 客户端:如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。
  • 服务端:后续会通过keepalive机制结束连接

第二次挥手丢失了,会发生什么?

  • 客户端:ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,等到达一定时间之后关闭
  • 服务端:服务端处于 close_wait 状态,直到超时关闭

第三次挥手丢失了,会发生什么?

  • 服务端:如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文

第四次挥手丢失了,会发生什么?

  • 服务端:如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文

为什么 TIME_WAIT 等待的时间是 2MSL?

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间

为什么需要 TIME_WAIT 状态?

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
  • 保证「被动关闭连接」的一方,能被正确的关闭;

等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。

TIME_WAIT 过多有什么危害?

  • 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
  • 第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为 32768~61000,也可以通过 net.ipv4.ip_local_port_range参数指定范围。

服务器出现大量 TIME_WAIT 状态的原因有哪些?

  • 第一个场景:HTTP 没有使用长连接
  • 第二个场景:HTTP 长连接超时
  • 第三个场景:HTTP 长连接的请求数量达到上限

服务器出现大量 CLOSE_WAIT 状态的原因有哪些?

当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。

四次挥手可以变成三次吗?

答案是可以

什么是 TCP 延迟确认机制?

当发送没有携带数据的 ACK,它的网络效率也是很低的,因为它也有 40 个字节的 IP 头 和 TCP 头,但却没有携带数据报文。 为了解决 ACK 传输效率低问题,所以就衍生出了 TCP 延迟确认。 TCP 延迟确认的策略:

  • 当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方
  • 当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送
  • 如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK

当被动关闭方在 TCP 挥手过程中,如果「没有数据要发送」,同时「没有开启 TCP_QUICKACK(默认情况就是没有开启,没有开启 TCP_QUICKACK,等于就是在使用 TCP 延迟确认机制)」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。

close和shutdown的区别

  • close 函数,同时 socket 关闭发送方向和读取方向,也就是 socket 不再有发送和接收数据的能力。如果有多进程/多线程共享同一个 socket,如果有一个进程调用了 close 关闭只是让 socket 引用计数 -1,并不会导致 socket 不可用,同时也不会发出 FIN 报文,其他进程还是可以正常读写该 socket,直到引用计数变为 0,才会发出 FIN 报文。
  • shutdown 函数,可以指定 socket 只关闭发送方向而不关闭读取方向,也就是 socket 不再有发送数据的能力,但是还是具有接收数据的能力。如果有多进程/多线程共享同一个 socket,shutdown 则不管引用计数,直接使得该 socket 不可用,然后发出 FIN 报文,如果有别的进程企图使用该 socket,将会受到影响。

TCP 的 Keepalive

TCP 的 Keepalive 这东西其实就是 TCP 的保活机制

如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文。

如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件(默认是两个小时),那么内核里的 TCP 协议栈就会发送探测报文。

  • 如果对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。
  • 如果对端主机崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡

如何解决TCP的粘包问题?

什么是粘包问题?

粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。

一般有三种方式分包的方式:

  • 固定长度的消息:这种是最简单方法,即每个用户消息都是固定长度的,比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。
  • 特殊字符作为边界:我们可以在两个用户消息之间插入一个特殊的字符串,这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息。
  • 自定义消息结构:我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。

从输入 URL 到页面展示到底发生了什么?

总体来说分为以下几个过程:

  1. DNS解析
  2. TCP连接
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析渲染页面
  6. 连接结束

OSI 七层模型

image-20230511103843026

TCP/IP 四层模型

TCP/IP 四层模型 是目前被广泛采用的一种模型,我们可以将 TCP / IP 模型看作是 OSI 七层模型的精简版本,由以下 4 层组成:

  1. 应用层
  2. 传输层
  3. 网络层
  4. 网络接口层

image-20230511103924803

每一层的封装格式:

image-20230511103937416

既然有 HTTP 协议,为什么还要有 WebSocket?

  • TCP 协议本身是全双工的,但我们最常用的 HTTP/1.1,虽然是基于 TCP 的协议,但它是半双工的,对于大部分需要服务器主动推送数据到客户端的场景,都不太友好,因此我们需要使用支持全双工的 WebSocket 协议。
  • nginx默认超时时间是60S,所以websocket要采取ping/pong方式保证连接的活性

websocket的长连接与http的keep-alive的区别

WebSocket 和 HTTP 协议都是基于 TCP 连接实现的,但是它们的长连接机制有一些区别。

HTTP 长连接通常使用 HTTP/1.1 中的 "keep-alive" 机制,在客户端和服务器之间完成一次请求/响应后,会继续使用已建立的 TCP 连接来发送和接收多个请求/响应。这种方式可以减少频繁地建立、断开 TCP 连接所带来的性能开销,从而提高网络传输效率。但是,使用 HTTP 长连接时,客户端和服务器之间的通信仍然需要按照请求-响应模式进行,且无法实现即时通信和实时数据传输。

WebSocket 的长连接是指在服务器和客户端之间建立一条持久的双向通信通道,该通道会一直保持打开状态,直到显示地关闭。这种连接不仅可以支持即时通信和实时数据传输,而且可以使得服务器可以主动向客户端推送消息,从而实现更高效的数据传输和通信。此外,WebSocket 还有一个心跳检测机制(即“ping/pong”),用于检测连接是否处于活动状态,并保证连接的稳定性。

综上所述,HTTP 的长连接主要是为了减少建立和断开连接所带来的性能开销,而 WebSocket 的长连接则是为了支持实时通信和实时数据传输,并保持连接的稳定性。此外,WebSocket 通过心跳检测机制可以更好地保护长连接,而 HTTP 长连接则没有这个机制。

HTTPS建立连接过程

TCP握手之后HTTPS需要四次握手建立连接。

1. ClientHello

首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。

在这一步,客户端主要向服务器发送以下信息:

(1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。

(2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。

(3)客户端支持的密码套件列表,如 RSA 加密算法。

2. SeverHello

服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:

(1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。

(2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。

(3)确认的密码套件列表,如 RSA 加密算法。

(4)服务器的数字证书。

3.客户端回应

客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。

如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:

(1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。

(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。

(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。

上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。

服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」

4. 服务器的最后回应

服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。

然后,向客户端发送最后的信息:

(1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。

(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。

至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

HTTP2的特点

  1. 优化stream

image-20230524110037552

  • 一个TCP连接包含多个stream
  • 每个stream都是一个双向数据流包含多个message
  • message请求或者响应,包含多个frame
  • frame是https最小帧格式,包含头frame和数据frame
  1. 压缩头部
  • 静态字典
  • 动态字典
  • 哈夫曼编码

HTTP 与 HTTPS 有哪些区别?

  • HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
  • HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
  • 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
  • HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

HTTP的发展历史

  1. HTTP/0.9:

    • 于1991年发布,是最早的HTTP协议版本。
    • 仅支持GET方法,没有Header、Body等概念。
    • 请求响应都是纯粹的文本格式。
  2. HTTP/1.0:

    • 于1996年发布,引入了许多现代HTTP的基本概念。
    • 支持多种请求方法(GET、POST、HEAD等)和响应状态码。
    • 引入了Header字段,使得请求和响应可以携带更多的信息。
    • 支持多字符集、多部分发送、持久连接等特性。
  3. HTTP/1.1:

    • 于1999年发布,是目前广泛使用的HTTP版本。
    • 引入了持久连接(Keep-Alive)机制,复用TCP连接以减少建立连接的开销。
    • 支持管道化(Pipeline)技术,允许客户端发送多个请求而无需等待响应。
    • 引入了缓存管理、断点续传、虚拟主机等增强功能。
    • 同时也有一些性能瓶颈和限制,如队头阻塞问题。

优化部分:

  1. 通过缓存技术来避免发送 HTTP 请求
  2. 减少 HTTP 请求的次数

    • 将原本由客户端处理的重定向请求,交给代理服务器处理
    • 将多个小资源合并成一个大资源再传输
    • 按需访问资源
  3. 通过压缩响应资源,降低传输资源的大小

  4. 支持持久连接

  5. HTTP/2:

    • 于2015年发布,基于Google的SPDY协议进行扩展。
    • 引入了二进制分帧层(Binary Framing Layer),将消息划分为多个帧,实现并发处理和优先级控制。
    • 支持头部压缩,减少数据传输量。
    • 强制使用加密传输(TLS),提高安全性。
    • 多路复用(Multiplexing)机制,允许在一个连接上同时发送多个请求和响应。
  6. HTTP/3:

    • 正在进行中的开发,基于QUIC协议。
    • QUIC是由Google开发的基于UDP的传输协议,旨在解决TCP的一些性能问题。
    • HTTP/3将在传输层使用QUIC,提供更低的延迟和更好的性能。
    • 增加了一些新特性,如0-RTT连接建立、流量控制、拥塞控制等。

什么是RPC协议

RPC(Remote Procedure Call)协议是一种用于实现分布式系统中不同计算机之间通信的协议。它允许一个计算机程序在网络上请求另一个计算机上的服务,就像调用本地函数一样,而不需要开发者显式处理网络通信细节。

在 RPC 中,客户端程序发起请求,称为远程调用(remote call),而服务器程序提供服务并响应请求。客户端和服务器之间的通信过程对于开发者来说是透明的,就好像调用本地函数一样。

一般来说,RPC 协议涉及以下几个要素:

  1. 客户端(Client):发起远程调用的程序。
  2. 服务器(Server):提供服务的程序。
  3. 通信协议(Protocol):客户端和服务器之间通信的规则和格式,定义了数据的传输方式和序列化协议。
  4. 接口定义(Interface Definition):客户端和服务器之间必须约定好接口和方法的定义,以便进行远程调用。
  5. 序列化(Serialization):将数据从对象的形式转换为字节流的过程,用于在网络上传输数据。

虽然大部分 RPC 协议底层使用 TCP,但实际上它们不一定非得使用 TCP,改用 UDP 或者 HTTP,其实也可以做到类似的功能。

RPC协议与HTTP协议的区别

服务发现

首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道 IP 地址和端口。这个找到服务对应的 IP 端口的过程,其实就是服务发现

HTTP 中,你知道服务的域名,就可以通过 DNS 服务去解析得到它背后的 IP 地址,默认 80 端口。

RPC 的话,就有些区别,一般会有专门的中间服务去保存服务名和IP信息,比如 ZK,甚至是 Redis。想要访问某个服务,就去这些中间服务去获得 IP 和端口信息。

底层连接形式

以主流的 HTTP/1.1 协议为例,其默认在建立底层 TCP 连接之后会一直保持这个连接(Keep Alive),之后的请求和响应都会复用这条连接。

RPC 协议,也跟 HTTP 类似,也是通过建立 TCP 长链接进行数据交互,但不同的地方在于,RPC 协议一般还会再建个连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。

connection_pool

由于连接池有利于提升网络请求性能,所以不少编程语言的网络库里都会给 HTTP 加个连接池,比如 Go 就是这么干的。

可以看出这一块两者也没太大区别,所以也不是关键

传输的内容

基于 TCP 传输的消息,说到底,无非都是消息头 Header 和消息体 Body。

Header 是用于标记一些特殊信息,其中最重要的是消息体长度

Body 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串,毕竟计算机只认识这玩意。所以 TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01 串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01 串,这样的方案现在也有很多现成的,比如 Json,Protobuf。

这个将结构体转为二进制数组的过程就叫序列化,反过来将二进制数组复原成结构体的过程叫反序列化

序列化和反序列化

对于主流的 HTTP/1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 Json序列化结构体数据。但是这里面的内容非常多的冗余,显得非常啰嗦

而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据,同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。

HTTP 499 问题处理方法合集

nginx 中的 src/http/ngx_http_special_response.c 文件中对 499 状态码进行了定义:

c复制代码    ngx_string(ngx_http_error_494_page), /* 494, request header too large */
    ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
    ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
    ngx_string(ngx_http_error_497_page), /* 497, http to https */
    ngx_string(ngx_http_error_404_page), /* 498, canceled */
    ngx_null_string,                     /* 499, client has closed connection */

从注释上,我们可以看到 499 表示客户端主动断开连接

表面上 499 是客户端主动断开,然而在实际业务开发中,当出现 HTTP 499 状态码时,大部分都是由于服务端请求时间过长,导致客户端等的“不耐烦”了,因此断开了连接。

1、服务端接口请求超时

在客户端请求服务端接口时,有些接口请求确实很慢。这种情况呢,就是接口是真的慢,不是偶然现象,是什么时候请求都慢,这个也最好解决,优化接口即可。

2、nginx 导致断开连接

就是连续两次过快的 post 请求就会出现 499 的情况,是 nginx 认为这是不安全的连接,主动断开了客户端的连接。

3、固定时间出现 499 问题

可能是机器这段时间执行定时任务之类的

4、偶尔出现一下 499 问题

MYSQL 会有将脏页数据刷到磁盘的操作,这个我们具体我们会有一片单独的文章介绍。在 MYSQL 执行刷脏页的时候,会占用系统资源,这个时候,我们的查询请求就有可能比平时慢了。

调用close函数用户态内核态分别发送了什么

用户态

  1. 程序调用 close() 方法:在用户态,应用程序发起了关闭套接字连接的请求。
  2. 发送关闭请求到内核:操作系统会将关闭请求传递到内核态,准备执行套接字的关闭操作。
  3. 用户态等待:在关闭操作完成之前,用户态可能会等待,因为关闭操作可能涉及到网络数据传输和其他底层操作。

内核态

  1. 内核接收关闭请求:内核接收到用户态发起的关闭请求。
  2. 进行数据传输:如果还有未发送的数据,内核会尝试将这些数据发送出去,确保对方接收到数据。
  3. 发送关闭通知:内核会向对方发送一个关闭通知,表示本端即将关闭连接。
  4. 等待关闭通知:内核等待对方发送关闭通知,以确保双方都已经准备好关闭连接。
  5. 执行关闭操作:当双方都准备好关闭连接时,内核执行实际的关闭操作,释放相关资源。
  6. 回收资源:内核回收连接所占用的内存、文件描述符等资源。