网站开发岗位,网页设计与制作教程电,墨刀怎么做网站,做打牌的网站怎么办参考博客#xff1a;https://blog.csdn.net/qq_42611237/article/details/126029834
一、信号驱动I/O概念
信号驱动 IO#xff08;Signal-Driven I/O#xff09;是一种同步IO 模型#xff0c;允许进程在文件描述符#xff08;如套接字#xff09;就绪时收到内核发送的信…参考博客https://blog.csdn.net/qq_42611237/article/details/126029834
一、信号驱动I/O概念
信号驱动 IOSignal-Driven I/O是一种同步IO 模型允许进程在文件描述符如套接字就绪时收到内核发送的信号通知而无需主动轮询或阻塞等待。这种模型通过将 IO 事件转换为信号实现了程序与 IO 操作的解耦提高了系统的并发处理能力。
为什么信号驱动 I/O 是同步的 阻塞点 在信号驱动 I/O 中尽管进程在等待数据就绪时不被阻塞可以处理其他任务但当数据就绪的信号到达后进程必须在信号处理函数中调用一个阻塞的 read或 recvfrom 等系统调用来实际执行数据从内核缓冲区到用户缓冲区的拷贝工作。 这个 read 调用会阻塞进程直到数据拷贝完成。 进程的主动参与 进程需要主动发起这个阻塞的拷贝操作 (read)。内核只是通知了“数据准备好了”并没有完成拷贝。
信号驱动 I/O 在“数据就绪通知”阶段是非阻塞的符合异步的感觉但在关键的“数据拷贝”阶段仍然是阻塞的同步的本质。按照 I/O 操作是否导致进程阻塞在数据拷贝上的核心标准来划分它被归类为同步 I/O。
二、信号驱动工作流程 注册兴趣进程通过系统调用如fcntl告诉内核“我对这个文件描述符的 IO 事件感兴趣请在它就绪时通知我”。信号绑定进程通过sigaction注册信号处理函数指定当 IO 事件发生时应执行的代码。异步通知当文件描述符就绪时如有数据可读、可写空间可用内核向进程发送SIGIO信号。事件处理进程在信号处理函数中执行相应的 IO 操作
三、信号驱动IO核心 API
3.1 启用信号驱动模式
要让套接字支持信号驱动 IO必须进行两步配置
设置属主进程
fcntl(sock_fd, F_SETOWN, getpid());作用告诉内核 “当这个套接字就绪时向当前进程发送信号”。原理每个文件描述符在内核中维护一个属主进程 ID信号将发送到该进程。
启用异步模式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.hclass 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);}}
};
基于信号驱动的服务端
#includestring
#includesys/signal.h
#includefcntl.h
#includesignal.h
#includeunistd.h
#includesock.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 gCXXFLAGS -Wall -stdc14SRCS 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 的优缺点
优点
低延迟响应无需轮询事件触发即时响应适合实时系统。资源高效相比多线程 / 多进程模型节省线程创建和上下文切换开销。编程简单比epoll等复杂机制更容易实现代码结构清晰。异步非阻塞主线程可以继续执行其他任务提高 CPU 利用率。
缺点
信号处理限制 信号处理函数中只能执行异步安全的系统调用。复杂操作需通过管道等机制通知主线程处理。 信号丢失问题 标准信号如 SIGIO不支持排队多个同类信号可能合并为一个。若需要可靠处理应使用实时信号如 SIGRTMIN。 可扩展性有限 在高并发场景下信号处理函数可能成为瓶颈。现代系统更推荐使用epollLinux或kqueueBSD。
5.2 信号驱动 IO 对比 IO 多路复用
特性信号驱动 IOIO 多路复用epoll事件通知方式信号SIGIO主动查询就绪列表编程复杂度较低只需处理信号较高需维护事件循环并发处理能力中等受信号处理函数限制高适合万级连接延迟低信号触发即时响应中需等待轮询周期适用场景中小规模连接实时性要求高大规模连接高性能服务器
更多资料https://github.com/0voice