Linux网络编程之IO复用——Epoll

    科技2022-07-11  114

    目录

    epoll系统调用内核事件表epoll_wait函数LT和ET模式epoll编程实例epoll编程流程头文件tcp_socket.h头文件tcp_epoll.h主文件 tcp_epoll.cpp 参考文献

    epoll系统调用

    内核事件表

    跟select和poll不同,epoll使用一组函数来完成任务,而不是单个函数。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表,

    但是,epoll需要使用一个额外的文件描述符来唯一标识内核中的这个事件表。

    文件描述符由以下函数创建:

    #include <sys/epoll.h> int epoll_create(int size); //成功返回指向事件表的文件描述符

    下面的函数用来操作epoll的内核事件表:

    #include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); struct epoll_event { __uint32_t events; //epoll事件 epoll_data_t data; //用户数据 }; typedef union epoll_data { void* ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; fd参数是要操作得到文件描述符op参数指定操作类型 event参数指定事件,是epoll_event结构指针类型: events成员描述事件类型,与poll基本相同,但是宏前要加‘E’。 data成员用于存储用户数据,一般只使用fd。

    epoll_wait函数

    epoll_wait函数在一段超时时间内等待一组文件描述符上的事件。

    #include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); //成功返回就绪的文件描述符个数,失败返回-1 maxevents参数指定最多监听多少个事件且必须大于0

    epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向 的数组中。这个数组只用于输出epoll_wait检测到的就绪事件。

    LT和ET模式

    epoll对文件描述符的操作有两种:LT和ET。 LT相当于高效的poll,ET才是epoll的高效工作模式。

    对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait 时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应 用程序后,应用程序必须立即处理该事件,因为后续的epoll_ wait 调用将不再向应用程序通知这一事件。

    epoll编程实例

    epoll编程流程

    /* * 1.创建socket-->bind-->listen * 2.创建一个结构体数组来存储发生事件的fd并作为参数传入epoll_wait * 3.对发生事件的数组进行检测 * 4.循环: * ① 如果是新连接,放入内核时间表 * ③ 如果断开就从内核事件表删除 * ④ 都不是就收发数据 */

    头文件tcp_socket.h

    #include<iostream> #include<assert.h> #include<string.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> #include<unistd.h> #define BUFF_SIZE 128 using namespace std; class Socket { public: Socket() { sockfd_ = socket(PF_INET, SOCK_STREAM, 0); assert(sockfd_ >= 0); } int Get_socket() { return sockfd_; } ~Socket() { close(sockfd_); } protected: int sockfd_; }; class Socket_Ser :public Socket { public: Socket_Ser(const char* ip, int port = 6000, int backlog = 5) { struct sockaddr_in address; memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = inet_addr(ip); int ret = bind(sockfd_, (struct sockaddr*)&address, sizeof(address)); assert(ret != -1); ret = listen(sockfd_, backlog); assert(ret != -1); } int Accept() { struct sockaddr_in client; socklen_t len = sizeof(client); int connfd = accept(sockfd_, (struct sockaddr*)&client, &len); return connfd; } int Recv(int fd, char* buffer, int size) { int ret = recv(fd, buffer, size - 1, 0); if (ret == -1 || (strncmp(buffer, "end", 3) == 0)) { return -1; } } void Send(int fd, const char* buffer, int size) { send(fd, buffer, size, 0); } void Close_client(int fd) { close(fd); } }; class Socket_Cli :public Socket { public: Socket_Cli(const char* ip, int port = 6000) { struct sockaddr_in address; memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = inet_addr(ip); int ret = connect(sockfd_, (struct sockaddr*)&address, sizeof(address)); assert(ret != -1); } int Send(char* buffer, int size) { send(sockfd_, buffer, size, 0); } };

    头文件tcp_epoll.h

    #include"tcp_socket.h" #include<sys/epoll.h> #include <fcntl.h> #define MAXEVENTS 100 class Epoll { public: Epoll(const char* ip, int epoll_size = 5, int port = 6000, int backlog = 5) :ser(ip, port, backlog) { epollfd = epoll_create(epoll_size); assert(epollfd != -1); Insert(ser.Get_socket(), EPOLLIN); } int Epoll_Deal(int flag) { struct epoll_event events[MAXEVENTS]; int n = epoll_wait(epollfd, events, MAXEVENTS, -1); //将发生的事件存储在events中 if (n <= 0) { cout << "epoll errno!" << endl; return -1; } Deal_connect(n, events,flag); return n; } private: void Deal_connect(int n,struct epoll_event* events,int flag) { for (int i = 0; i < n; i++) { int fd = events[i].data.fd; if (fd == ser.Get_socket()) //服务器端新连接 { Accept(flag); } else //客户端 { if (events[i].events & EPOLLRDHUP) //断开连接 { Delete(events[i].data.fd); } else //收发数据 { char buffer[128] = { 0 }; ser.Recv(events[i].data.fd, buffer, 128); cout << "recv form " << events[i].data.fd << ":" << buffer << endl; } } } } void Accept(int flag) { int connfd = ser.Accept(); Insert(connfd, EPOLLIN | EPOLLRDHUP); if (flag == O_NONBLOCK) { SetUnblock(connfd); } } void Delete(int fd) { ser.Close_client(fd); epoll_ctl(epollfd, EPOLL_CTL_DEL, fd,NULL); } void Insert(int fd, int events) { struct epoll_event event_; event_.events = events; event_.data.fd = fd; int res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event_); } void SetUnblock(int fd) { int oldoption = fcntl(fd, F_GETFL); int newoption = oldoption | O_NONBLOCK; fcntl(fd, F_SETFL, newoption); } private: int epollfd; Socket_Ser ser; };

    主文件 tcp_epoll.cpp

    #include "tcp_epoll.h" int main(int argc,char* argv[]) { if (argc <= 1) { cout << "errno! please input again" << endl; } Epoll mypoll(argv[1]); while (1) { mypoll.Epoll_Deal(); } }

    参考文献

    [1]游双.Linux高性能服务器编程.机械工业出版社,2043.5.
    Processed: 0.016, SQL: 8