阳光天蓝色 发表于 2014-9-17 21:49:08

Linux TCP通信 一方关闭socket,另一方被强制退出(SIGPI...

本帖最后由 阳光天蓝色 于 2014-9-17 21:56 编辑

一、问题来源
Linux上一个TCP服务器负责采集发送图片数据,windows客户端接收数据,当客户端关闭socket或者退出时,服务器进程被强制退出。

二、分析(网络)
        具体的分析可以结合TCP的"四次握手"关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端的socket是调用了close还是shutdown.对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生SIGPIPE信号, 导致进程退出.
为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数:
        signal(SIGPIPE, SIG_IGN);
这样, 第二次调用write方法时, 会返回-1, 同时errno置为SIGPIPE. 程序便能知道对端已经关闭.
        在linux下写socket的程序的时候,如果尝试send到一个disconnected socket上,就会让底层抛出一个SIGPIPE信号。这个信号的缺省处理方法是退出进程,大多数时候这都不是我们期望的。因此我们需要重载这个信号的处理方法。调用以下代码,即可安全的屏蔽SIGPIPE:
        signal (SIGPIPE, SIG_IGN);
系统里边定义了三种处理方法:
(1)SIG_DFL信号专用的默认动作:
  (a)如果默认动作是暂停线程,则该线程的执行被暂时挂起。当线程暂停期间,发送给线程的任何附加信号都不交付,直到该线程开始执行,但是SIGKILL除外。
  (b)把挂起信号的信号动作设置成SIG_DFL,且其默认动作是忽略信号 (SIGCHLD)。
(2)SIG_IGN忽略信号
  (a)该信号的交付对线程没有影响
  (b)系统不允许把SIGKILL或SIGTOP信号的动作设置为SIG_DFL
(3)SIG_ERR

三、我的用法
1、包含头文件:
#include <signal.h>
2、声明定义一个处理函数
void signal_handler(int sigm);
2、在产生信号产生前调用一下代码:
struct sigaction act;
memset(&act,0,sizeof(act));
// act.sa_handler = SIG_IGN;//设定接受到指定信号后的动作为忽略
act.sa_handler = signal_handler;//设定接受到指定信号后的动作为 相关函数
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGPIPE,&act,NULL);//屏蔽SIGPIPE信号
sigaction(SIGCHLD,&act,NULL);//屏蔽子进程终结信号,让init进程去处

Xplain 发表于 2014-9-17 22:03:38

谢谢,收藏了

zulu 发表于 2014-9-17 22:34:31

mark{:victory:}

fengyun_524 发表于 2014-9-17 23:13:41

以前也遇到类似问题,收藏了

CharlesLu 发表于 2014-9-17 23:21:09

收藏了,帮顶

KongQuan 发表于 2014-9-17 23:41:26

好笔记,顶!

mmyer 发表于 2014-9-17 23:54:38

signal(SIGPIPE, SIG_IGN)是对整个进程有效吗,如果SOCKET是在多个子线程里面操作, signal(SIGPIPE, SIG_IGN)应该放在什么位置?

mangocity 发表于 2014-9-18 09:07:27

mmyer 发表于 2014-9-17 23:54
signal(SIGPIPE, SIG_IGN)是对整个进程有效吗,如果SOCKET是在多个子线程里面操作, signal(SIGPIPE, SIG_ ...

是对整个进程有效的,而且对 fork 出来的进程也有效。通常信号处理的初始化会放在 main 进去后的地方。

lkm_unication 发表于 2014-9-18 09:57:38

一般来说,只有在往close掉的socket发送时,才会接收到SIGPIPE信号,可以在调用send接口时,把flags设置为MSG_NOSIGNAL,告知系统不要发送SIGPIPE信号。

阳光天蓝色 发表于 2014-9-18 11:54:06

lkm_unication 发表于 2014-9-18 09:57
一般来说,只有在往close掉的socket发送时,才会接收到SIGPIPE信号,可以在调用send接口时,把flags设置为M ...

我试试,坑爹的书本一般将 flag 设为0;

cece_co 发表于 2014-9-18 12:38:10

没遇到过~不明觉厉

cece_co 发表于 2014-9-18 12:38:37

没遇到过~不明觉厉

TZQ95865 发表于 2014-9-18 13:47:18

谢谢,收藏了 mask

紫胤真人 发表于 2014-9-18 14:38:05

make即将遭遇此类问题.

abutter 发表于 2014-9-19 01:15:41

server 为什么要退出?不支持多客户端?

yj_yulin 发表于 2014-9-19 08:16:41

这个算是程序的问题了,一个文件handle如果已经无效,你还在读写,显然会出错,
基本原则应该是在handle无效后再不要对它操作.

armstrong 发表于 2014-9-19 09:03:41

lkm_unication 发表于 2014-9-18 09:57
一般来说,只有在往close掉的socket发送时,才会接收到SIGPIPE信号,可以在调用send接口时,把flags设置为M ...

嗯,这是标准有效的方式,仅用socket函数就可以解决这个问题了。

keinYe 发表于 2014-9-19 09:03:52

学习了,谢谢{:biggrin:}

阳光天蓝色 发表于 2014-9-19 09:07:52

yj_yulin 发表于 2014-9-19 08:16
这个算是程序的问题了,一个文件handle如果已经无效,你还在读写,显然会出错,
基本原则应该是在handle无效后 ...

不太明白你的意思,服务器是被动响应客户端的请求,数据是单方向从服务器传到客户端,服务器如何知道客户端已经不再了或者关闭通信?有其他方法吗?

lkm_unication 发表于 2014-9-19 09:10:40

to abutter and yj_yulin
并不是作者想退出的,因为即使你每时每刻都会检测socket的状态,但你保证不了在发送那一刻,对方强行退出的情况,一旦发生了,系统会发生PIPE信号上来的,如果事先没有处理该信号,进程会被强行关闭。

yj_yulin 发表于 2014-9-19 18:06:40

lkm_unication 发表于 2014-9-19 09:10
to abutter and yj_yulin
并不是作者想退出的,因为即使你每时每刻都会检测socket的状态,但你保证不了在发 ...

还是用标准处理吧(flags参数用MSG_NOSIGNAL ),这个不用安装signal handler,用全局signal总觉得像是在补救,c的处理方式还是检查返回值合理

yj_yulin 发表于 2014-9-19 18:09:59

阳光天蓝色 发表于 2014-9-19 09:07
不太明白你的意思,服务器是被动响应客户端的请求,数据是单方向从服务器传到客户端,服务器如何知道客户 ...

还是用标准处理吧(flags参数用MSG_NOSIGNAL ),这个不用安装signal handler,用全局signal总觉得像是在补救,c的处理方式还是检查返回值合理
如果不想每次都写这个参数,也可以在连接建立后用setsockopt设置一下

ckhf 发表于 2019-1-21 15:38:47

不明觉厉
页: [1]
查看完整版本: Linux TCP通信 一方关闭socket,另一方被强制退出(SIGPI...