百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-08-28 12:28 5 浏览

一、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. 这样,便能知道对端已经关闭,然后进行相应处理,而不会导致整个进程退出. 2. 使用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 不同的是,此信号不会被阻塞

SIGKILL and SIGSTOP不能被捕获、阻塞或者ignore,最好使用SIGTERM信号来结束程序,这种方式操作系统会执行一些清理操作。

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...