当前位置: 首页 > news >正文

【Linux】Linux 信号驱动I/O

参考博客:https://blog.csdn.net/qq_42611237/article/details/126029834

一、信号驱动I/O概念

  • 信号驱动 IO(Signal-Driven I/O)是一种同步IO 模型,允许进程在文件描述符(如套接字)就绪时收到内核发送的信号通知,而无需主动轮询或阻塞等待。这种模型通过将 IO 事件转换为信号,实现了程序与 IO 操作的解耦,提高了系统的并发处理能力。

为什么信号驱动 I/O 是同步的?

  • 阻塞点: 在信号驱动 I/O 中,尽管进程在等待数据就绪时不被阻塞(可以处理其他任务),但当数据就绪的信号到达后,进程必须在信号处理函数中调用一个阻塞的 read(或 recvfrom 等)系统调用来实际执行数据从内核缓冲区到用户缓冲区的拷贝工作。 这个 read 调用会阻塞进程,直到数据拷贝完成。

  • 进程的主动参与: 进程需要主动发起这个阻塞的拷贝操作 (read)。内核只是通知了“数据准备好了”,并没有完成拷贝。

信号驱动 I/O 在“数据就绪通知”阶段是非阻塞的(符合异步的感觉),但在关键的“数据拷贝”阶段仍然是阻塞的(同步的本质)。按照 I/O 操作是否导致进程阻塞在数据拷贝上的核心标准来划分,它被归类为同步 I/O。

二、信号驱动工作流程

在这里插入图片描述

  1. 注册兴趣:进程通过系统调用(如fcntl)告诉内核:“我对这个文件描述符的 IO 事件感兴趣,请在它就绪时通知我”。
  2. 信号绑定:进程通过sigaction注册信号处理函数,指定当 IO 事件发生时应执行的代码。
  3. 异步通知:当文件描述符就绪时(如有数据可读、可写空间可用),内核向进程发送SIGIO信号。
  4. 事件处理:进程在信号处理函数中执行相应的 IO 操作

三、信号驱动IO核心 API

3.1 启用信号驱动模式

要让套接字支持信号驱动 IO,必须进行两步配置:

  1. 设置属主进程
fcntl(sock_fd, F_SETOWN, getpid());
  • 作用:告诉内核 “当这个套接字就绪时,向当前进程发送信号”。
  • 原理:每个文件描述符在内核中维护一个属主进程 ID,信号将发送到该进程。
  1. 启用异步模式(O_ASYNC)
int flags = fcntl(sock_fd, F_GETFL);
fcntl(sock_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK);
  • O_ASYNC:启用异步通知模式,使套接字就绪时触发SIGIO信号。
  • O_NONBLOCK:设置非阻塞模式,避免在信号处理函数中阻塞(通常与 O_ASYNC 配合使用)。

3.2 信号处理:注册 SIGIO 处理函数

使用sigaction函数注册信号处理函数:

struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;  // 信号处理函数
sigemptyset(&sa.sa_mask);       // 处理信号期间不阻塞其他信号
sa.sa_flags = SA_RESTART;       // 自动重启被信号中断的系统调用if (sigaction(SIGIO, &sa, NULL) < 0) {perror("sigaction");exit(1);
}

3.3 数据读取:非阻塞操作

在信号处理函数中读取数据时,通常使用MSG_DONTWAIT标志进行非阻塞读取:

ssize_t len = recv(sock_fd, buffer, sizeof(buffer), MSG_DONTWAIT);
if (len < 0) {if (errno == EWOULDBLOCK || errno == EAGAIN) {// 没有数据可读,正常情况} else {// 处理其他错误}
} else if (len == 0) {// 客户端关闭连接
} else {// 处理读取到的数据
}

四、基于信号驱动IO的服务端实现

4.1 代码实现

socket封装

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>
#include <netinet/in.h>class Sock
{public:static int Socket(){int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(1);}return sock;}static void Bind(int sock, uint16_t port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons(port);if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){std::cout << "bind error !" << std::endl;exit(2);}}static void Listen(int sock){if (listen(sock, 5) < 0){std::cerr << "listen error" << std::endl;exit(3);}}static int Accept(int sock){struct sockaddr_in peer;socklen_t len = sizeof(peer);int fd = accept(sock, (struct sockaddr *)&peer, &len);if (fd >= 0){return fd;}else{return -1;}}static void Connect(int sock, std::string ip, uint16_t port){struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(port);server.sin_addr.s_addr = inet_addr(ip.c_str());if (connect(sock, (struct sockaddr *)&server, sizeof(server) == 0)){std::cout << "connect sucess" << std::endl;}else{std::cout << "connect failed" << std::endl;exit(4);}}
};

基于信号驱动的服务端

#include<string>
#include<sys/signal.h>
#include<fcntl.h>
#include<signal.h>
#include<unistd.h>
#include"sock.hpp"#define NUM 1024
int listen_fd = 0;
int fd_arrays[NUM];void handler(int signo){int sock = Sock::Accept(listen_fd);if(sock >= 0){ //新连接std::cout << "sock:" << listen_fd << " get a new connection :" << sock << std::endl;    int pos = 1;for(pos ; pos < NUM ; ++pos){if(fd_arrays[pos] == -1){break;}}if(pos < NUM){fd_arrays[pos] = sock;std::cout << "connect sucess !" << std::endl;//设置客户端套接字SIGIO属性fcntl(sock,F_SETOWN,getpid());int flags = fcntl(sock,F_GETFL);fcntl(sock,F_SETFL,O_ASYNC | O_NONBLOCK | flags);}else{std::cout << "connction is full !" << std::endl;close(sock);}}else{ //收到消息for(int i = 1 ; i < NUM ; ++i){if(fd_arrays[i] == -1){continue;}char buffer[1024];memset(buffer,0,sizeof(buffer));ssize_t len = recv(fd_arrays[i],buffer,sizeof(buffer) - 1 , MSG_DONTWAIT); // 非阻塞if(len > 0){std::cout << "sock:" << fd_arrays[i] << " send: " << buffer << std::endl;break;  }else if(len == 0){std::cout << "sock:" << fd_arrays[i] << " close" << std::endl;close(fd_arrays[i]);fd_arrays[i] = -1;}else if(errno != EAGAIN && errno != EWOULDBLOCK){ perror("recv");close(fd_arrays[i]);fd_arrays[i] = -1;}}}}int main(int argc, char* argv[]){if(argc < 2){std::cerr << "argc < 2" << std::endl;return 1;}uint16_t port = (uint16_t)atoi(argv[1]);listen_fd = Sock::Socket();int opt = 1;setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));Sock::Bind(listen_fd,port);Sock::Listen(listen_fd);std::cout << "Server is listening on port:" << port << std::endl; //设置SIGIO属性fcntl(listen_fd,F_SETOWN,getpid()); // 设置属主进程int flags = fcntl(listen_fd,F_GETFL); //获取文件状态fcntl(listen_fd,F_SETFL,flags | O_ASYNC | O_NONBLOCK); //异步非阻塞//注册SIGIO处理函数struct sigaction sa;memset(&sa,0,sizeof(sa));sa.sa_handler = handler; //注册回调函数sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;if(sigaction(SIGIO,&sa,NULL) < 0){perror("sigaction");return 1;}for(int i = 0 ; i < NUM ; ++i){fd_arrays[i] = -1;}fd_arrays[0] = listen_fd;while(true){pause();}close(listen_fd);for(int i = 1 ; i < NUM; ++i){if(fd_arrays[i] != -1){close(fd_arrays[i]);fd_arrays[i] = 0;}}return 0;
}

Makefile编译文件

CXX = g++CXXFLAGS = -Wall -std=c++14SRCS = main.cpp OBJS = $(SRCS:.cpp=.o)TARGET = serverall:$(TARGET)$(TARGET):$(OBJS)$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)%.o:%.cpp$(CXX) $(CXXFLAGS) -c $< -o $@clean:rm -f $(OBJS) $(TARGET).PHONY:all clean	

客户端代码

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>#define PORT 8080
#define BUFFER_SIZE 1024// 接收服务器消息
void receive_messages(int socket) {char buffer[BUFFER_SIZE];while (true) {memset(buffer, 0, sizeof(buffer));ssize_t bytes_received = recv(socket, buffer, BUFFER_SIZE, 0);if (bytes_received <= 0) {std::cout << "服务器断开连接。" << std::endl;close(socket);return;}std::cout << "收到消息: " << buffer << std::endl;}
}int main() {int client_socket;struct sockaddr_in server_addr;// 创建客户端套接字client_socket = socket(AF_INET, SOCK_STREAM, 0);if (client_socket < 0) {std::cerr << "套接字创建失败。" << std::endl;return -1;}// 初始化服务器地址server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = inet_addr("10.22.79.251");  // 服务器 IP// 连接到服务器if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {std::cerr << "连接服务器失败。" << std::endl;return -1;}std::cout << "成功连接服务器。" << std::endl;// 创建线程接收服务器消息std::thread receive_thread(receive_messages, client_socket);receive_thread.detach();  // 分离线程以便独立运行// 发送消息给服务器char message[BUFFER_SIZE];while (true) {std::cin.getline(message, BUFFER_SIZE);send(client_socket, message, strlen(message), 0);}close(client_socket);return 0;
}

4.2 测试结果

编译服务端代码

make

在这里插入图片描述

运行服务器

./server 8080

在这里插入图片描述

客户端连接服务端
在这里插入图片描述

客户端发送数据到服务端

在这里插入图片描述

客户端断开连接

在这里插入图片描述

五、总结

5.1 信号驱动 IO 的优缺点

优点

  1. 低延迟响应:无需轮询,事件触发即时响应,适合实时系统。
  2. 资源高效:相比多线程 / 多进程模型,节省线程创建和上下文切换开销。
  3. 编程简单:比epoll等复杂机制更容易实现,代码结构清晰。
  4. 异步非阻塞:主线程可以继续执行其他任务,提高 CPU 利用率。

缺点

  1. 信号处理限制
    • 信号处理函数中只能执行异步安全的系统调用。
    • 复杂操作需通过管道等机制通知主线程处理。
  2. 信号丢失问题
    • 标准信号(如 SIGIO)不支持排队,多个同类信号可能合并为一个。
    • 若需要可靠处理,应使用实时信号(如 SIGRTMIN)。
  3. 可扩展性有限
    • 在高并发场景下,信号处理函数可能成为瓶颈。
    • 现代系统更推荐使用epoll(Linux)或kqueue(BSD)。

5.2 信号驱动 IO 对比 IO 多路复用

特性信号驱动 IOIO 多路复用(epoll)
事件通知方式信号(SIGIO)主动查询就绪列表
编程复杂度较低(只需处理信号)较高(需维护事件循环)
并发处理能力中等(受信号处理函数限制)高(适合万级连接)
延迟低(信号触发即时响应)中(需等待轮询周期)
适用场景中小规模连接,实时性要求高大规模连接,高性能服务器

更多资料:https://github.com/0voice

相关文章:

  • Python中shutil.rmtree()的目录删除能力详解
  • NLP语言发展路径分享
  • ELK日志文件分析系统——补充(B——Beats)
  • ELK日志文件分析系统——K(Kibana)
  • Spring Boot诞生背景:从Spring的困境到设计破局
  • [windows工具]PDFOCR识别导出Excel工具1.1版本使用教程及注意事项
  • Linux之Python定制篇——新版Ubuntu24.04安装
  • Yocto vs Buildroot:SDK(软件开发套件)创建能力全面对比
  • 一款完美适配不同屏幕宽度的电商网站UI解决方案
  • python蓝色动态线
  • Python打卡训练营Day54
  • 《仿盒马》app开发技术分享-- 回收金提现记录查询(端云一体)
  • C++题解(35) 2025年顺德区中小学生程序设计展示活动(初中组C++) 换位(一)
  • 1.1、WDM基础
  • pyhton基础【9】容器介绍四
  • 解析Buildroot
  • 自增id用完怎么办?
  • Oracle21cR3之客户端安装错误及处理方法
  • 京东API接口最新指南:店铺所有商品接口的接入与使用
  • Axure应用交互设计:多种类型元件实现新增中继器数据
  • 网站开发多少钱一天是/厦门网站的关键词自动排名
  • 怎么做饲料电商网站/广州百度竞价外包
  • 寿光市建设局网站/企业网站模板免费
  • 恩施网站建设模板/全网网络营销推广
  • 宁波搭建网站/优化资讯
  • 做房产网站多少钱/营销推广计划怎么写