起源:
测试服务器时经常出现 signal SIGPIPE, Broken pipe,查看原因初步定位为客户端发起RST后导致。因此针对引起该信号的原因做了测试和总结。
过程:
UNIX网络编程卷1,第五章SIGPIPE节指出:当一个进程向某个已收到RST的套接字执行写操作时,内核向该进程发送一个SIGPIPE信号。该信号默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止。
根据上述描述能解释测试服务器出现的现象。
我实现了一个客户端服务器回射程序,测试后有两种情况会导致Broken pipe
1.客户端正常关闭socket,服务端连续两次write socket后出现
2.客户端RST关闭socket,服务端连续两次write socket后出现
3.客户端异常崩溃(实际就是发出RST),服务端连续两次write socket后出现
4.客户端RST关闭socket,服务端先read收到RST然后write socket后出现
客户端代码:
#include<stdio.h>
#include <sys/socket.h> //connect,send,recv,setsockopt等
#include <sys/types.h>
#include <netinet/in.h> // sockaddr_in, "man 7 ip" ,htons
#include <poll.h> //poll,pollfd
#include <arpa/inet.h> //inet_addr,inet_aton
#include <unistd.h> //read,write
#include <netdb.h> //gethostbyname
#include <error.h> //perror
#include <stdio.h>
#include <errno.h> //errno
#include <string.h> // memset
#include<stdlib.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(void)
{
int sock;
if((sock =socket(AF_INET, SOCK_STREAM, 0))<0)
{
ERR_EXIT("ERROR");
}
struct linger m_sLinger;
m_sLinger.l_onoff=1; //(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff;0; 则功能和2.)作用相同;
m_sLinger.l_linger=0; //(容许逗留的时间为5秒)
// setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&m_sLinger,sizeof(struct linger));
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = inet_addr("192.168.2.159");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))< 0)
{
printf("errno=%d", errno);
ERR_EXIT("connect");
}
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
write(sock,sendbuf,strlen(sendbuf) -1);
perror("ERROR1");
// close(sock);
read(sock,recvbuf,sizeof(recvbuf));
printf("recvice: %s\n", recvbuf);
perror("ERROR2");
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
break;
}
close(sock);
sleep(30);
return 0;
}
服务端代码:
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include "errno.h"
#define MAXLINE 1024
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8080);
bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
printf("start listen\n");
listen(listenfd, 1024);
for(;;){
ssize_t n;
char buf[MAXLINE];
printf("maxline=%d\n", MAXLINE);
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
printf("new conn\n");
again:
if((n = read(connfd, buf, MAXLINE)) > 0)
{
printf("readdata=%s, bytesnum=%d\n", buf, n);
sleep(5);
int ret = write(connfd, buf, strlen(buf));
//write(connfd, buf, strlen(buf));
perror("ERROR2");
printf("write first error, %d, err=%d\n", ret, errno);
}
else if(n < 0)
{
if(errno == EINTR)
{
continue;
}
perror("ERROR3")
write(connfd, buf, strlen(buf));
break;
}
else if(n == 0)
{
perror("ERROR4");
printf("write second error, %d, err=%d\n", n, errno);
// read(connfd, buf, MAXLINE);
write(connfd, buf, strlen(buf));
write(connfd, buf, strlen(buf));
perror("ERROR5");
close(connfd);
return 0;
}
goto again;
}
close(connfd);
return 0;
}
客户端中代码 setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&m_sLinger,sizeof(struct linger))代表close socket时发送RST,否则正常关闭socket.
服务端代码n<0代表read时接收到RST,然后write会导致 Broken pipe,;n==0代表客户端正常关闭,连续两次write后都会出现Broken pipe
如果客户端write后直接close(注释代码),服务端read后n>0连续write(注释代码)会出现Broken pipe
试验过程中的另外一个结果:对于客户端正常close socket的,服务端第一次write返回写出字节数不会返回0,read会返回0.