C++实现最简反向代理需手动处理TCP字节流透传:监听非阻塞socket(设SO_REUSEADDR)、accept后双连接建立、双向shutdown控制读写端、正确处理EOF/RST/ETIMEDOUT/SIGPIPE,不解析HTTP,仅为端口映射级透传。

如何用 C++ 基于 socket 实现最简反向代理转发
不能直接靠 std::forward_list 或 HTTP 库“自动处理”——C++ 标准库不提供 HTTP 解析或代理逻辑,必须手动收发原始字节流、维护连接状态、处理粘包与半关闭。核心就是:监听一个端口 → 接收客户端 TCP 连接 → 建立新 TCP 连接到后端服务 → 双向透传数据(非简单复制,需处理 EOF 和 RST)。
监听 socket 必须设为非阻塞 + 正确设置 SO_REUSEADDR
否则 bind() 失败报 Address already in use,或重启服务时卡住。Linux/macOS 下若没设 SO_REUSEADDR,TIME_WAIT 状态会阻塞端口重用;Windows 还需额外设 SO_EXCLUSIVEADDRUSE(但通常用前者即可)。
关键代码片段:
int listen_fd = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); fcntl(listen_fd, F_SETFL, O_NONBLOCK); // 非阻塞,避免 accept() 卡死
- 务必在
bind()前调用setsockopt(),顺序错会导致无效 -
listen()的 backlog 值建议设为 128 或 512,太小会丢连接请求 - 不要用
accept()阻塞等待,必须配合epoll/poll或轮询检测可读事件
转发时不能直接 send/recv 循环拷贝 —— 要处理连接关闭信号
常见错误是写成“收到 A 的数据就发给 B,收到 B 的数据就发给 A”,结果一端提前 close,另一端还在 recv,触发 Broken pipe 或无限等待。TCP 是全双工,但 FIN/RST 不会自动同步到对端 socket。
立即学习“C++免费学习笔记(深入)”;
正确做法是为每个代理连接维护两个方向的读写状态:
- 从 client_fd 读到 0 字节 → client 关闭了读端,应 shutdown(
backend_fd, SHUT_WR) - 从 backend_fd 读到 0 字节 → 后端关闭了读端,应 shutdown(
client_fd, SHUT_WR) - 任意一方 recv 返回 -1 且
errno == EAGAIN || errno == EWOULDBLOCK→ 当前无数据,继续等待 - 任意一方 recv 返回 -1 且
errno == ECONNRESET→ 对端异常断连,本方应立即 close 两端
示例判断逻辑(简化):
ssize_t n = recv(client_fd, buf, sizeof(buf), 0);
if (n == 0) {
shutdown(backend_fd, SHUT_WR); // client 完全关闭读,通知 backend 别再等
} else if (n > 0) {
send(backend_fd, buf, n, MSG_NOSIGNAL); // 避免 SIGPIPE
}
没有 HTTP 解析就别自称“反向代理” —— 纯 TCP 透传有明确局限
这种实现本质是 TCP 层端口映射(类似 nc -l 8080 | nc backend:80),不解析 Host、不改写 Location Header、不支持 WebSocket Upgrade、无法做基于路径的路由。如果你需要这些,必须引入 http_parser.h(如 llhttp)或用 boost.beast。
- HTTPS 流量完全无法处理(TLS 握手阶段就被截断,证书校验失败)
- 后端返回的
Connection: close不会被识别,连接不会主动关闭 - 客户端 Keep-Alive 请求会被原样转发,但代理自身不管理连接池,每个请求都新建 backend 连接,性能差
- 超时控制只能靠
setsockopt(SO_RCVTIMEO),无法按 HTTP 状态码或响应头做差异化策略
真正上线用的反向代理(如 nginx、envoy)都在应用层深度介入协议,而这里只是验证 socket 控制流的最小闭环。
最易被忽略的是:你得自己处理 SIGPIPE 信号(或用 MSG_NOSIGNAL),否则 client 断连后还往 backend_fd 写,进程直接挂掉。


















