Program received signal SIGPIPE, Broken pipe原因总结,socket关闭测试总结

本文深入探讨了UNIX网络编程中SIGPIPE信号的触发原因,通过客户端和服务端代码示例,解析了客户端RST关闭socket和正常关闭socket两种情况下,服务端出现Broken pipe错误的具体场景。

起源:

测试服务器时经常出现 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.

 

 

 

 

 

 

/**********************************************************************/ /* Execute a CGI script. Will need to set environment variables as * appropriate. * Parameters: client socket descriptor * path to the CGI script */ /**********************************************************************/ void execute_cgi(int client, const char *path, const char *method, const char *query_string) { // 缓冲区 char buf[1024]; int cgi_output[2]; int cgi_input[2]; pid_t pid; int status; int i = 0; char c; int numchars = 1; int content_length = -1; // 默认字符 buf[0] = 'A'; buf[1] = '\0'; // 忽略大小写比较字符串 if (strcasecmp(method, "GET") == 0) while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); else /* POST */ { numchars = get_line(client, buf, sizeof(buf)); while ((numchars > 0) && strcmp("\n", buf)) { // 如果是POST请求,就需要得到Content-Length,Content-Length:这个字符串一共长为15位,所以 // 取出头部一句后,将第16位设置结束符,进行比较 // 第16位置为结束 buf[15] = '\0'; if (strcasecmp(buf, "Content-Length:") == 0) // 内存从第17位开始就是长度,将17位开始的所有字符串转成整数就是content_length content_length = atoi(&(buf[16])); numchars = get_line(client, buf, sizeof(buf)); } if (content_length == -1) { bad_request(client); return; } } sprintf(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); // 建立output管道 if (pipe(cgi_output) < 0) { cannot_execute(client); return; } // 建立input管道 if (pipe(cgi_input) < 0) { cannot_execute(client); return; } if ((pid = fork()) < 0) { cannot_execute(client); return; } if (pid == 0) /* child: CGI script */ { char meth_env[255]; char query_env[255]; char length_env[255]; // 子进程输出重定向到output管道的1端 dup2(cgi_output[1], 1); // 子进程输入重定向到input管道的0端 dup2(cgi_input[0], 0); // 关闭无用管道口 close(cgi_output[0]); close(cgi_input[1]); // CGI环境变量 sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if (strcasecmp(method, "GET") == 0) { sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { /* POST */ sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } // 替换执行path execl(path, path, NULL); // int m = execl(path, path, NULL); // 如果path有问题,例如将html网页改成可执行的,但是执行后m为-1 // 退出子进程,管道被破坏,但是父进程还在往里面写东西,触发Program received signal SIGPIPE, Broken pipe. exit(0); } else { /* parent */ close(cgi_output[1]); close(cgi_input[0]); if (strcasecmp(method, "POST") == 0) for (i = 0; i < content_length; i++) { recv(client, &c, 1, 0); ssize_t bytes_written = write(cgi_input[1], &c, 1); if (bytes_written <= 0) { // 错误处理 perror("Failed to write to CGI"); close(cgi_input[1]); close(cgi_input[0]); break; } } // 从output管道读到子进程处理后的信息,然后send出去 while (read(cgi_output[0], &c, 1) > 0) send(client, &c, 1, 0); // 完成操作后关闭管道 close(cgi_output[0]); close(cgi_input[1]); // 等待子进程返回 waitpid(pid, &status, 0); } }
08-08
<think> 我们遇到了一个线程接收到SIGPIPE信号的场景。SIGPIPE通常发生在当一个进程试图向一个已经关闭的套接字或管道写入数据时。在这个例子中,线程名为"gr_client",表明这可能是一个客户端线程,在执行send函数时遇到了问题。 ### 原因分析: 1. **SIGPIPE触发机制**: - 当向已关闭连接的套接字写入数据时,系统会发送SIGPIPE信号。默认行为是终止进程,但程序可能捕获或忽略该信号。 - 这里线程13在执行`send`函数时触发了该信号。 2. **常见原因**: - 对端已关闭连接,但本端仍尝试发送数据。 - 网络连接异常断开(如网络故障、对端进程崩溃)。 ### 调试建议: 1. **检查连接状态**: ```bash (gdb) bt # 查看调用栈,确定send调用位置 (gdb) info threads # 查看其他线程状态,确认是否有线程处理连接关闭 ``` 2. **忽略SIGPIPE(临时方案)**: ```bash (gdb) handle SIGPIPE nostop noprint pass # GDB中忽略信号 ``` 或在代码中设置: ```c signal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE ``` 3. **检查套接字有效性**: - 在调用send前,应检查套接字是否有效(非负)及错误码(如EPIPE)。 4. **协议设计改进**: - 实现心跳机制检测连接存活。 - 设置TCP keepalive选项。 ### 预防措施: - **代码层面**: ```c // 设置SO_NOSIGPIPE(部分系统支持) setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &(int){1}, sizeof(int)); ``` - **处理send错误**: ```c ssize_t sent = send(sockfd, buf, len, MSG_NOSIGNAL); // Linux下避免触发SIGPIPE if (sent == -1) { if (errno == EPIPE) { /* 对端关闭处理 */ } } ``` §§相关问题§§ 1. 在多线程网络程序中,如何统一管理套接字以避免出现悬垂引用? 2. 当使用`MSG_NOSIGNAL`标志时,Linux与BSD系统在错误处理上有何差异? 3. 除了SIGPIPE,还有哪些信号会因网络连接异常而触发? 4. 如何通过GDB在SIGPIPE触发时自动捕获并打印对端关闭的堆栈信息? 5. 对于非阻塞套接字,send返回EAGAIN与EPIPE分别对应什么处理逻辑? </think> ### SIGPIPE信号分析与处理方案 #### 原因解析 1. **信号来源** - `SIGPIPE`在**向已关闭的管道/套接字写入数据**时触发 - 此处线程"gr_client"在`send()`系统调用时触发(`libpthread.so.0`) 2. **典型场景** ```mermaid graph LR A[客户端send数据] --> B{连接状态} B -->|连接已关闭| C[触发SIGPIPE] B -->|连接正常| D[发送成功] ``` #### 调试步骤 1. **查看调用栈定位问题点** ```bash (gdb) bt # 查看完整调用栈 (gdb) frame N # 切到具体栈帧 ``` 2. **检查套接字状态** ```bash (gdb) p errno # 查看错误码 (gdb) call close(fd) # 测试套接字有效性 ``` 3. **信号处理配置** ```bash (gdb) info signals SIGPIPE # 查看信号处理方式 (gdb) handle SIGPIPE nostop # 调试时忽略信号 ``` #### 解决方案 **代码层修复** ```c // 方案1:忽略SIGPIPE signal(SIGPIPE, SIG_IGN); // 方案2:检测send错误 (Linux专用) ssize_t sent = send(sockfd, buf, len, MSG_NOSIGNAL); if (sent == -1 && errno == EPIPE) { // 处理断连逻辑 } ``` **系统层配置** ```bash # 设置TCP keepalive setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &(int){1}, sizeof(int)); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值