一、网络编程
1、简述Reactor网络模型编程
Reactor 模式是一种典型的 I/O 多路复用 + 事件驱动 的高性能网络编程模型,被广泛应用于服务器端开发(如 Nginx、Netty、libevent)。它的核心思想是:通过事件分离、回调机制和非阻塞 I/O,提高服务器并发处理能力。
核心思想:
- 事件分发器(Reactor):负责监控多个客户端连接请求与I/O事件(read、write、accept),并在事件发生后分给对应的事件处理器。
- 事件处理器(handle):每一类事件对应一个处理器。当事件发生时Reactor调用相应的Handle进行业务逻辑处理
- 非阻塞IO(epoll、select、poll):多个连接复用一个线程进行监听,减少大量阻塞导致的线程开销。
Reactor 网络模型通过 I/O 多路复用 + 事件驱动的方式,让少量线程处理大量网络连接事件,是高性能服务器的经典编程模型。
分为:单Reactor单线程、单Reactor多线程、多Reactor多线程。
2、比较Reactor与Proactor区别
**Reactor是非阻塞同步I/O,**操作系统只负责通知"可以读/写",应用程序负责真正的 I/O 操作。
**Proactor 是异步 I/O 模式。**操作系统负责真正的读写,完成后通知应用程序处理结果。
3、连接断开有哪几种判定方式?
网络通信中,一条 TCP 连接是否"断开",通常可以通过以下几种方式判断
- recv()/read()返回0,对方正常关闭(FIN)
- recv()/read()返回-1且error表示严重错误(异常断开的)
- send()/write() 返回错误(如 EPIPE、SIGPIPE)
- epoll/select/kqueue 事件异常(HUP / ERR)
- 对端长时间无响应(心跳超时)
- TCP keepalive(内核级)检测断开
TCP 连接断开最可靠的判断是:read=0 或出现严重错误 ;对于无数据传输的情况需要"心跳机制 "或"TCP Keepalive"辅助判断。
4、接收客户端连接有几种方式?
- 单线程+阻塞accept
- 多线程+阻塞式accept
- 非阻塞+I/O复用
- epoll+非阻塞accept
- 主从Reactor实现高并发
- 使用事件库接收连接:libev
接收客户端连接的方式有:阻塞 accept、多线程 accept、非阻塞 + select/poll、epoll Reactor 模型、主从 Reactor、异步事件库、IOCP。
5、为什么用户态需要设计读写缓冲区?
提高网络 I/O 性能、减少系统调用成本、解决数据边界和协议解析问题,并保证高并发服务器的稳定运行。
可以从以下方面理解:
- 系统调用开销大,缓冲区能减少系统调用次数(批处理)
- TCP 是流(Stream),不保证消息边界 → 需要应用层自己"粘包、拆包"
- 避免数据丢失:内核缓冲区有限,不能无限接收
- 便于异步网络模型处理
- 处理半包、粘包、协议解析必须依赖用户态 buffer
- 异步写必须缓存未发送的数据
二、网络原理
1、水平触发与边缘触发的区别?在边缘触发下,一个Socket已读取200然后不再处理,是不是剩下的300就无法读取了?
1)水平触发:只要FD上仍有""数据可读/可写" ,epoll就持续通知。没有读完,下次epoll_wait()还会继续触发。最安全,最容易使用。
2)边缘触发 :只有状态从 无 → 有 时触发一次 。若未把 socket 数据读完,后续不会再次通知。性能更高,但需要一次把数据读完。
在边缘触发下,一个 Socket 已读取 200,然后不再处理,是不是剩下的 300 就无法读取了?
是的 ,剩下的 300 你将无法再读出来,除非有新的数据到达触发事件。这是 ET 的典型"事件丢失"情况。数据不会丢失,仍在内核缓冲期,直到下次事件触发才能读取。
2、CLOSE_WAIT和TIME_WAIT是什么?如何排查?有什么意义?
首先我们清楚这两个状态属于TCP'四次挥手。
产生原因:
- CLOSE_WAIT :收到对端FIN包并回复ACK后,等待应用层调用
close()发送FIN的阶段 - TIME_WAIT:发送最后一个ACK后,保持2MSL(通常60s)防止报文混淆
CLOSE_WAIT 状态表示:
对端主动关闭连接(发送 FIN)→ 本端已经收到 FIN,但本端还没有 close()。
**常见原因:**应用未正确关闭socket;文件描述符泄漏;线程阻塞导致无法处理关闭事件
CLOSE_WAIT 多=代码问题。可以通过以下解决:Valgrind检测内存泄漏
TIME_WAIT 状态表示:
主动关闭连接的一方,在发送 FIN + 收到 ACK 后,需要等待 2MSL 时间。
TIME_WAIT 多=业务模式问题,
服务端:短连接多,可以采用长连接,并使用epoll+连接池减少重复建立连接。
意义:
- CLOSE_WAIT:标识连接半关闭状态,等待应用层处理收尾工作
- TIME_WAIT:保证可靠终止、防止旧报文干扰新连接
3、TCP三次握手的过程?为什么可以是二次握手?
tcp建立过程:

- 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤
- 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
4、TCP四次挥手过程?TIME_WAIT为什么至少设置两倍的MSL时间?
TCP四次挥手:

MSL:指一个 TCP 报文在网络中可能存在的最长时间。
本质作用是
- 保证对端收到最后 ACK
- 让旧连接的残留报文全部在网络中消失,不影响新连接
场景1:对端没收到 ACK → 重发 FIN(等待 1×MSL)
场景 2:网络上可能还有旧连接的残留报文(再等 1×MSL)
一个用于等待重传,一个用于清除残留数据。
5、什么是连接的半打开,半关闭状态?
半关闭状态:TCP 连接的一端关闭了自己的"发送方向",但仍然保持"接收方向"畅通。
半关闭的用途:
- 应用层协议中表示"发送结束,但要继续接收"
- 防止死锁(双方都在等对方写)
- HTTP/1.0 的请求后关闭写端,等待响应
半打开状态:连接的一端已经异常退出(崩溃/网络断开/无正常 FIN),而另一端仍然认为连接是"打开"的。
半打开的危害:
- 服务端可能为"死连接"保留资源
- 心跳机制检测不到会造成长时间僵尸连接
- 高并发下会导致资源耗尽
检测方法:
- 心跳检测、keepalive
- 超时机制
半关闭:一端发送通道关闭,但接收通道仍开,是 TCP 协议正常行为。
半打开:连接一端失效而另一端不知情,是异常状态,需要心跳/超时清理。
6、Linux IO模型有哪几种?简述IO多路复用机制?
- 阻塞I/O:read() / recv() 会一直阻塞,直到数据到达并复制到用户缓冲区完成。
- 非阻塞 I/O :read() / recv() 不阻塞,若无数据立即返回
EAGAIN - I/O 多路复用:使用一个线程监控多个 fd,当某个 fd 准备好(可读/可写)时再进行 I/O。
- 信号驱动 I/O:内核收到数据时,向进程发送 SIGIO 信号,通知它可以读。
- 异步 I/O :用户发起 aio_read,内核异步执行读写并自动填写用户缓冲区,完成后发送通知。
IO 多路复用是什么?
通过一个线程监听多个 socket,当任意 socket 就绪时再执行 read/write,从而避免为每个连接建立一个线程。
常用实现:epoll、poll、select。
7、阻塞IO与非阻塞IO区别?
阻塞I/O:当应用调用read()/recv()时,如果数据没有到达线程就一直阻塞等待。直到数据准备好并复制到用户缓冲区后才返回。整个等待期间,线程被挂起,无法做其他事。
非阻塞 I/O:当应用调用read()/recv()时,若数据没有准备好立即返回-1并设置errno=EAGAIN,不会阻塞数据到达。函数不会阻塞立即返回,通过询问查询。
8、select、poll、epoll三者区别
|----------|----------|--------------|-------------------|
| | select | poll | epoll |
| 底层结构 | 位图 | 数组 | 红黑树+就绪列表 |
| 最大连接数 | 1024 | 无上限 | 无上限 |
| 事件检查方式 | 轮询扫描O(n) | 轮询扫描O(n) | 回调通知 O(1) |
| 内核、用户态拷贝 | 每次拷贝fd集合 | 每次拷贝整个 fd 列表 | 只在注册时拷贝一次 |
| 触发方式 | 水平触发 | 水平触发 | (LT)水平触发+(ET)边缘触发 |
| 性能 | 差 | 较差 | 高性能 |
| 使用难度 | 简单 | 简单 | 较复杂(边缘触发)ET难度大 |
9、为什么边缘触发一定是非阻塞IO?
ET 模式只在状态变化的瞬间触发事件,如果套接字是阻塞式的,那么程序在事件处理过程中可能因阻塞而错过读取全部数据,从而永远不会再次获得读事件 → 造成死锁或假死。
因此 ET 必须使用非阻塞 I/O,让程序能读到 EAGAIN 才能保证状态正确推进。
10、描述IO多路复用机制
把等待多个 I/O 事件的工作交给内核,让一个线程高效管理大量连接,从而避免为每个连接创建一个线程或进程。
IO 多路复用是让一个线程同时监控多个 socket,只在 socket 就绪时进行 I/O,
避免线程阻塞等待,是高并发服务器的基础。
select 和 poll 需要遍历所有 fd,效率 O(n);
epoll 使用事件通知机制,只处理就绪的 fd,效率 O(1),适合大量并发连接。