对于信号的介绍,我再前面的一篇博客中做过专门的总结,感兴趣的可以看看。本文主要介绍在网络编程中几个密切相关的函数:SIGUP,SIGPIPE,SIGURG。

SIGHUP信号

  在介绍SIGHUP信号之前,先来了解两个概念:进程组和会话。

进程组

  进程组就是一系列相互关联的进程集合,系统中的每一个进程也必须从属于某一个进程组;每个进程组中都会有一个唯一的 ID(process group id),简称 PGID;PGID 一般等同于进程组的创建进程的 Process ID,而这个进进程一般也会被称为进程组先导(process group leader),同一进程组中除了进程组先导外的其他进程都是其子进程; 
  进程组的存在,方便了系统对多个相关进程执行某些统一的操作,例如,我们可以一次性发送一个信号量给同一进程组中的所有进程。

会话

  会话(session)是一个若干进程组的集合,同样的,系统中每一个进程组也都必须从属于某一个会话;一个会话只拥有最多一个控制终端(也可以没有),该终端为会话中所有进程组中的进程所共用。一个会话中前台进程组只会有一个,只有其中的进程才可以和控制终端进行交互;除了前台进程组外的进程组,都是后台进程组;和进程组先导类似,会话中也有会话先导(session leader)的概念,用来表示建立起到控制终端连接的进程。在拥有控制终端的会话中,session leader 也被称为控制进程(controlling process),一般来说控制进程也就是登入系统的 shell 进程(login shell);


  
  执行睡眠后台进程sleep 50 & 之后,通过 ps命令查看该进程及shell信息如上图:

PPID 指父进程 id;PID 指进程 id;PGID 指进程组 id
SID 指会话 id;TTY 指会话的控制终端设备;COMMAND 指进程所执行的命令
TPGID 指前台进程组的 PGID。

SIGHUP信号的触发及默认处理

  在对会话的概念有所了解之后,我们现在开始正式介绍一下SIGHUP信号,SIGHUP 信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联. 系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。 
   
SIGHUP会在以下3种情况下被发送给相应的进程: 
  1、终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程); 
  2、session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程; 
   3、若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。 
   
  例如:在我们登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止。

此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 比如xinetd超级服务程序。 
  当xinetd程序在接收到SIGHUP信号之后调用hard_reconfig函数,它将循环读取/etc/xinetd.d/目录下的每个子配置文件,并检测其变化。如果某个正在运行的子服务的配置文件被修改以停止服务,则xinetd主进程讲给该子服务进程发送SIGTERM信号来结束它。如果某个子服务的配置文件被修改以开启服务,则xinetd将创建新的socket并将其绑定到该服务对应的端口上。

SIGPIPE信号

  在网络编程中,SIGPIPE这个信号是很常见的。当往一个写端关闭的管道或socket连接中连续写入数据时会引发SIGPIPE信号,引发SIGPIPE信号的写操作将设置errno为EPIPE。在TCP通信中,当通信的双方中的一方close一个连接时,若另一方接着发数据,根据TCP协议的规定,会收到一个RST响应报文,若再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不能再写入数据。 
  测试程序如下:简单的测试程序,函数未加错误判断 
  server.c:

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>#define port 8888void 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);}// 写入第二条消息,触发SIGPIPEchar* 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.c#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 1024int 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);//socket()if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){perror("socket");exit(-1);}//connect()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;
}

运行结果

  此外,因为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事件将被触发。

SIGURG信号

  在介绍SIGURG信号之前,先来说说什么是带外数据。

带外数据

  带外数据用于迅速告知对方本端发生的重要的事件。它比普通的数据(带内数据)拥有更高的优先级,不论发送缓冲区中是否有排队等待发送的数据,它总是被立即发送。带外数据的传输可以使用一条独立的传输层连接,也可以映射到传输普通数据的连接中。实际应用中,带外数据是使用很少见,有,telnet和ftp等远程非活跃程序。 
  UDP没有没有实现带外数据传输,TCP也没有真正的带外数据。不过TCP利用头部的紧急指针标志和紧急指针,为应用程序提供了一种紧急方式,含义和带外数据类似。TCP的紧急方式利用传输普通数据的连接来传输紧急数据。

SIGURG信号的作用

  内核通知应用程序带外数据到达的方式有两种:

一种就是利用IO复用技术的系统调用(如select)在接受到带外数据时将返回,并向应用程序报告socket上的异常事件。

 另一种方法就是使用SIGURG信号。

若对服务器同时处理普通数据和带外数据感兴趣的话可以参考示例程序。

/*************************************************************************> File Name: client.c> Author: > Mail: > Created Time: 2018年06月30日 星期六 15时12分52秒************************************************************************/
//发送带外数据#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main( int argc, char* argv[] )
{if( argc <= 2 ){printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );return 1;}const char* ip = argv[1];int port = atoi( argv[2] );struct sockaddr_in server_address;bzero( &server_address, sizeof( server_address ) );server_address.sin_family = AF_INET;inet_pton( AF_INET, ip, &server_address.sin_addr );server_address.sin_port = htons( port );int sockfd = socket( PF_INET, SOCK_STREAM, 0 );assert( sockfd >= 0 );if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 ){printf( "connection failed\n" );}else{printf( "send oob data out\n" );const char* oob_data = "abc";const char* normal_data = "123";send( sockfd, normal_data, strlen( normal_data ), 0 );send( sockfd, oob_data, strlen( oob_data ), MSG_OOB );send( sockfd, normal_data, strlen( normal_data ), 0 );}close( sockfd );return 0;
}
/*************************************************************************> File Name: server.c> Author: > Mail: > Created Time: 2018年06月30日 星期六 13时11分14秒************************************************************************/
//使用SIGURG检测带外数据是否到达#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>#define BUF_SIZE 1024
#define port 8888static int connfd;void sig_urg( int sig )
{int save_errno = errno;char buffer[ BUF_SIZE ];memset( buffer, '\0', BUF_SIZE );int ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );printf( "got %d bytes of oob data '%s'\n", ret, buffer );errno = save_errno;
}void addsig( int sig, void ( *sig_handler )( int ) )
{struct sigaction sa;memset( &sa, '\0', sizeof( sa ) );sa.sa_handler = sig_handler;sa.sa_flags |= SA_RESTART;sigfillset( &sa.sa_mask );assert( sigaction( sig, &sa, NULL ) != -1 );
}int main( int argc, char* argv[] )
{struct sockaddr_in address;bzero( &address, sizeof( address ) );address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons( port );int sock = socket( PF_INET, SOCK_STREAM, 0 );assert( sock >= 0 );int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );assert( ret != -1 );ret = listen( sock, 5 );assert( ret != -1 );struct sockaddr_in client;socklen_t client_addrlength = sizeof( client );connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );if ( connfd < 0 ){printf( "errno is: %d\n", errno );}else{addsig( SIGURG, sig_urg );//使用SIGURG信号之前,必须设置socket的宿主进程或进程组fcntl( connfd, F_SETOWN, getpid() );char buffer[ BUF_SIZE ];while( 1 )  //循环读取普通数据{memset( buffer, '\0', BUF_SIZE );ret = recv( connfd, buffer, BUF_SIZE-1, 0 );if( ret <= 0 ){break;}printf( "got %d bytes of normal data '%s'\n", ret, buffer );}close( connfd );}close( sock );return 0;
}

系统信号(SIGHUP ,SIGPIPE,SIGURG)相关推荐

  1. Linux系统信号定义

    Linux系统信号,即signal,定义通常在这个头文件中: /usr/include/asm-generic/signal.h 常见的信号如下: #define SIGHUP         1 # ...

  2. UNIX环境C - 系统信号

    一.信号的概念 信号就是一种软中断,进程与进程之间信号的传递,都是通过内核来当做中转站的,不能直接传递信号. 二.信号的分类(128位信号,不过可用信号就1~64除去32与33) 1.不可靠信号(SI ...

  3. QT界面布局、系统信号和槽、启动进程、EDIT回车快捷键、正常信号和槽、QMessageBox、UIC使用(界面重新生成)

    界面布局 ctrl+H :水平布局 ctrl+L:垂直布局 shift+alt+r:预览 设置固定大小 F4进入系统信号,鼠标拖拽(F3切换对话框) 启动进程 #include <QProces ...

  4. linux. qt信号崩溃,【创龙AM4379 Cortex-A9试用体验】之I/O中断异步通知驱动程序+QT捕获Linux系统信号+测试信号通知...

    2.驱动程序 安装字符设备驱动程序开发流程开发. 2.1资源定义 定义按键I/O端口号.I/O中断号,以及字符设备的主设备号变量: #define GPIO_KEY1_PIN_NUM (3*32 + ...

  5. 基于matlab的数字调制,基于MATLAB的多功能数字调制系统信号源仿真

    内容简介: 毕业设计 基于MATLAB的多功能数字调制系统信号源仿真,共60页,27225字 摘要 数字通信与模拟通信系统相比有着灵活性.高效性和保密性等突出特点.数字通信系统仿真对于系统分析起着越来 ...

  6. 手机通讯系统信号流程

    手机通讯系统信号流程 一.通信系统 通常我们把信息从发送者传送到接收者的过程称为通信,而实现信息传输过程的系统称为通信系统. 下面的框图表示了一个基本的通信系统. 信道根据传输介质.方式的不同可分为有 ...

  7. 31-【go】系统信号介绍、Go语言中使用os/signal包实现对信号的处理

    一.系统信号介绍 系统信号介绍_Agoni_xiao的博客-CSDN博客 二.Go语言中使用os/signal包实现对信号的处理 Go语言中os/signal包的学习与使用 - 人艰不拆_zmc - ...

  8. linux系统信号控制进程的重启,Linux系统中的信号(进程间通信)及其含义

    进程的管理主要是指进程的关闭与重启.我们一般关闭或重启软件,都是关闭或重启它的程序,而不是直接操作进程的.比如,要重启 apache 服务,一般使用命令"service httpd rest ...

  9. linux——signal信号 SIGHUP、SIGINT

    SIGHUP /* hangup */           SIGHUP,hong up ,挂断.本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一sessio ...

最新文章

  1. OpenAI开源机器人模拟Python库mujoco-py:可高效处理并行模拟
  2. CVPR2020检测类论文最全汇总:136篇论文方向细分(附代码和论文打包下载)
  3. SAP 对HU做货物移动报错-Only 0 serial numbers entered instead of 30 -
  4. Java异步通信实现
  5. 基础数据仓库环境搭建(二) Hadoop的环境搭建
  6. mondrain配置mysql_mondrian 4.7 源码部署(示例代码)
  7. [渝粤教育] 山东第一医科大学 健康教育与健康促进 参考 资料
  8. orm和mysql_orm与mysql
  9. Spark的性能调优
  10. 合泰单片机c语言halt指令,holtek单片机图文全面详解
  11. ORACLE利用scn赚钱,在Oracle中增进SCN及案例介绍
  12. Ubuntu 20.04 修改IP地址
  13. Struts2 通配符使用
  14. 【数据分析案例】如何定义你的“亲密关系”
  15. 十分钟django后台 simpleui -含自定义后台首页
  16. stm32f4有重映射么_stm32f4引脚重映射
  17. outlook qr码在哪里_原平防伪码溯源_橙程(北京)科技有限公司
  18. Lodash源码解析-------chunk函数
  19. 说说CAP 理论是什么?
  20. IBM创始人 托马斯·约翰·沃森父子

热门文章

  1. codility Nesting
  2. 图解HTTP学习_day06
  3. 一行命令给猎狐 F4 带口罩 检测是否带口罩
  4. 使用 NetworkInterface 获得本机在局域网内的 IP 地址
  5. 华为云服务器CentOS 8.2 安装docker
  6. 深入理解gRPC(一):gRPC介绍
  7. Python爬取网站图片数据
  8. 简单的字符驱动例子包括读写、装载等
  9. OpenCV+CUDA学习2---图像灰度化
  10. 12种新大傻 这是周立波总结的? 超级人才