Contents

一张思维导图看网络【持续迭代】

概述

受到《计算机网络-自顶向下方法》 的启发,按照网络请求的过程,总结自己的网络知识,按照时间线,画了一张图,xmind 导出的图比较大,后续持续更新,迭代这部分的内容。

/img/net/net_proceed_process.png

下面是一些常见的知识,将会慢慢补充进思维导图内

输入 url 到页面展示,经历的过程

  1. url 解析,提取 host;
  2. DNS 解析
    1. 匹配浏览器的 DNS 的缓存;
    2. 匹配 Host 文件;
    3. 匹配系统的 DNS 缓存;
    4. 系统向本地 DNS 服务器发送请求;
      1. 联网时候,系统会自动发送 DHCP Request,路由器收到这个广播后,会向电脑分配一个地址,这个地址就是 DNS 服务器的地址;
        1. 可以通过抓包看;
        2. 通过设置中心;
        3. 通过生成的临时文件查看;
      2. DNS 通过 DNS 迭代查询 查询域名的 ip 地址;
      3. DNS 服务器返回多个地址组合,浏览器根据 DNS 轮转策略 选择一个 IP 地址进行请求;
    5. 这个过程在各个 socket 库中都有系统实现;
  3. 委托系统网络协议栈发送请求数据
    1. 创建 socket 套接字;
      1. 套接字是内存中的一块内存,其中保存了通信的控制信息;
      2. 一个套接字对应了一个端口,用以区分不同的 socket 套接字;
    2. 将 socket 连接到 server;三次握手
    3. 发送数据;
      1. 协议栈不关心应用程序传来的数据内容。在它看来,要发送的数据就是一段二进制字节序列而已。
      2. 协议栈并不会在收到数据后马上发出,而是需要判断几个要素后决定是否发出:
      3. 当协议栈缓冲区中存在的数据长度超过或接近 MSS 时会将包发出。
      4. 在收到一段数据后缓冲区会开启一个计时器,经过一定时间后仍未有新数据包产生则将缓冲区内容发出。
      5. 如果应用程序指定了“立刻发出”,那么协议栈就会按照要求直接发送数据。(一般像浏览器这种会话型的应用程序在向服务器发送数据时,等待填满缓冲区会导致延迟影响用户体验,因此一般会要求协议栈直接发出)
      6. MTU:一个网络包的最大长度,以太网中一般为1500字节。
      7. MSS:除去头部之后,一个网络包所能容纳的TCP数据的最大长度。
    4. 接收数据
      1. 首先,协议栈尝试从接收缓冲区中取出数据并传递给应用程序,但这个时候请求消息刚刚发送出去,响应消息可能还没返回。响应消息的返回还需要等待一段时间,因此这时接收缓冲区中并没有数据,那么接收数据的操作也就无法继续。这时,协议栈会将应用程序的委托,也就是从接收缓冲区中取出数据并传递给应用程序的工作暂时挂起,等服务器返回的响应消息到达之后再继续执行接收操作。
      2. 协议栈接收到数据会会检查收到的数据块和 TCP 头部的内容,判断包是否合法可用,如果没问题则返回 ACK 确认号。然后将数据块暂存到缓冲区中,并拼接数据还原原始数据。
      3. 数据包恢复原样后将会将数据复制到应用程序指定内存地址后将控制流程转交回应用程序。然后再寻找合适时机进行窗口更新。
    5. 断开连接;四次挥手
    6. 删除套接字

DNS

作用

域名解析,将网址中的域名解析成协议栈的 IP 地址。

过程

DNS 域名解析的过程蛮有意思的,整个过程就和我们日常生活中找人问路的过程类似,只指路不带路

https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP/33.jpg

浏览器首先看一下自己的缓存里有没有,如果没有就向操作系统的缓存要,还没有就检查本机域名解析文件 hosts,如果还是没有,就会 DNS 服务器进行查询,查询的过程如下:

  1. 客户端首先会发出一个 DNS 请求,问www.server.com 的 IP 是啥,并发给本地 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。
  2. 本地域名服务器收到客户端的请求后,如果缓存里的表格能找到www.server.com,则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器:“老大, 能告诉我www.server.com 的 IP 地址吗?” 根域名服务器是最高层次的,它不直接用于域名解析,但能指明一条道路。
  3. 根 DNS 收到来自本地 DNS 的请求后,发现后置是 .com,说:“www.server.com 这个域名归 .com 区域管理”,我给你 .com 顶级域名服务器地址给你,你去问问它吧。”
  4. 本地 DNS 收到顶级域名服务器的地址后,发起请求问“老二, 你能告诉我www.server.com的 IP 地址吗?”
  5. 顶级域名服务器说:“我给你负责www.server.com区域的权威 DNS 服务器的地址,你去问它应该能问到”。
  6. 本地 DNS 于是转向问权威 DNS 服务器:“老三,www.server.com对应的IP是啥呀?” server.com 的权威 DNS 服务器,它是域名解析结果的原出处。为啥叫权威呢?就是我的域名我做主。
  7. 权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。
  8. 本地 DNS 再将 IP 地址返回客户端,客户端和目标建立连接。

Http

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZdCwxNydn5YuT0s7aLuqWCvCl3iaCJeUV6Oa8zESpNKPDicgibjwANs465zibfWwwUQlMZsjciaNicO1Vwg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

  • HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。
  • HTTP 的名字「超文本传输协议」,它可以拆成三个部分:
    • 超文本
    • 传输
    • 协议

https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP/6-%E4%BA%94%E5%A4%A7%E7%B1%BBHTTP%E7%8A%B6%E6%80%81%E7%A0%81.png

HTTP 常见的 header

在 HTTP 中,header 是用于描述请求或响应的元数据,提供了有关请求或响应的信息。以下是一些常见的 HTTP 请求和响应头:

通用 Headers (适用于请求和响应):

  1. Cache-Control: 指示缓存策略,如 max-age、no-cache、no-store 等。
  2. Connection: 控制是否保持网络连接,值可以为 keep-alive 或 close。
  3. Content-Encoding: 表示已对实体内容使用的编码方法(例如 gzip)。
  4. Content-Length: 描述实体内容的数据大小(单位:字节)。
  5. Content-Type: 描述实体内容的 MIME 类型(如:application/json、text/html)。
  6. Date: 表示消息创建的时间。
  7. Pragma: 提供向后兼容性的缓存控制。
  8. Transfer-Encoding: 描述应用于消息主体的传输编码方式,如 chunked。

请求 Headers:

  1. Accept: 指示客户端可以处理的 MIME 类型。
  2. Accept-Charset: 指示客户端可以接受的字符集。
  3. Accept-Encoding: 指示客户端可以处理的内容编码。
  4. Accept-Language: 列出客户端可以理解的自然语言。
  5. Authorization: 提供了用于身份验证的凭据。
  6. Host: 指定请求的目标服务器和端口号。
  7. If-Modified-Since: 请求的资源在指定日期之后更新时才返回。
  8. If-None-Match: 指定资源的 ETag 与目标资源的 ETag 匹配时才返回。
  9. User-Agent: 描述客户端的软件信息。

响应 Headers:

  1. Allow: 指示支持的 HTTP 方法,如 GET、POST 等。
  2. Content-Disposition: 附加说明信息,如对下载文件的文件名进行描述。
  3. Content-Language: 实体内容使用的自然语言。
  4. ETag: 资源的版本标识,用于缓存验证。
  5. Expires: 指定资源过期的时间。
  6. Last-Modified: 指示资源最后一次被修改的时间。
  7. Location: 用于重定向,指向新的 URL。
  8. Server: 描述服务器软件信息。
  9. Set-Cookie: 设置 Cookie。
  10. WWW-Authenticate: 要求客户端进行身份验证。

这只是 HTTP header 的一部分,实际上有更多的 header 用于定制和控制 HTTP 请求和响应。每个应用程序和服务器可能会使用不同的 header。

keep-alive 的原理

keep-alive 机制:若开启后,在一次 http 请求中,服务器进行响应后,不再直接断开 TCP 连接,而是将 TCP 连接维持一段时间。在这段时间内,如果同一客户端再次向服务端发起 http 请求,便可以复用此 TCP 连接,向服务端发起请求,并重置 timeout 时间计数器,在接下来一段时间内还可以继续复用。这样无疑省略了反复创建和销毁 TCP 连接的损耗。

常见问题

GET 和 POST 有什么区别?

  • 根据 RFC 规范,GET 的语义是从服务器获取指定的资源,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)。
  • 根据 RFC 规范,POST 的语义是根据请求负荷(报文body)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body 中,body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。

GET 和 POST 方法都是安全和幂等的吗? 安全和幂等的概念:

  • 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
  • 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。

结论

  1. GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。
  2. POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。

但是,实际应用过程中,不一定按照 RFC 的规范实现。

Get 请求可以带 body 吗? RFC 并没有规定 Get 请求不可以带 body,看具体的网络库的实现。

HTTP的缓存实现方式

  • 强制缓存:只要缓存没过期,浏览器就适用缓存中的数据; 强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:

    1. Cache-Control, 是一个相对时间;
    2. Expires,是一个绝对时间;
  • 协商缓存: 协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。

强制缓存是基于时间实现的,协商缓存是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。

H1 和 H2 的区别联系

H1 的优缺点

优点

  1. 简单。header + body,header 还是 kv 形式;
  2. 灵活易扩展。https 就是 http + tls;http3 就是将 tcp 改成了 UDP 方案;
  3. 应用广泛。

缺点

  1. 无状态。无状态可以减轻资源消耗,但是关联操作时,会非常麻烦。针对这个问题,提出了 cookie 技术。
  2. 明文传输、不安全。可以方便阅读,为调试提供了便利性。但是,带来了信息泄露的风险。
  3. 【性能】通信开销大。http1.0 每个连接都需要进行三次握手,消耗很大。http1.1 提出了长连接通信。
  4. 【性能】队头阻塞。虽然可以采用管道的方式进行 HTTP 的请求,但是服务器还是需要按照请求的顺序对管道中的请求进行响应的。所以,如果不采用管道,那么请求和响应都有队头阻塞的问题,如果采用管道,那么可以解决请求的队头阻塞,但是响应的队头阻塞没办法解决。而且,管道默认是关闭的。(造成了队列是顺序请求的,前面的请求 block,就会影响后面的请求。
  5. 【效率】请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
  6. 【效率】发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
  7. 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是队头阻塞;
  8. 没有请求优先级控制;
  9. 请求只能从客户端开始,服务器只能被动响应。

H2 优化点

头部压缩

  • H1 是明文的,多个请求发送的 header 可能存在大量的重复,header 越多,越大,浪费越严重;
  • H2 通信的双方共同维护索引表,传输的 header 只需要发送索引表中的索引即可;

HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。 这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

  • 双方维护一张头信息表;
  • 各自只发送信息表中的索引号;

二进制格式

  • H1 是明文传输,传输的报文更大,增加了传输成本。
  • H2 改成了二进制格式,传输的报文更小,降低了传输的成本。

HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)

这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率。

比如状态码 200 ,在 HTTP/1.1 是用 ‘2’‘0’‘0’ 三个字符来表示(二进制:00110010 00110000 00110000),共用了 3 个字节。 在 HTTP/2 对于状态码 200 的二进制编码是 10001000,只用了 1 字节就能表示,相比于 HTTP/1.1 节省了 2 个字节。

并发传输

我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。

HTTP/2 就很牛逼了,引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。1 个 TCP 连接包含多个 Stream,Stream 里可以包含 1 个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成。Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体)。

针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应。 服务端并行交错地发送了两个响应: Stream 1 和 Stream 3,这两个 Stream 都是跑在一个 TCP 连接上,客户端收到后,会根据相同的 Stream ID 有序组装成 HTTP 消息。

服务器推送

HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。 客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。

H2 的缺点

  • H2 解决了 H1 的 HTTP 层面的队头阻塞;
  • 但是依旧面临 TCP 的队头阻塞; HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。

一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。

H3

HTTP/3 的核心原理是基于 UDP 而不是 TCP,它采用 QUIC 协议。QUIC 是 Google 开发的一种传输层网络协议,用于优化网络延迟,提高网络连接利用率。QUIC 协议相比 TCP 主要有以下几点改进:

  1. 基于 UDP。QUIC 协议基于 UDP 而不是 TCP,可以避开 TCP 的拥塞控制和流量管理机制,获得更低的延迟和更高的吞吐量。
  2. 连接迅速建立。QUIC 协议的连接可以在 1 个 RTT(往返时延)内完成,比 TCP 快很多。这可以加速 HTTP/3 的连接建立和资源请求。
  3. 多路复用。QUIC 支持在一个连接上并行传输多条数据流,并且每个数据流都有自己的流量控制,相互不影响。这进一步提高了连接的利用率。
  4. 无头部压缩。QUIC 协议本身不定义头部压缩机制,直接使用 HPACK 算法对 HTTP/3 的头部信息进行压缩。
  5. 重传机制。QUIC 协议定义了自己的重传和流量控制机制来最小化网络拥塞和包丢失的影响。
  6. 零延迟 resumed 的连接。QUIC 允许连接处于静默状态一段时间后快速恢复,而不需要像 TCP 那样重新完成三次握手。这可以进一步减少延迟。 总之,HTTP/3 基于 UDP 和 QUIC 协议,在减少延迟,提高网络利用率和连接迅速建立等方面有很大提高,这使得基于 HTTP/3 的应用可以获得很好的性能提升和用户体验。

quic

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZfrcYJokkKzRXdVXXK5PLNIaz06bQsqRN2ibNxIic9dg6EXabSIec2YcEc1u7lmw9cAgsTtedg2Kheg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

QUIC 有以下 3 个特点。

  • 无队头阻塞;
  • 更快的连接建立;
  • 连接迁移;

QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。

QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

QUIC 是新协议,对于很多网络设备,根本不知道什么是 QUIC,只会当做 UDP,这样会出现新的问题,因为有的网络设备是会丢掉 UDP 包的,而 QUIC 是基于 UDP 实现的,那么如果网络设备无法识别这个是 QUIC 包,那么就会当作 UDP包,然后被丢弃。

HTTP/3 现在普及的进度非常的缓慢,不知道未来 UDP 是否能够逆袭 TCP。

Https

区别

  1. HTTP 是明文传输,HTTPS 是密文传输。
  2. HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
  3. 二者端口不一样,80 和 443 端口;
  4. https 还需要向 CA 申请证书;

解决的问题

  1. 窃听风险;
  2. 篡改风险;
  3. 冒充风险;

通过

  1. 信息加密;
  2. 校验机制;
  3. 身份证书;

安全保证

  1. 混合加密,防止被窃听;
  2. 摘要算法,防止被篡改;
  3. 数字证书,防止被伪造;

混合加密

  • 在通信建立前采用非对称加密的方式交换会话秘钥,后续就不再使用非对称加密。
  • 在通信过程中全部使用对称加密的会话秘钥的方式加密明文数据。

采用「混合加密」的方式的原因: - 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。 - 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。

摘要算法+数字签名

  • 用摘要算法(哈希函数)来计算出内容的哈希值,也就是内容的「指纹」,这个哈希值是唯一的,且无法通过哈希值推导出内容,指纹和内容一起发送给接收方。
  • 对方收到后,先是对内容也计算出一个「指纹」,然后跟发送方发送的「指纹」做一个比较,如果「指纹」相同,说明内容没有被篡改,否则就可以判断出内容被篡改了。

通过哈希算法可以确保内容不会被篡改,但是并不能保证「内容 + 哈希值」不会被中间人替换,因为这里缺少对客户端收到的消息是否来源于服务端的证明。

那为了避免这种情况,计算机里会用非对称加密算法来解决,共有两个密钥:

  • 一个是公钥,这个是可以公开给所有人的;
  • 一个是私钥,这个必须由本人管理,不可泄露。 这两个密钥可以双向加解密的,比如可以用公钥加密内容,然后用私钥解密,也可以用私钥加密内容,公钥解密内容。流程的不同,意味着目的也不相同:
  • 公钥加密,私钥解密。这个目的是为了保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容;
  • 私钥加密,公钥解密。这个目的是为了保证消息不会被冒充,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。

所以非对称加密的用途主要在于通过「私钥加密,公钥解密」的方式,来确认消息的身份,我们常说的数字签名算法,就是用的是这种方式,不过私钥加密内容不是内容本身,而是对内容的哈希值加密。

数字证书 由 CA 签发的证书,通信的时候,不仅会发送签名、公钥,还会发送证书。接收方会去 CA 校验证书的合法性。

https 连接建立过程

https://mmbiz.qpic.cn/mmbiz_jpg/J0g14CUwaZfrcYJokkKzRXdVXXK5PLNIVphk23zaVhxunUt5LfjuBuDvBOJptYVzsyKZWZQR2EU6x9I3jn4xSg/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1

SSL/TLS 协议基本流程:

  1. 客户端向服务器索要并验证服务器的公钥。
  2. 双方协商生产「会话秘钥」。
  3. 双方采用「会话秘钥」进行加密通信。

前两步也就是 SSL/TLS 的建立过程,也就是 TLS 握手阶段。

TLS 协议建立的详细流程:

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 协议,只不过用「会话秘钥」加密内容。

总结下

  1. TLS 三次握手;
  2. 身份验证 + 预生成密钥;
  3. 确认会话密钥;
  4. 用 HTTP 协议传输加密的数据;

TLS 的目的就在于获取那个会话密钥

证书校验

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZfrcYJokkKzRXdVXXK5PLNINE4QmP73SeJ88QotjoF6LqVsXFANSNU3qZBUzlibCMXS4hzM4w02UzA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1 CA 签发证书的过程,如上图左边部分:

  • 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值;
  • 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
  • 最后将 Certificate Signature 添加在文件证书上,形成数字证书;

客户端校验服务端的数字证书的过程,如上图右边部分:

  • 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;
  • 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密 Certificate Signature 内容,得到一个 Hash 值 H2 ;
  • 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。

多级证书 向上溯源到最后的根证书,现校验根证书,再向下一级一级校验。

为何不全由根证书签名 应该是为了分摊风险。

HTTPS 如何保证数据的完整性

TLS 在实现上分为握手协议记录协议两层:

  • TLS 握手协议就是我们前面说的 TLS 四次握手的过程,负责协商加密算法和生成对称密钥,后续用此密钥来保护应用程序数据(即 HTTP 数据);
  • TLS 记录协议负责保护应用程序数据并验证其完整性和来源,所以对 HTTP 数据加密是使用记录协议;

TLS 记录协议主要负责消息(HTTP 数据)的压缩,加密及数据的认证https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZfrcYJokkKzRXdVXXK5PLNIw1Ifnx7W3grVsJubEfMiaj4gpqhPuwTghUiak8lxJY9omu1pYsSVGLUQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

具体过程如下:

  • 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩。
  • 接下来,经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),这是为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。
  • 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密码进行加密。
  • 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。

记录协议完成后,最终的报文数据将传递到传输控制协议 (TCP) 层进行传输。

HTTPS 安全吗?

目前 https 是安全的,除非是用户 client 端,自己接受了中间人的证书。协议自身是安全的。 为何抓包工具可以 很多抓包工具 之所以可以明文看到 HTTPS 数据,工作原理与中间人一致的。

对于 HTTPS 连接来说,中间人要满足以下两点,才能实现真正的明文代理:

  1. 中间人,作为客户端与真实服务端建立连接这一步不会有问题,因为服务端不会校验客户端的身份;
  2. 中间人,作为服务端与真实客户端建立连接,这里会有客户端信任服务端的问题,也就是服务端必须有对应域名的私钥; 中间人要拿到私钥只能通过如下方式:
  3. 去网站服务端拿到私钥;
  4. 去CA处拿域名签发私钥;
  5. 自己签发证书,切要被浏览器信任;

不用解释,抓包工具只能使用第三种方式取得中间人的身份。 使用抓包工具进行 HTTPS 抓包的时候,需要在客户端安装 Fiddler 的根证书,这里实际上起认证中心(CA)的作用。 抓包工具能够抓包的关键是客户端会往系统受信任的根证书列表中导入抓包工具生成的证书,而这个证书会被浏览器信任,也就是抓包工具给自己创建了一个认证中心 CA,客户端拿着中间人签发的证书去中间人自己的 CA 去认证,当然认为这个证书是有效的。

可靠连接

  1. 数据不会损坏;
  2. 数据按照顺序传输交付;

TCP 可靠性由三个机制保证:1. 序号(TCP 报文的序号)2. 确认(ACK 机制)3. 重传(超时或者冗余的 ACK)

TCP

/img/net/tcp_open_close.png

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZdCwxNydn5YuT0s7aLuqWCvWT9m8xicZXKk6ayV6nKAiaUAhdpdicfibLGEYhHx9OBo7EocXKx8wgIgww/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

滑动窗口、慢启动

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

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

TCP 和 UDP 的区别联系

  1. 连接
  • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
  • UDP 是不需要连接,即刻传输数据。
  1. 服务对象
  • TCP 是一对一的两点服务,即一条连接只有两个端点。
  • UDP 支持一对一、一对多、多对多的交互通信
  1. 可靠性
  • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
  • UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议。

TCP 可靠性由三个机制保证:1. 序号(TCP 报文的序号)2. 确认(ACK 机制)3. 重传(超时或者冗余的 ACK)

  1. 拥塞控制、流量控制
  • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
  • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
  1. 首部开销
  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
  • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。 https://skminhaj.files.wordpress.com/2016/02/92926-tcp_udp_headers.jpg
  1. 传输方式
  • TCP 是流式传输,没有边界,但保证顺序和可靠。
  • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
  1. 分片不同
  • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
  • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

https://mp.weixin.qq.com/s/ZMV2izeYkBIqjPhsv_-wdw MTU 最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。是包或帧的最大长度,一般以字节记。如果MTU过大,在碰到路由器时会被拒绝转发,因为它不能处理过大的包。MSS的英文全称叫Max Segment Size,是TCP最大段大小。在建立TCP连接的同时,也可以确定发送数据包的单位,我们称为MSS,这个MSS正好是IP中不会被分片处理的最大数据长度。

总结:

TCP 是面向连接的、可靠的、有序的、速度慢的协议;UDP 是无连接的、不可靠的、无序的、速度快的协议。
TCP 开销比 UDP 大,TCP 头部需要 20 字节,UDP 头部只要 8 个字节。
TCP 无界有拥塞控制,UDP 有界无拥塞控制。

应用场景的区别 由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输;
  • HTTP / HTTPS;

由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNS 、SNMP 等;
  • 视频、音频等多媒体通信;
  • 广播通信;

为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢? 原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。

为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?

TCP Data的长度= IP总长度 - IP Header长度 - TCP Header长度。 UDP Data的长度= IP总长度 - IP Header长度 - UDP Header长度。

所以 UDP 的长度是多余的。有可能是UDP和IP协议并行发展的结果。或者说,UDP并不是等IP完全占领主导地位之后才开发的协议。

TCP 和 UDP 可以使用同一个端口吗? 可以的。 在数据链路层中,通过 MAC 地址来寻找局域网中的主机。在网际层中,通过 IP 地址来寻找网络中互连的主机或路由器。在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。 所以,传输层的「端口号」的作用,是为了区分同一个主机上不同应用程序的数据包。 传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。 当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。 https://cdn.xiaolincoding.com/gh/xiaolincoder/network/port/tcp%E5%92%8Cudp%E6%A8%A1%E5%9D%97.jpeg

建连

简单来说就是,用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号和窗口大小称为连接

TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

一个服务端在监听一个端口,那它能持有的连接数量? 因为连接是个四元组,server 的 ip 和端口号已经固定,但是客户端的 ip 以及 端口号是可以变的, 那么数量的公式为 : 客户端的 ip 数量 * 端口数量; 对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。

当然,服务端最大并发 TCP 连接数远不能达到理论上限,会受以下因素影响:

  • 文件描述符限制,每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:
    • 系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看;
    • 用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看;
    • 进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看;
  • 内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM。

建连的过程,以及对应的状态 这个所谓的「连接」,只是双方计算机里维护一个状态机,在连接建立的过程中,双方的状态变化时序图就像这样。 https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4/%E7%BD%91%E7%BB%9C/TCP%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B.drawio.png

从上图可以看出,前两次握手是不可以携带数据的,第三次是可以携带数据的。第三次握手可以包含数据,通过 TCP 快速打开(TFO)技术。其实只要涉及到握手的协议,都可以使用类似 TFO 的方式,客户端和服务端存储相同 cookie,下次握手时发出 cookie 达到减少 RTT 的目的。

握手是三次的原因 三次握手的目的:同步 Sequence 序列号;交换 TCP 通讯参数,比如 MSS、窗口比例因子、选择性确认、指定校验和算法等。

  • 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
  • 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

三次握手才能保证双方具有接收和发送的能力。体现在以下几个方面。

  1. 【最重要的原因】防止历史连接触发初始化造成混乱;
    1. 我们考虑一个场景,客户端先发送了 SYN(seq = 90)报文,然后客户端宕机了,而且这个 SYN 报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = 100)报文(注意!不是重传 SYN,重传的 SYN 的序列号是一样的)。
    2. 在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
    3. 三次握手为何可以解决这个问题?客户端连续发送多次 SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:
      1. 一个「旧 SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
      2. 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文。
      3. 服务端收到 RST 报文后,就会释放连接。
      4. 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
  2. 同步双方初始序列号
    1. TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:
      1. 接收方可以去除重复的数据;
      2. 接收方可以根据数据包的序列号按序接收;
      3. 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过 ACK 报文中的序列号知道);
    2. 可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
  3. 避免资源浪费
    1. 如果只有「两次握手」,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

为何要求初始化的序列号都不一样

起始 ISN 是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时。 RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。

  • M 是一个计时器,这个计时器每隔 4 微秒加 1。
  • F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。

可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。

  • 【主要原因】为了防止历史报文被下一个相同四元组的连接接收;
    • 客户端和服务端建立一个 TCP 连接,在客户端发送数据包被网络阻塞了,然后超时重传了这个数据包,而此时服务端设备断电重启了,之前与客户端建立的连接就消失了,于是在收到客户端的数据包的时候就会发送 RST 报文。
    • 紧接着,客户端又与服务端建立了与上一个连接相同四元组的连接;
    • 在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了服务端,刚好该数据包的序列号正好是在服务端的接收窗口内,所以该数据包会被服务端正常接收,就会造成数据错乱。
    • 如果每次建立连接客户端和服务端的初始化序列号都「不一样」,就有大概率因为历史报文的序列号「不在」对方接收窗口,从而很大程度上避免了历史报文。
    • 所以,每次初始化序列号不一样很大程度上能够避免历史报文被下一个相同四元组的连接接收,注意是很大程度上,并不是完全避免了(因为序列号会有回绕的问题,所以需要用时间戳的机制来判断历史报文)
  • 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;

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

  • MTU:一个网络包的最大长度,以太网中一般为 1500 字节;
  • MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
  1. 如果没有 MSS,则交由 ip 层进行分片处理,则效率很低丢包后,重传都是以 IP 包(MTU)为维度进行重传;
    1. 当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后,由目标主机的 IP 层来进行重新组装后,再交给上一层 TCP 传输层。那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。
  2. 反之,丢包后,重传的维度是以 TCP 包,MSS 大小为维度的;
    1. 经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。

第一次握手丢失了,会发生什么? 在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。 当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。

第二次握手丢失了,会发生什么? 第二次握手的 SYN-ACK 报文其实有两个目的 :

  • 第二次握手里的 ACK, 是对第一次握手的确认报文;
  • 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文; 当第二次握手丢失了,客户端和服务端都会重传:
  • 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries内核参数决定;
  • 服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。

具体过程:

  • 当客户端超时重传 1 次 SYN 报文后,由于 tcp_syn_retries 为 1,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。
  • 当服务端超时重传 2 次 SYN-ACK 报文后,由于 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。

第三次握手丢失了,会发生什么? 客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。

注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文

具体过程: https://cdn.xiaolincoding.com/gh/xiaolincoder/network/tcp/%E7%AC%AC%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E4%B8%A2%E5%A4%B1.drawio.png

  • 当服务端超时重传 2 次 SYN-ACK 报文后,由于 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。

什么是 SYN 攻击?如何避免 SYN 攻击? 在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;

  • 全连接队列,也称 accept 队列; https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230622886.png 正常流程:

  • 当服务端接收到客户端的 SYN 报文时,会创建一个半连接的对象,然后将其加入到内核的「 SYN 队列」;

  • 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;

  • 服务端接收到 ACK 报文后,从「 SYN 队列」取出一个半连接对象,然后创建一个新的连接对象放入到「 Accept 队列」;

  • 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出连接对象。

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

  • 调大 netdev_max_backlog;
  • 增大 TCP 半连接队列;
  • 开启 tcp_syncookies;
  • 减少 SYN+ACK 重传次数

断连

https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230614791.png

  • 客户端打算关闭连接,此时会发送一个 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 状态。

为什么需要四次 因为 TCP 是全双工的,两个方向的关闭是独立确认的;

再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。 从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手。

第一次挥手丢失了,会发生什么 https://cdn.xiaolincoding.com/gh/xiaolincoder/network/tcp/%E7%AC%AC%E4%B8%80%E6%AC%A1%E6%8C%A5%E6%89%8B%E4%B8%A2%E5%A4%B1.png

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2状态。

如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。

第二次挥手丢失了,会发生什么? https://cdn.xiaolincoding.com/gh/xiaolincoder/network/tcp/%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%8C%A5%E6%89%8B%E4%B8%A2%E5%A4%B1.png

  • ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
  • 当客户端超时重传 2 次 FIN 报文后,由于 tcp_orphan_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次挥手(ACK 报文),那么客户端就会断开连接。

这个过程和丢失一次的挥手的效果基本类似。

第三次挥手丢失了,会发生什么? https://cdn.xiaolincoding.com/gh/xiaolincoder/network/tcp/%E7%AC%AC%E4%B8%89%E6%AC%A1%E6%8C%A5%E6%89%8B%E4%B8%A2%E5%A4%B1.drawio.png

具体过程:

  • 当服务端重传第三次挥手报文的次数达到了 3 次后,由于 tcp_orphan_retries 为 3,达到了重传最大次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。
  • 客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。

第四次挥手丢失,会发生什么? 重点在于,time-wait 的计时器会被重置,time wait 的时间会被延长。

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。 在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。 如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。

https://cdn.xiaolincoding.com/gh/xiaolincoder/network/tcp/%E7%AC%AC%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%E4%B8%A2%E5%A4%B1drawio.drawio.png 具体过程:

  • 当服务端重传第三次挥手报文达到 2 时,由于 tcp_orphan_retries 为 2, 达到了最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK 报文),那么服务端就会断开连接。
  • 客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。

为什么需要 time-wait 状态 存在TIME_WAIT状态有两个理由:

  • 实现终止TCP全双工连接的可靠性; 假设最终的ACK丢丢失,服务器将重发最终的FIN,因此客户必须维护状态信息以允许它重发最终的ACK。如果不维护状态信息,它将响应以 RST(另外一个类型的TCP分节),而服务器则把该分节解释成一个错误。如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流(即全双工关闭),那么它必须正确处理连接终止序列四个分节中任何一个分节的丢失情况。本例子也说明执行主动关闭的一端为何进入TIME_WAIT状态.因为它可能不得不重发最终的ACK。

  • 允许老的重复分节在网络中消逝; 要理解存在TIME_WAIT状态的第二个理由,我们假设206.62.226.33端口1500和 198.69.10.2端口21之间有一个TCP连接。我们关闭这个连接后,在以后某个时候又重新建立起相同的IP地址和端口之间的TCP连接。后一个连接称为前一个连接的化身(incar-nation),因为它们的IP地址和端口号都相同。TCP必须防止来自某个连接的老重复分组在连接终止后再现,从而被误解成属于同一连接的化身。要实现这种功能,TCP不能给处于TIME_WAIT状态的连接启动新的化身。既然TIME_WAIT状态的持续时间是2MLS,这就足够让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃。通过实施这个规则,我们就能保证当成功建立一个TCP连接时,来自该连接先前化身的老重复分组都已在网络中消逝。

过多 time-wait 的危害

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

大量 TIME_WAIT 状态的原因有哪些?

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

为什么 time_wait 状态为何是 2msl

等待 2MSL 的真正目的是为了避免前后两个使用相同四元组的连接中的前一个连接的报文干扰后一个连接,换句话说,就是为了让此次 TCP 连接中的所有报文在网络中消失。 假如现在 A 发送 ACK 后,最坏情况下,这个 ACK 在 1MSL 时到达 B;此时 B 在收到这个 ACK 的前一刹那,一直在重传 FIN,这个 FIN 最坏会在 1MSL 时间内消失。因此从 A 发送 ACK 的那一刹那开始,等待 2MSL 可以保证 A 发送的最后一个 ACK,和 B 发送的最后一个 FIN 都在网络中消失

这样可以保证 fin 不会造成对后续的新的四元组的数据伤害。

服务器出现大量 CLOSE_WAIT 状态的原因有哪些? 我们先来分析一个普通的 TCP 服务端的流程:

  1. 创建服务端 socket,bind 绑定端口、listen 监听端口
  2. 将服务端 socket 注册到 epoll
  3. epoll_wait 等待连接到来,连接到来时,调用 accpet 获取已连接的 socket
  4. 将已连接的 socket 注册到 epoll
  5. epoll_wait 等待事件发生
  6. 对方连接关闭时,我方调用 close
  • 第一个原因:第 2 步没有做,没有将服务端 socket 注册到 epoll,这样有新连接到来时,服务端没办法感知这个事件,也就无法获取到已连接的 socket,那服务端自然就没机会对 socket 调用 close 函数了。
  • 第二个原因:第 3 步没有做,有新连接到来时没有调用 accpet 获取该连接的 socket,导致当有大量的客户端主动断开了连接,而服务端没机会对这些 socket 调用 close 函数,从而导致服务端出现大量 CLOSE_WAIT 状态的连接。
  • 第三个原因:第 4 步没有做,通过 accpet 获取已连接的 socket 后,没有将其注册到 epoll,导致后续收到 FIN 报文的时候,服务端没办法感知这个事件,那服务端就没机会调用 close 函数了。
  • 第四个原因:第 6 步没有做,当发现客户端关闭连接后,服务端没有执行 close 函数,可能是因为代码漏处理,或者是在执行 close 函数之前,代码卡在某一个逻辑,比如发生死锁等等。

TCP 保活机制

如果已经建立了连接,但是客户端突然出现故障了怎么办? 客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。发生这种情况的时候,如果服务端一直不会发送数据给客户端,那么服务端是永远无法感知到客户端宕机这个事件的,也就是服务端的 TCP 连接将一直处于 ESTABLISH 状态,占用着系统资源。 为了避免这种情况,TCP 搞了个保活机制。这个机制的原理是这样的: 定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。 在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

1
2
3
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
  • tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
  • tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

如果已经建立了连接,但是服务端的进程崩溃会发生什么? TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。

重传机制

常见的重传机制:

  • 超时重传
  • 快速重传
  • SACK
  • D-SACK

超时重传

需要超时重传的情况

  • 数据包丢失;
  • 确认的应答丢失;

几个专业术语

  • RTT 发送信号所花费的时间加上确认已接收该信号所花费的时间,也就是包的往返时间。
  • RTO - Retransmission TimeOut 称为重传的超时时间。

RTO 的选择

  • RTO 较大时,重发变慢,效率低,性能差;
  • RTO 较小时,重发很快,频率很高,网络拥塞,浪费带宽,效率低;

理想的 RTO:比 RTT 略大;但是 RTT 是经常波动的,是一个动态变化的值;

  • 如果产生超时重传,TCP 会将 RTO 加倍。

存在的问题 和时间相关,超时的周期可能相对较长。

快速重传:摆脱时间因素

快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。

快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

存在的问题 快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。

SACK:selective acknowledge

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。 如果要支持 SACK,必须双方都要支持。在 Linux 下,可以通过 net.ipv4.tcp_sack 参数打开这个功能(Linux 2.4 后默认打开)。 https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhg3RbEZItSOQY1TadJpUTuibIDziaibALaEmk7JO0Ill7iaeiaGI94Wyia0NXA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

DSACK

Duplicate SACK 又称 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。

两个很像

  • ack 大于 sack:有重复的包被发送;
  • ack 小于 sack:有丢失的包;

滑动窗口

为什么引入 如果以数据包为维度进行发送和确认应答,那么效率会很低,RTT 越长,效率越低。 故而 TCP 引入了窗口的概念。

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

如何生效:累计确认 https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgz9LqxXsGESpzbick5b29No4rkqEubmVxexM2pM5DASr53mk65Qx5icZA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通话下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。

窗口大小 那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。所以,通常窗口的大小是由接收方的决定的

TCP 头里有一个字段叫 Window,也就是窗口大小。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

发送方的滑动窗口 https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgesUOXicVCTx6j4WPOa8heRQc3aPPwWDAIMnUJocXjmABL8JSjnkyenw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

当收到 32-45 中的 ack,则滑动窗口向右移动对应大小的窗口距离。

发送方程序中的窗口 https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhge0ujfyrkBBMicd4U4qiaubDemmA9BhjDalRicd1cxrAkBQqlBQSL1oGzg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

  • SND.WND:表示发送窗口的大小(大小是由接收方指定的);
  • SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。
  • SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。
  • 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

那么可用窗口大小的计算就可以是:可用窗口大 = SND.WND -(SND.NXT - SND.UNA)

接收方的滑动窗口 这部分的内容存疑,涉及累计确认信息。个人认为这里合理的说法应该是累计确认的窗口。 /img/netreceive_window.png 关于窗口滑动的几个术语

  1. 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时。
  2. 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发 生在另一端的接收进程读取已经确认的数据并释放了 T C P 的接收缓存时。
  3. 当右边沿向左移动时,我们称之为窗口收缩。 Host Requirements RFC强烈建议不要使 用这种方式。但 T C P 必须能够在某一端产生这种情况时进行处理。第 2 2 . 3 节 给 出 了 这 样 的 一 个 例子,一端希望向左移动右边沿来收缩窗口,但没能够这样做。

拥塞控制

  • 参考连接1 TCP 还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。不能改变世界,就改变自己嘛。

为什么需要 在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大….

前面的流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。流量控制是控制的量,拥塞控制,是控制的速度。

所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。

控制的目的就是避免「发送方」的数据填满整个网络。

拥塞窗口? 拥塞窗口 cwnd 是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的,用以调节发送方要发送的量。

拥塞窗口与发送窗口的关系 由于入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;
  • 但网络中出现了拥塞,cwnd 就减少

如何识别拥塞 其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了用拥塞。

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?

慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,就拥塞窗口 cwnd 的大小就会加 1。

虽然叫慢启动,但是发包的个数是指数性地增长。

慢启动门限 ssthresh

  • 当 cwnd < ssthresh 时,使用慢启动算法。
  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」

一般来说 ssthresh 的大小是 65535 字节。

拥塞避免

那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。 拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。

发生拥塞如何是好?

拥塞窗口一直增长的话,下个阶段就可能触发拥塞,出现丢包现象,这时候就会进行数据包的重传。 重传的机制有两个

  1. 超时重传;
  2. 快速重传;
拥塞发生算法 - 超时重传对应的算法

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgDicBugvroe9EtiaFU38hk4JuVfDciauVPfecBNp8TPI1zkoqbibePA4dlg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

sshresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2 ;
  • cwnd 重置为 1 ; 接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。
快速恢复算法 - 快速重传对应的算法

TCP 认为你还能收到 3 个重复 ACK 说明网络也不那么糟糕,这种情况不严重,因为大部分没丢,只丢了一小部分,所以没有必要像 RTO 超时那么强烈,则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;

快速重传和快速恢复算法一般同时使用。

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgR3w50EdpWF95ZM6QPpELCF3P1niazia8nBrSQUvX7e7F7LXMiaXR3iayUA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

  • 也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。
  • 从当前的一半位置开始线性增长尝试,但是最高不超过当前拥塞的状态。

流量控制

TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。 如果无脑发送数据,接收方处理不及时,就会触发重传机制,浪费带宽,降低效率,同时性能也降低。

TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgrEtDiblwiaNET0Dx9BMfqyVXY6Kc00ib4BD6l1yyPEZrb2ptxV31VSmFw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

系统缓冲区和滑动窗口的关系 两个例子

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgEnFtyFXUGUib92pibiacHuqQDAhbnC4AcVuMiaLs50TjjW0nbyrSnLs9Rg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgoTmpebyoD5Qa0cbibkicaGibzdicpo4jibwkLR0L77fyrXyelL5nkZ40ibBA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

  • 系统的缓存大小决定了发送和接收窗口的大小;
  • 为了防止减少缓存和收缩窗口同时进行造成的丢包问题,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间在减少缓存,这样就可以避免了丢包情况。

窗口关闭 如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。 接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。 当窗口关闭后,接收方一直不发送 ack,更新滑动窗口,将出现死锁现象。

为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

窗口探查探测的次数一般为 3 此次,每次次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZeuicRMlA8rKvl5AVLibhibDhgcJTR8cgbbGy6iaWedts5nVE5bk1X0SsY8FibxpPyCgwvfcxyEX4la1Yw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

糊涂窗口综合症 如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。 当发送程序缓慢地生产数据,接收程序缓慢地消耗数据,或者两者同时存在时,滑动窗口运作会出现严重问题。 处理每个数据包都存在一定量的开销,数据包数增加会增加网络通信的开销,可能使数据处理量进一步减少。最终的结果就是抖动。

要解决糊涂窗口综合症

  • 让接收方不通告小窗口给发送方;

  • 让发送方避免发送小数据;

  • 怎么让接收方不通告小窗口呢? 当「窗口大小」小于 min( MSS,缓存空间/2 ) ,也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。

  • 怎么让发送方避免发送小数据呢? 使用 Nagle 算法,该算法的思路是延时处理,它满足以下两个条件中的一条才可以发送数据:

  1. 要等到窗口大小 >= MSS 或是 数据大小 >= MSS;
  2. 收到之前发送数据的 ack 回包 只要没满足上面条件中的一条,发送方一直在囤积数据,直到满足上面的发送条件。

另外,Nagle 算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet 或 ssh 这样的交互性比较强的程序,则需要关闭 Nagle 算法。 可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭)

TCP socket 编程

https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230545997.png

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。 所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

listen 时候参数 backlog 的意义?

https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230542373.png

Linux内核中会维护两个队列:

  • 半连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态;
  • 全连接队列(Accpet 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态;

accept 发生在三次握手的哪一步? https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4/%E7%BD%91%E7%BB%9C/socket%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B.drawio.png

客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

客户端调用 close 了,连接是断开的流程是什么?

  • 客户端调用 close,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
  • 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
  • 接着,当处理完数据后,自然就会读到 EOF,于是也调用 close 关闭它的套接字,这会使得服务端发出一个 FIN 包,之后处于 LAST_ACK 状态;
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
  • 客户端经过 2MSL 时间之后,也进入 CLOSE 状态;

Google BBR

这一节的内容是来自于 chatGPT 和 new bing 的问答;

BBR算法的核心原理是根据网络的带宽、RTT(Round Trip Time,往返延迟时间)等特性,不断地调整发送数据包的速率和数量,以达到最佳的网络性能。具体来说,BBR算法的实现包括以下几个关键步骤

  1. 测量网络带宽:BBR算法通过发送一系列数据包,并测量其发送时间和到达时间,计算出当前网络的带宽情况。
  2. 测量网络延迟:BBR算法通过测量数据包往返的时间,计算出当前网络的RTT。
  3. 确定发送速率:根据当前的带宽和延迟情况,BBR算法会自动调整数据包的发送速率和数量,以达到最佳的网络性能。
  4. 动态调整拥塞窗口:BBR算法通过动态调整拥塞窗口大小,以确保网络的带宽和延迟在最佳状态下。

Google BBR(Bottleneck Bandwidth and Round-trip propagation time)是一种新型的拥塞控制算法,与传统的拥塞控制和流量控制有以下改进的点:

  1. 更精确的拥塞控制:传统的拥塞控制算法主要依赖于丢包来判断网络拥塞情况,而BBR则通过估算网络带宽和RTT来动态调整发送窗口大小,从而更准确地控制网络拥塞。
  2. 更高的网络利用率:传统的拥塞控制算法通常会导致网络利用率不足,而BBR则通过更准确的拥塞控制,能够更有效地利用网络带宽,从而提高网络吞吐量和响应速度。
  3. 更好的抗拥塞能力:BBR通过对网络拥塞的精确控制,能够在网络拥塞时自适应地减小发送速率,从而避免网络拥塞恶化。
  4. 更低的延迟和抖动:BBR通过准确地测量RTT和带宽,能够更精确地控制数据包的发送时间,从而减少网络延迟和抖动。 总之,BBR相较于传统的拥塞控制和流量控制,能够更准确地控制网络拥塞,更高效地利用网络带宽,具有更好的抗拥塞能力和更低的网络延迟和抖动。

缺点

  1. 对于某些网络环境不适用:BBR算法主要适用于高速长距离网络,对于较短距离、低速网络,BBR算法可能不如其他算法表现优秀。
  2. 对于UDP流量不友好:BBR算法主要针对TCP流量进行优化,对于UDP流量,BBR算法可能会表现不如其他算法。
  3. 可能引起其他TCP流量拥塞:在某些情况下,BBR算法可能会过于占用网络带宽,导致其他TCP流量拥塞,从而影响整个网络的性能。
  4. 需要更高的CPU计算能力:BBR算法的实现需要更高的CPU计算能力,因此在某些设备上可能会导致性能下降或延迟增加。

适用场景 Google BBR算法主要适用于高速、长距离网络环境,例如数据中心、云计算、视频直播等网络场景。在这些场景中,网络拥塞控制是一个非常关键的问题,因为网络拥塞会导致丢包、延迟增加、吞吐量下降等问题,从而影响网络的性能和用户体验。

BBR算法具有以下几个适用场景:

  1. 高速数据中心网络:BBR算法可以有效地处理高速数据中心网络中的流量拥塞,提高网络的吞吐量和用户体验。
  2. 云计算环境:BBR算法可以应用于云计算环境中的虚拟机、容器等网络通信中,提高网络性能和稳定性。
  3. 视频直播场景:BBR算法可以应用于视频直播场景中,通过提高视频传输的速率和降低延迟,提高观看体验。
  4. 远程办公场景:在远程办公场景中,BBR算法可以优化视频会议、文件传输等网络通信,提高网络性能和稳定性,从而提高工作效率。

总体来说,Google BBR算法适用于高速、长距离网络环境,特别是在对网络性能和用户体验有较高要求的场景中,可以发挥出其优秀的拥塞控制和带宽利用效果。

子网掩码

子网掩码是一个32位的2进制数, 其对应网络地址的所有位都置为1,对应于主机地址的所有位置都为0。子网掩码告知路由器,地址的哪一部分是网络地址,哪一部分是主机地址,使路由器正确判断任意IP地址是否是本网段的,从而正确地进行路由。子网掩码由1和0组成,且1和0分别连续。

  • 将子网掩码和 IP 地址按位计算 AND,就可得到网络号。如果网络地址相同,表明接受方在本网络上,那么可以把数据包直接发送到目标主机。路由器寻址工作中,也就是通过这样的方式来找到对应的网络号的,进而把数据包转发给对应的网络内。
  • 划分子网,数量是 2 的子网位数 减去 2,这 2 指的是,全 1 的广播地址和全 0 的网络地址;

广播地址

广播是一种同时向网络上的所有设备发送消息的方法。 广播地址是一个特殊地址,用于向给定网段上的所有设备发送消息。

适用的场景

  • 发现:当新设备加入网络时,它可以发送广播消息来宣布它的存在。 然后网络上的所有其他设备都可以收到此消息并采取适当的措施。
  • 配置:在某些情况下,可能需要使用特定设置或参数配置网络上的所有设备。 通过发送广播消息,可以快速轻松地将此配置分发到网络上的所有设备。
  • 故障排除:当网络出现问题时,向所有设备发送广播消息以确定问题的根本原因可能很有用。

给定 ip 和 子网掩码,如何确定广播地址

  1. 确认几类地址,比如 C 类地址,前24 位是网路号,后 8 位是主机号;
  2. 然后将 ip 地址与子网掩码进行与操作,可以获知主机号中挪用几位作为子网号;
  3. 子网号进行组合,每个组合中主机号全部位 1 的就是广播地址;

IPV6

IPv4 的地址是 32 位的,大约可以提供 42 亿个地址,但是早在 2011 年 IPv4 地址就已经被分配完了。 但是 IPv6 的地址是 128 位的,这可分配的地址数量是大的惊人,说个段子 IPv6 可以保证地球上的每粒沙子都能被分配到一个 IP 地址。

但 IPv6 除了有更多的地址之外,还有更好的安全性和扩展性,说简单点就是 IPv6 相比于 IPv4 能带来更好的网络体验。

但是因为 IPv4 和 IPv6 不能相互兼容,所以不但要我们电脑、手机之类的设备支持,还需要网络运营商对现有的设备进行升级,所以这可能是 IPv6 普及率比较慢的一个原因。

Pv6 相比 IPv4 的首部改进:

  • 取消了首部校验和字段。 因为在数据链路层和传输层都会校验,因此 IPv6 直接取消了 IP 的校验。
  • 取消了分片/重新组装相关字段。 分片与重组是耗时的过程,IPv6 不允许在中间路由器进行分片与重组,这种操作只能在源与目标主机,这将大大提高了路由器转发的速度。
  • 取消选项字段。 选项字段不再是标准 IP 首部的一部分了,但它并没有消失,而是可能出现在 IPv6 首部中的「下一个首部」指出的位置上。删除该选项字段使的 IPv6 的首部成为固定长度的 40 字节。

ICMP

ICMP 全称是 Internet Control Message Protocol,也就是互联网控制报文协议

ICMP 主要的功能包括:确认 IP 包是否成功送达目标地址、报告发送过程中 IP 包被废弃的原因和改善网络设置等。

IP 通信中如果某个 IP 包因为某种原因未能达到目标地址,那么这个具体的原因将由 ICMP 负责通知

https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP/40.jpg

BGP

BGP

  • BGP协议的核心就是通过BGP对等关系不断地交换路由信息,选择和传播最佳路径,从而实现网络之间的互联互通。这是Internet能够稳定运行的基石。 

BGP 的过程

  1. BGP 路由器之间建立BGP对等关系(BGP Peering),用于交换路由信息。
  2. 每个BGP路由器都有一个独立的自治系统号(AS Number),用于标识自己所在的网络。
  3. BGP路由器会定期向对等路由器发送自己的路由表信息,这些信息包含了该路由器可以到达的网络和子网。
  4. BGP路由器在接收到对等路由器发送的路由信息后,会根据路由选择算法选择一条最佳路径,并把这条路径信息记录到自己的路由表中。
  5. BGP会定期检查路由表,如果最佳路径发生变化,就会立即传播更新信息给所有的对等路由器。
  6. BGP可以支持不同AS之间进行路由传播,所以BGP可以实现Internet这样庞大复杂网络的互联互通。
  7. BGP还支持各种路由策略,如设置本地优先级、过滤某个对等方的路由等,这使得网络管理员可以很灵活地控制路由选择和路由传播。

跨域问题

summary

  • 浏览器为了保护数据安全而采用的一种称为“同源策略”的机制,当跨域进行网络请时,浏览器将会进行拦截;
  • 解决方法,分为客户端和服务端两种,一种server全部放行;二是客户端请求,服务端根据 header 中设置的 domain 进行个性化处理;

定义,什么是跨域问题

  • 跨域问题产生的原因是浏览器的同源策略。所谓"同源"指的是"协议+域名+端口"三者相同。
  • 同源策略的目的是为了保证用户信息的安全,防止恶意的网站获取该信息。同源策略规定,A 网站中的 JavaScript 脚本,只能获得同域名的 Cookie、DOM 和 AJAX 信息。
  • 当从一个源加载的文档或脚本试图与另一个源交互时,就会产生跨域问题。例如,当从源 A 的网页试图使用 XMLHttpRequest 向源 B 的服务器发送请求,该请求会被 browsers 拒绝。

解决方案

  1. CORS(跨域资源共享)。这是最简单的方式,只需要在服务端设置 Access-Control-Allow-Origin 就可以了。
1
Header set Access-Control-Allow-Origin "*"
  1. JSONP。通过<script>\ 标签 src 属性请求一个 JSONP 接口,此接口会返回一个函数调用,并传入数据作为参数。
1
2
3
<script src="http://example.com/api?callback=func"></script>

func({ name: "John" });
  1. 代理。在同源服务器上设置一个代理,转发跨域请求。
1
2
3
4
5
6
var proxy = require('http-proxy-middleware');

app.use('/api', proxy({ 
  target: '[http://example.com](http://example.com)', 
  changeOrigin: true 
}));
  1. 跨域资源共享(CORS)手动设置。在请求头中手动设置 Origin。
1
2
3
4
var xhr = new XMLHttpRequest();
xhr.open('GET', '[http://example.com/api](http://example.com/api)');
xhr.setRequestHeader('Origin', '[http://mydomain.com](http://mydomain.com)'); 
xhr.send();

服务端根据 Origin 头决定是否同意请求。

断点续传

what 断点续传是一种文件下载技术。它允许你在下载中断的情况下,从中断的地方继续下载,而不是重新下载整个文件。这个技术的工作原理是:

  1. 在下载文件时,捕获下载进度并记录下已下载的数据块信息,如块的起始位置和结束位置。
  2. 如果下载中断,再次下载时,可以通过已记录的块信息直接请求服务器下载未完成的块,而不需要重新下载整个文件。
  3. 服务器也需要支持断点续传,能够根据客户端提供的块信息直接返回未完成的块数据。

why 断点续传的主要优势是:

  1. 避免重新下载整个文件,节省时间和流量。
    1. 适用于大文件下载和网络条件不稳定的情况。即使下载中断多次,也可以随时继续下载未完成的部分,直到整个文件下载完成。
    1. 支持多线程并发下载同一个文件,加快下载速度。不同线程可以请求下载文件不同的块,并最终合并成完整文件。

how 客户端:

  1. 记录下载文件的信息,如URL、文件大小、已下载大小等。这通常存放在一个下载任务对象中。
  2. 下载时记录每个数据块的起始和结束位置。可以使用数组来表示各块,数组中的每个元素记录一块的位置信息。
  3. 如果下载中断,能够根据上一步记录的块信息构造一个新的下载请求,请求服务器返回未完成块的数据。
  4. 收到服务器返回的数据后,按块位置信息将数据写入文件,实现续传。

服务器:

  1. 支持接收客户端上传的块位置信息,并能解析这些信息。
  2. 能够根据客户端提供的块信息直接读出对应文件区域的数据返回,而不需要返回整个文件。
  3. 应能处理多个客户端同时对一个文件进行断点续传的情况。可以为每个客户端维护一个独立的文件指针来记录各自的下载进度。

总体步骤如下:

  1. 客户端发起 GET 请求,包含必要的头信息如 Host、Range(表示数据块位置)等。
  2. 服务器接收到请求,解析Range头信息,获得客户端需要的块位置。
  3. 服务器从文件中读取对应区域的数据,构造206 Partial Content响应返回给客户端。
  4. 客户端接收到响应,将数据写入文件,并更新下载任务的相关信息,如已下载大小等。
  5. 客户端可以随时因为各种原因中断下载,并在需要时发起新的请求继续下载,直到整个文件下载完成。

protobuf

summary

  1. 高效:二进制存储、不保存字段名、默认值不存储、方便嵌套等等;
  2. 跨平台;
  3. 开源;
  4. 质量稳定;

Protobuf 相比普通 Java 对象具有更高的性能和更小的体积,非常适合做数据存储和网络传输。

优点:

  1. 高效。Protobuf 对数据进行了二进制序列化,体积更小,序列化和反序列化性能更高。与 XML 或 JSON 相比,Protobuf 的体积可以小1/3到1/10。
  2. 简单。Protobuf 的数据结构定义简单清晰,只需要定义消息和字段,而无需手动设置各种格式和协议。
  3. 跨平台。Protobuf 生成的代码和二进制数据可以在不同平台(Windows、Linux、Mac)之间交换使用。
  4. 与语言无关。 .proto 文件中的数据结构定义与具体使用的语言无关,可以通过 Protobuf 编译器生成任意支持的语言(C++、Java、Python 等)代码。
  5. 可扩展。新的字段可以随时新增,旧的应用也可以无缝读取,兼顾向前兼容性。移除字段也比较容易管理。
  6. 静态类型。通过 .proto 文件,Protobuf 使用静态的数据类型定义,能在编译时发现类型错误。
  7. 开源。Protobuf 由 Google 开发并开源,社区资源丰富,已有大量成功应用案例。
  8. 简单协议。Protobuf 及其生成的代码可以简单地在不依赖 Protobuf 的系统间传输,容易实现一个简单协议。
  9. 高质量实现。Google 在 GRPC 和 Protocol Buffers 两个项目中广泛使用 Protobuf ,质量非常高。

总之,Protobuf 是一个简单但强大的工具,用于构建高效的序列化结构数据,适用于数据存储、 rpc 数据交换等,有着广泛的应用。它的主要优点是体积小、序列化快、静态类型、可扩展、跨语言等特征。

缺点:

  1. 只支持结构化数据,不支持泛型和继承等面向对象的特性。Protobuf 主要用于传输和存储数据,而不是作为完整的对象系统。
  2. 需要 .proto 文件和编译步骤。在构建项目时需要运行Protobuf编译器protoc来生成代码,这增加了一定的复杂度。
  3. 繁琐的修改过程。当 .proto 文件定义修改后,需要重新运行 protoc 重新生成代码,会对现有的序列化后的数据造成不兼容。需要慎重设计数据结构。
  4. 性能开销。Protobuf 的序列化和反序列化需要额外的CPU和内存消耗,虽然比其他格式高效,但相比直接使用字节数组或普通对象来说还是有一定开销的。
  5. 缺少架构支持。Protobuf 本身不提供如服务注册、服务发现、负载均衡等服务架构需要的功能,需要配合其他解决方案使用。
  6. 有限的类型系统。Protobuf 支持的类型比较有限,没有对decimal、uuid、时间等类型的 native 支持。这些类型如果要使用 Protobuf 需要自定义实现。
  7. 迭代难度大。一旦 .proto 文件发布,其定义的消息就会被广泛使用,很难再进行不兼容的更改。设计 .proto 结构需要慎重考虑未来可能的变化。

所以总体来说,Protobuf 非常适合做高效的数据存储和传输,但不足以成为完整的对象或 RPC 系统。实际项目中,Protobuf 常与其他技术结合使用,来弥补其缺点。

Protobuf 高效的原因有以下几个:

  1. 采用二进制格式。Protobuf 使用二进制格式对数据进行序列化和反序列化,比如 JSON 这种文本格式要高效得多。
  2. 预定义消息格式。在 .proto 文件中预定义消息格式,所有数据都必须符合这个格式。这样在序列化和反序列化的时候就不需要额外的元数据来描述数据结构,节省空间。
  3. 使用varint编码整数。Protobuf 使用varint对整数进行紧凑编码。不同于常规的可变长编码,varint编码会使用1-5个字节来编码数字,这使得编码后的整数平均只占用1-2个字节,比常规编码方式高效。
  4. 不包含字段名。序列化后的Protobuf 数据不包含字段名,只包含字段值,这也节省了空间。
  5. 默认值省略。如果某个字段的值等于它的默认值,那么这些字段在序列化后的数据中会被省略,不会被编码进去。这也可以减少编码后数据的大小。
  6. 嵌套对象支撑。Protobuf 支持嵌套的消息(message)和枚举(enum),这使得它能够高效的序列化复杂的结构化数据。嵌套的消息和枚举也使用相同的空间节约技术,所以效率不会降低。

中间人攻击

https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZfrcYJokkKzRXdVXXK5PLNIO4PavaS9pZHriabZMapD23yYKBYyicZoTa7mW5m5wHvpqj9NkDCquicow/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1 具体过程如下:

  • 客户端向服务端发起 HTTPS 建立连接请求时,然后被「假基站」转发到了一个「中间人服务器」,接着中间人向服务端发起 HTTPS 建立连接请求,此时客户端与中间人进行 TLS 握手,中间人与服务端进行 TLS 握手;
  • 在客户端与中间人进行 TLS 握手过程中,中间人会发送自己的公钥证书给客户端,客户端验证证书的真伪,然后从证书拿到公钥,并生成一个随机数,用公钥加密随机数发送给中间人,中间人使用私钥解密,得到随机数,此时双方都有随机数,然后通过算法生成对称加密密钥(A),后续客户端与中间人通信就用这个对称加密密钥来加密数据了。
  • 在中间人与服务端进行 TLS 握手过程中,服务端会发送从 CA 机构签发的公钥证书给中间人,从证书拿到公钥,并生成一个随机数,用公钥加密随机数发送给服务端,服务端使用私钥解密,得到随机数,此时双方都有随机数,然后通过算法生成对称加密密钥(B),后续中间人与服务端通信就用这个对称加密密钥来加密数据了。
  • 后续的通信过程中,中间人用对称加密密钥(A)解密客户端的 HTTPS 请求的数据,然后用对称加密密钥(B)加密 HTTPS 请求后,转发给服务端,接着服务端发送 HTTPS 响应数据给中间人,中间人用对称加密密钥(B)解密 HTTPS 响应数据,然后再用对称加密密钥(A)加密后,转发给客户端。

从客户端的角度看,其实并不知道网络中存在中间人服务器这个角色。那么中间人就可以解开浏览器发起的 HTTPS 请求里的数据,也可以解开服务端响应给浏览器的 HTTPS 响应数据。相当于,中间人能够 “偷看” 浏览器与服务端之间的 HTTPS 请求和响应的数据。

但是要发生这种场景是有前提的,前提是用户点击接受了中间人服务器的证书。

中间人服务器与客户端在 TLS 握手过程中,实际上发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器(客户端)识别出是非法的,于是就会提醒用户该证书存在问题。

ARP

在传输一个 IP 数据报的时候,确定了源 IP 地址和目标 IP 地址后,就会通过主机「路由表」确定 IP 数据包下一跳。然而,网络层的下一层是数据链路层,所以我们还要知道「下一跳」的 MAC 地址。

由于主机的路由表中可以找到下一跳的 IP 地址,所以可以通过 ARP 协议,求得下一跳的 MAC 地址。

过程

  • 主机会通过广播发送 ARP 请求,这个包中包含了想要知道的 MAC 地址的主机 IP 地址。
  • 当同个链路中的所有设备收到 ARP 请求时,会去拆开 ARP 请求包里的内容,如果 ARP 请求包中的目标 IP 地址与自己的 IP 地址一致,那么这个设备就将自己的 MAC 地址塞入 ARP 响应包返回给主机。

问题

  • 通用知识:
    • 基础
      • 7层网络协议的基本概念,常用网络协议属于第几层?
      • 常见设备工作在那一层?(路由器、交换机、网桥)
      • json 格式描述
    • 进阶
      • protobuf 协议了解
      • 整个网络过程串联(一个请求整个网络的处理过程)
  • 网络层:
    • 入门
      • IPv4地址样例。
      • 子网掩码的作用。
      • 给定 ip 和 子网掩码,广播地址是什么?
    • 基础
      • 列举IP报文头部(源地址、目标地址、TTL)
    • 进阶
      • IPv6 的基本概念
      • 为什么引入 IPV6。
      • BGP、OSPF协议原理
  • 传输层:
    • 入门
      • TCP 和 UDP 各自的特点和区别;
      • TCP 和 UDP 各自适合的使用场景
    • 基础
      • TCP 建立连接和断开连接的过程
      • TCP和UDP的头部列举
    • 进阶
      • 什么是SYN 攻击?
      • TCP为什么三次握手 而需要 四次挥手?
      • 滑动窗口概念
      • 慢启动
      • 拥塞控制
  • DNS :
    • 入门
      • DNS用途
    • 基础
      • 描述 DNS 过程
    • 进阶
      • 列举典型的DNS记录格式
      • DNS 劫持概念,如何防止 DNS 劫持?
      • 主流的公有云的DNS服务端架构
  • 应用层 :https://hit-alibaba.github.io/interview/basic/network/HTTP.html
    • 入门
      • http协议的用途,列举一些使用场景
      • 举例常见应用层协议
      • http和https区别
    • 基础
      • header 中:Content-Type / Content-Length / Content-Encoding:用途
      • header,cookie,返回码,UA,HOST,域 等基本概念;
      • POST/GET 区别;
      • 常见返回码含义;
    • 进阶
      • http2.0 和 http1.1 的主要区别?
      • GET 的幂等的含义;
      • keep-alive 含义;
      • 为什么需要 url 编码?
      • 实现断点续传的原理;
      • https协商过程
      • CSRF(Cross-site request forgery,跨站请求伪造):原理和防范
      • XSS(Cross Site Scripting,跨站脚本攻击):原理和防范
      • 中间人攻击概念与防止。
      • QUIC、Http2特点

Socket 编程

https://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html