[SIGPIPE 错误] 一个 Linux socket 程序,没有任何报错打印直接退出程序

1. 问题

在编写一个程序的时候,当然程序很复杂,遇到了一个 Linux socket 程序,没有任何报错打印直接退出程序,但是在程序里面我有很多 error log ,在程序退出的时候完全没有打印。为了说明问题,我编写了一个 demo 来解释,并且也作为自己的记录。

demo 的 server 端如下:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024

void error_exit(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

int main()
{
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

    // 1. 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        error_exit("socket creation failed");
    }

    // 设置 SO_REUSEADDR 选项
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
    {
        error_exit("setsockopt failed");
    }

    // 2. 绑定地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
    {
        error_exit("bind failed");
    }

    // 3. 监听端口
    if (listen(server_fd, MAX_CLIENTS) == -1)
    {
        error_exit("listen failed");
    }

    printf("Server listening on port %d...\n", PORT);

    while (1)
    {
        // 4. 接受客户端连接
        if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len)) == -1)
        {
            perror("accept failed");
            continue; // 继续接受其他连接
        }

        // 打印客户端信息
        printf("New connection from %s:%d\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        // 5. 处理客户端请求
        while (1)
        {
            ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);

            if (bytes_read == -1)
            {
                perror("read error");
                break;
            }
            else if (bytes_read == 0)
            {
                printf("Client disconnected\n");
                break;
            }


            buffer[bytes_read] = '\0'; // 添加字符串终止符
            printf("Received %zd bytes: %s\n", bytes_read, buffer);
            
            // 等待 1 秒,为了更好的测试
            sleep(1);
            printf("[info] 1 \n");
            // 回显数据
            // 往一个对端关闭的客户端的 套接字 write 数据,会返回一个 sigpipe 信号,该信号会直接终止进程。
            // 解决办法: 忽略 SIGPIPE 信号
            if (write(client_fd, buffer, bytes_read) != bytes_read)
            {
                perror("write error");
                // break;
            }

            printf("[info] 2 \n");
        }

        // 关闭客户端socket
        close(client_fd);
    }

    // 关闭服务器 socket(实际不会执行到这里)
    close(server_fd);
    return 0;
}

demo 的 客户端如下:

py 复制代码
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 文件名:client.py
 
import socket               # 导入 socket 模块
 
s = socket.socket()         # 创建 socket 对象
host = "192.168.0.4" # 获取本地主机名
port = 8080                # 设置端口号
 
s.connect((host, port))
# 发送数据很长
send_data = "Today is Mother's Day" * 10000
print("send data len: {} ".format(len(send_data)))

s.send((send_data).encode())
print(s.recv(1024))
s.close()

2. 现象

在上面的服务端程序 ,会遇到下面的打印,在打印到了 [info] 1 后就没有打印 [info] 2 了。

复制代码
...
...
...
[info] 1 
[chen@localhost 1.sigpipe]$ 

以上的打印是在 write(client_fd, buffer, bytes_read) 的时候出错的

3. 原因

经过查阅资料,发现 对一个对端已经关闭的 socket 调用两次 write , 第二次将会生成 SIGPIPE 信号, 该信号默认结束进程.

SIGPIPE 产生的流程:

当服务器 close 一个连接时,若 client 端接着发数据。根据 TCP 协议的规定,会收到一个 RST 响应,client 再往这个服务器发送数据时,系统会发出一个 SIGPIPE 信号给进程,告诉进程这个连接已经断开了,不要再写了。

4. 解决方法

在 main 程序开头设置该进程忽略 SIGPIPE 信号。气质就是在 main() 函数的一开头就添加一行这样的代码:

C 复制代码
signal(SIGPIPE, SIG_IGN);

5. 感想

在短的 demo 中比较好定位到原因,但是在一个真实开发中的功能的子模块中,代码已经写了几千行,甚至在几万行的代码,莫名奇妙的进程没有任何提示就退出,是真的叫人发狂。

在这里以作记录。

相关推荐
风行無痕23 分钟前
Ubuntu Linux系统配置账号无密码sudo
linux·服务器·ubuntu
zhczzm1 小时前
深入浅出之STL源码分析2_stl与标准库,编译器的关系
c++
驱动小百科1 小时前
WiFi出现感叹号上不了网怎么办 轻松恢复网络
网络·智能路由器·wifi出现感叹号怎么解决·wifi无法上网·电脑wifi
好多知识都想学1 小时前
协议路由与路由协议
网络·智能路由器
爆农1 小时前
centos搭建dokcer和vulhub
linux·运维·centos
SZ1701102311 小时前
中继器的作用
服务器·网络·智能路由器
chenxy021 小时前
如何快速分享服务器上的文件
运维·服务器
重启就好2 小时前
【Ansible】模块详解
linux·服务器·ansible
o0o_-_2 小时前
【瞎折腾/mi50 32G/ubuntu】mi50显卡ubuntu运行大模型开坑(三)安装风扇并且控制转速
linux·运维·ubuntu
Huazzi.3 小时前
Ubuntu 22虚拟机【网络故障】快速解决指南
linux·网络·学习·ubuntu·bash·编程