epoll工作流程
首先,需要调用epoll_create创建epoll;
此后我们就可以进行socket/bind/listen;
然后调用epoll_ctl进行注册;
接下来,就可以通过一个while(1)循环调用epoll_wait来等待事件的发生;
然后循环查看接收到的事件并进行处理;
1)如果事件是sever的socketfd我们就要进行accept,并且把接收到client的socketfd加入到要监听的事件中;
2)如果在监听过程中,需要修改操作方式(读/写),可以调用epoll_ctl来重新修改;
3)如果监听到某一个客户端关闭,那么我就需要再次调用epoll_ctl把它从epoll监听事件中删除。
epoll的结构体 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 */ }; #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/epoll.h> #include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <unistd.h> #define SERV_PORT 8802 int main() { int i,flag; int sockfd,clntfd,newfd; int epfd,nfds; ssize_t n; char buffer[1024]; int s = sizeof(struct sockaddr); struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; //定义epoll数据结构 struct epoll_event ev,events[20]; epfd = epoll_create(256); //创建socket,并初始化事件ev sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket error!\n"); return -1; } ev.data.fd = sockfd; ev.events = EPOLLIN|EPOLLET; //注册epoll事件 flag = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); if (flag < 0) { perror("epoll_ctl error!\n"); return -1; } bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl( INADDR_ANY ); flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr)); if (flag < 0) { perror("bind error!\n"); return -1; } printf("bind\n"); flag = listen(sockfd, 20); if (flag < 0) { perror("listen error!\n"); return -1; } printf("listen\n"); //开始循环 while (1) { //等待事件发生,返回请求数目 nfds = epoll_wait(epfd, events, 20, 500); //一次处理请求 for (i = 0; i < nfds; ++i) { if (events[i].data.fd == sockfd){ clntfd = accept(sockfd, (struct sockaddr*)&clnt_addr,(unsigned int*)&s); if (clntfd < 0) { perror("accept error"); continue; } printf("accept\n"); char *str = inet_ntoa(clnt_addr.sin_addr); printf("accepnt the client ip : %s\n",str); //设置文件标识符,设置操作属性:写操作 ev.data.fd = clntfd; ev.events = EPOLLOUT | EPOLLET; //向创建的的epoll进行注册写操作 epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev); } else if (events[i].events & EPOLLOUT) { printf("EPOLLOUT\n"); if ((newfd = events[i].data.fd) < 0) continue; bzero(buffer,sizeof(buffer)); strcpy(buffer,"welcome to myserver!\n"); flag = send(newfd, buffer, 1024, 0); if (flag < 0) { perror("send error"); continue; } //修改操作为读操作 ev.data.fd = clntfd; ev.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev); } else if (events[i].events & EPOLLIN) { printf("EPOLLIN\n"); bzero(buffer,sizeof(buffer)); if ((newfd = events[i].data.fd) < 0) continue; if ((n = read(newfd, buffer, 1024)) < 0) { if (errno == ECONNRESET){ close(newfd); events[i].data.fd = -1; printf("errno ECONRESET!\n"); } else { perror("readbuffer error!\n"); } } else if (n == 0) {//表示客户端已经关闭 close(newfd); events[i].data.fd = -1; printf("n为0\n"); } if (buffer[0] != '0') printf("have read: %s\n", buffer); } } } close(sockfd); return 0; }
引自:https://www.bbsmax.com/A/l1dymR3Gde/
优化
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/epoll.h> #include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <unistd.h> #define SERV_PORT 8802 #define MAX_EVENTS 20 int main() { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); //若成功则返回非负描述符,若失败则返回-1,第一个参数指明协议族(IPv4或IPv6等) if (listen_fd < 0) { //第二个参数指明套接字类型,字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM) perror("socket error!\n"); return -1; } int epfd = epoll_create(256); //参数会被忽略,但是要大于0, //若成功返回一个大于 0 的值,表示 epoll 实例;若返回 -1 表示出错 //针对监听的sockfd,创建epollevent struct epoll_event event; event.data.fd = listen_fd; event.events = EPOLLIN | EPOLLET; //注册epoll事件 int flag = epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event); //int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); if (flag < 0) { //成功返回0,出错返回-1 perror("epoll_ctl error!\n"); return -1; } if (bindAndListenFd(listen_fd) < 0) return -1; //定义epoll数据结构 struct epoll_event events[MAX_EVENTS]; //可以使用vector,参见muduo源码中的使用 while (1) { //等待事件发生,返回请求数目 int nfds = epoll_wait(epfd, events, MAX_EVENTS, 500); //maxevents: 返回的events的最大个数,如果最大个数大于实际触发的个数,则下次epoll_wait的时候仍然可以返回 //int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //成功返回的是一个大于 0 的数,表示事件的个数;返回 0 表示的是超时时间到;若出错返回 -1. for (int i = 0; i < nfds; ++i) { if (events[i].data.fd == listen_fd) { struct sockaddr_in client_addr; int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)); //若成功则为非负描述符,若出错则返回-1 if (client_fd < 0) { perror("accept error"); continue; } char *str = inet_ntoa(client_addr.sin_addr); printf("accept the client ip : %s\n",str); onRecvNewConnect(epfd, client_fd); } else if (events[i].events & EPOLLOUT) { int sockfd = events[i].data.fd; if (sockfd < 0) continue; onWriteFd(epfd, sockfd); } else if (events[i].events & EPOLLIN) { int sockfd = events[i].data.fd; if (sockfd < 0) continue; if (onReadFd(epfd, sockfd) < 0) { events[i].data.fd = -1; } } } } close(sockfd); return 0; } int bindAndListenFd(int sockfd) { ::fcntl(sockfd, F_SETFL, O_NONBLOCK); //设置非阻塞模式 struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); //void bzero(void *dest, size_t nbytes); serv_addr.sin_family = AF_INET; //类似void *memset(void *dest, int c, size_t len); serv_addr.sin_port = htons(SERV_PORT); //本地端口号转化为网络端口号 host to network short serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY代表本机所有的IP地址 host to network long int flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //成功返回0,出错返回-1 if (flag < 0) { perror("bind error!\n"); return -1; } flag = listen(sockfd, 20); //成功返回0,出错返回-1 if (flag < 0) { perror("listen error!\n"); return -1; } return 0; } void onRecvNewConnect(int epfd, int clientfd) { ::fcntl(sockfd, F_SETFL, O_NONBLOCK); //设置非阻塞模式 //设置文件标识符,设置操作属性:写操作 struct epoll_event ev_client; ev_client.data.fd = clintfd; ev_client.events = EPOLLOUT | EPOLLET; //向创建的的epoll进行注册写操作 epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev_client); } void onWriteFd(int epfd, int sockfd) { char buffer[1024]; bzero(buffer, sizeof(buffer)); strcpy(buffer, "welcome to myserver!\n"); int flag = send(sockfd, buffer, 1024, 0); if (flag < 0) { perror("send error"); return; } //修改操作为读操作 struct epoll_event ev_client; ev_client.data.fd = sockfd; ev_client.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev_client); } int onReadFd(int epfd, int sockfd) { char buffer[1024]; bzero(buffer, sizeof(buffer)); int n = read(sockfd, buffer, 1024); if (n < 0) { if (errno == ECONNRESET) { close(sockfd); printf("errno ECONRESET!\n"); return -1; } else { perror("readbuffer error!\n"); } } else if (n == 0) { //表示客户端已经关闭 close(sockfd); printf("n为0\n"); return -1; } if (buffer[0] != '0') printf("have read: %s\n", buffer); return 0; }