poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
poll的原型如下:
int poll(struct pollfd *fd, int nfds, int timeout)
fds:是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体定义如下
struct pollfd
{
int fd;/*关注的文件描述符*/
short events;/*关注的文件描述符上的哪种事件*/
short revents;/*由内核修改的,返回此文件描述符上发生的事件类型(必须是events指定的关注的事件)*/
};
poll的事件类型
注意:POLLRDHUP事件发生也会触发POLLIN事件
nfds:参数指定被监听事件集合的大小(类型为unsigned long int)。
timeout:参数指定poll的超时值,单位毫秒,当timeout为-1时,poll调用将永远阻塞,知道某个事件发生;当timeout为0时,poll调用将立即返回。
返回值:成功返回就绪文件描述符的个数,失败返回-1,超时时间到达无文件描述符就绪返回0。
poll相比于select的优点:
1.将用户关注的事件与内核修改的就绪事件分隔开,每次调用poll不需要重新设置
2.poll通过int类型记录文件描述符,文件描述符的取值范围扩大到系统最大值65535
3.用户关注的所有文件描述符通过fds执行的用户数组来记录,介意关注的文件描述符的个数由用户决定,可以扩大到系统最大值。
仍然存在的问题
1.返回值依旧是就绪文件描述符的个数,仍然需要去探测数组中文件描述符,时间复杂度为O(n)
2.内核也是采用轮询的方式处理
具体实现代码如下:
//初始化该用户数组,文件描述符置-1,事件置0
void InitFds(struct pollfd *fds)
{
int i = 0;
for(; i<FDMAX; ++i)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
//将获取到的文件描述符,添加事件(POLLIN | POLLRDHUP)
void ADDFd(int fd, struct pollfd *fds)
{
int i = 0;
for(; i<FDMAX; ++i)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = POLLIN | POLLRDHUP;
return;
}
}
}
//将断开链接的文件描述符从用户数组删除,事件置0
void DeleteFd(int fd, struct pollfd *fds)
{
int i = 0;
for(; i<FDMAX; ++i)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds[i].events = 0;
return;
}
}
}
//获取的文件描述符是新客户端的连接,接收(accept())新的链接,并将该文件描述符存入用户数组中并设置事件
void GetClientLink(int fd, struct pollfd *fds)
{
struct sockaddr_in cli;
int len = sizeof(cli);
int c = accept(fd, (struct sockaddr*)&cli, &len);
if(c == -1)
return;
ADDFd(c, fds);
}
//获取的文件描述符是客户端的信息时,处理收到的信息
void DealClientData(int fd, struct pollfd *fds)
{
char buff[128] = {0};
int n = recv(fd, buff, 127, 0);
if(n <= 0)
{
DeleteFd(fd, fds);
return;
}
printf("%s\n", buff);
send(fd, "OK", 2, 0);
}
int main()
{
//进行TCP连接
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd != -1);
struct sockaddr_in ser;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));
listen(listenfd, 5);
//定义存放文件描述符和事件结构的用户数组
struct pollfd fds[FDMAX];
//初始化该数组
InitFds(fds);
//第一个listenfd存入到用户数组中等待处理
fds[0].fd = listenfd;
fds[0].events = POLLIN;
while(1)
{
//调用poll将用户数组,数组大小,超时时间传入
int n = poll(fds, FDMAX, -1);
if(n <= 0)
{
exit(0);
}
int i = 0;
//轮询方式去检索符合数组中的文件描述符是否有对应的事件发生,对不同类型的文件描述符进行不同处理
for(; i<FDMAX; ++i)
{
if(fds[i].fd != -1)
{
//若事件是关闭连接,则删除掉存在用户数组中的文件描述符
if(fds[i].revents & POLLRDHUP)
{
close(fds[i].fd);
DeleteFd(fds[i].fd, fds);
printf("one Client unlink\n");
}
else if(fds[i].revents & POLLIN)//若事件是读则判断文件描述符是新客户端连接还是客户端信息
{
if(fds[i].fd == listenfd)
{
GetClientLink(fds[i].fd, fds);
}
else
{
DealClientData(fds[i].fd, fds);
}
}
}
}
}
}