I/O复用函数
select
poll
epoll<Linux的独有的I/O复用>
接下来我们分三次进行介绍I/O复用
poll
poll的原型是
int poll (struct pollfd *fds,int nfds,int timeout);
比起select更加简洁了一些,事件不再由内核在线修改,而是存入系统提供的结构体中,将文件描述符和从内核拷贝而来的改变数据划分开,不需要每次重置poll,比select聪明了一些,以下是struct pollfd的内容
struct pollfd
{ int fd//关注的文件描述符
short events;//fd将会发生什么事情,或者期望看到的事情,写在这里
short revents;//由内核修改,返回此文件描述符发生的事件类型(必须是events指定的关注的事件)
}
ndfs:传入的结构体数组长度,因为此时的*fds会退化为指针,更准确的来说,ndfs的类型是ndfs_t是无类型的long int数据
timeout:等同于select中timeout的效果
由于pollfd的特性,我么能关注的事件类型就更多了,如下图(来自Linux高性能服务器编程)
使用前,在文件一开始加入#define _GNU_SOURCE保证POLLRDHUP正常使用,上图中POLLRDNORM,POLLRDBAND,POLLWRNORM,POLLWRBANDLinux并不是完全支持,这实际上是POLLIN和POLLOUT更加细化的事件
其中POLLIN和POLLRDHUP事件有一定的关联,如果POLLRDHUP事件触发,则POLLIN也会被触发,但是反过来不一定。在处理响应事件时要注意判断到底是POLLIN还是POLLRDHUP。
也就是说POLLRDHUP嵌套在POLLIN事件里,先判断范围大的然后判断范围小的,逐步细分。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#define FDMAXNUM 100
void Init_Fds(struct pollfd *fds)
{
int i = 0;
for (; i <FDMAXNUM;++i)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
void DeleteFd(int fd,struct pollfd *fds)
{
int i = 0;
for(;i < FDMAXNUM;++i)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds[i].events = 0;
break;
}
}
}
void AddFds(int fd,struct pollfd *fds)
{
int i = 0;
for(;i < FDMAXNUM;++i)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = POLLIN | POLLRDHUP;
break;
}
}
}
void GetClientLink(int fd,struct pollfd *fds,struct sockaddr_in cli)
{
int len = sizeof(cli);
int c = accept(fd,(struct sockaddr*)&cli,&len);
AddFds(c,fds);
}
void DealClientData(int fd,struct pollfd *fds,int rdhup)
{
if(rdhup)
{
DeleteFd(fd,fds);
close(fd);
return;
}
char buff[128] = {0};
int n = recv(fd,buff,127,0);
if(n <= 0)
{
DeleteFd(fd,fds);
close(fd);
return;
}
printf("%s\n", buff);
send(fd,"OK",2,0);
}
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
listen(sockfd,5);
struct pollfd fds[FDMAXNUM];
Init_Fds(fds);
fds[0].fd = sockfd;
fds[0].events = POLLIN;
while(1)
{
int n = poll(fds,FDMAXNUM,-1);
if(n <= 0)
{
exit(0);
}
int i = 0;
for(;i <FDMAXNUM;++i)
{
if(fds[i].fd == -1)
{
continue;
}
if(fds[i].revents & POLLIN)
{
if(fds[i].fd == sockfd)
{
GetClientLink(fds[i].fd,fds,cli);
}
else if(fds[i].revents &POLLRDHUP)
DealClientData(fds[i].fd,fds,1);
else
DealClientData(fds[i].fd,fds,0);
}
}
}
return 0;
}
poll的特点:
- 将用户关注的事件与内核修改的就绪事件分隔开,每次调用poll不需要重新设置
- poll通过int类型记录文件描述符,文件描述符的取值范围扩大到系统最大限制65535
- 用户关注的所有文件描述符通过fds指向的用户数组来记录,则可以关注的文件描述符的个数由用户指定,能扩大到系统最大限制
poll存在的问题:
- 返回值返回就绪的文件描述符个数,poll也仅仅是在fds指向的数组元素中标记出那个文件描述符就绪,用户探测的时候依然需要for循环,时间复杂度依旧是O(n)
- 在内核中依旧是轮询方式处理