一.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,失败返回-1
nfds 参数指定被监听事件集合 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); // 创建socket
if (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) { // 开始监听,设置队列长度为5
return -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; // 事件掩码设置为0
fds[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; // 设置感兴趣的事件为POLLIN
fds[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; // 将文件描述符重置为-1
fds[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); // 如果接收失败或客户端关闭连接,则关闭socket
printf("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(); // 初始化socket
if (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) { // 如果是监听的socket
accept_cli(sockfd, fds); // 接受新的客户端连接
} else { // 如果是已连接的客户端
recv_data(fds[i].fd, fds); // 接收数据
}
}
}
}
}
}