SIGPIPE信号处理
今天写程序遇到这个问题,搜索一番之后觉得该文较好,于是转了过来。
SIGPIPE信号详解
当服务器 close 一个连接时,若 client 继续向服务器发数据,根据 TCP 协议的规定,客户端会收到一个 RST 响应,client再往这个服务器发送数据时,系统会发出一个 SIGPIPE 信号给客户端进程,导致客户端进程退出。
具体分析可以结合 TCP 的“四次握手”关闭。TCP 是全双工的信道,可以看作两条单双工的信道,TCP 连接两端的两个端点各负责一条。当对端调用 close 时,虽然默认行为是关闭整个两个信道,但本端只是收到 FIN 包,按照 TCP 协议的语义,表示对端只是关闭了其所负责的那一条单工信道,本端仍然可以接收数据。也就是说,因为 TCP 协议的限制,一个端点无法获知对端的 socket 是调用了 close 还是 shutdown。
close 函数和 shutdown 函数的区别
假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,close不能保证,只有当某个sockfd的引用计数为0,close 才会发送FIN段,否则只是将引用计数减1而已。也就是说只有当所有进程(可能fork多个子进程都打开了这个套接字)都关闭了这个套接字,close 才会发送FIN 段),此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0。但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,
而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。
有时候代码中需要连续多次调用write,可能还来不及调用read得知对方已关闭了连接就被SIGPIPE信号终止掉了,这就需要在初始化时调用sigaction处理SIGPIPE信号,对于这个信号的处理我们通常忽略即可,signal(SIGPIPE, SIG_IGN); 如果SIGPIPE信号没有导致进程异常退出(捕捉信号/忽略信号),write返回-1并且errno为EPIPE(Broken pipe)。(非阻塞地write)
close 关闭了自身数据传输的两个方向。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
shutdown 可以选择关闭某个方向或者同时关闭两个方向,shutdown how = 0 or how = 1 or how = 2 (SHUT_RD or SHUT_WR or SHUT_RDWR),后两者可以保证对等方接收到一个EOF字符(即发送了一个FIN段),而不管其他进程是否已经打开了这个套接字。所以说,如果是调用shutdown how = 1 ,则意味着往一个已经发送出FIN的套接字中写是允许的,接收到FIN段仅代表对方不再发送数据,但对方还是可以读取数据的,可以让对方可以继续读取缓冲区剩余的数据。
对于产生信号,系统里边定义了三种处理方法:
(1)SIG_DFL信号专用的默认动作:
(a)如果默认动作是暂停线程,则该线程的执行被暂时挂起。当线程暂停期间,发送给线程的任何附加信号都不交付,直到该线程开始执行,但是SIGKILL除外。
(b)把挂起信号的信号动作设置成SIG_DFL,且其默认动作是忽略信号 (SIGCHLD)。
(2)SIG_IGN忽略信号
(a)该信号的交付对线程没有影响
(b)系统不允许把SIGKILL或SIGTOP信号的动作设置为SIG_DFL
3)SIG_ERR
几个比较常用的信号
SIGIGN:忽略改信号
SIGDEF:采用系统默认方式处理信号
SIGCHLD:在一个进程终止或者停止时,将该信号发送给其父进程,父进程的wait函数通常用来捕捉这个信号
SIGINT:当用户按中断键(delete/ctrl+c)时将产生这个信号
SIGKILL:此信号可以用于杀死一个进程
SIGSTOP:这是个作业控制信号,用于停止一个进程 这个信号和SIGKILL是仅有的两个不能被捕获或忽略的信号
SYSUSR1/2:用户定义的信号,用于应用程序
xupeng1644 发布了269 篇原创文章 · 获赞 31 · 访问量 3万+ 私信 关注