linux网络编程的坑,网络编程中的坑(二):服务器程序自动退出

首先看代码。(完整代码在下载)

server.c

int main(void)

{

int listenfd;

if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)

ERR_EXIT("socket error");

struct sockaddr_in servaddr;

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(5188);

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

int on = 1;

if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)

ERR_EXIT("setsockopt error");

if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)

ERR_EXIT("bind error");

if (listen(listenfd, SOMAXCONN) < 0)

ERR_EXIT("listen error");

while (1) {

int conn;

struct sockaddr_in peeraddr;

socklen_t peerlen = sizeof(peeraddr);

if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)

ERR_EXIT("accept error");

printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

while (1) {

char recvbuf[1024];

int readlen, writelen;

readlen = recv(conn, recvbuf, sizeof(recvbuf), 0);

printf("read len = %d\n", readlen);

if (readlen <= 0)

break;

writelen = send(conn, recvbuf, readlen, 0);

printf("write1 len = %d\n", writelen);

sleep(1);

writelen = send(conn, recvbuf, readlen, 0);

printf("write2 len = %d\n", writelen);

writelen = send(conn, recvbuf, readlen, 0);

printf("write3 len = %d\n", writelen);

if (writelen < 0)

perror("write error");

}

printf("close\n");

close(conn);

}

close(listenfd);

return 0;

}

client.c

int main(int argc, const char *argv[])

{

int sock;

char buffer[1024];

if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)

ERR_EXIT("socket error");

struct sockaddr_in servaddr;

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(5188);

servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)

ERR_EXIT("connect error");

printf("write...\n");

send(sock, buffer, sizeof(buffer), 0);

printf("read...\n");

recv(sock, buffer, sizeof(buffer), 0);

printf("close\n");

close(sock);

return 0;

}

代码很简单,客户端发送一串数据,等待接收,然后关闭socket退出。服务器端接收到数据后,把数据发送三次给客户端。

为了让问题容易暴露,在第一和第二次发送之间加了延时。

执行结果:

# ./server

recv connect ip=127.0.0.1 port=60944

read len = 1024

write len = 1024

write len = 1024

# ./client

write...

read...

close

我们看到,服务器在发送两次数据之后竟然退出了!而且没有任何错误信息!

使用gdb试试有没有线索:

# gdb ./server

GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1

Copyright (C) 2014 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later

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-linux-gnu".

Type "show configuration" for configuration details.

For bug reporting instructions, please see:

.

Find the GDB manual and other documentation resources online at:

.

For help, type "help".

Type "apropos word" to search for commands related to "word"...

Reading symbols from ./server...done.

(gdb) r

Starting program: /root/zhengdi/network/network_examples/sigpipe/server

recv connect ip=127.0.0.1 port=60947

read len = 1024

write len = 1024

write len = 1024

Program received signal SIGPIPE, Broken pipe.

0x00007ffff7b104fd in __libc_send (fd=4, buf=0x7fffffffe140, n=1024, flags=-1)

at ../sysdeps/unix/sysv/linux/x86_64/send.c:27

27      ../sysdeps/unix/sysv/linux/x86_64/send.c: No such file or directory.

(gdb) bt

#0  0x00007ffff7b104fd in __libc_send (fd=4, buf=0x7fffffffe140, n=1024, flags=-1)

at ../sysdeps/unix/sysv/linux/x86_64/send.c:27

#1  0x0000000000400cc3 in main () at server.c:61

可以看到,服务器在调用第三次send函数时收到了SIGPIPE信号。

关于SIGPIPE信号的解释:

SIGPIPE

The SIGPIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the otherend.

由于程序没对该信号处理,该信号的默认动作是退出进程。

我们结合TCP断开连接时的四次握手来分析一下。

假设客户端调用close函数,其本意是结束与服务器端的通信,即不再进行发送和接收。但是,根据TCP协议,协议栈会只发送一个FIN给服务器 ,此时,客户端只是关闭了对服务器“写”的通道,服务器并不知道它还能不能对客户端写数据(即,服务器不知道客户端调用的是close还是shutdown)。

如果服务器再发送数据给客户端,由于客户端的连接已断开,服务器会收到RST,协议栈无法把该状态返回给应用层。当服务器再次给客户端发送数据时,由于TCP协议层已处于RST状态,数据将不会发出,而是返回SIGPIPE信号给应用层。

SIGPIPE在写服务器端的程序时,一般都需要忽略掉。在程序开始处添加:signal(SIGPIPE, SIG_IGN);

运行程序:

# ./server

recv connect ip=127.0.0.1 port=32832

read len = 1024

write1 len = 1024

write2 len = 1024

write3 len = -1

write error: Broken pipe

read len = 0

close

recv connect ip=127.0.0.1 port=32833

read len = 1024

write1 len = 1024

write2 len = 1024

write3 len = -1

write error: Broken pipe

read len = 0

close

程序还是接收到了SIGPIPE信号,但不会自动退出了。

最后一个问题,既然写服务器程序的时候都要忽略SIGPIPE,那么SIGPIPE用在什么时候呢?

答案是,在命令行执行管道程序时,这个信号就很有用。例如,cat log | grep error | head,当log数据量很大,只要找出了前10行包含error行,grep和cat就会依次接收到SIGPIPE信号退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值