epoll工作流程
首先,需要調(diào)用epoll_create創(chuàng)建epoll;
此后我們就可以進行socket/bind/listen;
然后調(diào)用epoll_ctl進行注冊;
接下來,就可以通過一個while(1)循環(huán)調(diào)用epoll_wait來等待事件的發(fā)生;
然后循環(huán)查看接收到的事件并進行處理;
1)如果事件是sever的socketfd我們就要進行accept,并且把接收到client的socketfd加入到要監(jiān)聽的事件中;
2)如果在監(jiān)聽過程中,需要修改操作方式(讀/寫),可以調(diào)用epoll_ctl來重新修改;
3)如果監(jiān)聽到某一個客戶端關(guān)閉,那么我就需要再次調(diào)用epoll_ctl把它從epoll監(jiān)聽事件中刪除。

image.png
epoll的結(jié)構(gòu)體
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數(shù)據(jù)結(jié)構(gòu)
struct epoll_event ev,events[20];
epfd = epoll_create(256);
//創(chuàng)建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");
//開始循環(huán)
while (1) {
//等待事件發(fā)生,返回請求數(shù)目
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);
//設(shè)置文件標識符,設(shè)置操作屬性:寫操作
ev.data.fd = clntfd;
ev.events = EPOLLOUT | EPOLLET;
//向創(chuàng)建的的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) {//表示客戶端已經(jīng)關(guān)閉
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/
優(yōu)化
#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,第一個參數(shù)指明協(xié)議族(IPv4或IPv6等)
if (listen_fd < 0) { //第二個參數(shù)指明套接字類型,字節(jié)流套接字(SOCK_STREAM)和數(shù)據(jù)報套接字(SOCK_DGRAM)
perror("socket error!\n");
return -1;
}
int epfd = epoll_create(256); //參數(shù)會被忽略,但是要大于0,
//若成功返回一個大于 0 的值,表示 epoll 實例;若返回 -1 表示出錯
//針對監(jiān)聽的sockfd,創(chuàng)建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數(shù)據(jù)結(jié)構(gòu)
struct epoll_event events[MAX_EVENTS]; //可以使用vector,參見muduo源碼中的使用
while (1) {
//等待事件發(fā)生,返回請求數(shù)目
int nfds = epoll_wait(epfd, events, MAX_EVENTS, 500); //maxevents: 返回的events的最大個數(shù),如果最大個數(shù)大于實際觸發(fā)的個數(shù),則下次epoll_wait的時候仍然可以返回
//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//成功返回的是一個大于 0 的數(shù),表示事件的個數(shù);返回 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); //設(shè)置非阻塞模式
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); //本地端口號轉(zhuǎn)化為網(wǎng)絡(luò)端口號 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); //設(shè)置非阻塞模式
//設(shè)置文件標識符,設(shè)置操作屬性:寫操作
struct epoll_event ev_client;
ev_client.data.fd = clintfd;
ev_client.events = EPOLLOUT | EPOLLET;
//向創(chuàng)建的的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) { //表示客戶端已經(jīng)關(guān)閉
close(sockfd);
printf("n為0\n");
return -1;
}
if (buffer[0] != '0')
printf("have read: %s\n", buffer);
return 0;
}