출처: http://doi-t.hatenablog.com/entry/2014/06/10/033309
연결이 끊어진 소켓에 쓰기( send or write 등)를 하면 SIGPIPE 시그널이 발생하여 프로세스가 종료하기 때문에 제대로 SIGPIPE 시그널을 핸들링 해 두자는 이야기이다.
다음은 샘플 코드를 사용하여 실제로 파이프의 기입 처를 kill 하여 SIGPIPE 발생을 유사 체험 해 본다.
SISGPIPE를 받은 프로세스의 행동과 소켓 프로그래밍에서의 대응책
"sigpipe"로 검색하면 같은 이야기는 얼마든지 기사가 있고, 예를 들면,
"내가 쓴 서버가 갑자기 사망하는 거예요 이유는 무엇입니까?' 라는 질문을 받을 수 있다. 이것은 종종 SIGPIPE 처리를 잊는 것이 원인이다. SIGPIPE는 절단된 네트워크 소켓 등에 데이터를 쓰려고 했을 때 전달되는 UNIX 시그널이다. 특히 설정하지 않으면, 프로세스는 SIGPIPE를 받으면 종료한다. 따라서 통신이 갑자기 끊어 질 수있는 TCP 서버에서는 SIGPIPE를 무시하도록 설정해야 한다.
제 6회 UNIX 프로그래밍의 급소 (3)
라고 설명되어 있기도 하는 등 비교적 자주 있는 이야기 같다.
SIGPIPE 시그널을 받은 프로세스의 기본 동작은 core 파일 조차 뱉지 않고 프로세스 종료 한다.
이 동작은 위 인용대로 소켓 프로그래밍을 하고있는 측면에서는 아무런 오류 출력도 없이 갑자기 다운하는 것이다.
대응으로서는(이것도 위 인용대로), 예를 들어 이전의 Signal 함수를 사용한다면 Signal(SIGPIPE, SIG_IGN); 라는 것을 프로그램 시작 직후에 넣어두는 것만으로 좋다.
이렇게 하면 send 실행 시 연결이 끊어져 있어도 프로세스를 종료하지 않고 단순히 send(2)가 -1을 돌려 준다.
프로세스의 종료 상태와 SIGPIPE의 발생을 확인
여기에서는 다음의 샘플에서 실제로 파이프의 기입처를 kill 하여 SIGPIPE의 발생을 유사 체험 해 본다.
소켓 프로그래밍적으로는 아래의
- sender.sh 가 송신자 (write (2), send (2))
- receiver.sh가 수신자 (read (2), recv (2))
라고 생각해 주면 좋다.
즉 수신이 중단된 곳에 송신자가 wirte(2)를 실행 했을 때 어떻게 되는지를 본다.

strace 명령으로 SIGPIPE의 발생을 확인
SIGPIPE의 발생 확인은 strace 명령의 결과와 프로세스의 종료 상태 에서 하려고 생각한다.
다음은 위 예제 중 sigpipe.bash 을 실행하여 그 때 출력되는 trace.log의 끝(이 장의 논의에 필요한 부분 만)를 출력 한 결과이다.

먼저 strace 명령의 결과를 보면 write(2)가 실패하고 있다( EPIPE (Broken pipe))을 확인할 수 있다. 프로세스는 killed by SIGPIPE.
종료 상태의 결과를 출력하고 ${PIPESTATUS[@]} 는 bash 고유의 기능이다 (편리하다!).
exit status:141 143 는 sender.sh이 SIGPIPE에 의해 종료 (141) 한 것을 receiver.sh이 SIGTERM 종료(143) 한 것을 의미하고 있다.
적어도 Linux 는 128에 시그널 번호를 더한 것을 종료 상태로 하고 있는 것 같다.
Posix 에서는 반환 값은 128 이상이어야 한다고 정해져 있을뿐이지만 Linux 는 128+ 시그널 번호를 반환한다.
Linux의 표준 신호의 값을 확인하면 SIGPIPE : 13, SIGTERM : 15 이다.
reciever.sh는 kill 명령에서 SIGTERM 전송하고 종료하고있다. 즉 128 + 15 = 143
sender.sh는 대상 프로세스가 죽기 때문에 SIGPIPE 시그널이 발생하고 종료한다. 즉 128 + 13 = 141
종료 상태의 의미적으로도 확실히 SIGPIPE로 다운하고 있는지 확인할 수 있다.
(역자 추가)
C++ 오픈 소스 라이브러리 Poco 라이브러리를 보면 위의 문제는 Unix 계열에서만 발생하는 문제이고 Linux에서는 발생하지 않는다고 한다.

관련 코드의 스크린 샷
CentOS 테스트 - SIGPIPE
정말 linux 에서는 연결이 끊어진 소켓에 쓰기를 하여도 시그널이 발생하지 않는지 테스트 하였다.
결과는 리눅스에서는 시그널이 발생하지 않았다.
즉 SIGPIPE 시그널 관리는 유닉스 계열(OS X 포함)에서만 하면 된다.
예제 코드
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/epoll.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #define DEFAULT_PORT 32451 #define MAX_CONN 16 #define MAX_EVENTS 32 #define BUF_SIZE 16 #define MAX_LINE 256 void server_run(); int main() { server_run(); return 0; } static void epoll_ctl_add(int epfd, int fd, uint32_t events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) { perror("epoll_ctl()\n"); exit(1); } } static void set_sockaddr(struct sockaddr_in *addr) { bzero((char *)addr, sizeof(struct sockaddr_in)); addr->sin_family = AF_INET; addr->sin_addr.s_addr = INADDR_ANY; addr->sin_port = htons(DEFAULT_PORT); } static int setnonblocking(int sockfd) { if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK) == -1) { return -1; } return 0; } /* * epoll echo server */ void server_run() { int i; int n; int epfd; int nfds; int listen_sock; int conn_sock; int socklen; char buf[BUF_SIZE]; struct sockaddr_in srv_addr; struct sockaddr_in cli_addr; struct epoll_event events[MAX_EVENTS]; listen_sock = socket(AF_INET, SOCK_STREAM, 0); set_sockaddr(&srv_addr); bind(listen_sock, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); setnonblocking(listen_sock); listen(listen_sock, MAX_CONN); printf("listening port: %d:\n", DEFAULT_PORT); epfd = epoll_create(1); epoll_ctl_add(epfd, listen_sock, EPOLLIN | EPOLLOUT | EPOLLET); socklen = sizeof(cli_addr); for (;;) { nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); for (i = 0; i < nfds; i++) { if (events[i].data.fd == listen_sock) { /* handle new connection */ conn_sock = accept(listen_sock, (struct sockaddr *)&cli_addr, (socklen_t*)&socklen); inet_ntop(AF_INET, (char *)&(cli_addr.sin_addr), buf, sizeof(cli_addr)); printf("[+] connected with %s:%d\n", buf, ntohs(cli_addr.sin_port)); setnonblocking(conn_sock); epoll_ctl_add(epfd, conn_sock, EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLHUP); } else if (events[i].events & EPOLLIN) { /* handle EPOLLIN event */ for (;;) { bzero(buf, sizeof(buf)); n = read(events[i].data.fd, buf, sizeof(buf)); if (n <= 0 /* || errno == EAGAIN */ ) { break; } else { printf("[+] data: %s\n", buf); write(events[i].data.fd, buf, strlen(buf)); } } } else { printf("[+] unexpected\n"); } /* check if the connection is closing */ if (events[i].events & (EPOLLRDHUP | EPOLLHUP)) { printf("[+] connection closed\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL); close(events[i].data.fd); // SIGPIPE 테스트를 위해 접속이 끊어진 소켓에 일부러 쓰기를 한다 printf("SIGPIPE test !!!\n"); char sendData[] ="sigpipe"; if(write(events[i].data.fd, sendData, strlen(sendData)) < 0) { perror("send closed socket\n"); } continue; } } } } |
아래 실행 결과를 보면 접속이 끊어진 상태에서 일부러 쓰기를 하면 에러만 발생하고, 이후 새로운 접속을 문제 없이 받아들인다.

즉 SIGPIPE 시그널을 핸들링 하지 않아도 프로그램이 죽지 않는다.