赞
踩
我们知道实现服务器的高并发,可以用多线程或多进程去实现。但还可以利用多路IO技术:select来实现,它可以同时监听多个文件描述符,将监控的文件描述符交给内核去处理。
现在来看看这个函数的原型:
int select(int nfds,fd_set*readflds,fd_writefds,set*exceptfds,struct timeval*timeout) nfds:最大的文件描述符+1; readflds:读集合,是一个传入传出参数; 传入:指的是告诉内核哪些文件描述符需要监控; 传出:指的是告诉内核那些文件描述符发生了变化; writefds:写文件描述符的集合(传入传出参数); execptfds:异常文件描述符集合(传入传出参数); timeout(超出时间): NULL:表示永久阻塞,直到有事件发生。 0: 表示不阻塞,不管有无事件发生,立即返回。 >0: (1)表示阻塞,若没有超过时长就会一直阻塞。 (2)超过阻塞时间,没有事件发生,会返回。 (3)阻塞时间之内,有事件发生,返回。
过程如下图:
(1)通过操作得到监听文件描述符lfd,并且加入集合中。
(2)通过内核来监视lfd,如果lfd有行为,就说明有客户链接到来,把得到的通信文件描述符就交给内核监控。
内核发现哪个文件描述符有读行为,取出它进行通信,并把它取出集合。
下面是实现的代码:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/fcntl.h> // 初始化服务端的监听端口。 int initserver(int port); int main(int argc,char *argv[]) { if (argc != 2) { printf("usage: ./tcpselect port\\n"); return -1; } // 初始化服务端用于监听的socket。 int listensock = initserver(atoi(argv[1])); printf("listensock=%d\\n",listensock); if (listensock < 0) { printf("initserver() failed.\\n"); return -1; } fd_set readfdset; // 读事件的集合,包括监听socket和客户端连接上来的socket。 int maxfd; // readfdset中socket的最大值。 // 初始化结构体,把listensock添加到集合中。 FD_ZERO(&readfdset); FD_SET(listensock,&readfdset); maxfd = listensock; while (1) { // 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。 fd_set tmpfdset = readfdset; int infds = select(maxfd+1,&tmpfdset,NULL,NULL,NULL); // printf("select infds=%d\\n",infds); // 返回失败。 if (infds < 0) { printf("select() failed.\\n"); perror("select()"); break; } // 超时,在本程序中,select函数最后一个参数为空,不存在超时的情况,但以下代码还是留着。 if (infds == 0) { printf("select() timeout.\\n"); continue; } // 检查有事情发生的socket,包括监听和客户端连接的socket。 // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。 for (int eventfd=0; eventfd <= maxfd; eventfd++) { if (FD_ISSET(eventfd,&tmpfdset)<=0) continue; if (eventfd==listensock) { // 如果发生事件的是listensock,表示有新的客户端连上来。 struct sockaddr_in client; socklen_t len = sizeof(client); int clientsock = accept(listensock,(struct sockaddr*)&client,&len); if (clientsock < 0) { printf("accept() failed.\\n"); continue; } printf ("client(socket=%d) connected ok.\\n",clientsock); // 把新的客户端socket加入集合。 FD_SET(clientsock,&readfdset); if (maxfd < clientsock) maxfd = clientsock; continue; } else { // 客户端有数据过来或客户端的socket连接被断开。 char buffer[1024]; memset(buffer,0,sizeof(buffer)); // 读取客户端的数据。 ssize_t isize=read(eventfd,buffer,sizeof(buffer)); // 发生了错误或socket被对方关闭。 if (isize <=0) { printf("client(eventfd=%d) disconnected.\\n",eventfd); close(eventfd); // 关闭客户端的socket。 FD_CLR(eventfd,&readfdset); // 从集合中移去客户端的socket。 // 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。 if (eventfd == maxfd) { for (int ii=maxfd;ii>0;ii--) { if (FD_ISSET(ii,&readfdset)) { maxfd = ii; break; } } printf("maxfd=%d\\n",maxfd); } continue; } printf("recv(eventfd=%d,size=%d):%s\\n",eventfd,isize,buffer); // 把收到的报文发回给客户端。 write(eventfd,buffer,strlen(buffer)); } } } return 0; } // 初始化服务端的监听端口。 int initserver(int port) { int sock = socket(AF_INET,SOCK_STREAM,0); if (sock < 0) { printf("socket() failed.\\n"); return -1; } // Linux如下 int opt = 1; unsigned int len = sizeof(opt); setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len); setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len); struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 ) { printf("bind() failed.\\n"); close(sock); return -1; } if (listen(sock,5) != 0 ) { printf("listen() failed.\\n"); close(sock); return -1; } return sock; }
除了select模型,还有poll模型,poll的做法和selec几乎是一样的。
poll 和 select 的实现非常类似,本质上的区别就是存放 fd 集合的数据结构不一样。select 在一个进程内可以维持最多 1024 个连接,poll 在此基础上做了加强,可以维持任意数量的连接。但本质上都是将需要检测的集合拷贝到内核中去,内核来轮询遍历整个集合,反复从头到尾的去查询,再将发生事件的所有的文件描述符拷贝到用户区,这样就导致随着并发量的增大,效率也会随之下降。
int poll(struct pollfd *fds, nfds_t nfds, int timeout); 功能:监视并等待多个文件描述符的属性变化 参数: fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件 struct pollfd{ int fd; //文件描述符 short events; //等待的事件 short revents; //实际发生的事件 }; fds结构体参数说明: fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。 events:指定监测fd的事件(输入、输出、错误),每一个事件有多个取值,如下: revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回. 注意:每个结构体的 events 域是由用户来设置,告诉内核我们关注的是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件 nfds:用来指定第一个参数数组元素个数 timeout:指定等待的毫秒数,无论 I/O 是否准备好,poll() 都会返回. 返回值: 成功时,poll() 返回结构体中 revents 域不为 0 的文件描述符个数;如果在超时前没有任何事件发生,poll()返回 0; 失败时,poll() 返回 -1,并设置 errno 为下列值之一: EBADF:一个或多个结构体中指定的文件描述符无效。 EFAULT:fds 指针指向的地址超出进程的地址空间。 EINTR:请求的事件之前产生一个信号,调用可以重新发起。 EINVAL:nfds 参数超出 PLIMIT_NOFILE 值。 ENOMEM:可用内存不足,无法完成请求。
下面是代码的实现
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> /* superset of previous */ #include <poll.h> /* 定义服务器初始化函数 */ int server_init(char *ip, short port) { int ret; int listenfd; struct sockaddr_in srvaddr; /* 创建套接字文件 */ listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) { perror("server_init->socket"); return -1; } printf("listenfd = %d\n", listenfd); /* 绑定服务器的ip地址和端口号 */ memset(&srvaddr, 0, sizeof(srvaddr)); srvaddr.sin_family = AF_INET; srvaddr.sin_port = htons(port); if (ip == NULL) srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); else srvaddr.sin_addr.s_addr = inet_addr(ip); ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr)); if (ret == -1) { perror("server_init->bind"); return -1; } printf("bind success\n"); /* 启动监听 */ ret = listen(listenfd, 1024); if (ret == -1) { perror("server_init->listen"); return -1; } return listenfd; } /* 定义服务器等待客户端的连接请求,建立连接 */ int server_wait_client_connect(int listenfd) { int connfd; socklen_t addrlen; struct sockaddr_in cltaddr; //accept(listenfd, NULL, NULL); addrlen = sizeof(cltaddr); connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen); if (connfd == -1) { perror("accept"); return -1; } printf("IP : %s connet success connfd = %d\n", inet_ntoa(cltaddr.sin_addr), connfd); return connfd; } int main() { int ret; int listenfd; int connfd; char buf[256]; int nfds; int i; int j; int fd; struct pollfd fds[128]; /* 1. 服务器的初始化 */ listenfd = server_init("127.0.0.1", 8888); //listenfd = server_init(NULL, 8888); if (listenfd == -1) exit(EXIT_FAILURE); printf("server init success\n"); /* 创建集合,清空集合 */ for (i = 0; i < 128; i++) fds[i].fd = -1; /* 将文件描述符listenfd及其对应事件添加到集合fds中 */ fds[0].fd = listenfd; fds[0].events = POLLIN; nfds = 1; while(1) { /* c, 调用poll函数,检测是否有准备就绪的事件,如果没有事件准备就绪,函数一直阻塞。如果有事件准备就绪函数返回;*/ ret = poll(fds, nfds, 5000); if (ret == -1) { perror("poll"); return -1; } else if (ret == 0) { printf("timeout ......\n"); continue; } for (i = 0; i < nfds; i++) { /* 读资源准备就绪,进行读IO操作 */ if (fds[i].revents == POLLIN) { fd = fds[i].fd; if (fd == listenfd) { /* 监听套接字 */ /* 2. 服务器等待客户端的连接请求,建立连接 */ connfd = server_wait_client_connect(listenfd); if (connfd == -1) exit(EXIT_FAILURE); /* 连接成功,将文件描述符connfd及其对应事件添加到集合fds中 */ fds[nfds].fd = connfd; fds[nfds].events = POLLIN; nfds ++; continue; } /* 通信套接字 */ /* 3. 服务器处理客户端的数据请求,并处理数据,反馈处理结果 */ memset(buf, 0, sizeof(buf)); ret = read(fd, buf, sizeof(buf)); if (ret == -1) { perror("server->read"); return -1; } else if (ret == 0) { /* 客户端退出的时候,需要将套接字从集合中删除 */ for (j = i; j < nfds-1; j++) fds[j] = fds[j+1]; close(fd); break; } printf("buf : %s\n", buf); ret = write(fd, buf, sizeof(buf)); if (ret == -1) { perror("server->write"); return -1; } } //if (fds[i].revents == POLLIN) end if (fds[i].events == POLLOUT) { } } } return 0; }
select 和 poll 方式有一个很大的问题就是,我们不难看出来 select 是通过轮训的方式来查找是否可读或者可写,打个比方,如果同时有100万个连接都没有断开,而只有一个客户端发送了数据,所以这里它还是需要循环这么多次,造成资源浪费。所以后来出现了 epoll系统调用。
当某一个进程去调用epoll_creat()的时候,linux的内核会创建一个eventpoll的一个结构体,这个结构题中有两个成员和epoll的使用方式相关。
struct eventpoll
{
//红黑树的根节点,这颗树中存储这我们添加的所有的epoll中的事件。
struct rb_root rbr;
//双向链表rblist中存储的是要通过epoll_wait()返回个用户的满足的事件。
struct list_head rbllist;
}
我们在调用epoll_creat()的时候,内核出来帮我们在epoll文件系统中创建了一个file节点,在内核中还创建了一棵红黑树用来存储加入的socket以外,还会建立一个rbllist双向链表,用来存储准备就绪的事件,当调用epoll_wait()的时候,就只需要观察这个双向链表中有无数据,如果没用就sleep阻塞等待,当阻塞时间超过timeout的时候,就直接返回。用数据的话直接把双向链表中的数据返回给用户。所以epoll非常高效。
所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做ep_poll_callback,它会把这样的事件放到上面的rdllist双向链表中。
在epoll中对于每一个事件都会建立一个epitem结构体,如下所示:
struct epitem {
...
//红黑树节点
struct rb_node rbn;
//双向链表节点
struct list_head rdllink;
//事件句柄等信息
struct epoll_filefd ffd;
//指向其所属的eventepoll对象
struct eventpoll *ep;
//期待的事件类型
struct epoll_event event;
...
}; // 这里包含每一个事件对应着的信息。
当调用epoll_wait检查是否有发生事件的连接时,只是检查eventpoll对象中的rdllist双向链表是否有epitem元素而已,如果rdllist链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。因此epoll_waitx效率非常高。epoll_ctl在向epoll对象中添加、修改、删除事件时,从rbr红黑树中查找事件也非常快,也就是说epoll是非常高效的,它可以轻易地处理百万级别的并发连接。
当我们要把数据拷贝到用户区的时候,这就要涉及到用户态到内核态的转化, 会造成数据的多次拷贝。但是使用了mmap技术,建立了一片共享的内存,这样就不会造成内核态到用户态的内存拷贝,减小了开销。就如下图:
下面是相关的函数
//创建一颗epoll树
int epoll_create(int size);
功能:调用epoll_create方法创建一个epoll的句柄,该句柄代表着一个事件表
参数:size参数现在并不起作用,它只是给内核一个提示,告诉内核事件表需要多大
返回值:1. 成功:返回epoll句柄,它会占用一个fd值(使用完也需要关闭)
失败:返回-1并设置errno值
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
fd表示文件描述符
op参数则指定操作类型,操作类型有一下三种:
EPOLL_CTL_ADD //往事件表上注册fd的事件
EPOLL_CTL_MOD //修改fd上的注册事件
EPOLL_CTL_DEL //删除fd上的注册事件
event是需要传入一个结构体指针类型,表示一个事件,epoll_even结构体t定义如下:
struct epoll_event
{
_uint32_t events; //epoll事件
epolla_data_t data; //用户数据
}
events和poll支持的事件类型基本相同,还可以额外支持两个事件边缘触发和水平触发。epoll默认的触发方式是水平触发。
水平触发:水平触发就是只要缓冲区中有数据,就会一直触发,直到将缓冲区中的数据读完。
边沿触发:只有数据到来的时候才会触发,不管缓冲区中是否有数据。
当使用边沿触发(ET触发)的时候,我们要将监听的文件描述设置成非阻塞,因为我们每次不一定读完缓冲区的数据,而read函数是一个阻塞函数,就会一直阻塞住,导致死锁。
typedef union epoll_data
{
void* ptr; //指定与fd相关的用户数据
int fd; //指定事件所从属的目标文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
这里的ptr指针是可以存储用户的数据的,他是一个viod*的万能指针。
#include <sys/epoll.h>
int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );
epfd:epoll_create()返回的文件描述符
events:检测到事件,将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。
maxevents:最多监听多少个事件。
timeout:为-1表示永久阻塞
=0 表示调用后立即返回
>0 表示在这个时间内无事件发生一直阻塞,有事件发生立即返回;超过时间也会返回。
epoll反应堆利用了c++的封装的思想,封装了一个自己的结构体,每个结构体都用函数指针,通过不同的事件,来回调不同的函数。将ptr指针指向我们自己封装的结构体就可以实现自动回调函数啦。
下面是代码:
/* *epoll基于非阻塞I/O事件驱动 */ #include <stdio.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <time.h> #define MAX_EVENTS 1024 //监听上限数 #define BUFLEN 4096 #define SERV_PORT 8080 void recvdata(int fd, int events, void *arg); void senddata(int fd, int events, void *arg); /* 描述就绪文件描述符相关信息 */ struct myevent_s { int fd; //要监听的文件描述符 int events; //对应的监听事件 void *arg; //泛型参数 void (*call_back)(int fd, int events, void *arg); //回调函数 int status; //是否在监听:1->在红黑树上(监听), 0->不在(不监听) char buf[BUFLEN]; int len; long last_active; //记录每次加入红黑树 g_efd 的时间值 }; int g_efd; //全局变量, 保存epoll_create返回的文件描述符 struct myevent_s g_events[MAX_EVENTS+1]; //自定义结构体类型数组. +1-->listen fd /*将结构体 myevent_s 成员变量 初始化*/ void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg) { ev->fd = fd; ev->call_back = call_back; ev->events = 0; ev->arg = arg; ev->status = 0; memset(ev->buf, 0, sizeof(ev->buf)); ev->len = 0; ev->last_active = time(NULL); //调用eventset函数的时间 return; } /* 向 epoll监听的红黑树 添加一个 文件描述符 */ //eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); void eventadd(int efd, int events, struct myevent_s *ev) { struct epoll_event epv = {0, {0}}; int op; epv.data.ptr = ev; epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT if (ev->status == 0) { //已经在红黑树 g_efd 里 op = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1 ev->status = 1; } if (epoll_ctl(efd, op, ev->fd, &epv) < 0) //实际添加/修改 printf("event add failed [fd=%d], events[%d]\n", ev->fd, events); else printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events); return ; } /* 从epoll 监听的 红黑树中删除一个 文件描述符*/ void eventdel(int efd, struct myevent_s *ev) { struct epoll_event epv = {0, {0}}; if (ev->status != 1) //不在红黑树上 return ; //epv.data.ptr = ev; epv.data.ptr = NULL; ev->status = 0; //修改状态 epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv); //从红黑树 efd 上将 ev->fd 摘除 return ; } /* 当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */ void acceptconn(int lfd, int events, void *arg) { struct sockaddr_in cin; socklen_t len = sizeof(cin); int cfd, i; if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) { if (errno != EAGAIN && errno != EINTR) { /* 暂时不做出错处理 */ } printf("%s: accept, %s\n", __func__, strerror(errno)); return ; } do { for (i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素 if (g_events[i].status == 0) //类似于select中找值为-1的元素 break; //跳出 for if (i == MAX_EVENTS) { printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS); break; //跳出do while(0) 不执行后续代码 } int flag = 0; if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) { //将cfd也设置为非阻塞 printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno)); break; } /* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */ eventset(&g_events[i], cfd, recvdata, &g_events[i]); eventadd(g_efd, EPOLLIN, &g_events[i]); //将cfd添加到红黑树g_efd中,监听读事件 } while(0); printf("new connect [%s:%d][time:%ld], pos[%d]\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i); return ; } void recvdata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg; int len; len = recv(fd, ev->buf, sizeof(ev->buf), 0); //读文件描述符, 数据存入myevent_s成员buf中 eventdel(g_efd, ev); //将该节点从红黑树上摘除 if (len > 0) { ev->len = len; ev->buf[len] = '\0'; //手动添加字符串结束标记 printf("C[%d]:%s\n", fd, ev->buf); eventset(ev, fd, senddata, ev); //设置该 fd 对应的回调函数为 senddata eventadd(g_efd, EPOLLOUT, ev); //将fd加入红黑树g_efd中,监听其写事件 } else if (len == 0) { close(ev->fd); /* ev-g_events 地址相减得到偏移元素位置 */ printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events); } else { close(ev->fd); printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno)); } return; } void senddata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg; int len; len = send(fd, ev->buf, ev->len, 0); //直接将数据 回写给客户端。未作处理 eventdel(g_efd, ev); //从红黑树g_efd中移除 if (len > 0) { printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf); eventset(ev, fd, recvdata, ev); //将该fd的 回调函数改为 recvdata eventadd(g_efd, EPOLLIN, ev); //从新添加到红黑树上, 设为监听读事件 } else { close(ev->fd); //关闭链接 printf("send[fd=%d] error %s\n", fd, strerror(errno)); } return ; } /*创建 socket, 初始化lfd */ void initlistensocket(int efd, short port) { struct sockaddr_in sin; int lfd = socket(AF_INET, SOCK_STREAM, 0); fcntl(lfd, F_SETFL, O_NONBLOCK); //将socket设为非阻塞 memset(&sin, 0, sizeof(sin)); //bzero(&sin, sizeof(sin)) sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); bind(lfd, (struct sockaddr *)&sin, sizeof(sin)); listen(lfd, 20); /* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg); */ eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]); /* void eventadd(int efd, int events, struct myevent_s *ev) */ eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); return ; } int main(int argc, char *argv[]) { unsigned short port = SERV_PORT; if (argc == 2) port = atoi(argv[1]); //使用用户指定端口.如未指定,用默认端口 g_efd = epoll_create(MAX_EVENTS+1); //创建红黑树,返回给全局 g_efd if (g_efd <= 0) printf("create efd in %s err %s\n", __func__, strerror(errno)); initlistensocket(g_efd, port); //初始化监听socket struct epoll_event events[MAX_EVENTS+1]; //保存已经满足就绪事件的文件描述符数组 printf("server running:port[%d]\n", port); int checkpos = 0, i; while (1) { /* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */ long now = time(NULL); //当前时间 for (i = 0; i < 100; i++, checkpos++) { //一次循环检测100个。 使用checkpos控制检测对象 if (checkpos == MAX_EVENTS) checkpos = 0; if (g_events[checkpos].status != 1) //不在红黑树 g_efd 上 continue; long duration = now - g_events[checkpos].last_active; //客户端不活跃的世间 if (duration >= 60) { close(g_events[checkpos].fd); //关闭与该客户端链接 printf("[fd=%d] timeout\n", g_events[checkpos].fd); eventdel(g_efd, &g_events[checkpos]); //将该客户端 从红黑树 g_efd移除 } } /*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/ int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000); if (nfd < 0) { printf("epoll_wait error, exit\n"); break; } for (i = 0; i < nfd; i++) { /*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/ struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr; if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) { //读就绪事件 ev->call_back(ev->fd, events[i].events, ev->arg); //lfd EPOLLIN } if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) { //写就绪事件 ev->call_back(ev->fd, events[i].events, ev->arg); } } } /* 退出前释放所有资源 */ return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。