计网面试复习

在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。

  • ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息。
  • ARP 用于根据 IP 地址查询相应的以太网 MAC 地址。

在传输层中有TCP协议UDP协议

在应用层有HTTP,FTP、TELNET、SMTP、DNS等协议。

IP 层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。

如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。

TCP

头部

序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。

确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。

控制位:

  • ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1
  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
  • SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

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

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

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

  • Socket:由 IP 地址和端口号组成
  • 序列号:用来解决乱序问题等
  • 窗口大小:用来做流量控制

可靠性

  • 重传机制:通过序列号与确认应答

    • 序列号Seq:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决乱序问题等
    • 确认应答号ACK:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
  • 常见

    • 超时重传
      • 发送方发送完一个分组后,就会设置一个超时计时器,如果超时计时器到期之前没有收到接收方发来的确认信息,则会重发刚发送过的分组;如果收到确认信息,则撤销该超时计时器。
      • 超时重传时间 RTO 的值应该略大于报文往返 RTT 的值
      • 超时间隔加倍每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
    • 快速重传
      • 不以时间为驱动,而是以数据驱动重传
      • 当发送端收到三个相同的ACK应答,就会知道对应分组丢失,此时面临一个问题,重传的时候,是重传之前的一个,还是重传那个接下来所有的问题
    • SACK
      • 需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据
    • Duplicate SACK
      • 主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。
      • 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  • ARQ协议,即自动重传请求(Automatic Repeat-reQuest)

    • 通过使用确认超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。
    • 停止等待ARQ协议
      • 发送一个报文段后等待应答,再继续发送,若超时则重传
      • 接收方收到重复报文段直接舍弃,重传确认消息
        • 确认丢失
        • 确认延时
    • 连续ARQ协议
      • 由于停止等待ARQ协议信道利用率太低,所以需要使用连续ARQ协议来进行改善
      • 连续ARQ协议通常是结合滑动窗口协议来使用的,发送方需要维持一个发送窗口;位于发送窗口内的所有分组都可以连续发送出去,而不需要等待对方的确认,这样就提高了信道利用率。
      • 累积重传:接收方一般都是采用累积确认的方式。也就是说接收方不必对收到的分组逐个发送确认。而是在收到几个分组后,对按序到达的最后一个分组发送确认。如果收到了这个分组确认信息,则表示到这个分组为止的所有分组都已经正确接收到了。
      • Go-back-N(回退N),表示需要再退回来重传已发送过的N个分组。若中间第n个丢失,则从第n+1个开始重传
  • 流量控制

    • 发送方不能无脑的发数据给接收方,要考虑接收方处理能力,否则会导致触发重发机制,从而导致网络流量的无端的浪费。
    • 为了解决这种现象发生,TCP 提供滑动窗口机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。
    • 发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整
    • 当发送方可用窗口变为 0 时,会发生窗口关闭;发送方实际上会定时发送窗口探测报文,以便知道接收方的窗口是否发生了改变
      • 若无窗口探测报文,当接收端处理完数据窗口增大后,通知发送端增大窗口的报文丢失时,发送端会一直等待接收端的非0窗口通知,接收端一直等待发送端的数据,陷入死锁
      • 为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。超时则发送窗口探测报文
    • 先减少缓存再收缩窗口,接收端发现数据大小超过了接收窗口的大小,于是就把数据包丢失了。为了防止丢包情况发生,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。
  • 拥塞控制

    • 流量控制是避免「发送方」的数据填满「接收方」的缓存;而拥塞控制是避免「发送方」的数据填满整个网络。

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

    • 变量

      • 拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。还有发送窗口 swnd 和接收窗口 rwnd,发送窗口的值是swnd = min(cwnd, rwnd)
    • 只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了用拥塞。

      • ssthresh:慢启动阈值
    • 拥塞控制主要是四个算法:

      • 慢启动

      • 当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1

        • 实质上发包数量呈指数增长,因为收到的ACK数量以指数增加
      • 拥塞避免

        • cwnd >= ssthresh 时,就会使用「拥塞避免算法」
      • 每当收到一个 ACK 时,cwnd 增加 1/cwnd

        • 实质上发包数量呈线性增长,每次+1
      • 拥塞发生

        • 当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
          • 超时重传:缺点:突然减少数据流,会造成网络卡顿。
          • 快速重传:对应快速恢复
        • 超时重传的拥塞发生算法,ssthresh 和 cwnd 的值会发生变化:
        • ssthresh 设为 cwnd/2
          • cwnd 重置为 1

        • 快速重传的拥塞发生算法
          • cwnd = cwnd/2 ,也就是设置为原来的一半;
        • ssthresh = cwnd;
        • 进入快速恢复算法
      • 快速恢复

        • 快速重传和快速恢复算法一般同时使用
        • 进入快速恢复算法如下:
          • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
          • 重传丢失的数据包;
        • 如果再收到重复的 ACK,那么 cwnd 增加 1;
          • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

IP数据报分片

  • 片偏移占13 位,指出:较长的分组在分片后某片在原分组中的相对位置。

  • 片偏移以 8 个字节为偏移单位。

  • 分片例子

    • 一数据报的总长度为 3820 字节,其数据部分的长度为 3800 字节(使用固定首部),需要分片为长度不超过 1420 字节的数据报片。
    • 因固定首部长度为 20 字节,因此每个数据报片的数据部分长度不能超过 1400 字节。
    • 于是分为 3 个数据报片,其数据部分的长度分别为 1400、1400 和 1000 字节。
    • 原始数据报首部被复制为各数据报片的首部,但必须修改有关字段的值

    NgefK0.png

滑动窗口

TCP通过滑动窗口的概念来进行流量控制。设想在发送端发送数据的速度很快而接收端接收速度却很慢的情况下,为了保证数据不丢失,显然需要进行流量控制, 协调好通信双方的工作节奏。

所谓滑动窗口,可以理解成接收端所能提供的缓冲区大小。

  • 窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

  • 规则:

    (1)凡是已经发送过的数据,在未收到确认之前,都必须暂时保留,以便在超时重传时使用。

    (2)只有当发送方A收到了接收方的确认报文段时,发送方窗口才可以向前滑动几个序号。

    (3)当发送方A发送的数据经过一段时间没有收到确认(由超时计时器控制),就要使用回退N步协议,回到最后接收到确认号的地方,重新发送这部分数据。

  • 此外,TCP利用滑动窗口协议来进行流量控制

UDP

头部

  • 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
  • 包长度:该字段保存了 UDP 首部的长度跟数据的长度之和。
  • 校验和:校验和是为了提供可靠的 UDP 首部和数据而设计。

TCP与UDP

  • 连接

    • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
    • UDP 是不需要连接,即刻传输数据。
  • 服务对象

    • TCP 是一对一的两点服务,即一条连接只有两个端点。
    • UDP 支持一对一、一对多、多对多的交互通信
  • 可靠性

    • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
    • UDP 是尽最大努力交付,不保证可靠交付数据。
  • 拥塞控制、流量控制

    • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
    • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
  • 首部开销

    • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
    • UDP 首部只有 8 个字节,并且是固定不变的,开销较小
  • 传输方式

    • TCP 是字节流式传输,没有边界,但保证顺序和可靠,同时对「重复」的报文会自动丢弃。
    • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
  • 分片不同

    • TCP 的数据大小如果大于 MSS(最大报文段) 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片
    • UDP 的数据大小如果大于 MTU (最大传输单元)大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU。
  • 应用背景不同

    • 由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
      • FTP 文件传输
      • HTTP / HTTPS
    • 由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:
      • 包总量较少的通信,如 DNSSNMP
      • 视频、音频等多媒体通信
      • 广播通信

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

如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传,因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传,因此可见效率不高

为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值

三次握手

过程

  • 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
  • 客户端会随机初始化序列号(client_isn),同时把 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,之后客户端处于 SYN-SENT 状态
  • 服务端收到客户端的 SYN 报文后,也随机初始化自己的序号(server_isn),其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1,接着把 SYNACK 标志位置为 1。最后把该报文发给客户端,之后服务端处于 SYN-RCVD 状态
  • 客户端收到服务端报文后,将 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,之后客户端处于 ESTABLISHED 状态
  • 服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。

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

为何需要三次握手?

  • 最主要的原因:防止历史连接初始化了连接

    客户端连续发送多次 SYN 建立连接的报文,在网络拥堵情况下:

    • 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
    • 那么此时服务端就会回一个 SYN + ACK 报文给客户端;
    • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。
  • 能帮助双方同步初始化序列号

    • 序列号需要一来一回,才能确保双方的初始序列号能被可靠的同步
  • 能减少双方不必要的资源开销

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

不使用「两次握手」和「四次握手」的原因

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

四次挥手

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

主动关闭连接的,才有 TIME_WAIT 状态。

为什么挥手需要四次?

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

服务端通常需要等待完成数据的发送和处理,所以服务端的 ACKFIN 一般都会分开发送,从而比三次握手导致多了一次。

为什么需要 TIME_WAIT 状态?

  • 防止具有相同「四元组」的「旧」数据包被收到
    • 若没有这个状态,相同端口重新启动可能接收到之前旧的数据包
    • 经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
  • 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭;

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

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。

MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。

比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

如果没有等待2MSL,那么将客户端进入了closed状态而服务器没有;若等待时间里收到服务端重发的 FIN 报文,那么 2MSL 时间将重新计时

SYN 攻击

假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

  • 通过修改 Linux 内核参数,控制队列大小和当队列满时应做什么处理。

HTTP

HTTP全称是HyperText Transfer Protocal,即:超文本传输协议

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”

特点

  • 简单:HTTP 基本的报文格式就是 header + body,头部信息也是 key-value 简单文本的形式,易于理解,降低了学习和使用的门槛。
  • 灵活和易于扩展:下层可以随意变化,HTTPS 也就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层,HTTP/3 甚至把 TCPP 层换成了基于 UDP 的 QUIC。
  • 应用广泛和跨平台

HTTP 协议里有优缺点一体的双刃剑,分别是「无状态、明文传输」,同时还有一大缺点「不安全」。

  • 无状态

    • 无状态的好处,因为服务器不会去记忆 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担,能够把更多的 CPU 和内存用来对外提供服务。

    • 无状态的坏处,既然服务器没有记忆能力,它在完成有关联性的操作时会非常麻烦。

    • 解决方法:Cookie 通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。

      相当于,在客户端第一次请求后,服务器会下发一个装有客户信息的「小贴纸」,后续客户端请求服务器的时候,带上「小贴纸」,服务器就能认得了了

  • 明文传输

    • 优点:明文意味着在传输过程中的信息,是可方便阅读的,通过浏览器的 F12 控制台或 Wireshark 抓包都可以直接肉眼查看;方便调试
    • 缺点:信息裸奔,容易被窃
  • 不安全

    • 通信使用明文(不加密),内容可能会被窃听
    • 不验证通信方的身份,因此有可能遭遇伪装
    • 无法证明报文的完整性,所以有可能已遭篡改

HTTPS

HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议

HTTPS是HTTP over SSL/TLS,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL/TLS:

SSL (Secure Socket Layer,安全套接字层)

TLS (Transport Layer Security,传输层安全协议)

HTTP与HTTPS

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

DNS

域名解析

在域名没有被发明之前,人们访问网站都是通过IP地址

但是IP地址不直观,而且用户记忆十分不方便,于是人们又发明了另一套字符型的地址方案,即所谓的域名地址

怎么样才能让域名地址和IP地址一一对应呢

这个时候DNS(Domain name server)就出现了,域名地址和IP地址的对应关系就放在DNS内

域名解析就是查询域名对应的IP地址

步骤

  1. 客户端发起查询该域名的 IP 地址的 DNS 请求。该请求发送到了本地 DNS 服务器上。本地 DNS 服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地 DNS 服务器还要向 DNS 根服务器进行查询。
  2. 本地 DNS 服务器向根服务器发送 DNS 请求,请求目标域名 的 IP 地址。
  3. 根服务器经过查询,没有记录该域名及 IP 地址的对应关系。但是会告诉本地 DNS 服务器,可以到域名服务器上继续查询,并给出域名服务器的地址(.com 服务器)。
  4. 本地 DNS 服务器向 .com 服务器发送 DNS 请求,请求目标域名的 IP 地址。
  5. .com 服务器收到请求后,不会直接返回域名和 IP 地址的对应关系,而是告诉本地 DNS 服务器,该域名可以在 baidu.com 域名服务器上进行解析获取 IP 地址,并告诉 baidu.com 域名服务器的地址。
  6. 本地 DNS 服务器向 baidu.com 域名服务器发送 DNS 请求,请求域名的 IP 地址。
  7. baidu.com 服务器收到请求后,在自己的缓存表中发现了该域名和 IP 地址的对应关系,并将IP地址返回给本地 DNS 服务器。
  8. 本地 DNS 服务器将获取到与域名对应的 IP 地址返回给客户端,并且将域名和 IP 地址的对应关系保存在缓存中,以备下次别的用户查询时使用。

简单概括

  1. 查询该域名的 IP 地址的 DNS 请求依次到达本地 DNS 服务器(查询缓存记录),根服务器,.com服务器,目标域名服务器
  2. 目标域名服务器收到请求后,在自己的缓存表中发现了该域名和 IP 地址的对应关系,并将IP地址返回给本地 DNS 服务器。
  3. 本地 DNS 服务器将获取到与域名对应的 IP 地址返回给客户端,并且将域名和 IP 地址的对应关系保存在缓存中,以备下次别的用户查询时使用。

访问网页全过程

应用层

首先浏览器做的第一步工作就是要对 URL 进行解析,从而生成发送给 Web 服务器的请求信息。

URL 进行解析之后,浏览器确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息了。

通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器。

但在发送之前,还有一项工作需要完成,那就是查询服务器域名对于的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。

所以,有一种服务器就专门保存了 Web 服务器域名与 IP 的对应关系,它就是 DNS 服务器。通过DNS进行域名解析

通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈

传输层与网络层

HTTP 是基于 TCP 协议传输的,于是先进行TCP三次握手,并添加TCP头部;TCP协议确保数据包可靠

接着委托 IP 模块将数据封装成网络包发送给通信对象;IP协议提供远程定位

当存在多个网卡时,在填写源地址 IP 时,就需要判断到底应该填写哪个地址。这个判断相当于在多块网卡中判断应该使用哪个一块网卡来发送包。

这个时候就需要根据路由表规则,来判断哪一个网卡作为源地址 IP。

生成了 IP 头部之后,接下来网络包还需要在 IP 头部的前面加上 MAC 头部

MAC 头部是以太网使用的头部,包含发送方 MAC 地址接收方目标 MAC 地址,以及协议类型

  • 0800 : IP 协议
  • 0806 : ARP 协议

发送方的 MAC 地址通过读取网卡ROM获得;接收方的 MAC 地址通过ARP 协议在以太网中以广播的形式帮我们找到路由器的 MAC 地址。(之后会存入ARP缓存)

直此,网络包生成了

网络接口层

IP 生成的网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电信号,才能在网线上传输

负责执行这一操作的是网卡,要控制网卡还需要靠网卡驱动程序

网卡驱动从 IP 模块获取到包之后,会将其复制到网卡内的缓存区中,接着会其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列

最后网卡会将包转为电信号,通过网线发送出去。

首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号。

然后通过包末尾的 FCS 校验错误,如果没问题则放到缓冲区。

交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口

网络包经过交换机之后,现在到达了路由器,并在此被转发到下一个路由器或目标设备。

接下来,下一个路由器会将包转发给再下一个路由器,经过层层转发之后,网络包就到达了最终的目的地。

服务器

数据包抵达服务器后,服务器进行扒皮,最后服务器的 HTTP 进程看到,原来这个请求是要访问一个页面,于是就把这个网页封装在 HTTP 响应报文里。

然后进行相同的方式返回客户端,交给浏览器去渲染页面,完成一次页面访问

协议栈的内部分为几个部分,分别承担不同的工作。上下关系是有一定的规则的,上面的部分会向下面的部分委托工作,下面的部分收到委托的工作并执行。

当 MAC 地址表找不到指定的 MAC 地址会怎么样?

交换机无法判断应该把包转发到哪个端口,只能将包转发到除了源端口之外的所有端口上

只有相应的接收者才接收包,而其他设备则会忽略这个包

IP协议

  • 源地址IP,即是客户端输出的 IP 地址;
  • 目标地址,即通过 DNS 域名解析得到的 Web 服务器 IP。

路由

路由器与交换机

  • 路由器是基于 IP 设计的,俗称三层网络设备,路由器的各个端口都具有 MAC 地址和 IP 地址;
  • 交换机是基于以太网设计的,俗称二层网络设备,交换机的端口不具有 MAC 地址。

路由表的建立

  • 路由器在刚刚开始工作时,路由表是空的
  • 经过若干次更新后,所有的路由器最终都会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器的地址。

路由表更新(距离向量算法)

  1. 将收到的路由表的跳数都+1,下一跳都变成X (从X收到的路由表)
  2. 分情况更新
    1. 若有新信息(目的网络不在表中),直接加
    2. 若信息相同(目的网络+下一跳),更新为收到的信息
    3. 若目的网络相同,下一跳不同,择跳数小为优进行更新
  3. 若 3 分钟还没有收到相邻路由器的更新路由表,则把此相邻路由器记为不可达路由器,即将距离置为 16(表示不可达)
  4. 生成新路由表

查找路由表

  • 在路由表中,对每一条路由,最主要的是:目的网络地址+下一跳地址
  • 经过多次间接交付,只有到达最后一个路由器时,才试图向目的主机进行直接交付。
  • 路由匹配和前面讲的一样,每个条目的子网掩码和 目标IP 做 & 与运算后,得到的结果与对应条目的目标地址进行匹配,如果匹配就会作为候选转发目标,如果不匹配就继续与下个条目进行路由匹配。

路由器分组转发算法

  1. 从数据报的首部提取目的主机的 IP 地址 D, 得出目的网络地址为 N
  2. 网络 N 与此路由器直接相连,则把数据报直接交付目的主机 D;否则是间接交付,执行 (3)
  3. 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给路由表中所指明的下一跳路由器;否则,执行 (4)
  4. 若路由表中有到达网络 N 的路由,则把数据报传送给路由表指明的下一跳路由器;否则,执行 (5)。
  5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器;否则,执行 (6)。
  6. 报告转发分组出错。

路由表指出,到某个网络应当先到某个路由器(即下一跳路由器)。

多人网络

基本知识

消息

  • 消息(Message)就是根据双方制定的协议(Protocol)来封装与解析的数据。
  • 把消息封装与解析的过程叫做序列化(Serialize)与反序列化(Deserialize)

网络传输协议

  • 传输层
    • TCP
    • UDP
  • 应用层
    • HTTP

序列化工具

不同计算机之间传输的是二进制数据,为了将游戏中的信息(角色状态,位置)转换成二进制数据,需要序列化工具。

一般是客户端和服务器双方先制定特定的协议并提供序列化工具。然后均按照该协议中制定的类型来创建特定的对象。

常用的网络通信协议

  • Protocol Buffer
  • Json
  • Xml

同步解决方案/同步策略

  • 帧同步
    • 帧同步是指客户端把操作上传服务端,服务端不模拟操作,把操作转发给所有的客户端
  • 状态同步
    • 状态同步是指客户端把操作上传服务端,服务端模拟操作,然后把模拟的结果转发给所有客户端
  • 介于两者之间的同步策略
    • 在对准确性要求高,实时性要求不高的地方采用状态同步
    • 在对准确性要求不高,实时性要求高的地方采用帧同步

I/O多路复用

前置知识

在 TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列:

  • 一个是还没完全建立连接的队列,称为 TCP 半连接队列,这个队列都是没有完成三次握手的连接,此时服务端处于 syn_rcvd 的状态;
  • 一个是一件建立连接的队列,称为 TCP 全连接队列,这个队列都是完成了三次握手的连接,此时服务端处于 established 状态;

当 TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。

注意,监听的 Socket 和真正用来传数据的 Socket 是两个:

  • 一个叫作监听 Socket
  • 一个叫作已连接 Socket

文件描述符

每一个进程都有一个数据结构 task_struct,该结构体里有一个指向「文件描述符数组」的成员指针。该数组里列出这个进程打开的所有文件的文件描述符。数组的下标是文件描述符,是一个整数,而数组的内容是一个指针,指向内核中所有打开的文件的列表,也就是说内核可以通过文件描述符找到对应打开的文件。

服务器单机理论最大能连接多少个客户端

相信你知道 TCP 连接是由四元组唯一确认的,这个四元组就是:本机IP, 本机端口, 对端IP, 对端端口

最大 TCP 连接数 = 客户端 IP 数×客户端端口数

对于 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数约为 2 的 48 次方

但是服务器肯定承载不了那么大的连接数,主要会受两个方面的限制:

  • 文件描述符Socket 实际上是一个文件,也就会对应一个文件描述符。在 Linux 下,单个进程打开的文件描述符数是有限制的,没有经过修改的值一般都是 1024,不过我们可以通过 ulimit 增大文件描述符的数目;
  • 系统内存,每个 TCP 连接在内核中都有对应的数据结构,意味着每个连接都是会占用一定内存的;

最基础的 TCP 的 Socket 编程,是阻塞 I/O 模型,基本上只能一对一通信

多进程/线程模型

比较传统的方式是使用多进程/线程模型,每来一个客户端连接,就分配一个进程/线程,然后后续的读写都在对应的进程/线程,这种方式处理 100 个客户端没问题,但是当客户端增大到 10000 个时,10000 个进程/线程的调度、上下文切换以及它们占用的内存,都会成为瓶颈。

  • 多进程模型

    • 为每个客户端分配一个子进程来处理请求

    • 服务器的主进程负责监听客户的连接,一旦与客户端连接完成,accept() 函数就会返回一个「已连接 Socket」,这时就通过 fork() 函数创建一个子进程,实际上就把父进程所有相关的东西都复制一份,包括文件描述符、内存地址空间、程序计数器、执行的代码等。

      fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程

      fork函数被调用一次,返回两次;子进程返回0,父进程返回子进程ID;否则,出错返回-1

    • 子进程退出后回收资源,分别是调用 wait()waitpid() 函数。

    • 进程的上下文切换开销比线程大得多

  • 多线程模型

    • 当服务器与客户端 TCP 完成连接后,通过 pthread_create()函数创建线程,然后将「已连接 Socket」的文件描述符传递给线程函数,接着在线程里和客户端进行通信,从而达到并发处理的目的。
    • 可以使用线程池的方式来避免线程的频繁创建和销毁,所谓的线程池,就是提前创建若干个线程,这样当由新连接建立时,将这个已连接的 Socket 放入到一个队列里,然后线程池里的线程负责从队列中取出已连接 Socket 进程处理。
    • 队列是全局的,每个线程都会操作,为了避免多线程竞争,线程在操作这个队列前要加锁

为了解决上面这个问题,就出现了 I/O 的多路复用,可以只在一个进程里处理多个文件的 I/O

I/O多路复用

只使用一个进程来维护多个 Socket ,一个进程虽然任一时刻只能处理一个请求,但是处理每个请求的事件时,耗时控制在 1 毫秒以内,这样 1 秒内就可以处理上千个请求;把时间拉长来看,多个请求复用了一个进程,这就是多路复用

进程可以通过一个系统调用函数从内核中获取多个事件

select/poll/epoll 是如何获取网络事件的呢?在获取事件时,先把所有连接(文件描述符)传给内核,再由内核返回产生了事件的连接,然后在用户态中再处理这些连接对应的请求即可。

select

使用固定长度BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。

poll

使用链表形式来表示集合,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合

两次拷贝两次遍历

  • 首先需要把关注的 Socket 集合通过 select/poll 系统调用从用户态拷贝到内核态,然后由内核检测事件,
  • 当有网络事件产生时,内核需要遍历进程关注 Socket 集合,找到对应的 Socket,并设置其状态为可读/可写,
  • 然后把整个 Socket 集合从内核态拷贝到用户态,
  • 用户态还要继续遍历整个 Socket 集合找到可读/可写的 Socket,然后对其处理。

select 和 poll 的缺陷在于,当客户端越多,也就是 Socket 集合越大,Socket 集合的遍历和拷贝会带来很大的开销

epoll

  • epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删查一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配。
  • epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket ),大大提高了检测的效率。

epoll 支持边缘触发和水平触发的方式,而 select/poll 只支持水平触发,一般而言,边缘触发的方式会比水平触发的效率高,边缘触发可以减少 epoll_wait 的系统调用次数,系统调用也是有一定的开销的的,毕竟也存在上下文的切换。

事件触发模式

  • 边缘触发(edge-triggered,ET)
    • 当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,
    • 即只有当I/O 事件发生时才会通知一次
    • 因此我们程序要保证在收到通知后应尽可能地读写数据,以免错失读写的机会
    • 所以需要配合使用非阻塞I/O,一直读写直到系统调用返回错误
  • 水平触发(level-triggered,LT)
    • 当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;
    • 即当I/O 事件发生了,会不断通知事件已发生
posted @ 2021-05-05 14:14  AMzz  阅读(321)  评论(0编辑  收藏  举报
//字体