1、poll
函数原型:
#include
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数说明:
fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;
nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;
timeout:是poll函数调用阻塞的时间,单位:毫秒;
如果timeout==0,那么poll() 函数立即返回而不阻塞,如果timeout==INFTIM(宏 表示为-1),那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发生是才返回
函数返回值:
poll函数的返回值与select函数的返回值一样。
若返回0:表示超时
若为-1:错误
若>0:返回就绪事件的个数
使用poll检测输入输出:
#include#include int main() { struct pollfd pfd[1]; int len = 1; pfd[0].fd = 0; pfd[0].events = POLLIN; pfd[0].revents = 0; int done = 0; while(!done) { switch(poll(pfd,1,-1)) { case 0: printf("timeout"); break; case -1: perror("select"); break; default: { char buf[1024]; if(pfd[0].revents & POLLIN) { ssize_t _s = read(pfd[0].fd,buf,sizeof(buf)-1); if(_s > 0) { buf[_s] = '\0'; printf("echo:%s\n",buf); } } } break; } } }
poll函数的缺点:
(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
(2)与select一样,poll返回后,需要轮询poolfd来获取就绪的描述符。
poll函数的优点:
(1)poll函数不要求计算最大文件描述符的大小
(2)poll函数在应付大数目的文件描述符的时候速度更快,相比于select
(3)它没有最大连接数的限制,原因是它基于链表来存储的。
2. epoll
epoll只有epoll_create,epoll_ctl,epoll_wait3个系统调用。
(1)int epoll_create(int size)
创建一个epoll的句柄。size参数是可以被忽略的。当创建好epoll句柄后,它就会占用一个fd值。所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
(2)int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event)
该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、 EPOLL_CTL_DEL 删除;
fd:关联的文件描述符;
event:告诉内核需要监听的事件;
如果调用成功则返回0,不成功则返回-1。
(3)int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;
#include#include #include #include #include #include #include #include #include void usage(const char* _proc) { printf("Usage:%s [ip] [port]\n",_proc); } void set_nonblock(int fd) { int fl = fcntl(fd,F_GETFL); //读取文件状态标志 fcntl(fd,F_SETFL,fl|O_NONBLOCK); //设置文件状态标志 } int startup(const char* _ip,int _port) { int sock = socket(AF_INET,SOCK_STREAM,0); //创建套接字 if(sock < 0) { perror("socket"); exit(2); } int opt = 1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置处于TIME_WAIT时,端口号还可以用 struct sockaddr_in local; //设置本地local local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = inet_addr(_ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0) { perror("bind"); exit(3); } if(listen(sock,5) < 0) { perror("listen"); exit(4); } return sock; } int main(int argc,char* argv[]) { if(argc != 3) { usage(argv[0]); exit(1); } int listen_sock = startup(argv[1],atoi(argv[2])); //创建监听套接字 int epfd = epoll_create(256); //创建一个epoll的句柄,当创建好句柄后,会占用一个fd值 if(epfd < 0) { perror("epoll_create"); exit(5); } struct epoll_event _ev; _ev.events = EPOLLIN; //设置要处理的事件类型 _ev.data.fd = listen_sock; //设置与要处理的事件相关的文件描述符 epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&_ev); //注册函数,注册监听事件的类型, struct epoll_event revs[64]; int timeout = -1; int num = 0; int done = 0; while(!done) { switch(num = epoll_wait(epfd,revs,64,timeout)) //revs从内核得到的事件集合,返回需要处理的事件数目 { case 0: printf("timeout\n"); break; case -1: perror("epoll_wait"); break; default: { struct sockaddr_in peer; socklen_t len = sizeof(peer); int i = 0; for(;i 0) { printf("get a new client :%s:%d\n",\ inet_ntoa(peer.sin_addr),\ ntohs(peer.sin_port)); set_nonblock(new_fd); _ev.events = EPOLLIN | EPOLLET; _ev.data.fd = new_fd; epoll_ctl(epfd,EPOLL_CTL_ADD,\ new_fd,&_ev); } } else { if(revs[i].events & EPOLLIN) { char buf[1024]; ssize_t _s = read(rsock,buf,sizeof(buf)-1); if(_s > 0) { buf[_s] = '\0'; printf("client:%s\n",buf); _ev.events = EPOLLOUT | EPOLLET; _ev.data.fd = rsock; epoll_ctl(epfd,EPOLL_CTL_MOD,\ rsock,&_ev); } else if(_s == 0) { printf("client %d close...\n",rsock); epoll_ctl(epfd,EPOLL_CTL_DEL,\ rsock,NULL); close(rsock); } else { perror("read"); } } else if(revs[i].events & EPOLLOUT) { const char* msg = "hello\n"; //const char* msg = \ "HTTP/1.0 200 OK\r\n\r\n hello word!+_+||
\r\n"; write(rsock,msg,strlen(msg)); epoll_ctl(epfd,EPOLL_CTL_DEL,\ rsock,NULL); close(rsock); } else {} } } } break; } } return 0; }
测试结果:
epoll的优点:
(1)支持一个进程打开大数目的socket描述符
(2)IO效率不随socket描述符数目的增加而线性下降