一.poll概述
poll是一个多路复用的I/O模型,一个进程监视多个文件描述符,当文件描述符就绪时,poll返回可读并做相应处理。
1.poll的模型
#include <poll.h>struct pollfd
{int fd; //文件描述符short events; //事件类型 short revents; //实际发送事件
}int poll(struct pollfd *fds, nfds_t nfds, int timeout);/*poll 系统调用成功返回就绪文件描述符的总数,超时返回 0,失败返回-1nfds 参数指定被监听事件集合 fds 的大小。timeout 参数指定 poll 的超时值,单位是毫秒,timeout 为-1 时,poll 调用将永久
阻塞,直到某个事件发生,timeout 为 0 时,poll 调用将立即返回。
2.事件类型
二.测试代码
SER.C
#include<stdio.h> // 标准输入输出库
#include<stdlib.h> // 标准库,提供动态内存分配等
#include<string.h> // 字符串操作库
#include<unistd.h> // UNIX标准函数库,提供close函数等
#include<sys/socket.h>// 套接字库
#include<netinet/in.h> // 网络头文件,提供IPv4地址格式
#include<arpa/inet.h> // 网络地址转换库
#include<poll.h> // poll系统调用#define MAXFD 10 // 定义最大的文件描述符数量// 初始化socket函数
int socket_init() {int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建socketif (sockfd == -1) {return -1; // 如果创建失败,返回-1}struct sockaddr_in saddr; // 服务器地址结构memset(&saddr, 0, sizeof(saddr)); // 清零saddr.sin_family = AF_INET; // 地址族saddr.sin_port = htons(6000); // 端口号saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定地址if (res == -1) {printf("bind err\n");return -1; // 绑定失败,返回-1}if (listen(sockfd, 5) == -1) { // 开始监听,设置队列长度为5return -1; // 监听失败,返回-1}return sockfd; // 返回socket文件描述符
}// 初始化pollfd数组
void fds_init(struct pollfd fds[]) {for (int i = 0; i < MAXFD; i++) {fds[i].fd = -1; // 文件描述符设置为-1,表示未使用fds[i].events = 0; // 事件掩码设置为0fds[i].revents = 0; // 事件返回掩码设置为0}
}// 将新的文件描述符添加到pollfd数组
void fds_add(struct pollfd fds[], int fd) {for (int i = 0; i < MAXFD; i++) {if (fds[i].fd == -1) {fds[i].fd = fd; // 设置文件描述符fds[i].events = POLLIN; // 设置感兴趣的事件为POLLINfds[i].revents = 0; // 重置事件返回掩码break; // 退出循环}}
}// 从未使用的pollfd数组中删除文件描述符
void fds_del(struct pollfd fds[], int fd) {for (int i = 0; i < MAXFD; i++) {if (fds[i].fd == fd) {fds[i].fd = -1; // 将文件描述符重置为-1fds[i].events = 0; // 重置事件掩码fds[i].revents = 0; // 重置事件返回掩码break; // 退出循环}}
}// 接受客户端连接请求并添加到pollfd数组
void accept_cli(int sockfd, struct pollfd fds[]) {int c = accept(sockfd, NULL, NULL); // 接受连接if (c < 0) {return; // 如果返回-1,表示出错}printf("accept c = %d\n", c);fds_add(fds, c); // 添加到pollfd数组
}// 接收客户端数据
void recv_data(int c, struct pollfd fds[]) {char buff[128] = {0}; // 创建接收缓冲区int n = recv(c, buff, 127, 0); // 接收数据if (n <= 0) {close(c); // 如果接收失败或客户端关闭连接,则关闭socketprintf("cli close = %d\n", c);fds_del(fds, c); // 从pollfd数组中删除该文件描述符return;}printf("buff(%d)=%s\n", c, buff); // 打印接收到的数据send(c, "ok", 2, 0); // 发送确认消息给客户端
}// 主函数
int main() {int sockfd = socket_init(); // 初始化socketif (sockfd == -1) {exit(1); // 如果初始化失败,退出程序}struct pollfd fds[MAXFD]; // 创建pollfd数组fds_init(fds); // 初始化数组fds_add(fds, sockfd); // 将监听的socket添加到数组while (1) { // 无限循环,等待事件int n = poll(fds, MAXFD, 5000); // 调用poll等待最多5000毫秒if (n == -1) { // 如果poll调用失败printf("poll err\n");} else if (n == 0) { // 如果超时printf("time out\n");} else { // 如果有事件发生for (int i = 0; i < MAXFD; i++) { // 遍历pollfd数组if (fds[i].fd == -1) { // 如果文件描述符未使用,跳过continue;}if (fds[i].revents & POLLIN) { // 如果有可读事件发生if (fds[i].fd == sockfd) { // 如果是监听的socketaccept_cli(sockfd, fds); // 接受新的客户端连接} else { // 如果是已连接的客户端recv_data(fds[i].fd, fds); // 接收数据}}}}}
}