在极客时间中学习专栏<网络编程实战>中有一个思考题,觉得下面的评论回答的有点浅显,结合工作中排查过的实际问题,想结合自己的理解展开讲下。
源代码(client.c):
#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <sys/time.h> /* timeval{} for select() */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <string.h>
#include <unistd.h>
#include <sys/un.h> /* for Unix domain sockets */
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamclient <local_path>");
}
int sockfd;
struct sockaddr_un servaddr;
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
error(1, errno, "create socket failed");
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, argv[1]);
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "connect failed");
}
char send_line[MAXLINE];
bzero(send_line, MAXLINE);
char recv_line[MAXLINE];
while (fgets(send_line, MAXLINE, stdin) != NULL) {
int nbytes = sizeof(send_line);
if (write(sockfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
if (read(sockfd, recv_line, MAXLINE) == 0)
error(1, errno, "server terminated prematurely");
fputs(recv_line, stdout);
}
exit(0);
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <sys/un.h> /* for Unix domain sockets */
#include <sys/select.h> /* for convenience */
#include <pthread.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
//signal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE 信号
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed"); /* back to for() */
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
printf("client quit");
exit(0);
}
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
if (write(connfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
}
close(listenfd);
close(connfd);
exit(0);
}
编译运行:
server端(先运行)
[root net]#./server /var/lib/unixstream.sock
Receive: hello,world
Receive: fine,thank you
client quit[root net]#
client端
[root net]#./client /var/lib/unixstream.sock
hello,world
Hi, hello,world
fine,thank you
Hi, fine,thank you
^C
思考题:
我们看到客户端被杀死后,服务器端也正常退出了。看下退出后打印的日志,你不妨判断一下引起服务器端正常退出的逻辑是什么?
解答:
在服务端的代码中,对收到的客户端发送的数据长度做了判断,如果长度为0,则主动关闭服务端程序。这是杀死客户端后引发服务端关闭的原因。这也说明客户端在被强行终止的时候,会最后向服务端发送一条空消息,告知服务器自己这边的程序关闭了。
那既然这里的代码对长度做了判断,如果长度为0则退出,那我们注释掉退出的代码是不是就不会退出了?
server端(先启动)
[root net]#./server /var/lib/unixstream.sock
Receive: g1
Receive: g2
[root net]#
client端
[root net]#./client /var/lib/unixstream.sock
g1
Hi, g1
g2
Hi, g2
^C
我们看到及时我们把exit(0)代码注释掉,server端还是依然退出。这里的原因又是什么呢?
这里分享一个小技巧,可以使用gdb排查退出原因:
server端
[root net]#gdb ./server
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/workspace/cplusplus/net/server...done.
(gdb) set args /var/lib/unixstream.sock
(gdb) r
Starting program: /root/workspace/cplusplus/net/./server /var/lib/unixstream.sock
Receive: g1
Receive: g2
Program received signal SIGPIPE, Broken pipe.
0x00007ffff7afcba0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb)
client端
[root net]#./client /var/lib/unixstream.sock
g1
Hi, g1
g2
Hi, g2
^C
我们可以看到服务端收到信号SIGPIPE,进而信号默认行为退出程序。
man 手册中关于该信号的解读:
SIGPIPE P1990 Term Broken pipe: write to pipe with no readers; see pipe(7)
连接建立,若某一端关闭连接,而另一端仍然向它写数据,第一次写数据后会收到RST响应,此后再写数据,内核将向进程发出SIGPIPE信号,通知进程此连接已经断开。而SIGPIPE信号的默认处理是终止程序,导致上述问题的发生。
我们添加代码屏蔽信号SIGPIPE
/* Catch Signal Handler functio */
void signal_callback_handler(int signum)
{
printf("Caught signal SIGPIPE %d\n",signum);
}
//main函数中添加
/* Catch Signal Handler SIGPIPE */
signal(SIGPIPE, signal_callback_handler);
重新编译运行后发现服务端程序依旧自动退出。
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <sys/un.h> /* for Unix domain sockets */
#include <signal.h>
#include <sys/select.h> /* for convenience */
#include <pthread.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
/* Catch Signal Handler functio */
void signal_callback_handler(int signum)
{
printf("Caught signal SIGPIPE %d\n",signum);
}
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
/* Catch Signal Handler SIGPIPE */
signal(SIGPIPE, signal_callback_handler);
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed"); /* back to for() */
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
printf("client quit");
//exit(0);
}
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
if (write(connfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
}
close(listenfd);
close(connfd);
exit(0);
}
参考stackoverflow上的一篇回答将write换成send,在client端ctrl+c退出后server端不会退出。
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <sys/un.h> /* for Unix domain sockets */
#include <signal.h>
#include <sys/select.h> /* for convenience */
#include <pthread.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed"); /* back to for() */
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
usleep(100);
}
if(strlen(buf) <= 0) continue;
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
int n_written = send(connfd,send_line,nbytes,MSG_NOSIGNAL);
if (n_written <= 0) {
error(1, errno, "send failed");
}
}
close(listenfd);
close(connfd);
exit(0);
}
但是不知道为什么参考stackoverflow中的答案没有屏蔽信号SIGPIPE.