目录
3.3 epoll_wait 等待 epoll 文件描述符上的 I/O 事件
- int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-
- struct pollfd {
- int d; /* 文件描述符 */
- short events; /* 请求的事件 */
- short revents; /* 返回的事件 */
- };
poll()
函数是一个系统调用,用于监视一组文件描述符,等待其中的一个或多个文件描述符变为就绪状态,从而进行读写操作。它与 select()
类似,但 poll()
没有最大文件描述符数量的限制,并且更加高效。以下是参数的含义:
fds
:指向 pollfd
结构体数组的指针,其中存放了需要监视的文件描述符和每个文件描述符所关注的事件。nfds
:文件描述符的数量。timeout
:超时时间(单位是毫秒),如果为 0 表示立即返回,如果为 -1 表示永远等待。poll()
的返回值表示就绪的文件描述符数量,如果返回值为 0,则表示超时。如果返回值为 -1,则表示发生错误,此时可以使用 errno
查看具体的错误信息。
事件类型 events:
server.c
- #include "net.h"
- #include
-
- #define MAX_SOCK_FD 1024
- int main(int argc, char *argv[])
- {
- int i, j, fd, newfd;
- nfds_t nfds = 1;
- struct pollfd fds[MAX_SOCK_FD] = {};
- Addr_in addr;
- socklen_t addrlen = sizeof(Addr_in);
- /*检查参数,小于3个 直接退出进程*/
- Argment(argc, argv);
- /*创建已设置监听模式的套接字*/
- fd = CreateSocket(argv);
- fds[0].fd = fd;
- fds[0].events = POLLIN;
- while(1){
- if( poll(fds, nfds, -1) < 0)
- ErrExit("poll");
- for(i = 0; i < nfds; i++){
- /*接收客户端连接,并生成新的文件描述符*/
- if(fds[i].fd == fd && fds[i].revents & POLLIN){ //判断服务端的fd是否有数据到来即第0个,并且文件描述符是数据可读,那么再执行把客户端接进来
- if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)
- perror("accept");
- fds[nfds].fd = newfd; //加入检查列表
- fds[nfds++].events = POLLIN;
- printf("[%s:%d][nfds=%lu] connection successful.\n",
- inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds);
- }
- /*处理客户端数据*/
- if(i > 0 && fds[i].revents & POLLIN){ //服务端不算,需要1开始
- if(DataHandle(fds[i].fd) <= 0){
- if( getpeername(fds[i].fd, (Addr *)&addr, &addrlen) < 0)
- perror("getpeername");
- printf("[%s:%d][fd=%d] exited.\n",
- inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd);
- close(fds[i].fd);
- for(j=i; j
-1; j++) //删除检查的目标,等于把后面的数组往前挪1 - fds[j] = fds[j+1];
- nfds--;
- i--;
- }
- }
- }
- }
- close(fd);
- return 0;
- }
socket.c
- #include "net.h"
-
- void Argment(int argc, char *argv[]){
- if(argc < 3){
- fprintf(stderr, "%s
\n" , argv[0]); - exit(0);
- }
- }
- int CreateSocket(char *argv[]){
- /*创建套接字*/
- int fd = socket(AF_INET, SOCK_STREAM, 0);
- if(fd < 0)
- ErrExit("socket");
- /*允许地址快速重用*/
- int flag = 1;
- if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
- perror("setsockopt");
- /*设置通信结构体*/
- Addr_in addr;
- bzero(&addr, sizeof(addr) );
- addr.sin_family = AF_INET;
- addr.sin_port = htons( atoi(argv[2]) );
- /*绑定通信结构体*/
- if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
- ErrExit("bind");
- /*设置套接字为监听模式*/
- if( listen(fd, BACKLOG) )
- ErrExit("listen");
- return fd;
- }
- int DataHandle(int fd){
- char buf[BUFSIZ] = {};
- Addr_in peeraddr;
- socklen_t peerlen = sizeof(Addr_in);
- if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )
- perror("getpeername");
- int ret = recv(fd, buf, BUFSIZ, 0);
- if(ret < 0)
- perror("recv");
- if(ret > 0){
- printf("[%s:%d]data: %s\n",
- inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
- }
- return ret;
- }
net.h
- #ifndef _NET_H_
- #define _NET_H_
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- typedef struct sockaddr Addr;
- typedef struct sockaddr_in Addr_in;
- #define BACKLOG 5
- #define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)
-
- void Argment(int argc, char *argv[]);
- int CreateSocket(char *argv[]);
- int DataHandle(int fd);
-
-
- #endif
- /*创建epoll句柄*/
- int epoll_create(int size); //size参数实际上已经被弃用
-
- /*epoll句柄的控制接口*/
- int epoll_ctl(int epfd, int op, int fd,
- struct epoll_event *event);
-
- /*等待 epoll 文件描述符上的 I/O 事件*/
- int epoll_wait(int epfd, struct epoll_event *events,
- int maxevents, int timeout);
-
epoll_create
创建epoll句柄int epoll_create(int size); //size参数实际上已经被弃用
epoll_create()
函数用于创建一个 epoll 实例,并返回一个与该实例相关联的文件描述符。以下是参数的含义:
size
:已经被弃用,原本用于指定 epoll 实例的大小,但在内核版本2.6.8之后不再使用。epoll_create()
的返回值为一个非负整数,表示与 epoll 实例相关联的文件描述符。如果返回值为 -1,则表示创建失败,此时可以使用 errno
查看具体的错误信息。
调用成功后,可以使用返回的文件描述符进行后续的操作,如注册、修改、等待文件描述符上的事件等。
epoll_ctl
epoll句柄控制接口- int epoll_ctl(int epfd, int op, int fd,
- struct epoll_event *event);
epoll_ctl()
函数用于向 epoll 实例中注册、修改或删除文件描述符的事件。它是 epoll 系统调用的控制接口。以下是参数的含义:
epfd
:epoll 实例的文件描述符,通过 epoll_create()
创建。op
:操作类型,可以是以下几种值之一:
EPOLL_CTL_ADD
:将文件描述符 fd
添加到 epoll 实例中,并关联一个事件结构。EPOLL_CTL_MOD
:修改文件描述符 fd
在 epoll 实例中关联的事件结构。EPOLL_CTL_DEL
:从 epoll 实例中移除文件描述符 fd
。fd
:需要注册、修改或删除的文件描述符。event
:指向 struct epoll_event
结构体的指针,用于指定文件描述符关心的事件类型。epoll_ctl()
的返回值为 0 表示操作成功,-1 表示操作失败,此时可以使用 errno
查看具体的错误信息。
epoll_event结构体
- 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 可以用来存储与该事件相关的用户数据信息。*/
- };
- int epoll_wait(int epfd, struct epoll_event *events,
- int maxevents, int timeout);
epoll_wait()
函数用于等待 epoll 实例中的文件描述符上发生事件,它会一直阻塞直到至少有一个文件描述符发生就绪事件。以下是参数的含义:
epfd
:epoll 实例的文件描述符,通过 epoll_create()
创建。events
:指向 struct epoll_event
结构体数组的指针,用于返回触发事件的文件描述符信息。maxevents
:表示 events
数组的最大大小,即最多可以返回多少个事件。timeout
:超时时间(单位是毫秒),如果为 0 表示立即返回,如果为 -1 表示永远等待。epoll_wait()
的返回值表示就绪的文件描述符数量,如果返回值为 0,则表示超时。如果返回值为 -1,则表示发生错误,此时可以使用 errno
查看具体的错误信息。在成功返回时,events
数组被填充了触发事件的文件描述符信息。
server.c
- #include "net.h"
- #include
-
- #define MAX_SOCK_FD 1024
-
- int main(int argc, char *argv[])
- {
- int i, nfds, fd, epfd, newfd;
- Addr_in addr;
- socklen_t addrlen = sizeof(Addr_in);
- struct epoll_event tmp, events[MAX_SOCK_FD] = {};
- /*检查参数,小于3个 直接退出进程*/
- Argment(argc, argv);
- /*创建已设置监听模式的套接字*/
- fd = CreateSocket(argv);
-
- if( (epfd = epoll_create(1)) < 0) //参数1是无意义得
- ErrExit("epoll_create");
- tmp.events = EPOLLIN;
- tmp.data.fd = fd;
- if( epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &tmp) )
- ErrExit("epoll_ctl");
-
- while(1) {
- if( (nfds = epoll_wait(epfd, events, MAX_SOCK_FD, -1) ) < 0)
- ErrExit("epoll_wait");
- printf("nfds = %d\n", nfds);
-
- for(i = 0; i < nfds; i++) {
- if(events[i].data.fd == fd){
- /*接收客户端连接,并生成新的文件描述符*/
- if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)
- perror("accept");
- printf("[%s:%d] connection.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
- tmp.events = EPOLLIN;
- tmp.data.fd = newfd;
- if( epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &tmp) )
- ErrExit("epoll_ctl");
- }else{/*处理客户端数据*/
- if(DataHandle(events[i].data.fd) <= 0){
- if( epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) )
- ErrExit("epoll_ctl");
- if( getpeername(events[i].data.fd, (Addr *)&addr, &addrlen) )
- perror("getpeername");
- printf("[%s:%d] exited.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
- close(events[i].data.fd);
- }
- }
- }
- }
- close(epfd);
- close(fd);
- return 0;
- }
socket.c
- #include "net.h"
-
- void Argment(int argc, char *argv[]){
- if(argc < 3){
- fprintf(stderr, "%s
\n" , argv[0]); - exit(0);
- }
- }
- int CreateSocket(char *argv[]){
- /*创建套接字*/
- int fd = socket(AF_INET, SOCK_STREAM, 0);
- if(fd < 0)
- ErrExit("socket");
- /*允许地址快速重用*/
- int flag = 1;
- if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
- perror("setsockopt");
- /*设置通信结构体*/
- Addr_in addr;
- bzero(&addr, sizeof(addr) );
- addr.sin_family = AF_INET;
- addr.sin_port = htons( atoi(argv[2]) );
- /*绑定通信结构体*/
- if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
- ErrExit("bind");
- /*设置套接字为监听模式*/
- if( listen(fd, BACKLOG) )
- ErrExit("listen");
- return fd;
- }
- int DataHandle(int fd){
- char buf[BUFSIZ] = {};
- Addr_in peeraddr;
- socklen_t peerlen = sizeof(Addr_in);
- if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )
- perror("getpeername");
- int ret = recv(fd, buf, BUFSIZ, 0);
- if(ret < 0)
- perror("recv");
- if(ret > 0){
- printf("[%s:%d]data: %s\n",
- inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
- }
- return ret;
- }
net.h
- #ifndef _NET_H_
- #define _NET_H_
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- typedef struct sockaddr Addr;
- typedef struct sockaddr_in Addr_in;
- #define BACKLOG 5
- #define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)
-
- void Argment(int argc, char *argv[]);
- int CreateSocket(char *argv[]);
- int DataHandle(int fd);
-
-
- #endif
总结select,poll和epoll的优缺点
select:
优点:
缺点:
poll:
优点:
缺点:
epoll:
优点:
缺点:
综上所述,epoll是更为高效、灵活的I/O多路复用机制,可以更好地适应高并发量的场景。而select和poll则适用于低并发量的情况。