C++ 中高级面试、后台开发高频考点,socket、TCP/UDP、IO多路复用为核心,EPoll/_select 为面试重灾区,结合项目场景命题较多。
1. socket 编程(TCP server/client)
1.1 TCP 服务端流程
cpp
// 1. 创建socket
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
// 3. 监听
listen(server_fd, 128);
// 4. 接受连接
int client_fd = accept(server_fd, NULL, NULL);
// 5. 读写数据
read(client_fd, buffer, size);
write(client_fd, buffer, size);
// 6. 关闭
close(client_fd);
close(server_fd);
1.2 TCP 客户端流程
cpp
// 1. 创建socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 2. 连接服务器
connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 3. 读写
write(sock, buffer, size);
read(sock, buffer, size);
// 4. 关闭
close(sock);
1.3 三次握手 / 四次挥手
三次握手:
- SYN -> 服务器
- <- SYN+ACK
- ACK -> 建立连接
四次挥手:
- FIN -> 关闭发送
- <- ACK
- <- FIN(关闭接收)
- ACK -> 确认关闭
1.4 面试追问
- 为什么是三次? 双方都需要确认对方接收能力
- 为什么是四次? 关闭是半关闭,需要分别确认
2. TCP vs UDP
2.1 核心区别
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 顺序 | 有序 | 乱序 |
| 开销 | 大 | 小 |
| 速度 | 慢 | 快 |
| 场景 | 文件/邮件 | 视频/游戏 |
2.2 TCP 保证可靠
- 序号 + 确认
- 超时重传
- 流量控制(滑动窗口)
- 拥塞控制
2.3 UDP 应用
- DNS 查询
- DHCP
- 视频流
- 在线游戏
3. IO 多路复用(select/poll/epoll)
3.1 select
cpp
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
struct timeval tv = {5, 0};
int ret = select(max_fd+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(server_fd, &readfds)) {
// 处理新连接
}
缺点:
- 最大文件描述符 1024
- 每次都要遍历全部 fd
- 用户态/内核态拷贝
3.2 poll
cpp
struct pollfd fds[1024];
fds[0].fd = server_fd;
fds[0].events = POLLIN;
int ret = poll(fds, nfds, 5000);
if (fds[0].revents & POLLIN) {
// 处理事件
}
改进:
- 无限 FD 数量(理论)
- 数组代替位图
3.3 epoll(重点)
cpp
int epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev);
struct epoll_event events[1024];
int nfds = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 处理新连接
}
}
三种模式:
- LT:水平触发(默认,阻塞)
- ET:边缘触发(高效,需一次读完)
- ONESHOT:只触发一次
3.4 对比
| 特性 | select | poll | epoll |
|---|---|---|---|
| FD 数量 | 1024 | 无限制 | 无限制 |
| 效率 | O(n) | O(n) | O(1) |
| 跨平台 | 是 | 是 | 仅 Linux |
| 边缘触发 | 否 | 否 | 是 |
4. 线程池
4.1 组件
- 任务队列
- 工作线程集合
- 管理线程(分配任务)
4.2 实现模板
cpp
class ThreadPool {
private:
queue<Task*> tasks;
vector<thread> workers;
mutex mtx;
condition_variable cv;
bool stop;
public:
ThreadPool(int n) {
for (int i = 0; i < n; i++) {
workers.emplace_back([this] {
while (true) {
Task* task = nullptr;
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) return;
task = tasks.front();
tasks.pop();
}
task->execute();
}
});
}
}
void submit(Task* t) {
{
lock_guard<mutex> lock(mtx);
tasks.push(t);
}
cv.notify_one();
}
~ThreadPool() {
stop = true;
cv.notify_all();
for (auto& w : workers) w.join();
}
};
4.3 面试参数
- 线程数量 = CPU核数 * (1 + 等待时间/工作时间)
- IO 密集 > CPU 密集
5. Reactor 模型
5.1 单 Reactor
┌─────────────┐
│ Reactor │ ← 单select/epoll监听
└─────────────┘
↓↑
┌──────────────┐
│ Handler │ ← 连接处理
└──────────────┘
5.2 多 Reactor(主从)
┌─────────────┐
│ Main │ ← 主 Reactor(监听)
│ Reactor │
└─────────────┘
↓
┌─────────────┐
│ Sub │ ← 从 Reactor(多线程)
│ Reactor │
└─────────────┘
5.3 组件
- acceptor:接收连接
- reactor:分发事件
- handler:业务处理
- reader/writer:读写
6. HTTP 协议
6.1 请求格式
GET /index.html HTTP/1.1\r\n
Host: www.example.com\r\n
User-Agent: Mozilla/5.0\r\n
\r\n
(可选body)
6.2 响应格式
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length: 123\r\n
\r\n
<html>...</html>
6.3 状态码
- 1xx:信息
- 2xx:成功(200)
- 3xx:重定向(301/302)
- 4xx:客户端错误(404)
- 5xx:服务端错误(500)
6.4 常见 Header
- Host、User-Agent
- Content-Type、Content-Length
- Cookie、Set-Cookie
7. 长连接与短连接
7.1 短连接
- 每次请求建立一个 TCP 连接
- 完成后立即关闭
- 适合请求偶尔
7.2 长连接
- 多个请求复用一个 TCP 连接
- 通过 Keep-Alive 保持
- 适合频繁请求
7.3 心跳
- 定期发送小数据包
- 检测连接存活
- 防止连接被防火墙关闭
8. 粘包与拆包
8.1 问题
- TCP 是字节流,无消息边界
- 一次 read 可能包含多条/半条消息
8.2 解决方案
- 固定长度:指定每个报文长度
- 分隔符:\r\n 或自定义
- Header + Body:Header 含 Body 长度
9. 阻塞 vs 非阻塞
9.1 四种 IO 模型
| 模型 | 阻塞 | 非阻塞 | 多路复用 | 信号驱动 |
|---|---|---|---|---|
| accept | 阻塞 | 非阻塞 | select | - |
| read | 阻塞 | EAGAIN | select | SIGIO |
9.2 设置方法
cpp
// 非阻塞
int flags = fcntql(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
9.3 异步 IO
cpp
aio_read(&cb);
10. 常见网络攻击与防御
10.1 DDoS
- 分布式拒绝服务
- 防御:流量清洗、IP 限流、CDN
10.2 SYN Flood
- 半连接队列耗尽
- 防御:SYN Cookie、减少超时时间
10.3 CC 攻击
- HTTP 请求耗尽资源
- 防御:验证码、限速、IP 黑名单
🔥 本章综合高频追问
-
问 :Epoll LT vs ET 区别?
答:LT 只要有数据就通知,ET 只通知一次直到buffer清空。
-
问 :为什么单线程 Epoll 高效?
答:无需线程切换,纯事件驱动。
-
问 :TCP 粘包如何解决?
答:定长、分隔符、Header+Body。
-
问 :TIME_WAIT 过多?
答:复用 TIME_WAIT 的 sockets(SO_REUSEADDR)。
📝 模块总结
本模块覆盖网络编程核心 Socket、TCP/UDP、IO多路复用、Reactor,结合项目深入理解 Epoll、线程池。可应对中高级网络编程面试。
