目录
1 epoll函数
1.1 epoll_create()
#include <sys/epoll.h>
int epoll_create(int size);
- size:无意义,必须大于0
- 返回值:失败返回-1;成功返回操作epoll实例的文件描述符
当某一进程调用epoll_create函数时,Linux内核会创建一个eventpoll结构体:
struct eventpoll{
/*红黑树的根节点,存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
};
1.2 epoll_ctl()
int epoll_ctl(epfd, int op,int fd,struct epoll_event *event);
int ret = epoll_wait(epfd, ...);
epfd:epoll实例对应的文件描述符
op:要进行的操作
EPOLL_CTL_ADD:添加
EPOLL_CTL_MOD:修改
EPOLL_CTL_DEL:删除fd:要监控的文件描述符
event:监控文件描述符发生的事件
struct epoll_event {
uint32_t events; //Epoll events
epoll_data_t data; //User data variable
};
/*
常见的epoll检测事件:
EPOLLIN
EPOLLOUT
EPOLLERR
*/
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
1.3 epoll_wait()
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- epfd:epoll实例对应的文件描述符
- events:传出参数,保存了发生变化的文件描述符的信息
- maxevents:第二个参数结构体数组的大小
- timeout:超时时间(毫秒)
- 返回值:成功返回就绪文件描述符个数,失败返回-1
2 epoll工作模式
2.1 LT模式(水平触发)
LT是默认的工作方式,并且同时支持block和no-block socket。在这种模式下,只要这个文件描述符还有数据可读,每次epoll_wait都会返回它的事件,提醒用户程序去操作。
2.1.1 程序示例
//水平触发
#include <sys/epoll.h>
#include <iostream>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define MAX_CON 1024
#define SERVER_PORT 9998
using namespace std;
int main() {
int lfd, cfd;
int epfd;
char recvBuf[5];
sockaddr_in lskt, cskt;
sockaddr_in Clientaddr[MAX_CON];
epoll_event epev, epevs[MAX_CON];
//创建服务端套接字
if((lfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(-1);
}
//初始化服务端地址结构信息
lskt.sin_family = AF_INET;
lskt.sin_port = htons(SERVER_PORT);
lskt.sin_addr.s_addr = htonl(INADDR_ANY);
//设置端口复用
socklen_t optval = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
//绑定服务端地址信息
if(bind(lfd, (sockaddr *)&lskt, sizeof(lskt)) == -1) {
perror("bind");
exit(-1);
}
//监听socket上的连接
if(listen(lfd, MAX_CON) == -1) {
perror("listen");
exit(-1);
}
//提示服务端初始化完毕
char ServerIP[16];
inet_ntop(AF_INET, &lskt.sin_addr.s_addr, ServerIP, 16);
cout << "init success" << endl;
cout << "host ip: " << ServerIP << ", port: " << ntohs(lskt.sin_port) << endl;
//调用epoll_create()创建一个epoll实例
epfd = epoll_create(100);
//将监听的文件描述符相关的检测信息加入到epoll实例中
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
while(1) {
int rfd = 0;
if((rfd = epoll_wait(epfd, epevs, sizeof(epevs), -1)) == -1) {
perror("epoll_wait");
exit(-1);
}
cout << "epoll_wait call" <<endl;
for(int i = 0; i < rfd ; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) { //说明有新连接请求
//客户端连接
socklen_t len = sizeof(cskt);
if((cfd = accept(lfd, (sockaddr *)&cskt, &len)) == -1) {
perror("accept");
exit(-1);
}
//输出连接进来的客户端信息
char ClientIP[16];
inet_ntop(AF_INET, &cskt.sin_addr.s_addr, ClientIP, 16);
cout << "client ip : " << ClientIP << " , port : " << ntohs(cskt.sin_port) << endl;
Clientaddr[cfd] = cskt;
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
}
else {//对于除监听描述符之外的数据,若有返回则说明有数据读取
//通信
if(epevs[i].events & EPOLLOUT) { //若监听很多事件,则针对不同事件需要进行不同的处理
continue;
}
int buflen = read(curfd, recvBuf, sizeof(recvBuf));
if(buflen == -1) {
perror("read");
exit(-1);
}
else if(buflen == 0) {
cout << "(SERVER) client closed..." << "(client port : " << ntohs(Clientaddr[curfd].sin_port) << ")"<< endl;
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
}
else if(buflen > 0) {
cout << "(SERVER) recv client data : ";
for(int j = 0; j < buflen; j++) cout << recvBuf[j];
cout << " (from port : " << ntohs(Clientaddr[curfd].sin_port) << ")"<< endl;
int j = 0;
for(; j < buflen; j++) {
recvBuf[j] = toupper(recvBuf[j]);
}
recvBuf[j] = '\0';
write(curfd, recvBuf, strlen(recvBuf));
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
2.1.2 运行结果
2.2 ET模式(边沿触发)
ET(edge - triggered)是高速工作模式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll通知用户程序。然后它会假设用户程序已知文件描述符已就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 I/O 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作使处理多个文件描述符的任务停滞。
ET模式(边沿触发):只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致epoll_wait返回;
LT 模式(水平触发,默认):只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回。
2.2.1 程序示例
//边沿触发
#include <sys/epoll.h>
#include <iostream>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_CON 1024
#define SERVER_PORT 9998
using namespace std;
int main() {
int lfd, cfd;
sockaddr_in lskt, cskt;
sockaddr_in Clientaddr[MAX_CON];
char recvBuf[5];
epoll_event epev;
epoll_event epevs[MAX_CON];
//创建服务端套接字
if((lfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(-1);
}
//初始化服务端地址结构信息
lskt.sin_family = AF_INET;
lskt.sin_port = htons(SERVER_PORT);
lskt.sin_addr.s_addr = htonl(INADDR_ANY);
//设置端口复用
socklen_t optval = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
//绑定服务端地址信息
if(bind(lfd, (sockaddr *)&lskt, sizeof(lskt)) == -1) {
perror("bind");
exit(-1);
}
//监听socket上的连接
if(listen(lfd, MAX_CON) == -1) {
perror("listen");
exit(-1);
}
//提示服务端初始化完毕
char ServerIP[16];
inet_ntop(AF_INET, &lskt.sin_addr.s_addr, ServerIP, 16);
cout << "init success" << endl;
cout << "host ip: " << ServerIP << ", port: " << ntohs(lskt.sin_port) << endl;
//调用epoll_create()创建一个epoll实例
int epfd = epoll_create(100);
//将监听的文件描述符相关的检测信息加入到epoll实例中
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
while(1) {
int ret = 0;
if((ret = epoll_wait(epfd, epevs, sizeof(epevs), -1)) == -1) {
perror("epoll_wait");
exit(-1);
}
cout << "epoll_wait call" <<endl;
for(int i = 0; i < ret ; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
//客户端连接
socklen_t len = sizeof(cskt);
if((cfd = accept(lfd, (sockaddr *)&cskt, &len)) == -1) {
perror("accept");
exit(-1);
}
//设置cfd属性非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL);
Clientaddr[cfd] = cskt;
//输出连接进来的客户端信息
char ClientIP[16];
inet_ntop(AF_INET, &cskt.sin_addr.s_addr, ClientIP, 16);
cout << "client ip : " << ClientIP << " , port : " << ntohs(cskt.sin_port) << endl;
epev.events = EPOLLIN | EPOLLET; //设置边沿触发
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
}
else {
//通信
if(epevs[i].events & EPOLLOUT) { //若监听很多事件,则针对不同事件需要进行不同的处理
continue;
}
//循环读出缓冲区所有数据
// memset(recvBuf, 0, sizeof(recvBuf));
int buflen = 0;
while((buflen = read(curfd, recvBuf, sizeof(recvBuf))) > 0) {
cout << "(SERVER) recv client data : ";
for(int j = 0; j < buflen; j++) cout << recvBuf[j];
cout << " (from port : " << ntohs(Clientaddr[curfd].sin_port) << ")"<< endl;
int j = 0;
for(; j < buflen; j++) {
recvBuf[j] = toupper(recvBuf[j]);
}
recvBuf[j] = '\0';
write(curfd, recvBuf, strlen(recvBuf));
}
if(buflen == 0) {
cout << "(SERVER) client closed..." << "(client port : " << ntohs(Clientaddr[curfd].sin_port) << ")"<< endl;
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
}
if(buflen == -1) {
if(errno == EAGAIN) {
cout << "client data over..." << endl;
}
else {
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
2.2.2 运行结果
版权声明:本文为weixin_42323999原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。