网络编程中需要处理的常用信号总结

一、SIGPIPE

当往一个写端关闭的管道或socket连接中连续写入数据时会引发SIGPIPE信号,引发SIGPIPE信号的写操作将设置errno为EPIPE。在TCP通信中,当通信的双方中的一方close一个连接时,若另一方接着发数据,根据TCP协议的规定,会收到一个RST响应报文,若再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不能再写入数据。

实例程序

  • server端
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>

#define port 8888

void handle(int sig)
{
    printf("SIGPIPE : %d\n",sig);
}

void mysendmsg(int fd)
{

    // 写入第一条消息
    char* msg1 = "first msg"; 
    int n = write(fd, msg1, strlen(msg1));

    if(n > 0)  //成功写入第一条消息,server 接收到 client 发送的 RST
    {
        printf("success write %d bytes\n", n);
    }

    sleep(1);
    // 写入第二条消息,触发SIGPIPE
    char* msg2 = "second msg";
    n = write(fd, msg2, strlen(msg2));
    if(n < 0)
    {
        printf("write error: %s\n", strerror(errno));
    }
}
int main()
{
    signal(SIGPIPE , handle); //注册信号捕捉函数

    struct sockaddr_in server_addr;

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    int listenfd = socket(AF_INET , SOCK_STREAM , 0);

    bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

    listen(listenfd, 128);

    int fd = accept(listenfd, NULL, NULL);
    if(fd < 0)
    {
        perror("accept");
        exit(1);
    }

    mysendmsg(fd);
    return 0;
}
  • client端
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<unistd.h>

#define PORT 8888
#define MAX 1024

int main()
{
    char buf[MAX] = {'0'};
    int sockfd;
    int n;
    socklen_t slen;
    slen = sizeof(struct sockaddr);
    struct sockaddr_in seraddr;

    bzero(&seraddr,sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(PORT);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket");
        exit(-1);
    }

    if(connect(sockfd,(struct sockaddr *)&seraddr,slen) == -1)
    {
        perror("connect");
        exit(-1);
    }

    int ret = shutdown(sockfd , SHUT_RDWR);
    if(ret < 0)
    {
        perror("shutdown perror");
    }

    return 0;
}
  • server端结果
    可以看到再第二次write时由于连接已关闭,所以收到了SIGPIPE 信号。
success write 9 bytes
SIGPIPE : 13
write error: Broken pipe

如何处理

因为SIGPIPE信号的默认行为是结束进程,而我们绝对不希望因为写操作的错误而导致程序退出,尤其是作为服务器程序来说就更恶劣了。所以我们应该对这种信号加以处理,在这里,介绍两种处理SIGPIPE信号的方式:
1. 给SIGPIPE设置SIG_IGN信号处理函数,忽略该信号:

signal(SIGPIPE, SIG_IGN);

前文说过,引发SIGPIPE信号的写操作将设置errno为EPIPE,。所以,第二次往关闭的socket中写入数据时, 会返回-1, 同时errno置为EPIPE. 这样,便能知道对端已经关闭,然后进行相应处理,而不会导致整个进程退出.

  1. 使用send函数的MSG_NOSIGNAL 标志来禁止写操作触发SIGPIPE信号。

send(sockfd , buf , size , MSG_NOSIGNAL);

我们可以根据send函数反馈的errno来判断socket的读端是否已经; 此外,我们也可以通过IO复用函数来检测管道和socket连接的读端是否已经关闭。以POLL为例,当socket连接被对方关闭时,socket上的POLLRDHUP事件将被触发。

二、SIGHUP

UNIX中进程组织结构为 session(会话)包含一个前台进程组及一个或多个后台进程组,一个进程组包含多个进程。一个session可能会有一个session首进程,而一个session首进程可能会有一个控制终端。一个进程组可能会有一个进程组首进程。进程组首进程的进程ID与该进程组ID相等。这儿是可能会有,在一定情况之下是没有的。与终端交互的进程是前台进程,否则便是后台进程。

SIGHUP会在以下3种情况下被发送给相应的进程:

  1. 终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程)
  2. session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程
  3. 若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。

下面观察几种因终端关闭导致进程退出的情况,在这儿进程退出是因为收到了SIGHUP信号。login shell是session首进程。

首先写一个测试程序,代码如下:

#include <stdio.h> 
#include<signal.h> 
char **args;
void exithandle(int sig)
{
       printf("%s : sighup received ",args[1]);
} 

int main(int argc,char **argv)
{
       args = argv;
       signal(SIGHUP,exithandle);
       pause();

       return 0;
}

程序中捕捉SIGHUP信号后打印一条信息,pause()使程序暂停。
编译后的执行文件为sigtest

  1. 命 令:sigtest front > tt.txt
    操 作:关闭终端
    结 果:tt.txt文件的内容为front : sighup received
    原 因: sigtest是前台进程,终端关闭后,根据上面提到的第1种情况,login shell作为session首进程,会收到SIGHUP信号然后退出。根据第2种情况,sigtest作为前台进程,会收到login shell发出的SIGHUP信号。
  2. 命 令:sigtest back > tt.txt &
    操 作:关闭终端
    结 果:tt.txt文件的内容为 back : sighup received
    原 因: sigtest是提交的job,根据上面提到的第1种情况,sigtest会收到SIGHUP信号。
  3. 命 令:写一个shell,内容为[sigtest &],然后执行该shell
    操 作:关闭终端
    结 果:ps -ef | grep sigtest 会看到该进程还在,tt文件为空
    原 因: 执行该shell时,sigtest作为job提交,然后该shell退出,致使sigtest变成了孤儿进程,不再是当前session的job了,因此sigtest即不是session首进程也不是job,不会收到SIGHUP。同时孤儿进程属于后台进程,因此login shell退出后不会发送SIGHUP给sigtest,因为它只将该信号发送给前台进程。第3条说过若进程组变成孤儿进程组的时候,若有进程处于停止状态,也会收到SIGHUP信号,但sigtest没有处于停止状态,所以不会收到SIGHUP信号。
  4. 命 令:nohup sigtest > tt
    操 作:关闭终端
    结 果:tt文件为空
    原 因: nohup可以防止进程收到SIGHUP信号

至此,我们就清楚了何种情况下终端关闭后进程会退出,何种情况下不会退出。
要想终端关闭后进程不退出有以下几种方法,均为通过shell的方式:

  1. 编写shell,内容如下
    trap "" SIGHUP #该句的作用是屏蔽SIGHUP信号,trap可以屏蔽很多信号
    sigtest
  2. nohup sigtest 可以直接在命令行执行,
    若想做完该操作后继续别的操作,可以 nohup sigtest &
  3. 编写shell,内容如下
    sigtest &
    其实任何将进程变为孤儿进程的方式都可以,包括fork后父进程马上退出。

三、SIGINT && SIGTERM && SIGKILL

信号 产生方式 对进程的影响
SIGINT ctrl+c 信号被当前进程树接收到,也就是说,不仅当前进程会收到信号,它的子进程也会收到
SIGTERM kill {pid} 只有当前进程收到信号,子进程不会收到。如果当前进程被kill了,那么它的子进程的父进程将会是init,也就是pid为1的进程
SIGKILL kill -9 {pid} 与SIGTERM 不同的是,此信号不会被阻塞
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容