IO多路复用之epoll
int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
注意:通过man epoll_create可以看到Linux 2.6.8之后size这个参数就没用了,填大于0的值即可
int epoll_create1(int flags);
flags可以设置为0 或者EPOLL_CLOEXEC,为0时函数表现与epoll_create一致
相对于epoll_create,epoll_create1的允许你指定标志EPOLL_CLOEXEC,可以在某些情况下解决掉一些问题 即在fock后关闭子进程中无用文件描述符的问题
详见:https://blog.csdn.net/ChrisNiu1984/article/details/7050663
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数epfd是epoll_create()的返回值
第二个参数op表示动作,有如下选项:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数fd即需要监控的描述符
第四个参数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 */
};
epoll_event .events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。
第一个参数epfd是epoll_create()的返回值
第二个参数events是需要处理的事件
第三个参数maxevents是本次可以返回的最大事件数目,通常与预分配的events数组的大小是相等的
第四个参数timeout是超时时间(毫秒,0会立即返回,-1永久阻塞)
工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式(水平触发):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式(边缘触发):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
用epoll实现的TCP通信例子:
tcpserver.cpp
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <map>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
const int MAXEVENTS = 2048;
#define CHECK_RETURN_EXIT(ret) \
if (ret != 0) { \
exit(-1); \
}
class TCPServer {
public:
TCPServer() : sockFd(-1) {}
int Socket()
{
sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd == -1) {
perror("socket");
return -1;
}
return 0;
}
int Bind()
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
// addr.sin_port = htons("127.0.0.1");
addr.sin_port = inet_addr("127.0.0.1"); // 将ip地址转换成网络字节序
addr.sin_port = htons(8081); // 将端口转换成网络字节序
if (bind(sockFd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
perror("bind");
return -1;
}
return 0;
}
int Listen()
{
if (listen(sockFd, SOMAXCONN) != 0) {
perror("listen");
return -1;
}
return 0;
}
std::pair<int, struct sockaddr_in> Accept()
{
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
// int newSocket = accept(sockFd, NULL, NULL); 不需要 clientAddr可以填NULL
int newSocket = accept(sockFd, (struct sockaddr*)&clientAddr, (socklen_t*)&len);
if (newSocket == -1) {
perror("accept");
}
return std::make_pair(newSocket, clientAddr);
}
int GetSockFd() { return sockFd; }
// 设置被绑定的端口在断连后可以立即重用
void SetSocketReuseAddr()
{
bool bReuseaddr = true;
setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(bool));
}
private:
int sockFd;
};
int EpollAdd(int epfd, int fd)
{
struct epoll_event event;
bzero(&event, sizeof(event));
event.events = EPOLLIN; // 监控读事件
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
perror("epoll_add");
return -1;
}
return 0;
}
int EpollDel(int epfd, int fd)
{
struct epoll_event event;
bzero(&event, sizeof(event));
event.events = EPOLLIN;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event) == -1) {
perror("epoll_del");
return -1;
}
return 0;
}
int main() {
TCPServer tcpServer;
CHECK_RETURN_EXIT(tcpServer.Socket());
tcpServer.SetSocketReuseAddr();
CHECK_RETURN_EXIT(tcpServer.Bind());
CHECK_RETURN_EXIT(tcpServer.Listen());
int sockFd = tcpServer.GetSockFd();
std::map<int, struct sockaddr_in> socketMap{};
char buf[1024] = {};
int ret;
// 通过epoll监控socket描述符和输入缓冲区
int epfd = epoll_create(1);
EpollAdd(epfd, STDIN_FILENO);
EpollAdd(epfd, sockFd);
struct epoll_event evs[MAXEVENTS];
for (;;) {
bzero(evs, sizeof(evs));
int nret = epoll_wait(epfd, evs, MAXEVENTS, -1);
// epoll轮训到集合中至少有一个描述符可读
for (int i = 0; i < nret; i++) {
// 新连接到来
if (sockFd == evs[i].data.fd) {
std::pair<int, struct sockaddr_in> tmpNewSockPair(tcpServer.Accept());
if (tmpNewSockPair.first != -1) {
EpollAdd(epfd, tmpNewSockPair.first);
socketMap.insert(tmpNewSockPair);
continue;
}
}
// 老连接可读
auto itr = socketMap.find(evs[i].data.fd);
if (itr != socketMap.end()) {
memset(buf, 0, sizeof(buf));
ret = recv(itr->first, buf, sizeof(buf), 0);
if (ret == -1) {
std::cout << "recv from client ip:" << inet_ntoa(itr->second.sin_addr) << " port:" << ntohs(itr->second.sin_port) << " failed" << std::endl;
close(sockFd);
for (auto &cit : socketMap) {
close(cit.first);
}
return -1;
} else if (ret == 0) {
// 对端断开
std::cout << "recv from client ip:" << inet_ntoa(itr->second.sin_addr) << " port:" << ntohs(itr->second.sin_port) << " disconnect" << std::endl;
socketMap.erase(itr);
EpollDel(epfd, itr->first);
close(itr->first);
break;
}
std::cout << "recv from client ip:" << inet_ntoa(itr->second.sin_addr) << " port:" << ntohs(itr->second.sin_port) << " message: " << buf;
}
// 标准输入可读,将输入的信息发送给对端
if (STDIN_FILENO == evs[i].data.fd) {
memset(buf, 0, sizeof(buf));
read(STDIN_FILENO, buf, sizeof(buf));
for (auto &cit : socketMap) {
ret = send(cit.first, buf, sizeof(buf) - 1, 0);
}
}
}
}
close(sockFd);
for (auto &cit : socketMap) {
close(cit.first);
}
return 0;
}
tcpclient.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <sys/epoll.h>
#define ERR_EXIT(m) \
do { \
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
const int MAXEVENTS = 2048;
int EpollAdd(int epfd, int fd)
{
struct epoll_event event;
bzero(&event, sizeof(event));
event.events = EPOLLIN; // 监控读事件
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
perror("epoll_add");
return -1;
}
return 0;
}
int EpollDel(int epfd, int fd)
{
struct epoll_event event;
bzero(&event, sizeof(event));
event.events = EPOLLIN;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event) == -1) {
perror("epoll_del");
return -1;
}
return 0;
}
int do_service(int sockfd)
{
int epfd = epoll_create(1);
EpollAdd(epfd, STDIN_FILENO);
EpollAdd(epfd, sockfd);
struct epoll_event evs[MAXEVENTS];
char buf[1024] = {};
for (;;) {
bzero(evs, sizeof(evs));
int nret = epoll_wait(epfd, evs, MAXEVENTS, -1);
// epoll轮训到集合中至少有一个描述符可读
for (int i = 0; i < nret; i++) {
// sockfd可读
if (sockfd == evs[i].data.fd) {
// bzero(buf, sizeof(buf));
memset(buf, 0, sizeof(buf));
int ret = recv(sockfd, buf, sizeof(buf), 0);
if (ret == -1) {
std::cout << "recv from server failed" << std::endl;
return -1;
} else if (ret == 0) {
std::cout << "recv from server disconnect" << std::endl;
return -1;
}
std::cout << "recv from server message: " << buf << std::endl;
}
// 标准输入可读,将输入的信息发送给对端
if (STDIN_FILENO == evs[i].data.fd) {
memset(buf, 0, sizeof(buf));
read(STDIN_FILENO, buf, sizeof(buf));
int ret = send(sockfd, buf, sizeof(buf) - 1, 0);
}
}
}
}
int main(int argc, const char *argv[])
{
int peerfd = socket(PF_INET, SOCK_STREAM, 0);
if(peerfd == -1) {
ERR_EXIT("socket");
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8081);
socklen_t len = sizeof(addr);
if(connect(peerfd, (struct sockaddr*)&addr, len) == -1) {
ERR_EXIT("Connect");
}
do_service(peerfd);
close(peerfd);
return 0;
}
epoll源码解析 https://blog.csdn.net/weixin_43705457/article/details/104522820
select和epoll区别 详见 https://baijiahao.baidu.com/s?id=1641172494287388070&wfr=spider&for=pc