一、 引言
本文主要介绍在linux中使用poll搭建回射服务端。我们在前面的文章中研究了使用select和epoll搭建服务端的方法。poll的用法和select类似,只不过用来描述操作符集合的是pollfd而非select的fd_set。Poll在Linux 2.5.44版本后被epoll取代,本文只为研究其用法。
二、函数原型
poll的函数原型如下:#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds是结构体pollfd数组的首地址,pollfd的定义如下:
struct pollfd{
int fd; //要监视的文件描述符
short events; //请求注册的事件
short revents; //返回的事件
};、
使用pollfd时,我们在events中注册需要监视的事件,每个事件都用数据位的宏表示:
常量 说明
常量 | 说明 |
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
当poll函数返回时,会将发生的事件写到revents里面,如果没发生事件,revents将会被自动清零。与select的fd_set不同,poll每当调用这个函数之后,系统不会清空fds这个数组,操作起来比较方便;特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只监视一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;
nfds是fds中元素的个数,也就是我们注册的需要监视的文件描述符的个数。
timeout是超时,设置成INFTIM,一直等待到事件的发生;设置成0不进行阻塞;设置成大于0的整数,表示等待阻塞的时间,单位是毫秒。
poll函数返回-1时,表示运行出错;返回0时表示等待超时;返回大于0的数字表示发生了事件的操作符的个数。三、使用范例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<poll.h>
#define BUF_SIZE 30
#define POLL_SIZE 20
void error_handler(const char* message);
int main(int argc,char* argv[])
{
int serv_sock,clnt_sock;
struct sockaddr_in serv_addr,clnt_addr;
int clnt_addr_sz,str_len;
char buf[BUF_SIZE];
struct pollfd fds[POLL_SIZE];
int plnum,fpnum,i,j;
if(argc!=2)
{
printf("Usage %s <port>\n",argv[0]);
exit(1);
}
serv_sock=socket(AF_INET,SOCK_STREAM,0);
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
error_handler("bind error");
if(listen(serv_sock,5)==-1)
error_handler("listen error");
for(i=0;i<POLL_SIZE;i++)
fds[i].fd=-1;
fds[0].fd=serv_sock;
fds[0].events=POLLIN;
fpnum=0;
while(1)
{
plnum=poll(fds,fpnum+1,INFTIM);
printf("poll returned");
if((fds[0].revents&POLLIN)==POLLIN)
{
clnt_addr_sz=sizeof(clnt_addr);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);
for(i=0;i<POLL_SIZE;i++)
{
if(fds[i].fd<0)
{
fds[i].fd=clnt_sock;
fds[i].events=POLLIN;
}
}
if(fpnum<i)
fpnum=i;
}
for(i=1;i<fpnum;i++)
{
if(fds[i].fd<0) continue;
str_len=read(fds[i].fd,buf,BUF_SIZE);
if(str_len<=0)
{
close(fds[i].fd);
fds[i].fd==-1;
}
else
{
write(fds[i].fd,buf,str_len);
}
}
}
return 0;
}
void error_handler(const char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
第20~21行,声明使用poll函数相关API。fds是我们用来存放需要监视的pollfd结构体变量的数组。
第42~43行,将eps所有元素的fd都置为-1,以便和正常的文件操作符进行区分。
第44~45行,向fds中注册serv_sock。
第49行,利用poll监视注册的socket。
第51行,判断是不是serv_sock触发POLLIN状态,如果是调用accept接收客户端连接。
第55~62行,将接收的clnt_sock注册到pds中。
第67~79行,接收客户端的数据,并进行回射。
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL50