目录
epoll接口使用
思路
注意点
代码
封装epoll接口
epoll.sever.hpp
运行结果
epoll接口使用
接口+epoll原理介绍 -- epoll接口介绍,epoll模型介绍+原理,接口和模型的关系,epoll优点(和select/poll进行对比)-CSDN博客
思路
我们可以先将系统提供的epoll简单封装一下,更方便我们使用
- 比如在对象被创建时,就创建epoll模型 ; 在对象被销毁时,自动关闭模型对应文件
- 每次在操作红黑树结点时,不需要我们手动传入struct epoll_event结构,而是在类内创建,只 ; 并且细分操作类型,在执行删除时就不用创建了
- 加入打印日志
虽然epoll原理上和select/poll截然不同,但在代码逻辑上是类似的
- 监听套接字创建好后,就将它加入红黑树
- 每次循环检测并获取[已就绪的文件及其事件]
- 根据fd,将读事件分为两类 -- 新连接到来,读取数据就绪,这样可以将事件派发给相应的处理器
- 处理还是老一套,和select,epoll是一样的,直接获取新连接/读取数据即可
注意点
将[通信用套接字文件]从红黑树移除时,需要调用epoll_ctl
- 但注意,在调用前要保证该文件的fd是有效的
- 也就是,先删除,再关闭文件
代码
封装epoll接口
#include <sys/epoll.h> #include <cstring> #include <errno.h> #include "Log.hpp" static const int def_epoll_size = 128; struct no_copy { no_copy() {} ~no_copy() {} no_copy(const no_copy &t) = delete; no_copy operator=(const no_copy &t) = delete; }; // 我们要保证这个对象唯一,因为没有必要存在多个[自定义操作epoll模型的方法],可以直接禁止拷贝/赋值,也可以继承一个无法拷贝/赋值的类 class MY_EPOLL : public no_copy { public: MY_EPOLL(int timeout) { sock_ = epoll_create(def_epoll_size); if (sock_ < 0) { lg(ERROR, "epoll_create error,%s\n", strerror(errno)); } else { lg(DEBUG, "epoll_create success,fd: %d\n", sock_); } timeout_ = timeout; } ~MY_EPOLL() { if (sock_ >= 0) { // 保证在fd有效的情况下,关闭 close(sock_); } } int wait(struct epoll_event *events, int max) { return epoll_wait(sock_, events, max, timeout_); } int ctl(int op, int fd, uint32_t event) // 这里我们设置成直接传入事件名,然后在内部构建epoll_event结构 { int n = 0; if (op == EPOLL_CTL_DEL) { n = epoll_ctl(sock_, op, fd, nullptr); // 删除后,无论什么事件都无所谓了 if (n == -1) { lg(ERROR, "EPOLL_CTL_DEL error\n"); } } else { struct epoll_event t; t.events = event; t.data.fd = fd; // 方便用户层拿到时,可以知道是哪个fd就绪 n = epoll_ctl(sock_, op, fd, &t); if (n == -1) { lg(ERROR, "EPOLL_CTL_ADD/MOD error,%s\n", strerror(errno)); } } return n; } private: int timeout_; int sock_; };
epoll.sever.hpp
#include "Log.hpp" #include "socket.hpp" #include "myepoll.hpp" #include <memory> static const int def_port = 8080; static const int def_timeout = 1000; static const int def_size = 64; class epoll_server { public: epoll_server() : p_listen_socket_(new MY_SOCKET), p_epoll_(new MY_EPOLL(def_timeout)) {} ~epoll_server() { } void start() { init(); // 添加监听套接字 int fd = p_listen_socket_->get_fd(); p_epoll_->ctl(EPOLL_CTL_ADD, fd, EPOLLIN); while (true) { struct epoll_event events[def_size]; int ret = p_epoll_->wait(events, def_size); if (ret > 0) // 有事件就绪 { handle(ret, events); } else if (ret == 0) // 超时 { continue; } else { perror("epoll_wait"); break; } } } private: void init() { p_listen_socket_->Socket(); p_listen_socket_->Bind(def_port); p_listen_socket_->Listen(); } void handle(int n, struct epoll_event *events) { int listen_sock_ = p_listen_socket_->get_fd(); for (int i = 0; i < n; ++i) // 遍历有效事件 { int fd = events[i].data.fd; // 这里我们就可以知道是哪个文件上的事件就绪了 if (events[i].events & EPOLLIN) // 有读事件就绪 { if (fd == listen_sock_) // 获取新连接 { accepter(fd); } else // 读事件 { receiver(fd); } } } } void receiver(int fd) { char in_buff[1024]; int n = read(fd, in_buff, sizeof(in_buff) - 1); if (n > 0) { in_buff[n - 1] = 0; std::cout << "get message: " << in_buff << std::endl; } else if (n == 0) // 客户端关闭连接 { lg(INFO, "%d quit", fd); p_epoll_->ctl(EPOLL_CTL_DEL, fd, 0); close(fd); } else { lg(ERROR, "fd: %d ,read error"); p_epoll_->ctl(EPOLL_CTL_DEL, fd, 0); close(fd); } } void accepter(int fd) { std::string clientip; uint16_t clientport; int sock = p_listen_socket_->Accept(clientip, clientport); if (sock == -1) { return; } else // 把新文件上的读事件加入红黑树 { p_epoll_->ctl(EPOLL_CTL_ADD, sock, EPOLLIN); lg(INFO, "get a new link, client info@ %s:%d", clientip.c_str(), clientport); } } private: std::shared_ptr<MY_SOCKET> p_listen_socket_; std::shared_ptr<MY_EPOLL> p_epoll_; };
运行结果
- 服务器可以成功读取到客户端发来的数据