如何防止 SIGPIPE 或防止服务器结束?

19

这是一个使用pthreadsbindlistenaccept的标准C++ TCP服务器程序。我碰到了这样的情况:当我杀死连接的客户端时,服务器会崩溃(也就是退出)

崩溃的原因是write()对文件的调用失败了,从而导致程序接收到SIGPIPE信号。我猜这是导致服务器退出的原因。

我想,“当然,未处理的信号意味着退出”,那么让我们使用signal()

signal(SIGPIPE, SIG_IGN);

因为以下引自 man 2 write:

EPIPE fd 连接了一个读端关闭的管道或socket。当这种情况发生时,写进程将会收到一个SIGPIPE信号 (因此,write返回值只有在程序捕获、阻塞或忽略该信号时才能看到)。

然而,无论是在服务器线程还是客户端线程中,这似乎都没有帮助。

那么,我该如何防止 write() 调用引起该信号,或者(为实际起见)如何防止服务器退出


我的诊断结果是:

  • 启动服务器线程,绑定、监听、接受。
  • 让客户端连接(例如通过telnet)
  • 发送pkill telnet以使客户端崩溃

不期望的行为:服务器退出,在gdb中如下:

... in write () at ../sysdeps/unix/syscall-template.S:82
82      T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)

以及 回溯信息

#0  ... in write () at ../sysdeps/unix/syscall-template.S:82
#1  ... in ClientHandler::mesg(std::string) ()
#2  ... in ClientHandler::handle() ()
#3  ... in start_thread (arg=<value optimized out>) at pthread_create.c:300
#4  ... in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
#5  ... in ?? ()

我猜测一下,也许你需要使用O_NOCTTY标志来创建文件描述符/套接字? - Vinicius Kamakura
1
@hexa:O_NOCTTY与此无关的可能性很小。 - Jonathan Leffler
1
signal(SIGPIPE, SIG_IGN) 是你想要的。真正的问题是为什么它对你不起作用。也许是其他代码通过安装单独的信号处理程序来扭转其效果?或者可能只是因为你传递给 write() 的参数不正确,导致了一个老式的崩溃(与 SIGPIPE 无关)? - Jeremy Friesner
@Jeremy:没有其他信号处理程序,只有一个select()。我会仔细检查写入调用()的。 - towi
2
@JeremyFriesner:signal() 的 man 手册说:“在多线程进程中使用 signal() 的效果是未指定的”,并建议使用 sigaction() 代替。(但是思路是相同的。) - Julien-L
3个回答

22

有点晚了,但是想为将来的参考添加一些内容: 如果您在gdb中调试代码,请不要忘记它会覆盖您的信号处理程序。

因此,如果您已经设置了信号处理程序,例如:signal(SIGPIPE, SIG_IGN),但似乎无效,请尝试在调试器外运行代码。

或者在gdb提示符下设置handle SIGPIPE nostop来防止gdb在接收到信号时停止。


感谢您对未来的提示。但是,gdb 指向了 SIGPIPE 的正确位置。也许是因为它是一个“无害”的信号。 - towi
等等,你的意思是即使我忽略了SIGPIPE信号,在我的代码在gdb下运行时,我仍然会收到这个信号,从而使得我的忽略指令似乎没有起作用? - Michael
3
回答自己,是的。然而,使用“handle SIGPIPE nostop noprint”可以指示gdb忽略它。 - Michael

12
当你忽略 SIGPIPE 信号时,将不再收到 SIGPIPE 信号,但是 write() 函数会得到一个 EPIPE 错误。

12

你是否在启动线程之前没有忽略 signal 信号? 如果你等到稍后再去处理,其他线程仍然可能会接收到该信号并退出应用程序。

如果这样仍然不起作用,你可以在尝试写入之前使用 poll/select 进行写入检查,以确保套接字可写。


好观点。所以,在任何线程之前,我必须先安装信号。好的,我会确保这样做,这将把我的尝试和错误案例缩小到约30%。谢谢。轮询是我正在考虑的事情,但我从未在套接字上执行过。我会调查一下。 - towi
我想我明白了:将它放在非常外层但仍在main()内部似乎有所帮助。但令人困惑的是,gdb 仍然一遍又一遍地停在信号处,但在 gdb 外部看起来很正常。 - towi
请注意,使用poll/select的方法可能会成功,但由于竞争条件,写入仍然可能失败--在调用poll/selectwrite之间,管道的读取端关闭。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接