Post

计网八股文

计网八股文

HTTP

经典面试题:当输入网址后,在网页渲染页面前,期间发生了什么

  • 浏览器工作的第一步就是对URL进行解析, 生成发送给服务器的请求信息。下面对URL组成成分进行解析:

URL组成成分:

  1. 协议:http
  2. 主机名:www.baidu.com
  3. 数据源(目录名和文件名,用斜杠分隔)

当没有指定数据源时, 服务器会返回一个默认的HTML文件,通常是index.html。

Http基本概念

q1:http是什么?

http名为超文本传输协议。顾名思义,也就是能够拆分成三部分:超文本、传输、协议。

  • 超文本:服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态
  • 传输
  • 协议:计算机之间交流通信的行为规范、相关的各种控制和错误处理方式。

q2:http常见的状态码有哪些?

  • 1xx类:提示信息,中间状态
  • 2xx类:成功状态
    • 200 OK:请求成功
    • 204 No Content:请求成功,但没有返回内容,即没有body
    • 206 Partial Content:请求成功,但只返回部分内容
  • 3xx类:重定向状态
    • 301 Moved Permanently:永久重定向
    • 302 Found:临时重定向
    • 304 Not Modified:资源未修改,使用缓存
  • 4xx类:客户端错误
    • 400 Bad Request:请求错误
    • 401 Unauthorized:未授权
    • 403 Forbidden:禁止访问
    • 404 Not Found:资源未找到
  • 5xx类:服务端错误
    • 500 Internal Server Error:服务器内部错误
    • 502 Bad Gateway:网关错误
    • 503 Service Unavailable:服务不可用
    • 504 Gateway Timeout:网关超时

q3:HTTP常见字段有哪些?

  • Host字段:主机名,用于指定服务器的域名。
  • Content-Length字段:表示本次回应的数据的长度
  • Connection 字段:用于客户端要求服务器用Http长连接机制,以便其他请求复用。只要任意一端没有主动断开连接,则保持TCP连接状态。
  • Connection: keep-alive:保持连接,客户端向同一个服务器的多个请求是可以复用同一个连接的。
  • Content-Type字段:表示本次回应的数据类型
  • Accept字段:表示客户端能够接受的数据类型
  • Content-Encoding字段:表示数据的压缩方式
  • Accept-Encoding字段:表示客户端能够接受的压缩方式

GET和POST

q1:GET和POST的区别是什么?

GET是拉的过程,POST是推的过程。GET从语义上说是从服务器获取指定的资源,POST从语义上说是根据请求负荷(报文body)对指定的资源进行处理。

HTTP 缓存技术

对于像GET这样的安全的幂等的调用,其结果是可以被缓存的,以减轻服务器负担。缓存有两种实现方式:强制缓存和协商缓存。

强制缓存: 浏览器判断缓存没有过期,直接使用浏览器的本地缓存。

SSL/TLS协议运行机制

q1:http和https的区别?

  • http是超文本传输协议,信息是明文传输,存在安全风险的问题。由于其明文传输的特性,存在三种安全问题:通信内容的窃听、传输内容的篡改、服务端的冒充风险。
  • https是在http层和tcp/ip层之间加入了ssl/tls协议,使得传输的内容加密,保证了传输的安全性。https在与服务端进行三次握手之后,会进行第三次握手的时候,会进行ssl/tls协议的握手,握手成功后,才会进入加密报文的传输。
  • 两者的端口也不同:http默认端口是80,https默认端口是443。

q2:那你来解释一下SSL/TLS协议的握手过程?

我直接用手画的两张图来解释:

SSL/TLS握手流程:

alt text

session key 明文加密解密过程:

alt text

q3:请你来回答一下半对称加密?

公钥加密,私钥解密:这个目的是为了保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容;(能确认消息是由客户端发送的)

私钥加密,公钥解密:这个目的是为了保证消息不会被冒充,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。(能确认消息是由服务端发送的)

哈希值加密可以保证数据的完整性,但不能保证消息来源的可靠性。所以需要结合半对称加密方式来保证消息的来源是可靠的。

q4:什么是数字证书? 数字证书是用来解决服务端冒充的问题的。数字证书是由第三方机构颁发的,能够证明其来源的合法性,防止服务端被冒充的风险。这里可以详细阅读q2中手画的SSL/TLS握手流程图,其中确保握手安全就是通过半对称加密和数字证书来保证的。

HTTPS的应用数据的完整性

q1:HTTPS是如何保证数据的完整性的?

首先我们要知道TLS协议分为两个部分:握手协议和记录协议。

  • 握手协议就是我前面手画的SSL/TLS握手流程,用来保证通信双方的身份和生成会话密钥。
  • TLS 记录协议负责保护应用程序数据并验证其完整性和来源

下面这张图详细介绍了TLS记录协议的流程图:

alt text

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

q4:https一定安全吗? 在真正能够验证了服务端密钥的合法性的前提下,https是安全的。但是如果你使用了不安全的数字证书,那么这个违法的服务端就会进行代理,导致你的数据被窃取。所以在使用https的时候,一定要保证数字证书的合法性。

HTTP/1.1、HTTP/2、HTTP/3 演变

q1:HTTP/1.1 提高了什么性能

  • 长连接
  • pipeline网络传输可以并发的发送多个请求,而不是等待上一个请求的返回再发送下一个请求(但是只是针对客户端而言,服务端还是需要按顺序处理请求、而且也没有什么浏览器会使用这个技术)

q2:HTTP/2做了什么优化

  • 头部压缩
  • 二进制格式
  • 并发传输
  • 服务器主动推送资源

我得好好讲讲HTTP/2的并发传输!

首先我们知道在常规的Http/1.1中,存在队头阻塞的问题,但是在HTTP/2中很好的解决这个问题,引入了Stream的概念,并且多个Stream复用在一个TCP连接中。

alt text

从上图可以看到,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 可以并行交错地发送请求和响应。

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

alt text

q3:HTTP/3做了哪些优化

HTTP/3把HTTP/2的下层的TCP替换为UDP。但是大家可能会好奇,UDP不是无序且不安全的吗?实际上基于UDP的QUIC协议是一个基于UDP的安全可靠的传输协议,可以类似于TCP。下面来详细讲讲QUIC协议。

QUIC协议

QUIC协议可以从一下三个方面认识:

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

无队头阻塞

  • QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。
  • QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。
  • 所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。

就是说,QUIC可以把一次HTTP请求流化,并且与其他的流化的HTTP请求并行传输,并且互不影响,这样就能解决tcp阻塞问题。

alt text

更快的连接建立

QUIC协议的握手非常的快,只需要一个RTT(Round-Trip Time,往返时间)就能完成握手,而TCP至少需要两个RTT。因为握手的目的就是确认双方的连接ID,连接迁移时基于连接ID实现的,这是非常快的。

但是HTTP/3的QUIC协议并不是与TLS分层的,而是内部集成了TLS,所以在QUIC协议中,TLS的加密和解密是在QUIC协议中完成的,而不是在应用层完成的。再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商。

alt text

连接迁移

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

RPC

q1:既然已经有了HTTP协议,那又为什么还要有RPC协议

IO 多路复用(非常关键、后台开发考察到的可能性非常大)

TCP三次握手和四次挥手面试题涉及相关知识

对于TCP八股,我将会以TCP基本认识、TCP连接建立、TCP连接断开(本人写该博客的时候非常不熟)来进行讲解。

TCP基本认识

在认识tcp的第一步是要把tcp的segment结构图深深的刻进自己的dna里!

alt text

这里里面的每个结构成分都对应着一个TCP相关技术。这里的结构的概念我这里先不说了,后面浏览到相关的知识会涉及到。 但是这里要说一下,为什么要把port的信息封装在TCP,因为内核中有维护一个端口表,上面记录着占有端口的进程。当tcp数据包到来时,内核需要根据这个端口表来查询最总将数据包发往哪个进程。

TCP 连接建立

面向连接:数据的传输是有关系状态的,需要维护传输数据的关系;

如何解决:使用建立连接,传输数据,断开连接的三步创建一个长期的数据传输机制,在同一个连接中的数据传输是有上下文关系的。需要维护seq序列号字段维护数据的顺序关系保证按序交付,和解决数据包重复的问题。需要部分特殊的状态标记的包来专门创建、断开和维护一个连接:syn,ack,fin,rst

  • syn:用于建立连接时的同步序列号。
  • ack:用于确认已收到的数据包。确认已成功接收数据,并告知对方下一个期望接收的数据序列号。
  • fin:用于断开连接时的标记。表示发送方已经完成数据传输,并请求断开连接。
  • rst:用于复位连接的标记。表示连接出现异常,需要强制断开并重新建立连接。

可靠:主要是指数据在传输过程中不会被损坏或者丢失,保证数据可以正确到达。而不做以上保证的就是不可靠。

如何解决:

引入数据传输的确认机制,即数据发送过后等待对方。于是需要维护确认字段ack状态。但每个包都要等待ack的确认才能继续发包的话,会引发带宽的利用率不高的问题,解决方案是引入窗口确认机制和滑动窗口,即不在以每个包发送之后进行确认,而是发送多个包后一起确认。

引入窗口后,如何在不同延迟的网络上选择不同的窗口大小,解决方法是引入窗口变量和窗口检测通告:

  • 发送端维护:

已发送并确认ack偏移量(窗口左边界)

已发送未确认ack偏移量(窗口当前发送字节位置)

即将发送偏移量(窗口右边界)

  • 接收端维护:

已接受并确认偏移量(窗口左边界)

接受后会保存的窗口大小(窗口右边界)

接收方会给发送方回复ack确认,ack中会有最新窗口通告长度,以便发送方调整窗口长度。此处会引入sack选择确认行为和窗口为0时的坚持定时器行为。

引入滑动窗口之后,带宽可以充分被利用了,但是网络环境是复杂的,随时可能因为大量的数据传输导致网络上的拥塞。于是要引入拥塞控制机制:当出现拥塞的时候,tcp应该能保证带宽是被每条tcp连接公平分享的。所以在拥塞的情况下,要能将占用带宽较大的连接调整为占用带宽变小,占用小的调大。以达到公平占用资源的目的。

拥塞控制对带宽占用的调整本质上就是调整滑动窗口的大小来实现的,所以需要在接受端引入一个新的变量叫做cwnd:拥塞窗口,来反应当前网络的传输能力,而之前的通告窗口可以表示为awnd。此时发送端实际可用的窗口为cwnd和awnd的较小者。

进而引发各种复杂的问题和概念等等等等。

为什么TCP可靠连接需要三次握手

首先我们要了解tcp握手的原因。有两个:

  1. 确认对端在线;
  2. 需要保证传输的包的顺序,所以要确认这次连接里传输数据的起始序列号。因为数据是双向传输的,所以需要两边都要确认对端序列号。

给一张非常经典的图:

alt text

前两次握手是为了知道双方的起始syn,第三次握手是为了让服务端确认客户端已经知道了自己的syn。三次握手是最基本的最简洁的。

这里就有个很好的面试问题?当已经建立完两次握手之后,服务端如果收到另外一个客户端的ack,会让当前的连接完成第三次握手吗?

答案是当然不会。首先另外一个客户端的建立的是另一个连接,ip和port信息都不一样,如果这个时候服务端收到这个客户端的ack,会返回rst,表示没有遵循tcp协议连接过程,需要重新建立连接。

这里可以引出一个问题,如何分辨收到的ack到底是第一次发给我的,还是对于我之前发送的syn+ack的回应。答案是内核会通过tcp四元组进行查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static inline struct sock *__inet_lookup(struct net *net,
                                         struct inet_hashinfo *hashinfo,
                                         struct sk_buff *skb, int doff,
                                         const __be32 saddr, const __be16 sport,
                                         const __be32 daddr, const __be16 dport,
                                         const int dif, const int sdif,
                                         bool *refcounted)
{
        u16 hnum = ntohs(dport);
        struct sock *sk;

        sk = __inet_lookup_established(net, hashinfo, saddr, sport,
                                       daddr, hnum, dif, sdif);
        *refcounted = true;
        if (sk)
                return sk;
        *refcounted = false;
        return __inet_lookup_listener(net, hashinfo, skb, doff, saddr,
                                      sport, daddr, hnum, dif, sdif);
}

先查询这个四元组是否已经在连接池里,然后再检查listener里是否存在连接,没有就直接返回send_reset, 发送rst。如果在连接池内就接受数据,如果在Listeners里就处理tcp状态,并且是TCP_LISTEN就进行第一次握手;如果状态时TCP_SYN_RECV状态,就要在缓存中记录客户端的syn包的内容,以便在收包过程中进行查找,也就是生成一个半连接体插入到半连接队列中。半连接队列的大小是backlog,表示可以同时连接的最大数量。

同时我们在考虑一个问题,如果说有攻击者估计发起多个客户端,只进行到第二次连接,这样不就导致服务端的半连接池被占满,无法进行新的连接吗?这就是著名的synflood攻击的原理。

解决方案是syncookie。既然sunflood能攻击的原因是,进行第一次握手后,服务端要缓存客户端信息在半连接上。我们可以不记录这个信息,这样就不需要缓存客户端信息在半连接上了。那我们又要如何知道发送来的ack是属于哪个半连接的呢?答案就是把第一次握手得到的信息放到发送回的数据包中,让客户端在发送的时候在发回来,然后解析这个信息和四元组就可以知道验证。为了要保证封装进数据包的内容够小,先将信息做哈希做哈希运算,这个运算出来的数据就叫做cookie。

下面来详细讲一下tcp三次握手时,内核到底发生了什么,各种标志时如何确定的

首先我们需要知道,tcp三次握手的过程实际上时连接体状态机的转变过程,服务端和客户端有各自的不同状态。服务端调用完listen后,会从CLOSE态转变为LISTEN态,客户端调用完connect后吗,会从CLOSE态转变为SYN_SENT态。这个时候客户端发送来一个数据包,这个数据包是如何组成的上文有讲,我们要知道里面有两个非常重要的位信息:SYN,表示该包是为了建立连接,seq,序号字段,表示该包在客户端的产生序号。

这个connect调用后客户端发送的第一份报文称为SYN报文。

alt text

第一次的seq时客户端随机生成的client_isn,SYN的标志置为1。

服务端接受到SYN报文后,拿到客户端的四元组信息,服务端也随机初始化自己的SYN+ACK报文序号seq(server_isn),将此序号填入seq,将接受到的client_isn+1填入ack,SYN和ACK标志都置为1,然后发送给客户端,此时服务端的状态变为SYN_RECV。

alt text

alt text

服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态

以上就是三次握手的过程。注意在第三次握手时,报文是可以携带应用层数据的,这个时候客户端可以开始传输数据了。

listen backlog

这里说明下listen系统调用的backlog参数。我们要知道backlog队列是什么。

q1:为什么TCP握手需要三次?

  • 阻止重复的历史连接的初始化
  • 三次是同步双方初始化序列号的最小握手次数
  • 避免浪费资源

阻止重复的历史连接的初始化

假设客户端发送了一个连接请求到服务端,但是由于网络的原因,服务端没有收到这个请求,那么客户端会再次发送一个连接请求,这个时候服务端就会收到两个连接请求,如果只需要两次握手,那么服务端就会认为这两个连接请求是同一个连接,这样就会导致历史连接的初始化,这样就会导致资源的浪费。但是如果有第三次握手,客户端就可以向服务端发送RST回复,说明这个连接已经被中止了,正在重新连接。这样就可以防止被网络等各种原因阻塞的历史连接导致了连接被重置。

那如果第二次握手之后,客户端发送完ack进入established态,发送的ack包丢了,导致服务端没有进入esblished态,是否会造成客户端发送数据丢失?

不会,因为客户端在建立连接之后的发送的数据包都会带有ack确认号,且和ACK包里的ack是一样的。

同步双方初始化序列号

首先来谈谈序列号的作用。服务端客户端双方都需要维护序列号,序列号的作用:

  • 识别重复发送的数据包
  • 根据数据包的序列号按序接受
  • 可以标识发送出去的数据包哪些使被客户端收到的,收到的数据包seq = 接受的ack-1

这样在三次握手的时候,双方都可以初始化自己的序列号,这样就可以保证数据包的顺序。

避免浪费资源 浪费资源这个问题其实在好处一就已经解释了,如果tcp只用两次握手,那么就会将历史连接设置为Extablished态,这样就会浪费资源。

TCP 连接断开

q1:TCP四次握手是怎样的一个过程?

TCP连接断开的过程就是大名鼎鼎的四次挥手的过程。四次挥手的过程如下图所是示:

alt text

下面来详细讲讲各个阶段发生了什么。

  • 客户端打算关闭连接,此时会发送一个TCP首部中FIN位置为1的报文,即FIN报文。之后客户端进入FIN_WAIT_1状态。
  • 服务端收到FIN报文后,发送回一个ACK报文,进入CLOSE_WAIT状态。然后进入处理。
  • 客户端收到ACK报文后,进入FIN_WAIT_2状态。
  • 服务端处理完后,发送一个FIN报文,进入LAST_ACK状态。
  • 客户端收到FIN报文后,发送回一个ACK报文,进入TIME_WAIT状态。
  • 服务端收到ACK报文后,直接进入CLOSED状态,释放相关TCP连接资源。
  • 客户端经过2MSL后,进入CLOSED状态,释放相关TCP连接资源。自此TCP四次握手结束。

注意:和三次握手不同,四次握手是可以由双方主动发起的,也就是说这张图是可以反转的,主动断开连接的一方有TIME_WAIT状态。

q2:TCP四次挥手为什么需要四次?

首先挥手过程需要发送两次FIN报文,且需要被ACK确认。然后这两次FIN发送又有不同的作用,第一次发送是由主动方发送的FIN报文,表示不在发送数据但是还可接受数据。被动方接受到FIN报文后,会发送ACK表示知道收到FIN了,但是被动方可能还有在接受FIN报文之前的数据没有发送出去,当所有待处理的数据全部发送完后,被动方就会发送FIN报文,表示自己也不发送数据但可以接受数据。主动方接收到FIN后向被动方发送ACK报文,表示知道了FIN报文。被动方接收到ACK报文后,就可以关闭连接了。

但是主动方需要有个time_wait状态,这个状态是为了保证被动方接收到ACK报文,然后再关闭连接。这个状态的时间是2MSL,MSL是最大报文段生存时间,一般是30s,所以time_wait状态是60s。至于具体为什么需要time_wait状态,下面会详细介绍。

q3:四次挥手如果分别丢失会发生什么?

第一次挥手丢失 && 第二次挥手丢失:

我们要知道四次挥手是在那个调用中完成的,是close调用中。客户端调用close调用后就会发送FIN报文。如果收不到来自被动方的ACK报文,就会触发超时重传机制。重发次数由系统内置参数决定就是tcp_orphan_retries。如果重发次数用完,直接断开连接。(超时重传每次等待时间是上一次的两倍)

第三次挥手丢失:

主动方收到来自服务端的ack回复后,就会进入FIN_WAIT_2状态,这个状态不会持续太久,60秒内没有收到来自被动方的FIN直接主动断开连接进入TIME_WAIT状态。

服务端收到FIN报文后,内核会自动回复ACK。此时进入CLOSE_WAIT状态。当应用层在被动方进入CLOSE_WAIT状态后调用read / write时,调用会返回错误表示连接已断开,此时应用层就会调用close关闭连接。调用了close后,内核就会发送FIN报文,进去LAST_ACK状态。同样如果收不到ack就会超时重传,超过重传次数就会直接断开连接。

第四次挥手丢失:

一张图直接说明第四次挥手丢失会发生什么:

alt text

这样就告诉我们为什么需要TIME_WAIT状态。在TIME_WAIT状态内,主动方可能会不断收到来自被动方重传的FIN报文,这样主动方就需要不断回复ACK,并且重置MSL计时器。

TIME_WAIT状态

q4:为什么TIME_WAIT需要定时2msl?

MSL: Maximum Segment Lifetime,最大报文段生存时间,是指一个报文在网络中最长的存活时间。这个时间是由网络中最长的存活时间决定的,一般是30s。所以2MSL就是60s。MSL的值是由TTL影响的。TTL是IP段可以经过的最大路由数,一般是64。内核认为TTL跳过64次需要的时间是30秒,如果30秒内没有到达,就说明网络包已丢失。一来一回需要等待 2 倍的时间。可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。

q5:为什么需要TIME_WAIT状态?

为什么需要time wait呢?在发送完ack后直接关闭连接不就好?为什么只有主动方才需要time_wait状态呢?

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

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

CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。

所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。 那什么情况会导致服务端的程序没有调用 close 函数关闭连接?这时候通常需要排查代码。

我们先来分析一个普通的 TCP 服务端的流程:

  • 创建服务端 socket,bind 绑定端口、listen 监听端口
  • 将服务端 socket 注册到 epoll
  • epoll_wait 等待连接到来,连接到来时,调用 accpet 获取已连接的 socket 将已连接的 socket 注册到 epoll
  • epoll_wait 等待事件发生 对方连接关闭时,我方调用 close

可能导致服务端没有调用 close 函数的原因,如下。

第一个原因:第 2 步没有做,没有将服务端 socket 注册到 epoll,这样有新连接到来时,服务端没办法感知这个事件,也就无法获取到已连接的 socket,那服务端自然就没机会对 socket 调用 close 函数了。 不过这种原因发生的概率比较小,这种属于明显的代码逻辑 bug,在前期 read view 阶段就能发现的了。

第二个原因: 第 3 步没有做,有新连接到来时没有调用 accpet 获取该连接的 socket,导致当有大量的客户端主动断开了连接,而服务端没机会对这些 socket 调用 close 函数,从而导致服务端出现大量 CLOSE_WAIT 状态的连接。 发生这种情况可能是因为服务端在执行 accpet 函数之前,代码卡在某一个逻辑或者提前抛出了异常。

第三个原因:第 4 步没有做,通过 accpet 获取已连接的 socket 后,没有将其注册到 epoll,导致后续收到 FIN 报文的时候,服务端没办法感知这个事件,那服务端就没机会调用 close 函数了。 发生这种情况可能是因为服务端在将已连接的 socket 注册到 epoll 之前,代码卡在某一个逻辑或者提前抛出了异常。

第四个原因:第 6 步没有做,当发现客户端关闭连接后,服务端没有执行 close 函数,可能是因为代码漏处理,或者是在执行 close 函数之前,代码卡在某一个逻辑,比如发生死锁等等。 可以发现,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用 close。

TCP 重传、滑动窗口、流量控制、拥塞控制相关面试题

前面讲述TCP连接的时候、经常讲到超时重传,重传机制实际上就是TCP保证可靠性的重要手段。todo

解决TCP的粘包问题

粘包问题是因为对于TCP来说,是一个流式传输的协议,没有明确的消息边界,所以在传输过程中,可能会将多个消息合并成一个消息发送,接收端不是到怎么把消息拆分开来,这就是粘包问题。解决方法呢有两个:第一个是用特殊字符作为消息边界,第二个是自定义消息的格式,比如消息可以由消息头和数据构成,消息头的长度是固定的,里面有提供数据的长度,这样接收方就知道怎么接收了。

Ip相关八股

IP层主要位于OSI模型中的第三层–网络层。实现主机与主机之间的通信,也叫点对点(end to end)通信。

IP基础知识

为保证正常通信,需要给每个设备配置正确的IP地址。IPv4地址由32位正整数构成,每8 位为组,共分为 4 组,每组以「.」隔开,再将每组转换成十进制。例:192.168.0.1。IP由网络标识和主机标识组成,网络标识用于标识网络,主机标识用于标识主机。IP地址分为 A、B、C、D、E 五类,其中 A、B、C 三类用于主机,D 类用于多播,E 类用于实验。如何判断网络标识和主机标识的位数呢,答案是子网掩码。

alt text

  • 主机号全为0,表示某个网络;
  • 主机号全为1,表示网络内的所有主机,即广播地址;

说到广播地址,广播地址是什么?

广播地址是用于在用一个链路中相互连接的主机之间发送数据包。广播地址可以分为本地广播和直接广播两种。在子网内进行进行广播的地址叫做本地广播地址,直接广播是指在不同子网之间进行广播的地址,也就是某个子网内的主机向另一个子网内的所有主机进行广播。

alt text

D类和E类地址的作用:D类E类地址没有主机号,D类用来多播,E类是预留号

说到多播,什么是多播?

多播(Multicast)是一种网络通信方式,它允许将数据包从一个源发送到多个指定的接收者,而不是发送给网络中的所有主机(广播)或单个主机(单播)。多播使用D类IP地址(224.0.0.0到239.255.255.255)来标识多播组,任何希望接收多播数据的主机可以加入相应的多播组。

  • 224.0.0.0 ~ 224.0.0.255 为预留的组播地址,只能在局域网中,路由器是不会进行转发的。
  • 224.0.1.0 ~ 238.255.255.255 为用户可用的组播地址,可以用于 Internet 上。
  • 239.0.0.0 ~ 239.255.255.255 为本地管理组播地址,可供内部网在内部使用,仅在特定的本地范围内有效

由于ip地址分类存在弊端,所以引入CIDR无地址分类。a.b.c.d/x。子网掩码还可以划分子网网络地址,没错,在内网里还可以接着划分子网。也就是一个内网中有多个子网。网络号是由x决定的。

公有ip地址和私有ip地址。

alt text

IP地址和路由控制。

IP地址中的网络号是负责进行路由控制的。重要的是我们需要知道自己的主机的路由表和路由器上的路由表有什么不同。主机的路由表的表项一般由两项组成,一是广播地址,二是默认路由。发送子网的ip会被广播,发送其他网络的ip会被默认路由route到router上进行网络转发。

alt text

路由器的路由表则记录了和路由器有直接链路连接关系的各个网络的下一跳信息。

alt text

IP的分片和重组

IP的分片和重组会导致IP分片丢失导致整个IP包的作废。所以在TCP中引入MSS,也就是数据最大长度,使其和tcp头ip头加起来不会超过MTU,实现在TCP层面分片,由于TCP的可靠性来保证分片的可靠性。但是UDP不提供重传机制,所以UDP的报文尽量不要超过MTU;

Ipv6的基本认识

IPv6一共128位,每16位为一组,用:隔开。

Ipv4和IPv6的头部!最好记下来,了解每个部分的作用

alt text

This post is licensed under CC BY 4.0 by the author.