嵌入式 Linux网络编程(五)——epoll机制

一、epoll简介

        epoll是在2.6内核中提出的,是selectpoll的增强版本。epoll更加灵活,没有描述符限制使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中。

1epoll函数

#include

int epoll_create(int size);

    创建一个epoll的句柄,size表示监听的文件描述的数量

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

    epoll的事件注册函数,先注册要监听的事件类型。epfd是epoll_create()的返回值,op参数表示动作,用三个宏来表示:
        EPOLL_CTL_ADD:注册新的fdepfd中;
        EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
        EPOLL_CTL_DEL:从epfd中删除一个fd
    fd参数是需要监听的fd,event参数是告诉内核需要监听什么事

struct epoll_event {

               __uint32_t   events;      /* Epoll events */

               epoll_data_t data;        /* User data variable */

           };

        EPOLLIN :表示对应的文件描述符可以读

        EPOLLOUT:表示对应的文件描述符可以写
        EPOLLPRI:表示文件描述符有紧急的数据可读

        EPOLLERR:表示文件描述符发生错误
        EPOLLHUP:表示文件描述符被挂断
        EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式

        EPOLLONESHOT:只监听一次事件

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

    等待事件的产生,类似于select()调用。

    参数events用来从内核得到事件的集合

    maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size

    参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。

    函数返回需要处理的事件数目,如返回0表示已超时。

2、epoll工作模式

        epoll对文件描述符的操作有两种模式:LTlevel trigger)和ETedge trigger)。LT模式是默认模式

        LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

        ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

        ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

二、epoll编程模型

epoll编程的基本框架:

while(1)
{  
    nfds = epoll_wait(epfd,events,20,500);  
    for(i=0;ifd;           
             send( sockfd, md->ptr, strlen((char*)md->ptr),0 );//发送数据 
             ev.data.fd=sockfd;            
             ev.events=EPOLLIN|EPOLLET;            
             epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 
          }         
         else         
         {            
         //其他的处理         
         }   
      }//end of for
}//end of while

程序实例:

server.c:

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include  
#include 
#include 
#include 
#include 
 
#define PORT 8888
#define RT_ERR (-1)
#define RT_OK 0
#define SERVERIP "192.168.0.200"
#define LISTEN_QUEUE 10
#define BUFFER_SIZE 1024
#define MAX_EVENTS 1024
 
int main(int argc, char *argv[])
{
    int listenfd, connsockfd, fd, len;
    char readbuf[BUFFER_SIZE];
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
            fprintf(stderr, "socket function failed.\n");
            exit(RT_ERR);
    }
    
    struct sockaddr_in serveraddr, clientaddr;
//    bzero(&clientaddr, sizeof(clientaddr));
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVERIP);
    
    unsigned int client_len = sizeof(struct sockaddr_in);
    if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
            fprintf(stderr, "bind function failed.\n");
            close(listenfd);
            exit(RT_ERR);
    }
   
    if(listen(listenfd,LISTEN_QUEUE) < 0)
    {
            fprintf(stderr, "listen function failed.\n");
            close(listenfd);
        exit(RT_ERR);
    }
    int opts=fcntl(listenfd, F_GETFL);  
    if(opts<0)  
    {  
       fprintf(stderr, "fcntl(sock,GETFL)\n");  
       exit(-1);  
    } 
   
    opts = opts|O_NONBLOCK;  
    if(fcntl(listenfd,F_SETFL,opts)<0)  
    {  
       fprintf(stderr, "fcntl(sock,SETFL,opts)\n");  
       exit(-1);  
    }  
    fprintf(stdout, "The server IP is %s, listen on port: %d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
   
    struct epoll_event event, events[MAX_EVENTS];
    int epfd = epoll_create(MAX_EVENTS);
    event.data.fd=listenfd;  
    event.events=EPOLLIN|EPOLLET; 
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);  
    
    int nepfd, n;
    while(1)
    {
        nepfd = epoll_wait(epfd, events, MAX_EVENTS, 0);
        for(fd = 0; fd < nepfd; fd++)
        {
            bzero(&clientaddr, sizeof(clientaddr));
            fprintf(stdout, "fd is %d, listenfd is %d\n", fd, listenfd);
            if(events[fd].data.fd == listenfd)//新的请求连接用户
            {
    
                connsockfd = accept(listenfd, (struct sockaddr *)&clientaddr, &len);
                if(connsockfd < 0)
                {
                    fprintf(stderr, "accept function failed.\n");
                    exit(RT_ERR);
                }
                fprintf(stdout, "accept a new session from %s port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
                event.data.fd = connsockfd;
                event.events = EPOLLIN|EPOLLET; 
                epoll_ctl(epfd, EPOLL_CTL_ADD, connsockfd, &event);
             }
            else if(events[fd].events&EPOLLIN)//如果是已连接用户,有数据接收
            {
                struct sockaddr_in connaddr;
                int connlen;
                if((connsockfd = events[fd].data.fd) < 0)
                    continue;
                if((n = recv(connsockfd, readbuf, sizeof(readbuf), 0)) < 0) 
                {  
                     if (errno == ECONNRESET)
                     {  
                       close(connsockfd);  
                       events[fd].data.fd = -1;  
                     }
                     else 
                       fprintf(stderr, "recv function failed.\n");
                     } 
                     else if(n == 0)
                     {
                        close(connsockfd);  
                        events[fd].data.fd = -1;  
                      }
                 fprintf(stdout, "mesage: %s", readbuf);
                 event.data.fd = connsockfd;    
                      event.events = EPOLLOUT|EPOLLET;  
            }
            else if(events[fd].events&EPOLLOUT)//有数据发送
            {
                connsockfd = events[fd].data.fd;  
                     send(connsockfd, readbuf, sizeof(readbuf), 0);  
                     event.data.fd = connsockfd;  
                     event.events=EPOLLIN|EPOLLET;  
                      epoll_ctl(epfd, EPOLL_CTL_MOD, connsockfd, &event);    
            }
        }//end of for
    }//end of while
    return 0;
}

client.c:

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include  
#include 
 
#define SERVERIP "192.168.0.200"
#define PORT 8888
#define BUFFER_SIZE 512
 
int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in server;
    char sendbuf[BUFFER_SIZE];
    bzero(&sendbuf,sizeof(sendbuf));
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&server,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = inet_addr(SERVERIP);
    
 
    if(connect(sockfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) < 0)
    {
            perror("connect failed.\n");
            return -1;
    }
    while(1)
    { 
        fgets(sendbuf, sizeof(sendbuf), stdin);
        send(sockfd, sendbuf, sizeof(sendbuf), 0);
        bzero(&sendbuf,sizeof(sendbuf));
     }
    close(sockfd);
    return 0;
}