epoll多路复用


—epoll I/O多路复用—

​ 提高服务器并发量的重要手段

1、epoll I/O多路复用技术

​ 只需要在进程启动时建立1个epoll对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait的效率就会非常高,因为调用epoll_wait时并没有向它传递这100万个连接,内核也不需要去遍历全部的连接。

2、epoll原理

执行epoll_create()时,创建了红黑树(rbr)和就绪双向链表(rdllist)(均存在于eventpoll结构体中);

二叉查找树(BST)–> AVL树 –> 红黑树

二叉查找树在某些特殊的情况下会退化成链表,AVL是严格平衡的BST(平衡因子不超过1),二叉平衡树的严格平衡策略以牺牲建立查找结构(插入,删除操作)的代价,换来了稳定的O(logN) 的查找时间复杂度。

执行epoll_ctl()时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据;

执行**epoll_wait()**时立刻返回准备就绪链表里的数据即可。

3、epoll的两种触发模式

  epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。

  • ET模式(边缘触发)只有数据到来才触发不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致epoll_wait返回;

  • LT 模式(水平触发,默认)只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回。

4、函数原型

4.1 epoll_create()

#include <sys / epoll.h>

int nfd1 = epoll_creat(max_size)
int nfd1 = epoll_create1(int flags);

4.2 epoll_ctl()

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

4.3 epoll_wait()

  int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

5、代码实例

5.1 client.cpp

#include <iostream>
using namespace std;
#include <cstring>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

int main()
{

    //client -----  socket -> connect -> send -> close
    
    //1.socket
    int c_fd = socket(AF_INET,SOCK_STREAM,0);
    if(c_fd == -1)
        cout << "socket():" <<strerror(errno) <<endl;
        
    //2.connect 
    
    sockaddr_in c_addr;
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = htons(5188);
    inet_pton(AF_INET,"127.0.0.1",&c_addr.sin_addr);
    
        int ret =connect(c_fd, (sockaddr *)&c_addr,sizeof(c_addr));
    if(ret == -1)
        cout<<"connect():" <<strerror(errno) <<endl;
    
    //3.send
    //char buf[4] = "YES";
    while(1)
    {
        char buf[1024];
        memset(buf,0,sizeof(buf));
        scanf("%s",buf);
        
        if ( send(c_fd,buf,sizeof(buf),0) ==-1)
            cout<<"send():" <<strerror(errno) <<endl;
    
        cout << buf <<endl;
        cout << "send successed! " <<endl;
    }
        
    //4.recv
    //char recvbuf[3];
    //memset(buf,0,sizeof(recvbuf));
    //if ( recv(c_fd,recvbuf,3,0) ==-1)
    //    cout<<"send():" <<strerror(errno) <<endl;
    
    //cout << "sizeof(recvbuf):"<<sizeof(recvbuf)<<endl;
    //cout << "back():"<<recvbuf <<endl
    //4.close
    close(c_fd);
    return 0;

}

5.2 server.cpp

#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
#include <sys/types.h>           
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <algorithm>
#include <signal.h>
int main()
{
    //server  socket->bind->listen->accept->recv->close
    

    signal(SIGPIPE,SIG_IGN);
    signal(SIGCHLD,SIG_IGN);
    
    //创建一个备用文件描述符
    int idlefd = open("/dev/null",O_RDONLY|O_CLOEXEC);
    //1.socket
    int listenfd = socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC,0);
    if(listenfd == -1)
    {
        cout<< "socket():" <<strerror(errno) <<endl;
        return -1;
    }



     //2.bind
     sockaddr_in bindaddr;    
     bindaddr.sin_family = AF_INET;
     bindaddr.sin_port = htons(5188);
     inet_pton(AF_INET,"0.0.0.0",&bindaddr.sin_addr);
     
     if ( bind(listenfd,(sockaddr *)&bindaddr,sizeof(bindaddr)) == -1)
     {
         cout<< "bind():" <<strerror(errno) <<endl;
         return -1;
     }


    //3.listen
    if ( listen(listenfd,SOMAXCONN)  == -1)
    {
        cout<< "listen():" <<strerror(errno) <<endl;
        return -1;
    
    }
    
    // epoll LT
    vector<int> clients;
    int epollfd = epoll_create1(EPOLL_CLOEXEC);
    if(epollfd == -1)
    {
        cout << "epoll_create1():" << strerror(errno) <<endl;
        return -1;
    }
    
    epoll_event event;
    event.data.fd = listenfd;
    event.events = EPOLLIN; //注册读事件

    if( epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&event)  == -1)
    {
        cout << "epoll_ctl():" << strerror(errno) <<endl;
        return -1;
    }
     
    vector<epoll_event> events(16);
    sockaddr_in peeraddr;
    socklen_t peerlen;
    int connfd;
    int nready;
    int cnt = 0;
    while(true)
    {
         //放在events的事件队列里
        nready = epoll_wait(epollfd,&*events.begin(),static_cast<int>(events.size()),-1);
    
        if(nready == -1)
        {
            if(errno == -1)
                continue;
            cout << "epoll_wait():" << strerror(errno)<<endl;
            return -1;
        }
        if(nready == 0)
            continue;
        if( (size_t)nready == events.size())
            events.resize(events.size()*2);
            
        for(int i = 0 ; i < nready ; ++i)
        {
        
            //监听套接字处于活跃状态
            if(events[i].data.fd == listenfd)
            {
                peerlen = sizeof(peeraddr);
                //非阻塞的
                connfd = accept4(listenfd,(sockaddr *)&peeraddr,&peerlen,SOCK_NONBLOCK | SOCK_CLOEXEC);
            
                if(connfd == -1)
                {
                    //如果文件描述符满了
                    if(errno == EMFILE)
                    {
                        close(idlefd);
                        idlefd = accept(listenfd,NULL,NULL);
                        close(idlefd);
                        idlefd = open("/dev/null",O_RDONLY|O_CLOEXEC);
                        continue;
                    }
                    else
                    {
                        cout << "accept4():"<<strerror(errno)<<endl;
                        return -1;                        
                    }
            

                }
                
                //cout << "ip= " <<inet_ntoa(peeraddr.sin_addr) << "port = "<<ntohs(peeraddr.sin_port) << endl;
                clients.push_back(std::move(connfd));
                
                //添加到epollfd文件描述符所在的事件队列里
                event.data.fd = connfd;
                event.events = EPOLLIN;
                epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&event);
            
            }
            else if(events[i].events & EPOLLIN) // & 都为1则1 否则0 //?? 若产生了很多事件
            {
                connfd =events[i].data.fd;
                if(connfd <0)
                    continue;
                char buf[1024] ;
                int ret = recv(connfd,buf,sizeof(buf),0);
                if(ret == -1)
                {
                    cout << "recv():" << strerror(errno)<<endl;
                    return -1;
                }
                if(ret == 0)
                {
                    cout << cnt << "--client close" <<endl;
                    close(connfd);
                    event = events[i];
                    epoll_ctl(epollfd,EPOLL_CTL_DEL,connfd,&event);
                    //将connfd移动到(remove)最后的位置,然后删除(erase)
                    clients.erase( remove(clients.begin(),clients.end(),connfd),clients.end()  );
                    continue;
                }
                else
                {
                    ++cnt;
                    cout << cnt<< "-- connection is success! " << endl ;
                }
    
            }
            
        }
    
    }

 

    return 0;

}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏