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 中比较好定位到原因,但是在一个真实开发中的功能的子模块中,代码已经写了几千行,甚至在几万行的代码,莫名奇妙的进程没有任何提示就退出,是真的叫人发狂。
在这里以作记录。