Linux 服务器 C 代码在收到信号后停止
Linux server C code stalls after receiving a signal
我对这个简单的服务器代码有疑问,它按预期工作,直到收到信号。
对于调试,我在使用行调用 select 之前打印服务器和客户端文件描述符:
fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%d\n", server_socket_fd, client_socket_fd, fd_max);
正常运行时一直打印
server_socket_fd=3 client_socket_fd=4 fd_max=4
但是当它接收到一个信号时它打印这一行一次
server_socket_fd=3 client_socket_fd=-1 fd_max=3
然后程序停止。
使用 GDB 我在 signal_handler 中放置了一个断点,当它断点时我无法观察 client_socket_fd 变量,gdb 说
No symbol "client_socket_fd" in current context.
而且 signal_handler 函数没有 return 正确.. 如果我观察回溯:
(gdb) bt
#0 0xb7fdccf9 in ?? ()
#1 0xb7e26af3 in __libc_start_main (main=0x8048bdd <main>, argc=1, argv=0xbfffef24, init=0x8049a00 <__libc_csu_init>,
fini=0x8049a70 <__libc_csu_fini>, rtld_fini=0xb7fed160 <_dl_fini>, stack_end=0xbfffef1c) at libc-start.c:287
#2 0x08048b01 in _start ()
我不知道如何更深入地调试。
这是主要代码:
char receive_buf[2048];
int main(int argc, char *argv[]){
int server_socket_fd;
int client_socket_fd = -1;
int fd_max;
struct sockaddr_in s_in;
int one = 1;
int status;
fd_set readfds;
int port;
int next_option;
const char* short_options = "hp:d:";
const struct option long_options[] = {
{ "help", 0, NULL, 'h'},
{ "port", 1, NULL, 'p'},
{ "debug", 1, NULL, 'd'},
{ NULL, 0, NULL, 0}
};
program_name = argv[0];
port = DEFAULT_PORT;
debug = 0;
do{
next_option = getopt_long(argc, argv, short_options, long_options, NULL);
switch(next_option){
case 'h':
print_usage(stdout, 0);
break;
case 'p':
port = atoi(optarg);
if((port < 0)||(port > 65535)){
fprintf(stderr, "Invalid port number (%d), using default: %d", port, DEFAULT_PORT);
port = DEFAULT_PORT;
}
break;
case 'd':
debug = atoi(optarg);
if(debug < 0 || debug > 3)
debug = 0;
break;
case '?':
print_usage(stderr, 1);
break;
case -1:
break;
default:
abort();
}
}while(next_option != -1);
/************************* SIGNAL DEFINITIONS ***************************/
signal_action.sa_handler = (void *)signal_handler;
sigemptyset(&signal_action.sa_mask);
signal_action.sa_flags = SA_RESTART; // | SA_NOCLDSTOP;
if(sigaction(SIGINT, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGINT signal handler\n");
exit(1);
}
if(sigaction(SIGTERM, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGTERM signal handler\n");
exit(1);
}
if(sigaction(SIGWINCH, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGWINCH signal handler\n");
exit(1);
}
/* // ALSO TRIED WITH SIGNAL WITH SAME RESULT
if(signal(SIGWINCH, signal_handler) == SIG_ERR){
fprintf(stderr, "signal error\n");
return 1;
}
*/
s_in.sin_family = PF_INET;
s_in.sin_port = htons(port);
s_in.sin_addr.s_addr = INADDR_ANY;
if ((server_socket_fd = socket(s_in.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1){
perror("Error creating socket");
return 1;
}
if(setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1){
perror("Error setting socket parameters");
return 1;
}
////////////////////////////////////////////
int x;
x=fcntl(server_socket_fd,F_GETFL,0); // Get socket flags
fcntl(server_socket_fd,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag
////////////////////////////////////////////
if(bind(server_socket_fd, (struct sockaddr*) &s_in, sizeof(s_in)) == -1){
perror("Error binding socket");
return 1;
}
if(listen(server_socket_fd, 1) == -1){
perror("Error creating listening socket");
return 1;
}else{
printf("Server (%d) listening on port %d\n", server_socket_fd, port);
}
memset(receive_buf, '[=16=]', sizeof(receive_buf));
gettimeofday(&t_print, NULL);
while(1){
// SERVER
FD_ZERO(&readfds);
FD_SET(server_socket_fd, &readfds);
fd_max = server_socket_fd;
// ADD CLIENT IF CONNECTED
if(client_socket_fd > 0){
FD_SET(client_socket_fd, &readfds);
if(client_socket_fd > server_socket_fd)
fd_max = client_socket_fd;
}
// ADDED THIS FPRINTF TO CHECK VARIABLES <----------------------------------
fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%d\n", server_socket_fd, client_socket_fd, fd_max);
if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
if(errno != EINTR){
perror("select failed");
}
}
// ACCEPT CLIENT
if(FD_ISSET(server_socket_fd, &readfds)){
struct sockaddr_in s_in;
socklen_t len;
len = sizeof(s_in);
if((client_socket_fd = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
if(errno != EWOULDBLOCK){
perror("En accept");
}
}else
printf("New client connected from %s\n", inet_ntoa(s_in.sin_addr));
}
// RECEIVE FROM CLIENT
if(client_socket_fd > 0){
if(FD_ISSET(client_socket_fd, &readfds)){
handle_client(client_socket_fd);
}
}
}
return 0;
}
int handle_client(int cl_fd){
int n;
n = recv(cl_fd, receive_buf, sizeof(receive_buf) - 1, MSG_DONTWAIT);
if(n == 0){
fprintf(stderr,"--------------> DEBUG: handle_client:client %d closed connection\n", cl_fd);
}else if(n < 0){
if(errno == EAGAIN){
return 0;
}else{
fprintf(stderr,"--------------> DEBUG: handle_client: recv ERROR: client %d closed connection (errno: %d : %s)\n", cl_fd, errno, strerror(errno));
memset(receive_buf, 0, sizeof(receive_buf));
return -1;
}
}else{
receive_buf[n] = '[=16=]';
fprintf(stderr, "%s\n", receive_buf);
}
return 0;
}
void signal_handler(int sig){
switch(sig){
case SIGINT:
exit_properly(0);
break;
case SIGTERM:
exit_properly(1);
break;
case SIGABRT:
fprintf(stderr, "SIGABRT signal received\n");
break;
case SIGWINCH:
fprintf(stderr, "[2J");
fflush(stdout);
break;
default:
fprintf(stderr, "Unhandled signal %d received\n",sig);
break;
}
}
我不知道我还能做些什么来调试这个问题,我被困住了。任何帮助将不胜感激!
已编辑:
这是失败时的 strace 输出,如您所见,它打印(并且 select 使用)正确的文件描述符,然后,在信号发生后,client_socket_fd 是错误的,因为接受失败并返回 EAGAIN。我已经评论了 exit_properly 调用以及 SIGTERM 和 SIGINT 的信号处理。对于 SIGWINH 信号,我什么都不做,只是 return。
STRACE 输出:
write(2, "server_socket_fd=3 client_socket"..., 47server_socket_fd=3 client_socket_fd=4 fd_max=4
) = 47
select(5, [3 4], NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask []) = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16]) = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket"..., 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask []) = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16]) = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket"..., 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL
现在的signal_handler:
void signal_handler(int sig){
switch(sig){
/*
case SIGINT:
exit_properly(0); //sigint_flag = 1;
break;
case SIGTERM:
exit_properly(1); //sigterm_flag = 1;
break;
*/
case SIGWINCH:
//sigwinch_flag = 1;
/*
fprintf(stderr, "[2J");
fflush(stdout);
*/
break;
default:
//fprintf(stderr, "Unhandled signal %d received\n",sig);
break;
}
}
也在没有 SA_RESTART
标志的情况下尝试...同样的结果...?: /
我找到了使用时间变量的解决方案。
我不知道为什么,但是 select 发出的信号标记了要读取的 server_socket_fd(如果有人知道为什么请分享),就像客户端尝试连接一样,并接受返回的 EAGAIN 错误,因此 client_socket_fd 变量被写入 -1.
我只是使用临时变量 (ret) 解决了这个问题,而不是将接受结果直接分配给 client_socket_fd:
// ACCEPT CLIENT
if(FD_ISSET(server_socket_fd, &readfds)){
struct sockaddr_in s_in;
socklen_t len;
len = sizeof(s_in);
if((ret = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
if(errno != EWOULDBLOCK){
perror("En accept");
}
}else{
client_socket_fd = ret;
printf("New client connected from %s\n", inet_ntoa(s_in.sin_addr));
}
}
只有在确定变量没问题时才更新变量...经验教训! :)
select()
将被信号中断,return -1,并将 errno 设置为 EINTR,但您的代码无法处理此问题。
即使你安装了带有 SA_RESTART
的信号处理程序,仍然有一些系统调用会被中断,return 一个错误条件并将 errno 设置为 EINTR
.
查看 "Interruption of system calls and library functions by signal handlers"
http://man7.org/linux/man-pages/man7/signal.7.html/
部分
如果 select 失败,您的代码将继续检查 readfds
,如下所示:
if(FD_ISSET(server_socket_fd, &readfds)){
但是,如果 select() 失败,您传递给它的 fd_set
变量处于不确定状态,您不应该依赖它们的值。
相反,如果 select 失败,您应该重新开始循环,例如像这样的继续语句:
if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
if(errno != EINTR){
perror("select failed");
//Might be severe enough to quit your program...
}
continue;
}
我对这个简单的服务器代码有疑问,它按预期工作,直到收到信号。 对于调试,我在使用行调用 select 之前打印服务器和客户端文件描述符:
fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%d\n", server_socket_fd, client_socket_fd, fd_max);
正常运行时一直打印
server_socket_fd=3 client_socket_fd=4 fd_max=4
但是当它接收到一个信号时它打印这一行一次
server_socket_fd=3 client_socket_fd=-1 fd_max=3
然后程序停止。
使用 GDB 我在 signal_handler 中放置了一个断点,当它断点时我无法观察 client_socket_fd 变量,gdb 说
No symbol "client_socket_fd" in current context.
而且 signal_handler 函数没有 return 正确.. 如果我观察回溯:
(gdb) bt
#0 0xb7fdccf9 in ?? ()
#1 0xb7e26af3 in __libc_start_main (main=0x8048bdd <main>, argc=1, argv=0xbfffef24, init=0x8049a00 <__libc_csu_init>,
fini=0x8049a70 <__libc_csu_fini>, rtld_fini=0xb7fed160 <_dl_fini>, stack_end=0xbfffef1c) at libc-start.c:287
#2 0x08048b01 in _start ()
我不知道如何更深入地调试。
这是主要代码:
char receive_buf[2048];
int main(int argc, char *argv[]){
int server_socket_fd;
int client_socket_fd = -1;
int fd_max;
struct sockaddr_in s_in;
int one = 1;
int status;
fd_set readfds;
int port;
int next_option;
const char* short_options = "hp:d:";
const struct option long_options[] = {
{ "help", 0, NULL, 'h'},
{ "port", 1, NULL, 'p'},
{ "debug", 1, NULL, 'd'},
{ NULL, 0, NULL, 0}
};
program_name = argv[0];
port = DEFAULT_PORT;
debug = 0;
do{
next_option = getopt_long(argc, argv, short_options, long_options, NULL);
switch(next_option){
case 'h':
print_usage(stdout, 0);
break;
case 'p':
port = atoi(optarg);
if((port < 0)||(port > 65535)){
fprintf(stderr, "Invalid port number (%d), using default: %d", port, DEFAULT_PORT);
port = DEFAULT_PORT;
}
break;
case 'd':
debug = atoi(optarg);
if(debug < 0 || debug > 3)
debug = 0;
break;
case '?':
print_usage(stderr, 1);
break;
case -1:
break;
default:
abort();
}
}while(next_option != -1);
/************************* SIGNAL DEFINITIONS ***************************/
signal_action.sa_handler = (void *)signal_handler;
sigemptyset(&signal_action.sa_mask);
signal_action.sa_flags = SA_RESTART; // | SA_NOCLDSTOP;
if(sigaction(SIGINT, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGINT signal handler\n");
exit(1);
}
if(sigaction(SIGTERM, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGTERM signal handler\n");
exit(1);
}
if(sigaction(SIGWINCH, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGWINCH signal handler\n");
exit(1);
}
/* // ALSO TRIED WITH SIGNAL WITH SAME RESULT
if(signal(SIGWINCH, signal_handler) == SIG_ERR){
fprintf(stderr, "signal error\n");
return 1;
}
*/
s_in.sin_family = PF_INET;
s_in.sin_port = htons(port);
s_in.sin_addr.s_addr = INADDR_ANY;
if ((server_socket_fd = socket(s_in.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1){
perror("Error creating socket");
return 1;
}
if(setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1){
perror("Error setting socket parameters");
return 1;
}
////////////////////////////////////////////
int x;
x=fcntl(server_socket_fd,F_GETFL,0); // Get socket flags
fcntl(server_socket_fd,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag
////////////////////////////////////////////
if(bind(server_socket_fd, (struct sockaddr*) &s_in, sizeof(s_in)) == -1){
perror("Error binding socket");
return 1;
}
if(listen(server_socket_fd, 1) == -1){
perror("Error creating listening socket");
return 1;
}else{
printf("Server (%d) listening on port %d\n", server_socket_fd, port);
}
memset(receive_buf, '[=16=]', sizeof(receive_buf));
gettimeofday(&t_print, NULL);
while(1){
// SERVER
FD_ZERO(&readfds);
FD_SET(server_socket_fd, &readfds);
fd_max = server_socket_fd;
// ADD CLIENT IF CONNECTED
if(client_socket_fd > 0){
FD_SET(client_socket_fd, &readfds);
if(client_socket_fd > server_socket_fd)
fd_max = client_socket_fd;
}
// ADDED THIS FPRINTF TO CHECK VARIABLES <----------------------------------
fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%d\n", server_socket_fd, client_socket_fd, fd_max);
if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
if(errno != EINTR){
perror("select failed");
}
}
// ACCEPT CLIENT
if(FD_ISSET(server_socket_fd, &readfds)){
struct sockaddr_in s_in;
socklen_t len;
len = sizeof(s_in);
if((client_socket_fd = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
if(errno != EWOULDBLOCK){
perror("En accept");
}
}else
printf("New client connected from %s\n", inet_ntoa(s_in.sin_addr));
}
// RECEIVE FROM CLIENT
if(client_socket_fd > 0){
if(FD_ISSET(client_socket_fd, &readfds)){
handle_client(client_socket_fd);
}
}
}
return 0;
}
int handle_client(int cl_fd){
int n;
n = recv(cl_fd, receive_buf, sizeof(receive_buf) - 1, MSG_DONTWAIT);
if(n == 0){
fprintf(stderr,"--------------> DEBUG: handle_client:client %d closed connection\n", cl_fd);
}else if(n < 0){
if(errno == EAGAIN){
return 0;
}else{
fprintf(stderr,"--------------> DEBUG: handle_client: recv ERROR: client %d closed connection (errno: %d : %s)\n", cl_fd, errno, strerror(errno));
memset(receive_buf, 0, sizeof(receive_buf));
return -1;
}
}else{
receive_buf[n] = '[=16=]';
fprintf(stderr, "%s\n", receive_buf);
}
return 0;
}
void signal_handler(int sig){
switch(sig){
case SIGINT:
exit_properly(0);
break;
case SIGTERM:
exit_properly(1);
break;
case SIGABRT:
fprintf(stderr, "SIGABRT signal received\n");
break;
case SIGWINCH:
fprintf(stderr, "[2J");
fflush(stdout);
break;
default:
fprintf(stderr, "Unhandled signal %d received\n",sig);
break;
}
}
我不知道我还能做些什么来调试这个问题,我被困住了。任何帮助将不胜感激!
已编辑:
这是失败时的 strace 输出,如您所见,它打印(并且 select 使用)正确的文件描述符,然后,在信号发生后,client_socket_fd 是错误的,因为接受失败并返回 EAGAIN。我已经评论了 exit_properly 调用以及 SIGTERM 和 SIGINT 的信号处理。对于 SIGWINH 信号,我什么都不做,只是 return。
STRACE 输出:
write(2, "server_socket_fd=3 client_socket"..., 47server_socket_fd=3 client_socket_fd=4 fd_max=4
) = 47
select(5, [3 4], NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask []) = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16]) = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket"..., 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask []) = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16]) = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket"..., 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL
现在的signal_handler:
void signal_handler(int sig){
switch(sig){
/*
case SIGINT:
exit_properly(0); //sigint_flag = 1;
break;
case SIGTERM:
exit_properly(1); //sigterm_flag = 1;
break;
*/
case SIGWINCH:
//sigwinch_flag = 1;
/*
fprintf(stderr, "[2J");
fflush(stdout);
*/
break;
default:
//fprintf(stderr, "Unhandled signal %d received\n",sig);
break;
}
}
也在没有 SA_RESTART
标志的情况下尝试...同样的结果...?: /
我找到了使用时间变量的解决方案。
我不知道为什么,但是 select 发出的信号标记了要读取的 server_socket_fd(如果有人知道为什么请分享),就像客户端尝试连接一样,并接受返回的 EAGAIN 错误,因此 client_socket_fd 变量被写入 -1.
我只是使用临时变量 (ret) 解决了这个问题,而不是将接受结果直接分配给 client_socket_fd:
// ACCEPT CLIENT
if(FD_ISSET(server_socket_fd, &readfds)){
struct sockaddr_in s_in;
socklen_t len;
len = sizeof(s_in);
if((ret = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
if(errno != EWOULDBLOCK){
perror("En accept");
}
}else{
client_socket_fd = ret;
printf("New client connected from %s\n", inet_ntoa(s_in.sin_addr));
}
}
只有在确定变量没问题时才更新变量...经验教训! :)
select()
将被信号中断,return -1,并将 errno 设置为 EINTR,但您的代码无法处理此问题。
即使你安装了带有 SA_RESTART
的信号处理程序,仍然有一些系统调用会被中断,return 一个错误条件并将 errno 设置为 EINTR
.
查看 "Interruption of system calls and library functions by signal handlers" http://man7.org/linux/man-pages/man7/signal.7.html/
部分如果 select 失败,您的代码将继续检查 readfds
,如下所示:
if(FD_ISSET(server_socket_fd, &readfds)){
但是,如果 select() 失败,您传递给它的 fd_set
变量处于不确定状态,您不应该依赖它们的值。
相反,如果 select 失败,您应该重新开始循环,例如像这样的继续语句:
if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
if(errno != EINTR){
perror("select failed");
//Might be severe enough to quit your program...
}
continue;
}