八股集录

本文最后更新于 2024年9月24日 下午

day1

OSI模型

Open System Interconnect,OSI将计算机网络通信协议分为7层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

TCP/IP模型

应用层——应用、表示、会话:HTTP、SSH、FTP

传输层——传输:TCP、UDP

网络层——网络:IP

网络接口层——数据链路层、物理层:以太网、WIFI、MAC地址

理解:TCP/IP更早,更实际、实用;OSI更偏向于理论研究,更细化,更通用

从输入URL到页面展示的过程

  1. 输入URL,解析URL,准备发送HTTP请求
  2. 检查浏览器缓存,如果有则直接返回,如果没有则准备发送网络请求
  3. 发送网络请求前要进行DNS解析,获取域名的IP地址。如果请求协议是HTTPS,还要建立TLS连接。DNS解析从本地缓存、本地Host文件、路由器缓存、DNS服务器,一直查询域名直到根DNS服务器,直到找到为止。
  4. TCP三次握手建立浏览器和服务器之间的TCP连接
  5. 连接建立后,客户端构建请求信息,发送HTTP请求,如果是HTTPS,还要加解密
  6. 服务器收到请求之后根据请求响应数据
  7. TCP四次挥手断开连接
  8. 浏览器解析响应数据,展示出相应的页面

理解:刚开始检查用不用发请求,如果需要发请求,就先解析,然后三次握手,后端响应,四次挥手断开,最后前端展示

day2

HTTP请求、响应报文是怎样的,有哪些常见的字段

请求报文: 由请求行、请求头、空行、请求体构成。请求行包括:

  • 方法:GET、POST、PUT、DELETE等
  • 资源路径,也就是url
  • HTTP版本,使用的http协议,比如1.1、2.0

请求头的字段比较多,常用的有:

  • Host:请求的服务器的域名
  • Accept:客户端能够处理的媒体类型
  • Accept-Encoding:客户端能够解码的内容编码
  • Authorization:用于认证的凭证信息,比如token
  • Content-Length:请求体的长度
  • Content-Type:请求体的媒体类型
  • Cookie:存储在客户端的cookie数据
  • If-None-Match:资源的ETag值,用于缓存控制
  • Connection:管理连接的选项,如keep-alive

空行是分隔请求头和请求体的空行

响应报文:服务器向客户端返回的数据格式,通常包含状态行、响应 头、空行、响应体

状态行包含HTTP版本、状态码、状态信息,比如http/2.0 404 notFound

响应头的常见字段有:

  • Content-Type:响应主体的媒介类型
  • Content-Length:响应主体的长度
  • Server:指定服务器的信息
  • Expires:响应的过期时间
  • ETag:响应体的实体标签,用于缓存和条件请求
  • Last-Modified:资源最后被修改的日期和时间
  • Location:在重定向时指定新的资源位置
  • Set-Cookie:在重定向时指定新的资源位置

理解:行+头+空行分隔+体

行里基本配置信息,头里一堆字段,体里是数据

HTTP有哪些请求方式

Get:请求指定的资源

Post:向指定资源提交数据进行处理

Put:更新资源

DELETE:删除资源

HEAD:获取报文首部,不返回报文主题(判断资源是否存在)

OPTIONS:查询服务器支持的请求方法(通过Allow头来判断)

PATCH:对资源进行部分更新

理解:望文生意即可

GET请求和POST请求的区别

  • get一般用来获取数据,post如其名用来提交数据
  • get参数放在路径里,而post的在请求体里
  • 由于参数的位置,post相比get更安全,参数量也更大
  • get请求不会改变资源的状态,而post会改变,称为幂等性
  • get可以被缓存,而post默认不缓存

理解:仍然望文生义,注意最后一点

day3

HTTP中常见的状态码有哪些

  • 200:客户端请求成功
  • 201:创建了新资源
  • 204:无内容,服务器成功处理了请求,但未返回内容

  • 301:永久重定向
  • 302:临时重定向
  • 304:请求的内容未修改,浏览器使用缓存返回

  • 401:需要身份验证
  • 403:禁止访问
  • 404:资源未找到

  • 500:服务器内部错误
  • 503:服务器刚启动,未准备好

理解:大体分四类,2开头为成功,3开头表示资源变化,4开头请求错误,5开头服务错误

整百读整百,其他单个读:500-伍佰、404-四零四

什么是强缓存和协商缓存

强缓存:浏览器对之前请求过的资源进行缓存,下一次请求时不发送请求,而直接返回本地缓存的资源。

  • 在Http 1.0版本中,通过Expire响应头来实现强制缓存,是一个时间点,表示资源过期时间。

  • 在1.1版本中,引入了Cache-Control响应头,可以通过max-age设置最大生存时间

  • 如果响应头中没有任何和缓存相关的字段,浏览器会采用启发式算法决定是否缓存资源。通常基于DateLast-modified值计算

强缓存可能导致资源更新时的不一致,需要合理设置更新频率。


协商缓存:浏览器和服务器通信以确认缓存资源是否有效。

主要涉及两组响应头字段

  • ETag/If-None-Match:浏览器第一次请求时,服务器会返回ETag,是该资源的唯一标识符,后续再请求,请求头中的If-None_Match就会携带ETag,服务器来判断该资源是否更新,如果未更新,则返回304 Not Modified,如果更新了则返回新的资源和ETag。
  • Last-Modified/If-Modified-Since:服务器会返回资源和其最后修改时间Last-Modified,后续请求的请求头会携带If-Modified-Since,服务端检查是否过期。返回同上。

协商缓存确保获取最新资源,避免不必要的资源传输。


理解:强缓存应用于不常变动的静态资源,如图片,css,js等

协商缓存适用于变动频繁的资源

day4

HTTP 1.0和HTTP 1.1的区别

  • 持久连接:HTTP 1.1默认持久连接,也就是一个TCP连接可以发送多个HTTP请求响应,减少了连接和关闭的开销。而HTTP 1.0默认短连接,每次请求都需要建立一个TCP连接,通过Connection:keep-alive头来实现持久连接(可被拒绝)
  • 管道化:HTTP 1.1支持管道化,允许客户端再第一个请求的响应到达前发送多个请求,减少等待时间。HTTP 1.0不支持管道化。管道化的前提是使用长连接
  • 缓存控制:HTTP 1.0主要使用If-Modified-SinceExpires来缓存控制。HTTP 1.1引入了ETag/If-None-Match等更多的缓存头来控制缓存
  • 错误码:HTTP 1.1有更多错误响应码,如 426 Upgrade Required
  • Host字段:HTTP 1.1加入了Host头,让客户端指定请求的主机名,可以在同一台服务器上托管多个域名。HTTP 1.0没有这个字段
  • 请求方法:HTTP 1.1有更多请求方法,如OPTIONS、PUT、DELETE、HEAD、PATCH
  • 带宽优化:HTTP 1.1中可以部分请求资源,使用range头设定范围,返回码是206 Partial Content

理解:降低了原子性:持久、管道;加入了更多的功能

HTTP 1.1和HTTP 2.0的区别

  • 二进制协议vs文本协议:HTTP 2.0 采用二进制格式传输数据,解析高效还不容易出错。而HTTP 1.1使用可读的文本格式,解析和处理都较慢
  • 多路复用:HTTP 2.0支持多路复用,允许单个TCP连接上并行交错发送多个请求和响应,缓解了HTTP 1.1中的队头阻塞问题。在丢包场景下还是会出现队头阻塞问题
  • 头部压缩:HTTP 2.0引入了HPACK压缩算法,对请求响应头压缩,减少了冗余头部信息的传输
  • 服务器推送:HTTP 2.0允许服务器主动推送资源给客户端
  • 优先级和依赖:HTTP 2.0允许客户端为请求设置优先级,并表达请求之间的依赖关系,让资源加载更有序。

理解:改进传输,实现双向,优先级

HTTP 3.0

HTTP 3是HTTP的最新版本,基于QUIC协议,特点有:

  • 无队头阻塞:QUIC使用UDP来传输数据,一个连接上多个stream之间没有依赖,如果一个stream丢了一个UDP包,不会影响后面的stream,不存在队头阻塞的问题。
  • 零RTT连接建立:QUIC允许首次连接时进行零往返时间连接建立,QUIC 协议通过将握手过程(包括加密握手)减少到一次往返(甚至为 0-RTT),大大缩短了连接建立的时间。这意味着第一次与服务器连接时延迟显著降低,用户体验更加流畅。
  • 连接迁移:QUIC允许在网络切换(如从WIFI切到移动网络),将连接迁移到新的IP地址,从而减少连接的中断时间。
  • 向前纠错机制:每个数据包除了它本身的内容之外,还有其他包的数据,所以少量丢包可以用其他包的数据而不需要重新传。虽然降低了每个包的有效信息,但是减少了丢包数据重传。
  • 安全性:HTTP 3.0默认TLS加密,确保了数据传输的安全

理解:从TCP变成了UDP,基于QUIC,所以少了一些TCP的问题:建立连接开销大、队头阻塞。

需要结合 层 来理解,看图

day 5

HTTPS 和 HTTP 有什么区别

区别主要在于安全性和数据加密:

  • 加密层:HTTPS在HTTP的基础上增加了SSL/TLS(现代只使用TLS)协议作为加密层。HTTP是明文的
  • HTTP在TCP三次握手后开始报文传输,HTTPS在三次握手后还要SSL/TLS的握手才能进入加密报文传输。
  • 端口:HTTPS通常使用443端口,HTTP使用80端口
  • HTTPS需要CA证书,来保证服务器身份可信

HTTPS的S就是Secure,所以主要区别在于安全性:多了TLS -> 多了TLS的握手;多了CA证书

HTTPS的工作原理(HTTPS建立连接的过程)

过程如下:

  1. 密钥交换:客户端发起HTTPS请求后,服务器会发送其公钥证书给客户端
  2. 证书验证:客户端会验证服务器的证书是否由CA签发,检查有效性
  3. 加密通信:证书验证通过,客户端生成一个随机的对称加密密钥,使用服务器的公钥加密该密钥,发送给服务器
  4. 建立安全连接:服务器使用自己的私钥解密得到对称加密密钥,此时客户端与服务端都有了相同的密钥,可加解密
  5. 数据传输:将所有数据对称加密传输
  6. 完整性校验:SSL/TLS协议还包括消息完整性校验,如消息认证码,确保数据在传输过程中未被篡改
  7. 结束连接:数据传输完成后,通信双方销毁密钥。

理解:公钥用于加密,私钥用于解密,只有对应的公私钥才能彼此加解密,通过非对称加密传递对称加密的密钥,实现对称加密。

TCP和UDP的区别

  1. TCP需要在传输前建立可靠连接,UDP不需要建立连接
  2. TCP保证数据包的顺序和完整,UDP不保证
  3. TCP可根据网络状况调整传输速率,UDP传输速率固定
  4. TCP通过滑动窗口进行流量控制,UDP没有流量控制
  5. TCP能检测重传丢包,UDP没有
  6. TCP报文头复杂,UDP报文头简单
  7. TCP建立连接开销大,UDP简单开销小
  8. TCP适用于可靠传输,UDP适用于实时性高的传输

理解:TCP稳定可靠,代价就是事儿多;UDP事儿不多,但是不稳定不可靠

day 6

TCP连接如何确保可靠性

TCP通过这些机制来保证可靠性:

  • 序列号:每个TCP都有一个序列号,确保数据包的顺序正确
  • 数据校验:TCP使用校验和来检测数据在传输过程中是否出现错误,如果错误,就会丢弃该包并等待重传
  • 确认应答:接收方会发送ACK确认收到数据。
  • 超时重传:如果发送方的定时器超时了还未收到ACK,会触发超时重传
  • 流量控制:TCP通过滑动窗口进行流量控制,确保接收方能够处理发送方的数据量
  • 拥塞控制:TCP通过慢启动、快重传、快恢复、拥塞避免等控制数据的发送速率,防止网络拥塞

拥塞控制如何实现的

  • 慢启动:初始阶段,TCP发送方从小发送窗口开始,越成功窗口越大,指数级增长,称为慢启动。在开始时逐步增加速率,避免引起拥塞
  • 拥塞避免:一旦达到一定的阈值,TCP发送方就会进入拥塞避免阶段:发送方以线性增加的方式增加发送窗口的大小。
  • 快速重传:如果发送方连续收到相同的确认,会认为丢包,并快速重传未确认的包,而不等待超时。
  • 快速恢复:发生快速重传后,发送方不会重新慢启动,而将慢启动阈值设置为当前窗口的一半,将拥塞窗口的大小设置为慢启动阈值加上已确认但未快速重传的包的数量。

理解:先慢后快,然后试探上限,过线就减半重来,目的就是在靠近上限的地方波动,保持总体较高的传输速率而不导致拥塞

TCP流量控制是怎么实现的

主要目的就是发送方不能太快,接收方来得及接受。这就需要动态地根据网络情况来调整传输速率。

  • 滑动窗口大小:每个TCP报文段都有一个窗口字段,提示发送方可以发送多少字节的数据而不等待确认。
  • 接收方窗口大小:接收方通过该字段告诉发送方目前可接受的窗口大小,主要依据接收方缓冲区大小,如果接收方缓冲区快满,会减小窗口,或通知发送方暂停发送
  • 发送方根据窗口的大小动态调整发送速率,根据ACK中的窗口字段获取最新的窗口大小

理解:每次ACK都是接收方告诉发送方当前速率是否合适

UDP如何实现可靠传输

UDP(User Datagram Protocol)本身是一种无连接、不可靠的传输协议,不提供像 TCP 那样的可靠性机制,如数据重传、数据包顺序控制、流量控制和拥塞控制。因此,UDP 在设计上更适合需要低延迟或能够容忍一定数据丢失的应用,比如实时视频流、在线游戏和语音通信。

然而,在一些需要可靠传输的应用场景中,可以通过在应用层(即在 UDP 之上)自行实现额外的可靠性机制来弥补 UDP 的不足。

  • 添加序列号
  • 超时重传
  • 向前纠错
  • 错误校验
  • 流量控制

理解:貌似TCP的特性加在UDP上就好了

day7

TCP三次握手的过程,为什么是三次,可以少或者多吗

三次握手的过程:

  • 第一次握手:客户端向服务端发送一个SYN(同步序列编号)报文,请求建立连接,客户端进入SYN_SENT状态
  • 第二次握手:服务端收到SYN报文后,如果同意,则发送一个SYN_ACK(同步确认)报文作为响应,同时进入SYN_RCVD状态
  • 第三次握手:客户端收到服务器的SYN_ACK报文后,会发送一个ACK确认报文作为最终响应,之后客户端和服务端都进入ESTABLISHED状态,连接建立成功。

三次正好可以确认双方的收发功能正常。多了冗余,少了不行。

TCP四次挥手的过程,为什么是四次

四次挥手的过程:

  • 第一次挥手:客户端发送FIN报文给服务端,表示要断开数据传送,报文中会指定一个序列号(seq=x)。然后,客户端进入FIN-WAIT-1状态。
  • 第二次挥手:服务端收到FIN报文后,回复ACK报文给客户端,且把客户端的序列号+1,作为ACK报文的序列号(seq=x+1)。然后,服务端进入CLOSE-WAIT(seq=x+1)状态,客户端进入FIN-WAIT-2状态。
  • 第三次挥手:服务端也要断开连接时,发送FIN给客户端,且指定序列号(seq=y+1),随后服务端进入LAST-ACK状态。
  • 第四次挥手:客户端收到FIN报文后,发出ACK应答,把服务端的序列号+1作为ACK序列号(seq=y+2)。此时,客户端进入TIME-WAIT状态。服务端收到客户端的ACK后进入CLOSE状态,如果客户端等待2MSL没有收到服务端的回复,则关闭连接。

为什么四次:

TCP是全双工通信,双向传输数据,双方都可以提出释放连接的通知,让对方进入半关闭状态,待对方传输完毕后,发回连接释放通知,己方确认后才会完全关闭TCP连接。前两次让发起方发出通知和对方发出确认;后两次让对方做完最后的传输,发出同意,让己方收到确认,最终关闭。

发起方提前预警,接收方开始准备断开,接收方准备完成,发起方收到确认放心断开

HTTP的Keep-Alive是什么?TCP的Keepalive是和HTTP的Keep-Alive是一个东西吗

  • HTTP的Keep-Alive,是由应用层实现的,称为HTTP长连接。HTTP短连接为:建立TCP连接 -> HTTP请求资源 -> 响应资源 -> 释放连接。但是每次连接只能请求一次资源,所以HTTP的Keep-Alive实现了使用同一个TCP连接来发送接受多个HTTP的请求应答,减少多次连接建立和释放的开销。
  • TCP的Keepalive,由TCP层(内核态)实现的,称为TCP保活机制,是一种用在TCP连接上检测空闲状态的机制。当TCP连接建立后,如果一段时间没有数据传输,TCP的Keepalive会发送探测包来检查连接是否还有效。

HTTP的是长连接,TCP的是检测连接是否还有效。一个高层一个底层

day8

DNS查询过程

DNS称为域名服务器(Domain Name System),用于将主机名和域名转换为IP地址,一般查询过程为:

  • 本地DNS缓存检查:首先查询本地的DNS缓存,如果缓存里有对应的IP,则直接返回结果
  • 本地的DNS服务器:通常是当地的ISP提供,向其发送DNS查询请求
  • 根DNS服务器:如果本地DNS服务器没有,则其会向根DNS服务器查询。根DNS服务器不负责具体的域名解析,但能够告诉本地NDS服务器应该向哪个顶级域的DNS服务器继续查询
  • 顶级域名DNS服务器:本地DNS服务器接着向顶级域名DNS服务器发出查询请求。顶级域名DNS服务器也不负责具体的域名解析,但会告诉本地DNS服务器应该前往哪个权威DNS服务器查询下一步信息。
  • 权威DNS服务器:本地DNS服务器最后向权威DNS服务器发送查询,权威DNS服务器会返回对应的IP地址
  • 本地DNS解析器收到IP后返回给浏览器,并且缓存该解析结果。
  • 浏览器获得IP后,与目标服务器建立连接。

本地->本地ISP的DNS->根->顶级域名->权威

CDN是啥,有什么用

CDN是内容分发网络(Content Delivery Network),通过将内容存储在分布式服务器上,使用户可以从距离较近的服务器获得所需的内容,加速互联网的内容传输。

  • 就近访问:CDN在服务范围内会部署多个服务器节点,用户的请求会送到最近的CDN节点,加速访问。
  • 内容缓存:CDN会缓存静态资源(图片、CSS、Script)。当用户访问时,CDN会使用缓存,没有才去源服务器获取资源并缓存。这减少了源服务器的负载
  • 可用性:一个节点出了问题,还可以被重定向到其他的节点

对于博客,这可太熟悉了,当分布式说吧

Cookie和Session是什么

Cookie和Session都用于管理用户的状态和身份,Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。

  • cookie由服务器发给浏览器,浏览器保存cookie并在将来的请求中包含cookie,服务器收到后续请求后可以分析cookie从而确定身份。
  • Session时服务器把客户端信息处理后记录在本地,主要用于维护登陆状态、存储临时数据等。服务器为每个用户分配Sesson ID,通常存在cookie中发给客户端

Cookie和Session的区别:

  • 存储位置:Cookie存在浏览器,Session存在服务器
  • 数据容量:Cookie较小,Session较大
  • 安全性:Cookie存在浏览器内,可以被篡改,而Session不会
  • 生命周期:Cookie可设置过期时间,Session依赖于会话的持续时间或用户活动
  • 传输方式:Cookie在每次HTTP请求中都会被自动发送给服务器,而Session ID由Cookie或URL传递参数给客户端

cookie浏览器拿着,有信息;session防止被改,自己拿着,但容易拿不下

day9

进程和线程的区别

进程是资源分配和调度的最小单位

线程是程序执行的最小单位

  • 线程是进程内的子任务,一个进程中的多个线程共享内存空间,进程之间内存空间独立。进程切换、创建、销毁的开销都大于线程。

  • 进程之间通信需要一些机制:管道、消息队列、共享内存等。但线程可以直接访问共享数据。

  • 进程相互隔离,而线程的错误可能会影响其他线程,影响整个进程的稳定性。

今天都是操作系统的基础,老考点了

并行和并发的区别

并行是真正意义上的同时进行,同一时刻有多个在执行。而并发是在短时间内相继执行大量任务,在宏观上看起来像同时发生,实际上仍然是交替执行的。

老考点了,可以结合实例讲

解释用户态和内核态,什么情况下会发生切换

用户态和内核态是操作系统为了保护系统资源和权限控制而设计的两种不同的CPU运行级别,可以控制进程或程序对计算机硬件资源的访问权限和操作范围

  • 用户态:进程和程序只能访问受限的资源和执行受限的指令集,不能直接访问OS的核心部分,也不能直接访问硬件资源。
  • 核心态:OS的特权级别,允许进程或程序执行特权指令和访问操作系统的核心部分。在核心态下,进程可以直接访问硬件资源,执行系统调用,管理内存、文件系统等操作。

发生切换的场景:

  • 系统调用
  • 异常
  • 中断

也是经典知识点,看看就行了,已经知道了

day10

什么是死锁,如何避免

死锁就是系统中两个或多个进程在执行过程中,因争夺资源而造成的一种僵局。当每个进程都持有一定的资源并等待其他进程释放他们的所需的资源时,这些资源都被其他进程占据却不释放,就导致了死锁。

死锁的条件有四个:

  • 互斥
  • 请求保持
  • 不可剥夺
  • 循环等待

所以可以通过破坏除互斥之外的三个条件之一来预防死锁。也可以通过银行家算法等检测死锁,可以通过挂起或终止某些进程来解除死锁。

老知识点,思路就是:预防(破坏条件)-检测(银行家)-解除(挂起,暴力终止)

介绍一下几种典型的锁

  • 互斥锁:最常见的锁类型。任何时刻只允许一个线程持有,其他线程必须等待。
  • 自旋锁:基于忙等待的锁,线程在尝试获取锁时会不断轮询,直到锁被释放。

其他的锁都是基于这两个锁的:

  • 读写锁:允许多个线程同时读写和共享资源,只允许一个线程进行写操作。分为读(共享)和写(排他)两种状态。
  • 悲观锁:认为多线程同时修改资源的可能性比较高,所以访问共享资源时要上锁。
  • 乐观锁:多线程能够同时访问和修改,但是如果修改前后原资源发生变化,证明出现同时修改,则放弃本次操作。

讲一讲你理解的虚拟内存

虚拟内存不是真实存在,而通过映射与实际物理地址空间对应。使每个进程看起来有连续的地址空间。

需要虚拟内存的原因:

  • 内存扩展:虚拟内存使得程序能够使用比实际内存更大的存储空间
  • 内存隔离:虚拟地址空间让进程之间的地址空间相隔离
  • 物理内存管理:虚拟内存允许操作系统动态地将数据和程序离散地加载到物理内存中,并且可以将不常用的数据或程序移到硬盘上,从而释放内存供其他进程使用。
  • 页面交换:物理内存不足时,可以通过页面交换将外存作为虚拟内存使用,虚拟地扩大了内存空间。

day11

线程同步的方式有哪些

线程同步是多线程编程中,保证线程间互不干扰的一种机制,常见的线程同步机制有以下几种:

  • 互斥锁
  • 条件变量:用于线程间通信,允许一个线程等待某个条件满足,其他线程可以发出信号通知等待线程,通常与互斥锁一起使用。
  • 读写锁:允许多个读,只允许一个写
  • 信号量

有哪些页面置换算法

  • OPT最佳置换算法
  • FIFO先进先出
  • LRU最近最久未使用
  • LFU最不经常使用
  • CLOCK轮转

比较基础,很八股

day12

熟悉哪些linux命令

lscdpwdrmtouchcatvi/vimchmodchownpingsshpskillapt-getnetstartgrep、awk、sed

作用就不写了,太基础了,除了最后的三剑客,其他不会就搜搜吧

Linux如何查看进程、杀进程、看端口

  • 看进程:ps aux:列出所有进程和信息
  • 杀进程:kill -9 pid:强制杀死这个pid
  • 看端口:lsof -i:端口号查看特定端口占用。或者用 netstat -tulnp | grep 端口号显示监听在该端口的服务及其进程ID。

说一下select、poll、epoll

I/O多路复用通常通过select、poll、epoll等系统调用实现

  • select:最古老的I/O多路复用机制,可以监视多个文件描述符的可读、可写和错误状态。然而select的效率可能随着监视的文件描述符数量的增加而降低。
  • poll:poll是select的一种改进,使用轮询检查多个文件描述符
  • epoll:Linux特有的IO多路复用机制,相较于select和poll,在处理大量文件描述符时更高效。使用事件通知的方式,只有在文件描述符就绪时才会通知应用程序,而不使用轮询。

尚未学习……


八股集录
https://novelyear.github.io/2024/08/21/八股集录/
作者
Leoo Yann
更新于
2024年9月24日
许可协议