poll() 函数是一个系统调用,用于在一个文件描述符数组中等待多个文件描述符上的 I/O 事件,并返回就绪的文件描述符的数量。它与 select() 函数类似,但提供了更好的性能。
poll() 函数原型如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中,各个参数的含义如下:
fds:一个指向 pollfd 结构体数组的指针,用于传递需要监听的文件描述符和对应的 I/O 操作类型(读、写和异常);
nfds:需要监听的文件描述符数目;
timeout:等待事件的超时时间,单位是毫秒。如果超过这个时间仍然没有任何事件发生,则 poll() 函数将返回 0,表示超时。
pollfd 结构体类型用于描述一个文件描述符和需要监听的事件类型。其原型定义如下:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 需要监听的事件类型 */
short revents; /* 实际发生的事件类型 */
};
其中,fd 是需要监听的文件描述符;events 是需要监听的事件类型,包括 POLLIN(可读)、POLLOUT(可写)和 POLLERR(错误)等;revents 是实际发生的事件类型,对于需要监听的事件类型,如果事件发生了,对应的位将被设置为 1。
使用 poll() 函数的步骤如下:
构建 pollfd 结构体数组,初始化需要监听的文件描述符和对应的事件类型;
调用 poll() 函数进行监听;
根据 poll() 返回的结果和实际发生的事件类型,进行相应的处理。
以下是一个简单的示例程序,演示了如何使用 poll() 函数等待多个文件描述符上的事件:
#include <iostream>
#include <cstring>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
// 创建两个 TCP 套接字
int socket1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int socket2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 监听地址和端口
struct sockaddr_in addr1 = {0};
addr1.sin_family = AF_INET;
addr1.sin_port = htons(8888);
inet_pton(AF_INET, "0.0.0.0", &addr1.sin_addr);
bind(socket1, (struct sockaddr*)&addr1, sizeof(addr1));
struct sockaddr_in addr2 = {0};
addr2.sin_family = AF_INET;
addr2.sin_port = htons(8889);
inet_pton(AF_INET, "0.0.0.0", &addr2.sin_addr);
bind(socket2, (struct sockaddr*)&addr2, sizeof(addr2));
// 设置套接字为监听状态,等待连接
listen(socket1, 10);
listen(socket2, 10);
// 构建 pollfd 结构体数组
struct pollfd fds[2];
fds[0].fd = socket1;
fds[0].events = POLLIN;
fds[1].fd = socket2;
fds[1].events = POLLIN;
// 等待事件的发生
int res = poll(fds, 2, -1);
if (res == -1) {
std::cerr << "poll error: " << std::strerror(errno) << std::endl;
return 1;
}
if (res == 0) {
std::cout << "poll timed out." << std::endl;
return 0;
}
// 检查哪些文件描述符上发生了事件
if (fds[0].revents & POLLIN) {
std::cout << "socket 1 is readable." << std::endl;
// TODO: 处理文件描述符 1 上的 I/O 事件
}
if (fds[1].revents & POLLIN) {
std::cout << "socket 2 is readable." << std::endl;
// TODO: 处理文件描述符 2 上的 I/O 事件
}
return 0;
}
在上面的示例中,我们创建了两个 TCP 套接字,并将它们添加到一个 pollfd 数组中进行监听。我们在代码中使用 poll() 函数来阻塞等待多个文件描述符上的 I/O 事件的发生,直到有文件描述符上发生了事件,poll() 函数才会返回。在检查 revents 指示的实际发生的事件类型,根据处理需要,可以在对应的条件分支中添加事件处理的代码。
需要注意的是,相比于 select() 函数,poll() 函数可以动态地添加和删除事件,因此可以用于动态管理和扩展任务列表。因此,如果我们需要处理动态管理的套接字,应该使用 poll() 函数。
总结来说,poll() 函数是实现高效网络编程中 I/O 多路复用的一种方法,它提供了更好的性能和更灵活的动态事件管理方式。但是与 epoll() 相比,它在处理大量文件描述符时性能较低,因此在高并发的情况下,应该优先选择使用 epoll()。
服务端和客户端示例代码
server
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
// ulimit -n
#define MAXNFDS 1024
// 初始化服务端的监听端口。
int initserver(int port);
int main(int argc,char *argv[])
{
if (argc != 2)
{
printf("usage: ./tcppoll port\n"); return -1;
}
// 初始化服务端用于监听的socket。
int listensock = initserver(atoi(argv[1]));
printf("listensock=%d\n",listensock);
if (listensock < 0)
{
printf("initserver() failed.\n"); return -1;
}
int maxfd; // fds数组中需要监视的socket的大小。
struct pollfd fds[MAXNFDS]; // fds存放需要监视的socket。
for (int ii=0;ii<MAXNFDS;ii++) fds[ii].fd=-1; // 初始化数组,把全部的fd设置为-1。
// 把listensock添加到数组中。
fds[listensock].fd=listensock;
fds[listensock].events=POLLIN; // 有数据可读事件,包括新客户端的连接、客户端socket有数据可读和客户端socket断开三种情况。
maxfd=listensock;
while (1)
{
int infds = poll(fds,maxfd+1,5000);
// printf("poll infds=%d\n",infds);
// 返回失败。
if (infds < 0)
{
printf("poll() failed.\n"); perror("poll():"); break;
}
// 超时。
if (infds == 0)
{
printf("poll() timeout.\n"); continue;
}
// 检查有事情发生的socket,包括监听和客户端连接的socket。
// 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。
for (int eventfd=0; eventfd <= maxfd; eventfd++)
{
if (fds[eventfd].fd<0) continue;
if ((fds[eventfd].revents&POLLIN)==0) continue;
fds[eventfd].revents=0; // 先把revents清空。
if (eventfd==listensock)
{
// 如果发生事件的是listensock,表示有新的客户端连上来。
struct sockaddr_in client;
socklen_t len = sizeof(client);
int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
if (clientsock < 0)
{
printf("accept() failed.\n"); continue;
}
printf ("client(socket=%d) connected ok.\n",clientsock);
if (clientsock>MAXNFDS)
{
printf("clientsock(%d)>MAXNFDS(%d)\n",clientsock,MAXNFDS); close(clientsock); continue;
}
fds[clientsock].fd=clientsock;
fds[clientsock].events=POLLIN;
fds[clientsock].revents=0;
if (maxfd < clientsock) maxfd = clientsock;
printf("maxfd=%d\n",maxfd);
continue;
}
else
{
// 客户端有数据过来或客户端的socket连接被断开。
char buffer[1024];
memset(buffer,0,sizeof(buffer));
// 读取客户端的数据。
ssize_t isize=read(eventfd,buffer,sizeof(buffer));
// 发生了错误或socket被对方关闭。
if (isize <=0)
{
printf("client(eventfd=%d) disconnected.\n",eventfd);
close(eventfd); // 关闭客户端的socket。
fds[eventfd].fd=-1;
// 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
if (eventfd == maxfd)
{
for (int ii=maxfd;ii>0;ii--)
{
if ( fds[ii].fd != -1)
{
maxfd = ii; break;
}
}
printf("maxfd=%d\n",maxfd);
}
continue;
}
printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);
// 把收到的报文发回给客户端。
write(eventfd,buffer,strlen(buffer));
}
}
}
return 0;
}
// 初始化服务端的监听端口。
int initserver(int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
printf("socket() failed.\n"); return -1;
}
// Linux如下
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
{
printf("bind() failed.\n"); close(sock); return -1;
}
if (listen(sock,5) != 0 )
{
printf("listen() failed.\n"); close(sock); return -1;
}
return sock;
}
client
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("usage:./tcpclient ip port\n"); return -1;
}
int sockfd;
struct sockaddr_in servaddr;
char buf[1024];
if ((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) { printf("socket() failed.\n"); return -1; }
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(atoi(argv[2]));
servaddr.sin_addr.s_addr=inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
{
printf("connect(%s:%s) failed.\n",argv[1],argv[2]); close(sockfd); return -1;
}
printf("connect ok.\n");
for (int ii=0;ii<10000;ii++)
{
// 从命令行输入内容。
memset(buf,0,sizeof(buf));
printf("please input:"); scanf("%s",buf);
// sprintf(buf,"1111111111111111111111ii=%08d",ii);
if (write(sockfd,buf,strlen(buf)) <=0)
{
printf("write() failed.\n"); close(sockfd); return -1;
}
memset(buf,0,sizeof(buf));
if (read(sockfd,buf,sizeof(buf)) <=0)
{
printf("read() failed.\n"); close(sockfd); return -1;
}
printf("recv:%s\n",buf);
// close(sockfd); break;
}
}
测试结果