Linux C 网络编程——7.select poll epoll
来源:互联网 发布:三级分销java源程序 编辑:程序博客网 时间:2024/05/20 20:17
上一节中我们讲到了IO复用,总结起来也就是分为同步和异步模型,以及阻塞和非阻塞模型,本文主要分析其中的异步阻塞模型。
IO的复用可以通过select poll epoll实现
1. select
头文件:sys/select.h sys/time.h
int select( int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout );
(1)maxfd
maxfd = max( readset, writeset, exceptset ) + 1;
(2)timeout
经过timeout时间后无论如何都要返回
timeout = 0 :非阻塞IO
timeout = NULL:永远等待
(3)当发生以下情况时select返回:
>readset中fd可读
>writeset中fd可写
>exceptset中fd发生异常
(4)返回值:
OK—— 准备好的fd的个数(0-n)
Fail—— -1 (非exceptset中的fd发生意外)
(5)FD_ISSET
如果返回值大于0,那么可用FD_ISSET测试readset中哪个文件准备好了
(6)实现
select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。
(7)缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
FD_ZERO(int fd, fd_set* fds) FD_SET(int fd, fd_set* fds) FD_ISSET(int fd, fd_set* fds) FD_CLR(int fd, fd_set* fds) int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
SOCKADDR_IN addrSrv;int reuse = 1;SOCKET sockSrv,connsock;SOCKADDR_IN addrClient;pool pool;int len=sizeof(SOCKADDR);/*创建TCP*/sockSrv=socket(AF_INET,SOCK_STREAM,0);/*地址、端口的绑定*/addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(port);if(bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))<0){fprintf(stderr,"Failed to bind");return ;}if(listen(sockSrv,5)<0){fprintf(stderr,"Failed to listen socket");return ;}setsockopt(sockSrv,SOL_SOCKET,SO_REUSEADDR,(const char*)&reuse,sizeof(reuse));init_pool(sockSrv,&pool);while(1){/*通过selete设置为异步模式*/pool.ready_set=pool.read_set;pool.nready=select(pool.maxfd+1,&pool.ready_set,NULL,NULL,NULL);if(FD_ISSET(sockSrv,&pool.ready_set)){connsock=accept(sockSrv,(SOCKADDR *)&addrClient,&len);//loadDeal()/*连接处理*///printf("test\n");add_client(connsock,&pool);//添加到连接池}/*检查是否有事件发生*/check_client(&pool);}
上面是一个服务器代码的关键部分,设置为异步的模式,然后接受到连接将其添加到连接池中。监听描述符上使用select,接受客户端的连接请求,在check_client函数中,遍历连接池中的描述符,检查是否有事件发生。
2. poll 轮询函数
头文件:poll.h
poll函数类似于select,但是其调用形式不同。poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。定义如下:
#include <sys/poll.h>int poll (struct pollfd *fds, unsigned int nfds, int timeout);struct pollfd {int fd; /* file descriptor可为负数 */short events; /* requested events to watch 用户设置*/short revents; /* returned events witnessed 内核返回*/};
(1)事件:
events:
POLLIN :可读除高优级外的数据,不阻塞
POLLRDNORM:可读普通数据,不阻塞
POLLRDBAND:可读O优先数据,不阻塞
POLLPRI:可读高优先数据,不阻塞
POLLOUT :可写普数据,不阻塞
POLLWRNORM:与POLLOUT相同
POLLWRBAND:写非0优先数据,不阻塞
revents:
POLLERR :已出错
POLLHUP:已挂起,当以描述符被挂起后,就不能再写向该描述符,但是仍可以从该描述符读取到数据。
POLLNVAL:此描述符并不引用一打开文件
(2)实现
poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
(3)缺点:
基本上与select一样,只不过解决了描述符数量的限制。
3. epoll
epoll解决了select,poll的上述缺点。
(1)数据结构
typedef union epoll_data{ void *ptr; int fd; __uint32_t u32; __uint64_t u64;} epoll_data_t;struct epoll_event{ __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */};
事件:
EPOLLIN:表示对描述符的可以读
EPOLLOUT:表示对描述符的可以写
EPOLLPRI:表示对描述符的有紧急数据可以读
EPOLLERR:发生错误
EPOLLHUP:挂起
EPOLLET:边缘触发
EPOLLONESHOT:一次性使用,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
(2)函数
int epoll_creae(int size);
功能:该函数生成一个epoll专用的文件描述符
参数:size为epoll上能关注的最大描述符数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:用于控制某个epoll文件描述符时间,可以注册、修改、删除
参数:epfd由epoll_create生成的epoll专用描述符
op操作:EPOLL_CTL_ADD 注册 EPOLL_CTL_MOD修改 EPOLL_DEL删除
fd:关联的文件描述符
evnet告诉内核要监听什么事件
int epoll_wait(int epfd,struct epoll_event*events,int maxevents,int timeout);
功能:该函数等待i/o事件的发生。
参数:epfd要检测的句柄
events:用于回传待处理时间的数组
maxevents:告诉内核这个events有多大,不能超过之前的size
timeout:为超时时间
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <unistd.h>#include <fcntl.h>#include <sys/epoll.h>#include <errno.h>#define MAXEVENTS 64static intmake_socket_non_blocking (int sfd){ int flags, s; flags = fcntl (sfd, F_GETFL, 0); if (flags == -1) { perror ("fcntl"); return -1; } flags |= O_NONBLOCK; s = fcntl (sfd, F_SETFL, flags); if (s == -1) { perror ("fcntl"); return -1; } return 0;}static intcreate_and_bind (char *port){ struct addrinfo hints; struct addrinfo *result, *rp; int s, sfd; memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */ hints.ai_flags = AI_PASSIVE; /* All interfaces */ s = getaddrinfo (NULL, port, &hints, &result); if (s != 0) { fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; s = bind (sfd, rp->ai_addr, rp->ai_addrlen); if (s == 0) { /* We managed to bind successfully! */ break; } close (sfd); } if (rp == NULL) { fprintf (stderr, "Could not bind\n"); return -1; } freeaddrinfo (result); return sfd;}intmain (int argc, char *argv[]){ int sfd, s; int efd; struct epoll_event event; struct epoll_event *events; if (argc != 2) { fprintf (stderr, "Usage: %s [port]\n", argv[0]); exit (EXIT_FAILURE); } sfd = create_and_bind (argv[1]); if (sfd == -1) abort (); s = make_socket_non_blocking (sfd); if (s == -1) abort (); s = listen (sfd, SOMAXCONN); if (s == -1) { perror ("listen"); abort (); } efd = epoll_create1 (0); if (efd == -1) { perror ("epoll_create"); abort (); } event.data.fd = sfd; event.events = EPOLLIN | EPOLLET; s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event); if (s == -1) { perror ("epoll_ctl"); abort (); } /* Buffer where events are returned */ events = calloc (MAXEVENTS, sizeof event); /* The event loop */ while (1) { int n, i; n = epoll_wait (efd, events, MAXEVENTS, -1); for (i = 0; i < n; i++){ if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { /* An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */ fprintf (stderr, "epoll error\n"); close (events[i].data.fd); continue; } else if (sfd == events[i].data.fd) { /* We have a notification on the listening socket, which means one or more incoming connections. */ while (1) { struct sockaddr in_addr; socklen_t in_len; int infd; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; in_len = sizeof in_addr; infd = accept (sfd, &in_addr, &in_len); if (infd == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { /* We have processed all incoming connections. */ break; } else { perror ("accept"); break; } } s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV); if (s == 0) { printf("Accepted connection on descriptor %d " "(host=%s, port=%s)\n", infd, hbuf, sbuf); } /* Make the incoming socket non-blocking and add it to the list of fds to monitor. */ s = make_socket_non_blocking (infd); if (s == -1) abort (); event.data.fd = infd; event.events = EPOLLIN | EPOLLET; s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event); if (s == -1) { perror ("epoll_ctl"); abort (); } } continue; } else { /* We have data on the fd waiting to be read. Read and display it. We must read whatever data is available completely, as we are running in edge-triggered mode and won't get a notification again for the same data. */ int done = 0; while (1) { ssize_t count; char buf[512]; count = read (events[i].data.fd, buf, sizeof buf); if (count == -1) { /* If errno == EAGAIN, that means we have read all data. So go back to the main loop. */ if (errno != EAGAIN) { perror ("read"); done = 1; } break; } else if (count == 0) { /* End of file. The remote has closed the connection. */ done = 1; break; } /* Write the buffer to standard output */ s = write (1, buf, count); if (s == -1) { perror ("write"); abort (); } } if (done) { printf ("Closed connection on descriptor %d\n", events[i].data.fd); /* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */ close (events[i].data.fd); } } } } free (events); close (sfd); return EXIT_SUCCESS;}
4. 参考文献
[1] Linux 环境下C编程指南
[2] select、poll、epoll的比较
- Linux C 网络编程——7.select poll epoll
- linux网络编程 select,poll,epoll模型
- Linux下网络编程(select/poll/epoll)
- linux网络编程中select/poll/epoll的比较分析
- Linux/Unix网络编程 epoll和select/poll的对比
- linux 网络编程 I/O复用 select,poll ,epoll
- Linux网络编程--select,poll和epoll的区别
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
- UNIX网络编程:select,epoll,poll比较
- 网络编程 - select、poll、epoll比较
- Unix网络编程select、poll、epoll比较
- UNIX网络编程:select,epoll,poll比较
- 网络编程2---select poll epoll
- HTTP工作原理
- 实际项目中的常见算法
- 自定义函数
- nutch的基本工作流程理解
- 白岩松:用理想和现实谈谈青春
- Linux C 网络编程——7.select poll epoll
- 第二届微软CRM交流年会
- Windows查看端口和结束进程
- 关于CSS中的下划线_,星号*和!important
- Oracle VM Virtualbox 中的网络连接类型
- git操作
- A Beginner's Guide To btrfs
- DSO(dynamic shared object)动态共享对象的原理
- 常用RGB颜色表