
Linux Poll Demo:高效I/O事件处理的典范
在现代操作系统中,高效地处理I/O事件是构建高性能应用程序的关键
Linux操作系统以其强大的内核功能和丰富的系统调用接口,为开发者提供了多种处理I/O事件的方法
其中,`poll`系统调用作为一种灵活且高效的机制,在处理多个文件描述符(file descriptors, FDs)的I/O事件时表现尤为突出
本文将通过一个详细的`linux polldemo`,展示如何使用`poll`系统调用来实现高效的I/O事件处理,并深入探讨其背后的原理与优势
一、`poll`系统调用简介
`poll`是POSIX标准定义的一个系统调用,用于监视多个文件描述符上的I/O事件
与`select`系统调用相比,`poll`提供了更丰富的功能集,能够处理更多类型的文件描述符(如管道、套接字、终端设备等),并且对每个文件描述符可以指定多种感兴趣的事件类型(如读就绪、写就绪、异常条件等)
`poll`函数原型如下:
include
int poll(struct pollfdfds, nfds_t nfds, int timeout);
- `fds`:指向`pollfd`结构体数组的指针,每个`pollfd`结构体代表一个要监视的文件描述符及其感兴趣的事件
- `nfds`:`fds`数组中的元素数量,即要监视的文件描述符总数
- `timeout`:等待事件的超时时间(毫秒)
-1表示无限等待,0表示立即返回,不阻塞
`pollfd`结构体定义如下:
struct pollfd {
int fd; // 文件描述符
short events; // 感兴趣的事件类型
short revents; // 实际发生的事件类型(由`poll`返回时填充)
};
二、`linux polldemo`实现
下面,我们通过一个具体的示例来展示如何使用`poll`系统调用来处理多个文件描述符上的I/O事件
这个示例将创建一个服务器,它同时监听一个TCP套接字和一个标准输入(用于接收用户命令),以展示`poll`在处理并发I/O事件时的能力
2.1 示例代码
include
include
include
include
include
include
define PORT 8080
defineBUFFER_SIZE 1024
void error_handling(constchar message) {
perror(message);
exit(EXIT_FAILURE);
}
int main() {
intserver_sock,client_sock;
structsockaddr_in server_addr, client_addr;
socklen_tclient_addr_size;
struct pollfdfds【2】;
charbuffer【BUFFER_SIZE】;
ssize_tstr_len;
// 创建TCP套接字
if((server_sock = socket(PF_INET, SOCK_STREAM, 0)) == -
error_handling(socket() error);
// 设置服务器地址信息
memset(&server_addr, 0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// 绑定套接字到指定端口
if(bind(server_sock, (struct sockaddr)&server_addr, sizeof(server_addr)) == -1)
error_handling(bind() error);
// 监听连接请求
if(listen(server_sock, == -
error_handling(listen() error);
// 初始化pollfd数组
fds【0】.fd = server_sock;
fds【0】.events = POLLIN; // 对server_sock感兴趣的事件是读就绪(即有新连接)
fds【0】.revents = 0;
fds【1】.fd = STDIN_FILENO; // 标准输入文件描述符
fds【1】.events = POLLIN; // 对标准输入感兴趣的事件也是读就绪
fds【1】.revents = 0;
printf(Server is listening on port %d...
, PORT);
printf(Press q to quit.n);
while(1) {
int ret =poll(fds, 2, -1); // 无限等待,直到有事件发生
if(ret == -
error_handling(poll() error);
// 检查server_sock是否有新连接
if(fds【0】.revents & POLLIN) {
client_addr_size = sizeof(client_addr);
client_sock = accept(server_sock, (struct sockaddr)&client_addr, &client_addr_size);
if(client_sock == -
error_handling(accept() error);
printf(New client connected: %s:%dn,inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 这里可以添加代码将client_sock加入到一个新的pollfd数组中,以便单独处理每个客户端的I/O事件
// 为简化示例,这里仅打印消息,不处理客户端数据
}
// 检查标准输入是否有数据可读
if(fds【1】.revents & POLLIN) {
str_len = read(STDIN_FILENO, buffer, BUFFER_SIZE - 1);
if(str_len == -
error_handling(read() error);
buffer【str_len】 = 0; // 确保字符串以null结尾
if(strncmp(buffer, q, 1) == 0) {
printf(Exiting...
);
break;
}
printf(You entered: %s
, buffer);
}
}
// 关闭套接字
close(server_sock);
return 0;
}
2.2 代码解析
1.创建TCP套接字并绑定端口:
-使用`socket()`创建TCP套接字
-使用`bind()`将套接字绑定到指定端口
-使用`listen()`开始监听连接请求
2.初始化pollfd数组:
-`fds【0】`用于监视服务器套接字(`server_sock`),感兴趣的事件是读就绪(`POLLIN`)
-`fds【1】`用于监视标准输入(`STDIN_FILENO`),同样感兴趣的事件是读就绪(`POLLIN`)
3.进入事件循环:
-使用`poll()`等待事件发生
`po