How can I trap a signal (`SIGPIPE`) for a socket that closes?
You could simply ignore SIGPIPE. It's a useless, annoying signal.
signal(SIGPIPE, SIG_IGN);
If you ignore it then your program will instead receive an EPIPE error code from the failed write() call. This lets you handle the I/O error at a sensible place in your code rather than in some global signal handler.
EPIPE
fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIGPIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.)
Related videos on Youtube
Sergey Kotyushkin
Updated on June 04, 2022Comments
-
Sergey Kotyushkin almost 2 years
I've written a server that accepts a socket connection on a secondary port for the purposes of streaming debugging information that normally goes to
stderr
. This second port --an error serving port-- is only intended to have one connection at a time, which, is convenient, because it allows to me redirectstderr
using adup2(2)
call. (See Can I redirect a parent process's stderr to a socket file descriptor on a forked process?).The following code is nearly satisfactory in every regard. When a client logs into the port, the
stderr
stream is directed to the socket. When another client logs in, the stream is redirected again, and the first client stops receiving: entirely satisfactory.Where it falls short in the design is when the client closes the connection, the server crashes because it is trying to
write()
to a socket that is closed.I've got a rudimentary signal handler for the normal child processes, but I'm not sure how to handle the specific signal from the parent process when the error socket closes.
How can I trap the signal (in the parent) that the connection on the ERR_PORT_NUM has closed and have the signal handler reopen (or
dup
) stderr back to/dev/null
for the next awaiting error client?Also, what should I do with an original error client connection when a second connects? Currently the first client is left dangling. Even a non-graceful shut-down of the first connection is acceptable.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <fcntl.h> #include <errno.h> #include <pwd.h> #include <signal.h> #include <netinet/in.h> #include <sys/mman.h> #define PORT_NUM 12345 #define ERR_PORT_NUM 54321 static void child_handler(int signum) { switch (signum) { case SIGALRM: exit(EXIT_FAILURE); break; case SIGUSR1: exit(EXIT_SUCCESS); break; case SIGCHLD: exit(EXIT_FAILURE); break; } } static void daemonize(void) { /* Trap signals that we expect to recieve */ signal(SIGUSR1, child_handler); signal(SIGALRM, child_handler); signal(SIGCHLD, SIG_IGN); /* A child process dies */ signal(SIGTSTP, SIG_IGN); /* Various TTY signals */ signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGHUP, SIG_IGN); /* Ignore hangup signal */ signal(SIGTERM, SIG_DFL); /* Die on SIGTERM */ freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); } static void server_work(void) { int sockfd, err_sockfd; socklen_t clilen; struct sockaddr_in serv_addr, cli_addr, err_serv_addr, err_cli_addr; struct timeval tv = { 0 }; int new_stderr; sockfd = socket(AF_INET, SOCK_STREAM, 0); err_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0 || err_sockfd < 0) return; memset((char *) &serv_addr, '\0', sizeof(serv_addr)); memset((char *) &err_serv_addr, '\0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(PORT_NUM); err_serv_addr.sin_family = AF_INET; err_serv_addr.sin_addr.s_addr = INADDR_ANY; err_serv_addr.sin_port = htons(ERR_PORT_NUM); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) return; if (bind (err_sockfd, (struct sockaddr *) &err_serv_addr, sizeof(err_serv_addr)) < 0) return; listen(sockfd, 5); listen(err_sockfd, 5); clilen = sizeof(cli_addr); while (1) { int maxfd; fd_set read_sockets_set; FD_ZERO(&read_sockets_set); FD_SET(sockfd, &read_sockets_set); FD_SET(err_sockfd, &read_sockets_set); maxfd = (err_sockfd > sockfd) ? err_sockfd : sockfd; if (select(maxfd + 1, &read_sockets_set, NULL, NULL, NULL) < 0) { break; } if (FD_ISSET(sockfd, &read_sockets_set)) { /* Typical process fork(2) and such ... not gremaine to the question. */ } if (FD_ISSET(err_sockfd, &read_sockets_set)) { new_stderr = accept(err_sockfd, (struct sockaddr *) &err_cli_addr, &clilen); dup2(new_stderr, STDERR_FILENO); } } close(sockfd); close(err_sockfd); return; } int main(int argc, char *argv[]) { daemonize(); /* greatly abbreviated for question */ server_work(); return 0; }
-
Sergey Kotyushkin over 12 yearshmm ... the failing
write()
calls are inside the library. Which appear to ignore the return values. I suppose I could do anif (ftell(stderr) < 0) ...
just before mydup2()
call. Messy ... but it is only a debugging tool. -
sudo almost 10 yearsWhere should we put
signal(SIGPIPE, SIG_IGN);
? Just anywhere before any sockets are created, or is it once per thread?