处理并发之一:LINUX Epoll机制介绍

发表于 5年以前  | 总阅读数:2761 次

Epoll可是当前在Linux下开发大规模并发网络程序的热门人选,Epoll 在Linux2.6内核中正式引入,和select相似,其实都I/O多路复用技术而已,并没有什么神秘的。

其实在Linux下设计并发网络程序,向来不缺少方法,比如典型的Apache模型(Process Per Connection,简称PPC),TPC(Thread Per Connection)模型,以及select模型和poll模型,那为何还要再引入Epoll这个东东呢?那还是有得说说的…

2. 常用模型的缺点

如果不摆出来其他模型的缺点,怎么能对比出Epoll的优点呢。

2.1 PPC/TPC模型

这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我。只是PPC是为它开了一个进程,而TPC开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程/线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

2.2 select模型

  1. 最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此Select模型的最大并发数就被相应限制了。自己改改这个FD_SETSIZE?想法虽好,可是先看看下面吧…

  2. 效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降,把FD_SETSIZE改大的后果就是,大家都慢慢来,什么?都超时了??!!

  3. 内核/用户空间 内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

2.3 poll模型

基本上效率和select是相同的,select缺点的2和3它都没有改掉。

3.Epoll的提升

把其他模型逐个批判了一下,再来看看Epoll的改进之处吧,其实把select的缺点反过来那就是Epoll的优点了。

3.1. Epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。

3.2. 效率提升,Epoll最大的优点就在于它只管你"活跃"的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

3.3. 内存拷贝,Epoll在这点上使用了"共享内存",这个内存拷贝也省略了。

4. Epoll为什么高效

Epoll的高效和其数据结构的设计是密不可分的,这个下面就会提到。

首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了快去处理,而应用程序必须轮询所有的FD集合,测试每个FD是否有事件发生,并处理事件;代码像下面这样:

int res = select(maxfd+1, &readfds, NULL, NULL, 120);
    if(res > 0)

    {
        for(int i = 0; i < MAX_CONNECTION; i++)
        {
            if(FD_ISSET(allConnection[i],&readfds))
            {
                handleEvent(allConnection[i]);
            }
        }
    }
    // if(res == 0) handle timeout, res < 0 handle error

Epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD集合。

intres = epoll_wait(epfd, events, 20, 120);

    for(int i = 0; i < res;i++)
    {
        handleEvent(events[n]);
    }

5. Epoll关键数据结构

前面提到Epoll速度快和其数据结构密不可分,其关键数据结构就是:

struct epoll_event {

        __uint32_t events;      // Epoll events

        epoll_data_t data;      // User datavariable

    };

    typedef union epoll_data {

        void *ptr;

       int fd;

        __uint32_t u32;

        __uint64_t u64;

    } epoll_data_t;

结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件.
其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据.
例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为:

  • EPOLLIN :表示对应的文件描述符可以读;
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET:表示对应的文件描述符有事件发生;

ET和LT模式
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
ET和LT的区别在于LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.

6. 使用Epoll

既然Epoll相比select这么好,那么用起来如何呢?会不会很繁琐啊…先看看下面的三个函数吧,就知道Epoll的易用了。

int epoll_create(int size);

生成一个Epoll专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个Epoll fd上能关注的最大socket fd数,大小自定,只要内存足够。

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

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

  • EPOLL_CTL_ADD:注册新的fd到epfd中;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事

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

等待I/O事件的发生;参数说明:

  • epfd:由epoll_create() 生成的Epoll专用的文件描述符;

  • epoll_event:用于回传代处理事件的数组;

  • maxevents:每次能处理的事件数;

  • timeout:等待I/O事件发生的超时值;

  • 返回发生事件数。

测试程序

首先对服务端和客户端做下说明:
我想实现的是客户端和服务端并发的程序,客户端通过配置并发数,说明有多少个用户去连接服务端。
客户端会发送消息:"Client: i send message Hello Server!",其中i表示哪一个客户端;收到消息:"Recv Server Msg Content:%s\n"。
例如:
发送:Client: 1 send message "Hello Server!"
接收:Recv Derver Msg Content:Hello, client fd: 6
服务端收到后给客户端回复消息:"Hello, client fd: i",其中i表示服务端接收的fd,用户区别是哪一个客户端。接收客户端消息:"Terminal Received Msg Content:%s\n"
例如:
发送:Hello, client fd: 6
接收:Terminal Received Msg Content:Client: 1 send message "Hello Server!"
备注:这里在接收到消息后,直接打印出消息,如果需要对消息进行处理(如果消息处理比较占用时间,不能立即返回,可以将该消息放入一个队列中,然后开启一个线程从队列中取消息进行处理,这样的话不会因为消息处理而阻塞epoll)。libenent好像对这种有2中处理方式,一个就是回调,要求回调函数,不占用太多的时间,基本能立即返回,另一种好像也是一个队列实现的,这个还需要研究。
服务端代码说明:
服务端在绑定监听后,开启了一个线程,用于负责接收客户端连接,加入到epoll中,这样只要accept到客户端的连接,就将其add EPOLLIN到epoll中,然后进入循环调用epoll_wait,监听到读事件,接收数据,并将事件修改为EPOLLOUT;反之监听到写事件,发送数据,并将事件修改为EPOLLIN。
服务器代码:

//cepollserver.h  
    #ifndef  C_EPOLL_SERVER_H  
    #define  C_EPOLL_SERVER_H  

    #include <sys/epoll.h>  
    #include <sys/socket.h>  
    #include <netinet/in.h>  
    #include <fcntl.h>  
    #include <arpa/inet.h>  
    #include <stdio.h>  
    #include <stdlib.h>  
    #include <iostream>  
    #include <pthread.h>  

    #define _MAX_SOCKFD_COUNT 65535  

    class CEpollServer  
    {  
            public:  
                    CEpollServer();  
                    ~CEpollServer();  

                    bool InitServer(const char* chIp, int iPort);  
                    void Listen();  
                    static void ListenThread( void* lpVoid );  
                    void Run();  

            private:  
                    int        m_iEpollFd;  
                    int        m_isock;  
                    pthread_t       m_ListenThreadId;// 监听线程句柄  

    };  

    #endif  

       #include "cepollserver.h"  

    using namespace std;  

    CEpollServer::CEpollServer()  
    {  
    }  

    CEpollServer::~CEpollServer()  
    {  
        close(m_isock);  
    }  

    bool CEpollServer::InitServer(const char* pIp, int iPort)  
    {  
        m_iEpollFd = epoll_create(_MAX_SOCKFD_COUNT);  

        //设置非阻塞模式  
        int opts = O_NONBLOCK;  
        if(fcntl(m_iEpollFd,F_SETFL,opts)<0)  
        {  
            printf("设置非阻塞模式失败!\n");  
            return false;  
        }  

        m_isock = socket(AF_INET,SOCK_STREAM,0);  
        if ( 0 > m_isock )  
        {  
            printf("socket error!\n");  
            return false;  
      }  
        
      sockaddr_in listen_addr;  
          listen_addr.sin_family=AF_INET;  
          listen_addr.sin_port=htons ( iPort );  
          listen_addr.sin_addr.s_addr=htonl(INADDR_ANY);  
          listen_addr.sin_addr.s_addr=inet_addr(pIp);  
        
          int ireuseadd_on = 1;//支持端口复用  
          setsockopt(m_isock, SOL_SOCKET, SO_REUSEADDR, &ireuseadd_on, sizeof(ireuseadd_on) );  
        
          if ( bind ( m_isock, ( sockaddr * ) &listen_addr,sizeof ( listen_addr ) ) !=0 )  
          {  
              printf("bind error\n");  
              return false;  
          }  
        
          if ( listen ( m_isock, 20) <0 )  
          {  
              printf("listen error!\n");  
              return false;  
          }  
          else  
          {  
              printf("服务端监听中...\n");  
          }  
        
          // 监听线程,此线程负责接收客户端连接,加入到epoll中  
          if ( pthread_create( &m_ListenThreadId, 0, ( void * ( * ) ( void * ) ) ListenThread, this ) != 0 )  
          {  
              printf("Server 监听线程创建失败!!!");  
              return false;  
          }  
      }  
      // 监听线程  
      void CEpollServer::ListenThread( void* lpVoid )  
      {  
          CEpollServer *pTerminalServer = (CEpollServer*)lpVoid;  
          sockaddr_in remote_addr;  
          int len = sizeof (remote_addr);  
          while ( true )  
          {  
              int client_socket = accept (pTerminalServer->m_isock, ( sockaddr * ) &remote_addr,(socklen_t*)&len );  
              if ( client_socket < 0 )  
              {  
                  printf("Server Accept失败!, client_socket: %d\n", client_socket);  
                  continue;  
              }  
              else  
              {  
                  struct epoll_event    ev;  
                  ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;  
                  ev.data.fd = client_socket;     //记录socket句柄  
                  epoll_ctl(pTerminalServer->m_iEpollFd, EPOLL_CTL_ADD, client_socket, &ev);  
              }  
          }  
      }  
        
      void CEpollServer::Run()  
      {  
          while ( true )  
          {  
              struct epoll_event    events[_MAX_SOCKFD_COUNT];  
              int nfds = epoll_wait( m_iEpollFd, events,  _MAX_SOCKFD_COUNT, -1 );  
              for (int i = 0; i < nfds; i++)  
              {  
                  int client_socket = events[i].data.fd;  
                  char buffer[1024];//每次收发的字节数小于1024字节  
                  memset(buffer, 0, 1024);  
                  if (events[i].events & EPOLLIN)//监听到读事件,接收数据  
                  {  
                      int rev_size = recv(events[i].data.fd,buffer, 1024,0);  
                      if( rev_size <= 0 )  
                      {  
                          cout << "recv error: recv size: " << rev_size << endl;  
                          struct epoll_event event_del;  
                          event_del.data.fd = events[i].data.fd;  
                          event_del.events = 0;  
                          epoll_ctl(m_iEpollFd, EPOLL_CTL_DEL, event_del.data.fd, &event_del);  
                      }  
                      else  
                      {  
                          printf("Terminal Received Msg Content:%s\n",buffer);  
                          struct epoll_event    ev;  
                          ev.events = EPOLLOUT | EPOLLERR | EPOLLHUP;  
                          ev.data.fd = client_socket;     //记录socket句柄  
                          epoll_ctl(m_iEpollFd, EPOLL_CTL_MOD, client_socket, &ev);  
                      }  
                  }  
      else if(events[i].events & EPOLLOUT)//监听到写事件,发送数据  
                  {  
                      char sendbuff[1024];  
                      sprintf(sendbuff, "Hello, client fd: %d\n", client_socket);  
                      int sendsize = send(client_socket, sendbuff, strlen(sendbuff)+1, MSG_NOSIGNAL);  
                      if(sendsize <= 0)  
                      {  
                          struct epoll_event event_del;  
                          event_del.data.fd = events[i].data.fd;  
                          event_del.events = 0;  
                          epoll_ctl(m_iEpollFd, EPOLL_CTL_DEL, event_del.data.fd, &event_del);  
                      }  
                      else  
                      {  
                          printf("Server reply msg ok! buffer: %s\n", sendbuff);  
                          struct epoll_event    ev;  
                          ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;  
                          ev.data.fd = client_socket;     //记录socket句柄  
                          epoll_ctl(m_iEpollFd, EPOLL_CTL_MOD, client_socket, &ev);  
                      }  
                  }  
                  else  
                  {  
                      cout << "EPOLL ERROR\n" <<endl;  
                      epoll_ctl(m_iEpollFd, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);  
                  }  
              }  
          }  
      }  

客户端代码:
说明:测试是两个并发进行测试,每一个客户端都是一个长连接。代码中在连接服务器(ConnectToServer)时将用户ID和socketid关联起来。用户ID和socketid是一一对应的关系。

    #ifndef _DEFINE_EPOLLCLIENT_H_  
        #define _DEFINE_EPOLLCLIENT_H_  
        #define _MAX_SOCKFD_COUNT 65535  

        #include<iostream>  
        #include <sys/epoll.h>  
        #include <sys/socket.h>  
        #include <netinet/in.h>  
        #include <fcntl.h>  
        #include <arpa/inet.h>  
        #include <errno.h>  
        #include <sys/ioctl.h>  
        #include <sys/time.h>  
        #include <string>  

        using namespace std;  

        /** 
         * @brief 用户状态 
         */  
        typedef enum _EPOLL_USER_STATUS_EM  
        {  
                FREE = 0,  
                CONNECT_OK = 1,//连接成功  
                SEND_OK = 2,//发送成功  
                RECV_OK = 3,//接收成功  
        }EPOLL_USER_STATUS_EM;  

        /*@brief 
         *@CEpollClient class 用户状态结构体 
         */  
        struct UserStatus  
        {  
                EPOLL_USER_STATUS_EM iUserStatus;//用户状态  
                int iSockFd;//用户状态关联的socketfd  
                char cSendbuff[1024];//发送的数据内容  
                int iBuffLen;//发送数据内容的长度  
                unsigned int uEpollEvents;//Epoll events  
        };  

        class CEpollClient  
        {  
                public:  

                        /** 
                         * @brief 
                         * 函数名:CEpollClient 
                         * 描述:构造函数 
                         * @param [in] iUserCount  
                         * @param [in] pIP IP地址 
                         * @param [in] iPort 端口号 
                         * @return 无返回 
                         */  
                        CEpollClient(int iUserCount, const char* pIP, int iPort);  

        /** 
                         * @brief 
                         * 函数名:CEpollClient 
                         * 描述:析构函数 
                         * @return 无返回 
                         */  
                        ~CEpollClient();  

                        /** 
                         * @brief 
                         * 函数名:RunFun 
                         * 描述:对外提供的接口,运行epoll类 
                         * @return 无返回值 
                         */  
                        int RunFun();  

                private:  

                        /** 
                         * @brief 
                         * 函数名:ConnectToServer 
                         * 描述:连接到服务器 
                         * @param [in] iUserId 用户ID 
                         * @param [in] pServerIp 连接的服务器IP 
                         * @param [in] uServerPort 连接的服务器端口号 
                         * @return 成功返回socketfd,失败返回的socketfd为-1 
                         */  
                        int ConnectToServer(int iUserId,const char *pServerIp,unsigned short uServerPort);  

        /** 
                         * @brief 
                         * 函数名:SendToServerData 
                         * 描述:给服务器发送用户(iUserId)的数据 
                         * @param [in] iUserId 用户ID 
                         * @return 成功返回发送数据长度 
                         */  
                        int SendToServerData(int iUserId);  

                        /** 
                         * @brief 
                         * 函数名:RecvFromServer 
                         * 描述:接收用户回复消息 
                         * @param [in] iUserId 用户ID 
                         * @param [in] pRecvBuff 接收的数据内容 
                         * @param [in] iBuffLen 接收的数据长度 
                         * @return 成功返回接收的数据长度,失败返回长度为-1 
                         */  
                        int RecvFromServer(int iUserid,char *pRecvBuff,int iBuffLen);  

                        /** 
                         * @brief 
                         * 函数名:CloseUser 
                         * 描述:关闭用户 
                         * @param [in] iUserId 用户ID 
                         * @return 成功返回true 
                         */  
                        bool CloseUser(int iUserId);  

        /** 
                         * @brief 
                         * 函数名:DelEpoll 
                         * 描述:删除epoll事件 
                         * @param [in] iSockFd socket FD 
                         * @return 成功返回true 
                         */  
                        bool DelEpoll(int iSockFd);  
                private:  

                        int    m_iUserCount;//用户数量;  
                        struct UserStatus *m_pAllUserStatus;//用户状态数组  
                        int    m_iEpollFd;//需要创建epollfd  
                        int    m_iSockFd_UserId[_MAX_SOCKFD_COUNT];//将用户ID和socketid关联起来  
                        int    m_iPort;//端口号  
                        char   m_ip[100];//IP地址  
        };  

        #endif  
        #include "cepollclient.h"  

    CEpollClient::CEpollClient(int iUserCount, const char* pIP, int iPort)  
    {  
        strcpy(m_ip, pIP);  
        m_iPort = iPort;  
        m_iUserCount = iUserCount;  
        m_iEpollFd = epoll_create(_MAX_SOCKFD_COUNT);  
        m_pAllUserStatus = (struct UserStatus*)malloc(iUserCount*sizeof(struct UserStatus));  
        for(int iuserid=0; iuserid<iUserCount ; iuserid++)  
        {  
            m_pAllUserStatus[iuserid].iUserStatus = FREE;  
            sprintf(m_pAllUserStatus[iuserid].cSendbuff, "Client: %d send message \"Hello Server!\"\r\n", iuserid);  
            m_pAllUserStatus[iuserid].iBuffLen = strlen(m_pAllUserStatus[iuserid].cSendbuff) + 1;  
            m_pAllUserStatus[iuserid].iSockFd = -1;  
        }  
        memset(m_iSockFd_UserId, 0xFF, sizeof(m_iSockFd_UserId));  
    }  

    CEpollClient::~CEpollClient()  
    {  
        free(m_pAllUserStatus);  
    }  
    int CEpollClient::ConnectToServer(int iUserId,const char *pServerIp,unsigned short uServerPort)  
    {  
        if( (m_pAllUserStatus[iUserId].iSockFd = socket(AF_INET,SOCK_STREAM,0) ) < 0 )  
        {  
            cout <<"[CEpollClient error]: init socket fail, reason is:"<<strerror(errno)<<",errno is:"<<errno<<endl;  
            m_pAllUserStatus[iUserId].iSockFd = -1;  
            return  m_pAllUserStatus[iUserId].iSockFd;  
        }  

        struct sockaddr_in addr;  
        bzero(&addr, sizeof(addr));  
        addr.sin_family = AF_INET;  
        addr.sin_port = htons(uServerPort);  
        addr.sin_addr.s_addr = inet_addr(pServerIp);  

        int ireuseadd_on = 1;//支持端口复用  
        setsockopt(m_pAllUserStatus[iUserId].iSockFd, SOL_SOCKET, SO_REUSEADDR, &ireuseadd_on, sizeof(ireuseadd_on));  

        unsigned long ul = 1;  
        ioctl(m_pAllUserStatus[iUserId].iSockFd, FIONBIO, &ul); //设置为非阻塞模式  

        connect(m_pAllUserStatus[iUserId].iSockFd, (const sockaddr*)&addr, sizeof(addr));  
        m_pAllUserStatus[iUserId].iUserStatus = CONNECT_OK;  
        m_pAllUserStatus[iUserId].iSockFd = m_pAllUserStatus[iUserId].iSockFd;  

        return m_pAllUserStatus[iUserId].iSockFd;  
    }  
    int CEpollClient::SendToServerData(int iUserId)  
    {  
        sleep(1);//此处控制发送频率,避免狂打日志,正常使用中需要去掉  
        int isendsize = -1;  
        if( CONNECT_OK == m_pAllUserStatus[iUserId].iUserStatus || RECV_OK == m_pAllUserStatus[iUserId].iUserStatus)  
        {  
            isendsize = send(m_pAllUserStatus[iUserId].iSockFd, m_pAllUserStatus[iUserId].cSendbuff, m_pAllUserStatus[iUserId  
    ].iBuffLen, MSG_NOSIGNAL);  
            if(isendsize < 0)  
            {  
                cout <<"[CEpollClient error]: SendToServerData, send fail, reason is:"<<strerror(errno)<<",errno is:"<<errno<  
    <endl;  
            }  
            else  
            {  
                printf("[CEpollClient info]: iUserId: %d Send Msg Content:%s\n", iUserId, m_pAllUserStatus[iUserId].cSendbuff  
    );  
                m_pAllUserStatus[iUserId].iUserStatus = SEND_OK;  
            }  
        }  
        return isendsize;  
    }  
    int CEpollClient::RecvFromServer(int iUserId,char *pRecvBuff,int iBuffLen)  
    {  
        int irecvsize = -1;  
        if(SEND_OK == m_pAllUserStatus[iUserId].iUserStatus)  
        {  
            irecvsize = recv(m_pAllUserStatus[iUserId].iSockFd, pRecvBuff, iBuffLen, 0);  
            if(0 > irecvsize)  
            {  
                cout <<"[CEpollClient error]: iUserId: " << iUserId << "RecvFromServer, recv fail, reason is:"<<strerror(errn  
    o)<<",errno is:"<<errno<<endl;  
            }  
            else if(0 == irecvsize)  
            {  
                cout <<"[warning:] iUserId: "<< iUserId << "RecvFromServer, STB收到数据为0,表示对方断开连接,irecvsize:"<<ire  
    cvsize<<",iSockFd:"<< m_pAllUserStatus[iUserId].iSockFd << endl;  
            }  
            else  
            {  
                printf("Recv Server Msg Content:%s\n", pRecvBuff);  
                m_pAllUserStatus[iUserId].iUserStatus = RECV_OK;  
            }  
        }  
        return irecvsize;  
    }  

    bool CEpollClient::CloseUser(int iUserId)  
    {  
        close(m_pAllUserStatus[iUserId].iSockFd);  
        m_pAllUserStatus[iUserId].iUserStatus = FREE;  
        m_pAllUserStatus[iUserId].iSockFd = -1;  
        return true;  
    }  

    int CEpollClient::RunFun()  
    {  
        int isocketfd = -1;  
        for(int iuserid=0; iuserid<m_iUserCount; iuserid++)  
        {  
            struct epoll_event event;  
            isocketfd = ConnectToServer(iuserid, m_ip, m_iPort);  
            if(isocketfd < 0)  
                cout <<"[CEpollClient error]: RunFun, connect fail" <<endl;  
            m_iSockFd_UserId[isocketfd] = iuserid;//将用户ID和socketid关联起来  

            event.data.fd = isocketfd;  
            event.events = EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP;  

            m_pAllUserStatus[iuserid].uEpollEvents = event.events;  
            epoll_ctl(m_iEpollFd, EPOLL_CTL_ADD, event.data.fd, &event);  
      }  
      while(1)  
          {  
              struct epoll_event events[_MAX_SOCKFD_COUNT];  
              char buffer[1024];  
              memset(buffer,0,1024);  
              int nfds = epoll_wait(m_iEpollFd, events, _MAX_SOCKFD_COUNT, 100 );//等待epoll事件的产生  
              for (int ifd=0; ifd<nfds; ifd++)//处理所发生的所有事件  
              {  
                  struct epoll_event event_nfds;  
                  int iclientsockfd = events[ifd].data.fd;  
                  cout << "events[ifd].data.fd: " << events[ifd].data.fd << endl;  
                  int iuserid = m_iSockFd_UserId[iclientsockfd];//根据socketfd得到用户ID  
                  if( events[ifd].events & EPOLLOUT )  
                  {  
                      int iret = SendToServerData(iuserid);  
                      if( 0 < iret )  
                      {  
                          event_nfds.events = EPOLLIN|EPOLLERR|EPOLLHUP;  
                          event_nfds.data.fd = iclientsockfd;  
                          epoll_ctl(m_iEpollFd, EPOLL_CTL_MOD, event_nfds.data.fd, &event_nfds);  
                      }  
                      else  
                      {  
                          cout <<"[CEpollClient error:] EpollWait, SendToServerData fail, send iret:"<<iret<<",iuserid:"<<iuser  
      id<<",fd:"<<events[ifd].data.fd<<endl;  
                          DelEpoll(events[ifd].data.fd);  
                          CloseUser(iuserid);  
                      }  
                  }  
      else if( events[ifd].events & EPOLLIN )//监听到读事件,接收数据  
                  {  
                      int ilen = RecvFromServer(iuserid, buffer, 1024);  
                      if(0 > ilen)  
                      {  
                          cout <<"[CEpollClient error]: RunFun, recv fail, reason is:"<<strerror(errno)<<",errno is:"<<errno<<e  
      ndl;  
                          DelEpoll(events[ifd].data.fd);  
                          CloseUser(iuserid);  
                      }  
                      else if(0 == ilen)  
                      {  
                          cout <<"[CEpollClient warning:] server disconnect,ilen:"<<ilen<<",iuserid:"<<iuserid<<",fd:"<<events[  
      ifd].data.fd<<endl;  
                          DelEpoll(events[ifd].data.fd);  
                          CloseUser(iuserid);  
                      }  
                      else  
                      {  
                          m_iSockFd_UserId[iclientsockfd] = iuserid;//将socketfd和用户ID关联起来  
                          event_nfds.data.fd = iclientsockfd;  
                          event_nfds.events = EPOLLOUT|EPOLLERR|EPOLLHUP;  
                          epoll_ctl(m_iEpollFd, EPOLL_CTL_MOD, event_nfds.data.fd, &event_nfds);  
                      }  
                  }  
                  else  
                  {  
                      cout <<"[CEpollClient error:] other epoll error"<<endl;  
                      DelEpoll(events[ifd].data.fd);  
                      CloseUser(iuserid);  
                  }  
              }  
      }  
      }  
        
      bool CEpollClient::DelEpoll(int iSockFd)  
      {  
          bool bret = false;  
          struct epoll_event event_del;  
          if(0 < iSockFd)  
          {  
              event_del.data.fd = iSockFd;  
              event_del.events = 0;  
              if( 0 == epoll_ctl(m_iEpollFd, EPOLL_CTL_DEL, event_del.data.fd, &event_del) )  
              {  
                  bret = true;  
              }  
              else  
              {  
                  cout <<"[SimulateStb error:] DelEpoll,epoll_ctl error,iSockFd:"<<iSockFd<<endl;  
              }  
              m_iSockFd_UserId[iSockFd] = -1;  
          }  
          else  
          {  
              bret = true;  
        
          }  
          return bret;  
      }  
      

服务器主程序:

#include <iostream>  
    #include "cepollserver.h"  

    using namespace std;  

    int main()  
    {  
            CEpollServer  theApp;  
            theApp.InitServer("127.0.0.1", 8000);  
            theApp.Run();  

            return 0;  
    }  

客户端主程序:

     #include "cepollclient.h"  

    int main(int argc, char *argv[])  
    {  
            CEpollClient *pCEpollClient = new CEpollClient(2, "127.0.0.1", 8000);  
            if(NULL == pCEpollClient)  
            {  
                    cout<<"[epollclient error]:main init"<<"Init CEpollClient fail"<<endl;  
            }  

            pCEpollClient->RunFun();  

            if(NULL != pCEpollClient)  
            {  
                    delete pCEpollClient;  
                    pCEpollClient = NULL;  
            }  

            return 0;  
    }  
 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录