根本原因是对方关闭连接后客户端仍向已关闭socket写数据,内核发送SIGPIPE信号默认终止进程;应于main开头调用signal(SIGPIPE, SIG_IGN)忽略信号,并检查write/send返回值与errno(如EPIPE时关闭socket)。

为什么C++客户端会收到“Broken pipe”错误
根本原因是对方进程关闭了连接(比如服务端崩溃、主动断连、超时踢出),而你的客户端仍在往已关闭的socket写数据。内核检测到后,会向进程发送SIGPIPE信号,默认行为是终止进程——此时write()或send()系统调用返回-1,并把errno设为EPIPE;如果信号没被处理,进程就直接挂了,日志里可能只看到“Broken pipe”或直接退出。
如何忽略SIGPIPE信号避免进程退出
最常用且安全的做法是在程序启动早期(比如main()开头)调用signal()或sigaction()屏蔽SIGPIPE:
#include <csignal>
int main() {
signal(SIGPIPE, SIG_IGN); // 简单有效
// 或更严谨地用 sigaction:
// struct sigaction sa = {};
// sa.sa_handler = SIG_IGN;
// sigaction(SIGPIPE, &sa, nullptr);
// ...
}
注意:SIG_IGN必须在任何socket操作前设置,否则可能漏掉第一个SIGPIPE;多线程程序中只需在主线程设置一次,信号处理是进程级的。
忽略SIGPIPE后write/send仍会失败,怎么正确处理
忽略SIGPIPE只是防止崩溃,不代表写操作成功。你仍需检查返回值和errno:
立即学习“C++免费学习笔记(深入)”;
-
write()或send()返回-1且errno == EPIPE:对端已关闭,应关闭本地socket并清理资源 - 返回值小于预期字节数(非阻塞socket常见):需要重试或使用
select()/epoll()等待可写 - 返回-1且
errno == EAGAIN或EWOULDBLOCK:非阻塞模式下暂时不可写,不是错误
典型处理片段:
ssize_t n = send(sock, buf, len, MSG_NOSIGNAL); // Linux可用MSG_NOSIGNAL绕过SIGPIPE
if (n == -1) {
if (errno == EPIPE) {
close(sock); // 对端断开,清理
return;
}
// 其他错误如EINTR需重试,EAGAIN则等下次可写
}
MSG_NOSIGNAL是Linux特有flag,能避免单次send()触发SIGPIPE,但不能替代全局SIG_IGN,因为其他系统调用(如write())仍可能触发。
容易被忽略的坑
忽略SIGPIPE本身很简单,但实际调试中常踩这些坑:
- 在gdb中运行时,
SIGPIPE默认被gdb捕获并暂停,看起来像卡住,其实只是信号未被传递给程序——用handle SIGPIPE nostop noprint pass让gdb透传 - 某些封装库(如libcurl、Boost.Beast)内部已处理
SIGPIPE,重复设置无害但没必要 - Docker容器或systemd服务中,进程意外退出可能被误判为OOM或超时,实际是没忽略
SIGPIPE导致的静默崩溃 - macOS上
signal(SIGPIPE, SIG_IGN)有效,但部分旧版本需配合setsockopt(..., SO_NOSIGPIPE, ...)(仅iOS/macOS socket选项)
真正关键的不是“忽略信号”,而是确认写失败后是否做了合理清理——很多问题表面是SIGPIPE,根子在连接生命周期管理缺失。


















