I/O复用使得程序可以监听多个文件描述符,能够用提高程序的性能;
select
selcet的系统调用的用途是:在一段时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。
#include<sys/select.h>
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);
1)nfds参数:指被监听的文件描述符的总数;它通常被设置为selcet监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。
2)readfds、writefds、exceptfds:分别指的是可读、可写和异常事件等事件对应的文件描述符集合。应用程序调用select函数时,通过这三个参数来传入自己感兴趣的文件描述符。
3)struct timeval * timeout 参数是设置select的超时时间。
select的返回值:成功时返回所关注的就绪的文件描述符的总数。如果超时时间内没有文件描述符就绪,select将返回0;
select失败时返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。
关于fd_set的结构体实际上是一个32位的整型数组,该数组每一位(bit)表示一个文件描述符,32x4x8 = 1024,所以最多可以表示1024个文件描述符;且1024位select所能关注的文件描述符的上限个数,如果想修改这个值,需要重新编译内核,这是select的缺点之一。
同时由于位操作繁琐,linux提供了一些列宏来设置fd_set中的位
#include<sys/select.h>
FD_ZERO(fd_set * fdset); //清除 fd_set的所有位
FD_SET(int fd,fd_set * fdset); //设置fd_set中的对应位的fd为1
FD_CLR(int fd,fd_set * fdset); //清除fd_set中的对应位的fd为0
int FD_ISSET(int fd,fd_set * fdset);//测试fd_set的对应位fd是否被设置
如果给timeout传递0,则selet会直接返回,如果给timeout传递NULL,则select将一直阻塞,直到有文件描述符就绪。
在这给出一个服务器采用select的例子:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<pthread.h>
#include<semaphore.h>
void Init_fds(int * fds, int len)
{
int i = 0;
for(;i< len ;++i)
{
fds[i] = -1;
}
}
void Insert_fd(int *fds,int fd,int len)
{
int i = 0;
for(;i<len ;++i)
{
if(fds[i] = -1);
{
fsd[i] = fd;
break;
}
}
}
void Delete_fd(int *fds,int fd,int len);
{
int i = 0;
for(;i<len ;i++)
{
if(fds[i] = fd)
{
fds[i] = -1;
break;
}
}
}
int main()
{
int socketfd = socket(AF_INET,SOCK_STREAM,0);
assert(socketfd != 0);
struct sockaddr_in ser,cli;
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(socketfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
listen(socketfd,5);
fd_set readfds;
int fsd[100] ;//数组用来保存文件描述符
Init_fds(fds,100);//初始化数组,
Insert_fd(fds,socketfd,fd);//将文件描述符插入数组
while(1)
{
int maxfd = -1;
FD_ZERO(&readfds);
int i = 0;
for(;i<len ;++i)
{
if(fds[i] != -1)
{
if(fds[i] > maxfd)//保存文件描述符最大的
{
maxfd = fds[i];
}
FD_SET(fds[i],&readfds);
}
}
int n = select(maxfd + 1,&readfds,NULL,NULL,NULL);
if(n <= 0)
{
printf("select fail\n");
continue;
}
for(i= 0;i < 100;i++)
{
if(fds[i] != -1 && FD_ISSET(fds[i],&readfds))
{
if(fds[i] == socketfd)
{
socklen_t clilen =sizeof(cli);
int c = accept(socketfd,(struct sockaddr*)&cli,&clilen);
if(c < 0)
{
continue;
}
}
Insert_fd(fds,c,100);
}
else
{
int fd = fds[i];
char buff[128] = {0};
int n = recv(fd,buff,127,0);
if(n <= 0)
{
close(fd);
Delete_fd(fds,c,100);
continue;
}
printf("%d: %s\n",fd,buff);
send(fd,"ok",2,0);
}
}
}
return 0;
}
poll
#include<poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
fds参数指的是一个pollfd结构体类型的一个数组,他指定所有我们感兴趣的文件描述符上发生的可读、可写和异常事件。
struct pollfd
{
int fd; //文件描述符
short events; //注册的事件
short revents; //实际发生的事件
}
events成员记录poll应该监听发的上的哪一些事件,它是一些系列事件按位或表示;
revents成员是由内核修改,来通知程序fd上实际发生了哪些事件。
poll支持的事件表如下:
nfds参数指的是被监听事件集合的fds的大小。是一个无符号long int类型的。
timeout参数指的是poll的超时时间,如果timeout= -1;poll将永远阻塞,直到有事件发生;当timeout为0时,poll调用立即返回。
返回值与select相同;
这给出服务器用poll的例子:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<poll.h>
void Init_Fds(struct pollfd *fds)
{
for(int i = 0; i < SIZE,i++)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
void Insert_Fd(struct pollfd *fds,int fd,short event)
{
int i = 0;
for(;i < SIZE;i++)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = event;
break;
}
}
}
void Delete_Fds(struct pollfd *fds,int fd)
{
int i= 0;
for(;i < SIZE;i++)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds.events = 0;
}
}
}
int main()
{
int socketfd = socket(AF_INET,SOCK_STREAM,0);
assert(socketfd != 0);
struct sockaddr_in ser,cli;
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(socketfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
listen(socketfd,5);
struct pollfd fds[100];
Init_Fds(fds);
Insert_Fd(fds,socketfd,POLLIN);
while(1)
{
int n = poll(fds,SIZE,-1);
if(n <= 0)
{
printf("error\n");
continue;
}
int i = 0;
for(; i < SIZE;i++)
{
if(fds[i].fd != -1 )
{
int fd = fds[i].fd;
if(fds[i].revents & POLLRDHUP)
{
close(fd);
Delete_Fds(fds,fd);
}
else if(fd[i].events & POLLIN)
{
if(fd = socketfd)
{
socklen_t clilen =sizeof(cli);
int c = accept(socketfd,(struct sockaddr*)&cli,&clilen);
if(c < 0 )
{
continue;
}
Insert_Fd(fds,c,POLLIN | POLLRDHUP);
}
else
{
char buff[128] = {0};
recv(fd,buff,127,0);
printf("%d: %s\n",fd,buff);
send(fd,"ok",2,0);
}
}
}
}
}
}
epoll
epoll是Linux特有的一种i/o复用,它与select、poll有很大差异;epoll用一组函数来完成任务,不是单个函数,其次epoll把用户关心的文件描述符上的事件放在内核的事件表中,从而无需像select和poll一样每次都要调用都要重复传入文件描述符集,或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核维护的事件表。
这个文件描述符使用epoll_create函数来创建时:
#include<sys/epoll.h>
int epoll_create(int size); //size为创建该事件表的大小;
int epoll_ctl(int epfd,int op,int fd,struct epoll_events);
fd 是要操作的文件描述符,op参数则指定操作类型,
EPOLL_CLT_ADD,往事件表上添加事件
EPOLL_CLT_MOD,修改事件表上的事件
EPOLL_CLT_DEL,删除事件表上的事件
events指定是贱,它是epoll_events结构体指针类型;定义如下:
struct epoll_events
{
_uint32_t events;
epoll_data_t data;
};
events成员描述事件类型。epoll支持的事件与poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上"E",比如epoll的数据可读事件是EPOLLIN。epoll有两个额外的事件类型——EPOLLET 和EPOLLONESHOT。
epoll_ctl成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno。
epoll的系统调用的主要接口是epoll_wait,
epoll_wait(int epfd,struct epoll_event *events, int maxevents, int timeout);
maxevents指定最多监听的事件,必须大于0;
events用来表示内核返回就绪的文件描述符。不用像select和poll那样一直进程轮询来查看哪一个文件描述符就绪,由内核直接将就绪的文件描述符返回,这就极大的提高了效率。
epoll的例子:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/epoll.h>
int main()
{
int socketfd = socket(AF_INET,SOCK_STREAM,0);
assert(socketfd != 0);
struct sockaddr_in ser,cli;
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(socketfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
listen(socketfd,5);
while(1)
{
struct epoll_event events[SIZE];
int n = epoll_wait(epollfd,events,SIZE,-1);
if(n <=0 )
{
stf::cout<<"fail"<<std::endl;
continue;
}
int i = 0;
for(;i<n;i++)
{
int fd = events[i].data.fd;
if(fd==socketfd)
{
socklen_t clilen = sizeof(clilen);
int c = accept(socketfd,(struct sockaddr*)&cli,&clilen);
if(c<0)
{
std::cout<<"accept fail"<<std::endl;
continue;
}
event.events = EPOLLIN | EPOLLRDHUP;
event.data.fd = c;
epoll_clt(epollfd,EPOLL_CTL_ADD,fd,NULL);
}
else if(events[i].events &EPOLLRDHUP)
{
epoll_clt(epollfd,EPOLL_CLT_DEL,fd,NULL);
close(fd);
std::cout<<fd;
std::cout<<"is over"<<std::endl;
}
else if(events[i].events & EPOLLIN)
{
char cmd[128] = {0};
recv(fd,cmd,127,0);
std::cout<< fd;
std::cout<<" " <<cmd<<std::endl;
send(fd,"ok",2,0);
}
}
}
}
以上select,poll,epoll的例子都为简单的实现。