poll函数
poll是Linux中的字符设备驱动中的一个函数,poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
函数格式如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
pollfd结构体定义如下:
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
1、第一个参数:用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以及测试 revents 标志的一些常值:
常量 | 说明 | 是否能作为 events 的输入 | 是否能作为revents的返回结果 |
---|---|---|---|
POLLIN | 普通或者优先级带数据可读 | 能 | 能 |
POLLRDNORM | 普通数据可读 | 能 | 能 |
POLLRDBAND | 优先级带数据可读 | 能 | 能 |
POLLPRI | 高优先级数据可读 | 能 | 能 |
POLLOUT | 普通数据可读 | 能 | 能 |
POLLWRNORM | 普通数据可写 | 能 | 能 |
POLLWRBAND | 优先级数据可写 | 能 | 能 |
POLLERR | 发生错误 | 能 | 能 |
POLLHUP | 发生挂起 | 能 | |
POLLNVAL | 描述字不是一个打开的文件 | 能 |
2、第二个参数: nfds 指定数组中监听的元素个数;
3、第三个参数: timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULTfds 指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
EINVALnfds 参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。
多路复用poll实现网络socket服务器的编程
Linux下的代码示例:
/*********************************************************************************
* Copyright: (C) 2020 makun<[email protected]>
* All rights reserved.
*
* Filename: poll_socket_server.c
* Description: This file poll_socket_server.c
*
* Version: 1.0.0(2020年02月28日)
* Author: makun <[email protected]>
* ChangeLog: 1, Release initial version on "2020年02月28日 13时59分51秒"
*
********************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/select.h>
#include <ctype.h>
#include <libgen.h>
#include <poll.h>
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))//求数组元素的个数
int socket_Server_init(char *listen_ip,int listen_port);
void print_usage(char *progname)
{
printf("%s usage: \n", progname);
printf("-p(--port): sepcify server listen port.\n");
printf("-h(--Help): print this help information.\n");
printf("-d(--daemon):set program running on background\n");
return ;
}
int main (int argc, char **argv)
{
int listenfd = -1;
int clifd;
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
socklen_t len;
int serv_port = 0;
int ch;
int rv ;
int on = 1;
char buf[1024];
int i,j;
int found;
int max;
int daemon_run = 0;
char *progname = NULL;
struct pollfd fds_arry[1024];
struct option opts[] =
{
{"daemon",no_argument,NULL,'b'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
progname = basename(argv[0]);
while( (ch=getopt_long(argc, argv, "bp:h", opts, NULL)) != -1 )
{
switch(ch)
{
case 'p':
serv_port=atoi(optarg);
break;
case 'b':
daemon_run=1;
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if( !serv_port )
{
print_usage(argv[0]);
return 0;
}
if( (listenfd = socket_Server_init(NULL, serv_port)) <0)
{
printf("ERROR %s server listen on port %d failure\n",argv[0],serv_port);
return -1;
}
printf("%s server start to listen on port %d\n",argv[0],serv_port);
if(daemon_run)//设置进程后台运行
{
daemon(0,0);
}
for(i=0; i<ARRAY_SIZE(fds_arry); i++)//遍历数组
{
fds_arry[i].fd=-1;//将整个数组初始化为-1,为什么是-1;因为这个数组存放的是文件描述符,系统会生成三个文件描述符0,1,2
}
fds_arry[0].fd = listenfd;//将第一个数组赋值为listenfd
//一个tcp的网络链接中包含一个四元组:源ip,目的ip,源端口,目的端口
fds_arry[0].events = POLLIN;
max = 0;
for( ; ; )
{
rv = poll(fds_arry, max+1, -1);
if(rv < 0)
{
printf("POLL failure:%s\n",strerror(errno));
break;
}
else if( rv ==0 )
{
printf("poll get timeout\n");
continue;
}
if( fds_arry[0].revents & POLLIN )//判断指定描述符是否在集合中
{
if( (clifd=accept(listenfd,(struct sockaddr *)NULL,NULL)) < 0)
{
printf("accept new client failure:%s\n",strerror(errno));
continue;
}
found = 0;
for(i=0; i<ARRAY_SIZE(fds_arry);i++)
{
if( fds_arry[i].fd< 0 )
{
printf("accept new client [%d] and add it into array\n",clifd);
fds_arry[i].fd = clifd;
fds_arry[i].events = POLLIN;
found = 1;
break;
}
}
if(!found)
{
printf("accept new client [%d] but full, so refuse it\n",clifd);
close(clifd);
continue;
}
max = i>max ? i:max;
if( rv <=0 )
continue;
}
else
{
for ( i=1; i<ARRAY_SIZE(fds_arry);i++)
{
if(fds_arry[i].fd < 0)
continue;
if( (rv=read(fds_arry[i].fd,buf,sizeof(buf))) <=0)
{
printf("socket [%d] read failure or get disconnected\n",fds_arry[i].fd);
close(fds_arry[i].fd);
fds_arry[i].fd=-1;
}
else
{
printf("socket [%d] read get %d bytes data\n",fds_arry[i].fd,rv);
for(j=0; j<rv; j++)
buf[j]=toupper(buf[j]);
if( write(fds_arry[i].fd, buf , rv) <0)
{
printf("socket[%d] write failure:%s\n",fds_arry[i].fd,strerror(errno));
close(fds_arry[i].fd);
fds_arry[i].fd = -1;
}
}
}
}
}
cleanup:
close(listenfd);
return 0;
}
int socket_Server_init(char *listen_ip, int listen_port)
{
struct sockaddr_in servaddr;
int rv = 0;
int on = 1;
int listenfd;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("use socket()to create a TCP socket failure:%s\n",strerror(errno));
return -1;
}
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port = htons(listen_port);
if( !listen_ip )
{
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
{
if(inet_pton(AF_INET, listen_ip, &servaddr.sin_addr) <=0)
{
printf("inet_pton set listen IP address failure\n");
rv = -2;
goto cleanup;
}
}
if( bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
{
printf("socket[%d] bind to port failure:%s\n",listenfd,strerror(errno));
rv = -3;
goto cleanup;
}
if( listen(listenfd,13) < 0)
{
printf("use bind to bind tcp socket failure:%s\n",strerror(errno));
rv = -4;
goto cleanup;
}
cleanup:
if(rv < 0)
close(listenfd);
else
rv = listenfd;
return rv;
}