前言
上篇文章我们讲解了使用select进行IO复用,这篇文章我们来讲解使用poll函数来进行多路IO复用。
一、poll函数讲解
poll() 函数是在网络编程中常用的一个系统调用函数,用于监视多个文件描述符的状态,以确定是否有文件描述符准备好进行读取、写入或出现异常。
以下是 poll() 函数的基本用法:
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds 参数是一个指向 pollfd 结构体数组的指针,每个结构体描述了一个文件描述符及其关注的事件。
nfds 参数是 fds 数组中元素的数量。
timeout 参数是超时时间,以毫秒为单位。指定 timeout 的值可以控制 poll() 函数的阻塞行为。
pollfd 结构体的定义如下:
struct pollfd { int fd; // 文件描述符 short events; // 感兴趣的事件 short revents; // 实际发生的事件 };
fd 是被监视的文件描述符。
events 是要监视的事件的掩码,可以是以下值之一或它们的组合:
POLLIN:有数据可读。
POLLOUT:可写入数据。
POLLERR:发生错误。
POLLHUP:连接关闭。
POLLNVAL:文件描述符非法。
revents 是 poll() 函数填充的实际发生的事件。
poll() 函数的返回值表示有几个文件描述符准备好了,即满足所关心的事件。返回 值的三种情况如下:
返回值大于 0:表示准备好的文件描述符的数量。
返回值等于 0:表示在指定的超时时间内没有文件描述符准备好。
返回值小于 0:表示执行发生错误。
使用 poll() 函数的步骤如下:
1.设置 pollfd 数组中每个文件描述符的 fd 和 events 字段。
2.调用 poll() 函数,并传递 pollfd 数组、元素数量和超时时间。
3.检查返回值,根据 revents 字段判断具体发生的事件。
二、使用poll函数完成并发服务器
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <poll.h> #define MAX_CLIENT 1024//最大可连接客户端的数量 int main() { int server = 0; struct sockaddr_in saddr = {0}; int client = 0; struct sockaddr_in caddr = {0}; socklen_t asize = 0; int len = 0; char buf[32] = {0}; int maxfd; int ret = 0; int i = 0; server = socket(PF_INET, SOCK_STREAM, 0); if( server == -1 ) { printf("server socket error\n"); return -1; } saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = htons(8888); if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 ) { printf("server bind error\n"); return -1; } if( listen(server, 128) == -1 ) { printf("server listen error\n"); return -1; } printf("server start success\n"); /*将fds中的fd全部置为-1*/ struct pollfd fds[MAX_CLIENT]; for(i = 0; i < MAX_CLIENT; i++) { fds[0].fd = -1; } /*将服务端套接字添加到fds数组中*/ fds[0].fd = server; fds[0].events = POLLIN; while( 1 ) { ret = poll(fds, MAX_CLIENT, -1); if(ret < 0) { printf("poll err\n"); return -1; } if(fds[0].revents & POLLIN) { /*有客户端连接上来了*/ asize = sizeof(caddr); client = accept(server, (struct sockaddr*)&caddr, &asize); printf("client is connect\n"); /*将新连接添加到fds数组中*/ for(i = 1; i < MAX_CLIENT; i++) { if(fds[i].fd == -1) { fds[i].fd = client; fds[i].events = POLLIN; break; } } } /*遍历现有连接进行读取和处理数据*/ for(i = 1; i < MAX_CLIENT; i++) { int clientfd = fds[i].fd; if(clientfd > 0 && (fds[i].revents & POLLIN)) { printf("process data\n"); len = read(clientfd, buf, 1024); if(len == 0) { /*客户端断开连接,关闭客户端文件描述符*/ close(clientfd); fds[i].fd = -1; printf("client is disconnect\n"); } else { printf("read buf : %s\n", buf); write(clientfd, buf, len); } } } } close(server); return 0; }
三、poll的优点缺点
优点:
1.简单易用:相对于低级别的系统调用如 select,poll 提供了更简单的 API,更易于使用和理解。
2.没有文件描述符数量限制:poll 没有预定义的文件描述符数量限制,可以适应更大规模的并发连接。
3.支持文件描述符数组:相对于 select 的位图方式,poll 使用了文件描述符数组,可以更方便地进行管理和操作。
4.高效:poll 采用轮询的方式监视文件描述符的状态变化,只有活动的文件描述符才会返回,减少了无用的轮询过程,提高了效率。
5.支持非阻塞IO:与 select 类似,poll 也支持非阻塞IO模式,可以在等待期间继续处理其他任务。
缺点:
1.每次调用都需要遍历整个文件描述符数组:即使只有少数文件描述符活跃,poll 在每次调用时都需要遍历整个文件描述符数组,这会带来性能上的开销。
2.没有提供超时精度控制:poll 的超时参数是以毫秒为单位的,无法提供更高的精度,因此在需要更精确超时的情况下,不太适用。
3.不可移植性:poll 是一个相对较新的系统调用,不是所有的操作系统都提供该接口,因此在编写跨平台代码时,需要考虑兼容性。
4.没有对信号处理的支持:与 select 不同,poll 不提供对信号处理的支持,因此无法直接处理信号事件。
总结
本篇文章主要讲解到了poll函数的使用方法并且使用poll实现了一个并发服务器,这个大家可以结合上篇文章的select函数进行对比思考。