Linux事件驱动编程:深入解析poll机制 (linux中的poll)
在Linux内核的事件驱动编程中,有一个非常重要的机制,它就是poll机制。poll机制能够帮助开发者在高效地处理I/O事件的同时,减少CPU的消耗,提高程序的运行效率。本文将从以下几个方面深入解析Linux内核中poll机制的实现原理及使用方法:
1、基本概念
2、原理分析
3、使用方法
4、示例分析
1、基本概念
poll机制是一种事件驱动的I/O多路复用机制,它主要通过等待内核发生某些事件而触发应用程序的执行。poll机制可以同时监听多个文件描述符(即socket句柄),当有事件发生时,便将相应的事件状态通知给应用程序。
2、原理分析
poll机制的实现原理与select机制非常相似,不同的是,poll机制会通过一个struct pollfd类型的数组将需要监听的文件描述符传递给内核。在应用程序中,如果需要监听某个文件描述符,就需要将该描述符的相关信息(如文件描述符、等待的事件类型等)注册到struct pollfd类型的结构体中,然后将该结构体添加到pollfd数组中。在这个数组中,每个元素对应一个文件描述符,同时还有需要监听的事件的相关信息。当内核发现某个文件描述符上有一个或多个事件处于正等待状态时,便会通知到应用程序。
在poll机制中,应用程序可以使用poll函数进行文件描述符的监听。poll函数的原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中,fds数组就是需要监听的文件描述符数组,nfds表示fds数组中元素的数量,timeout用于设置等待事件的超时时间(单位是毫秒)。
poll函数返回的是一个整型值,表示发生了事件的文件描述符的数量。如果返回值为-1,则表示poll函数调用失败。
3、使用方法
poll机制的使用非常简单,需要进行如下几个步骤:
1)使用open打开需要监听的文件(如socket文件)
2)将文件描述符(如socket fd)添加到struct pollfd数组中
3)使用poll函数监听struct pollfd数组
4)根据poll函数的返回值,处理已经发生事件的文件描述符
示例代码如下:
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENTS 10
#define SERVER_PORT 9999
int mn()
{
struct pollfd fds[MAX_EVENTS];
memset(fds, 0, sizeof(fds));
int listener_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listener_fd
perror(“socket error”);
return 1;
}
int reuseaddr = 1;
setsockopt(listener_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listener_fd, (struct sockaddr *)&server_addr, sizeof(server_addr))
perror(“bind error”);
return 1;
}
if(listen(listener_fd, 10)
perror(“listen error”);
return 1;
}
fds[0].fd = listener_fd;
fds[0].events = POLLIN | POLLPRI;
while(1) {
int result = poll(fds, MAX_EVENTS, 5000);
if(result
perror(“poll error”);
break;
}
if(result == 0) {
printf(“poll timeout\n”);
continue;
}
for(int i = 0; i
int fd = fds[i].fd;
short events = fds[i].events;
short revents = fds[i].revents;
if(fd
continue;
}
if(revents & POLLIN || revents & POLLPRI) {
if(fd == listener_fd) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listener_fd, (struct sockaddr *)&client_addr, &client_len);
printf(“accept a client: %s:%d\n”, inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
fds[i].fd = client_fd;
fds[i].events = POLLIN | POLLPRI;
}
else {
char buf[1024];
int len = recv(fd, buf, sizeof(buf), 0);
if(len > 0) {
buf[len] = 0;
printf(“recv: %s\n”, buf);
}
else if(len == 0) {
printf(“client closed\n”);
fds[i].fd = -1;
}
else {
perror(“recv error”);
fds[i].fd = -1;
}
}
}
}
}
close(listener_fd);
return 0;
}
在这个示例中,我们通过创建一个TCP连接的听者(listener),然后将listener的文件描述符添加到pollfd数组中。每次事件发生时,就根据revents的值来判断是listener fd上有新的TCP连接请求,还是已有的TCP连接上有数据到来。根据处理的不同,做出响应的处理。
4、示例分析
在这个示例中,我们通过使用poll函数对TCP连接文件描述符进行监听,实现了服务端的消息接收和响应。我们使用socket函数创建一个TCP监听者,并将其添加到fds数组中;然后使用poll函数不断地监听fds数组。
如果在poll函数调用时,发现有事件已经发生时,我们遍历fds数组,判断是否有listener fd上有新的TCP连接请求。如果确实有请求,我们调用accept函数,接受该请求,将返回的client_fd添加到fds数组中。如果已有的TCP连接上有数据到达,我们调用recv函数,进行数据读取,并处理读取到的数据。
如果发现TCP连接已经被关闭或者发生了错误,我们将该文件描述符从fds数组中删除,避免不必要的事件监听。通过这种方法,我们实现了TCP连接的并发处理,同时避免了CPU的过度使用,提高了程序的运行效率。