一、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. 這樣,便能知道對端已經關閉,然后進行相應處理,而不會導致整個進程退出.
- 使用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種情況下被發送給相應的進程:
- 終端關閉時,該信號被發送到session首進程以及作為job提交的進程(即用 & 符號提交的進程)
- session首進程退出時,該信號被發送到該session中的前臺進程組中的每一個進程
- 若父進程退出導致進程組成為孤兒進程組,且該進程組中有進程處于停止狀態(收到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
- 命 令:sigtest front > tt.txt
操 作:關閉終端
結 果:tt.txt文件的內容為front : sighup received
原 因: sigtest是前臺進程,終端關閉后,根據上面提到的第1種情況,login shell作為session首進程,會收到SIGHUP信號然后退出。根據第2種情況,sigtest作為前臺進程,會收到login shell發出的SIGHUP信號。 - 命 令:sigtest back > tt.txt &
操 作:關閉終端
結 果:tt.txt文件的內容為 back : sighup received
原 因: sigtest是提交的job,根據上面提到的第1種情況,sigtest會收到SIGHUP信號。 - 命 令:寫一個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信號。 - 命 令:nohup sigtest > tt
操 作:關閉終端
結 果:tt文件為空
原 因: nohup可以防止進程收到SIGHUP信號
至此,我們就清楚了何種情況下終端關閉后進程會退出,何種情況下不會退出。
要想終端關閉后進程不退出有以下幾種方法,均為通過shell的方式:
- 編寫shell,內容如下
trap "" SIGHUP #該句的作用是屏蔽SIGHUP信號,trap可以屏蔽很多信號
sigtest - nohup sigtest 可以直接在命令行執行,
若想做完該操作后繼續別的操作,可以 nohup sigtest & - 編寫shell,內容如下
sigtest &
其實任何將進程變為孤兒進程的方式都可以,包括fork后父進程馬上退出。
三、SIGINT && SIGTERM && SIGKILL
信號 | 產生方式 | 對進程的影響 |
---|---|---|
SIGINT | ctrl+c | 信號被當前進程樹接收到,也就是說,不僅當前進程會收到信號,它的子進程也會收到 |
SIGTERM | kill {pid} | 只有當前進程收到信號,子進程不會收到。如果當前進程被kill了,那么它的子進程的父進程將會是init,也就是pid為1的進程 |
SIGKILL | kill -9 {pid} | 與SIGTERM 不同的是,此信號不會被阻塞 |