epoll select的限制 条件触发 边缘触发

结论: epoll 要优于 select , 编程模型基本一致;编程

请注意,不管是epoll 仍是 select 都不是具备并发能力的服务器,仅仅是io复用

题外话: 在io复用中把监听套接字设为非阻塞ubuntu

以为理论麻烦的,能够直接往下拉,有代码例子;segmentfault

select 的缺陷:服务器

1.在sys/select.h 中 __FD_SETSIZE 为1024 , 意味默认状况最多监控1024个描述符,固然能够修改这个文件从新编译
2.select 的实现 ,在fs/select.c 中:
int do_select(int n, fd_set_bits *fds, s64 *timeout) 
        { 
            //略
            for (i = 0; i < n; ++rinp, ++routp, ++rexp) 
            { 
                //略
            } 
        }
在实现中 , 每次将通过 n-1 次循环, 随着描述符越多,性能线性降低

3.select 的3个 fd_set(read,write,except) 都是值-结果:
  例如:
fd_set rset,allset;
while(1){
    rset = allset;    //每次须要重置
    select(maxfd+1, rset, ....)
}
这种每次重置意味不断的从用户空间往内核空间中复制数据,
操做系统一旦轮询完成后,再将数据置位,而后复制到用户空间,
就是这种不断的复制形成了性能降低,还不得不这么干;

epoll 解决了select缺陷;
epoll 给每一个须要监听的描述符都设置了一个或多个 event :并发

struct epoll_event event;
    event.events = EPOLLIN;      //读操做 ,如要监控多个能够 EPOLLIN|EPOLLOUT;
    event.data.fd = listensock;  //赋值要监听的描述符, 就像 FD_SET(listensock,&rset);
  1. epoll的描述符限制能够查看 cat /proc/sys/fs/epoll/max_user_watches
  2. 复制数据也仅仅在 epoll_ctl (...,EPOLL_CTL_ADD,...) 添加时(EPOLL_CTL_ADD),复制一次到
    epoll_create 所建立的红黑树中

*3. 最重要的是,epoll_wait 并不会像select 去轮询, 而是在内部给监听的描述符一个callback.socket

一旦对应的事件发生(例如:EPOLLIN) 就将此事件添加到一个链表中.
epoll_wait(此函数相似select) 的功能就是去链表中收集发生事件相应的 struct epoll_event;

总的来讲:
epoll_create 在操做系统中建立一个用于存放struct epoll_event 的空间
epoll_ctl 在空间内 添加,修改,删除 struct epoll_event (内含描述符);
epoll_wait 收集已经发生事件的描述符;
close 关闭(减小引用) epoll函数

一个相似 select 的echo 服务器修改版本:性能

echo.c操作系统

#define EPOLL_SIZE 100

    int listensock = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t  socklen = sizeof(serv_addr);
    memset(&serv_addr,0,socklen);
    memset(&cli_addr,0,socklen);
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_family = AF_INET;

    if(bind(listensock,(SA*)&serv_addr,sizeof(serv_addr)) < 0){
        perror("bind");
        return 0;
    }

    if(listen(listensock,BACKLOG) < 0){
        perror("listen");
        return 0;
    }

    //向操做系统请求 建立一个 epoll , 参数看man
    int epfd = epoll_create(EPOLL_SIZE);
    if(epfd < 0){
        perror("epoll_create");
        return 0;
    }

    //监听listensock ,相似 FD_SET(listensock, &rset)
    struct epoll_event event;
    event.events = EPOLLIN;      //读取 . man中有详细解释
    event.data.fd = listensock;
    
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,listensock,&event) < 0)  //把listensock 注册到epfd,用于监听event事件
    {
        perror("epoll ctl");
        return 0;
    }
    
    //分配一块内存. 在 epoll_wait 返回后将存放发生事件的结构体
    struct epoll_event  * ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
    if(ep_events == NULL){
        perror("malloc");
        return 0;
    }

    puts("server is running");
    int n = 0;
    int client_fd = 0;
    char buf[BUFSIZ];
    int len = 0,return_bytes = 0, tmp_fd = 0;

    while(1){
    
        //收集已经发生事件的描述符 , 返回值与select一致
        n = epoll_wait(epfd,ep_events,EPOLL_SIZE,-1);
        if( n < 0){
            perror("epoll_wait");
            break;
        }
        printf("%d events returned\n", n);
        for ( int i = 0; i < n ; ++i){
        
            //若是有人链接
            if( listensock == ep_events[i].data.fd){
                socklen = sizeof(cli_addr);
                client_fd = accept(listensock,(SA*)&cli_addr,&socklen);

                //若是有人链接,则加入epoll
                event.events = EPOLLIN;  //读取
                event.data.fd = client_fd;
                if(epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&event) < 0){
                    perror("epoll add failed");
                    continue;
                }
                printf("accepted,client_fd:%d,ip:%s,port:%d\n",
                       client_fd,inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
            }
            else {
                tmp_fd =ep_events[i].data.fd;
                readagain:
                len = read(ep_events[i].data.fd,buf,BUFSIZ);
                
                //若是出错
                if(len < 0) {
                    if(errno == EINTR)
                        goto readagain;
                    else{
                        return_bytes = snprintf(buf,BUFSIZ-1,
                                "clientfd:%d errorno:%d\n",tmp_fd,errno);
                        buf[return_bytes] = 0;
                        epoll_ctl(epfd,EPOLL_CTL_DEL,tmp_fd,NULL); //从epoll中移除
                        close(tmp_fd);    //close(socket)
                        perror(buf);
                    }
                } else if( 0 == len){
                
                    // 对端断开
                    return_bytes = snprintf(buf,BUFSIZ-1,
                                            "clientfd:%d closed\n",tmp_fd);
                    buf[return_bytes] = 0;
                    epoll_ctl(epfd,EPOLL_CTL_DEL,tmp_fd,NULL);
                    close(tmp_fd);
                    puts(buf);
                } else{
                    //echo
                    write(tmp_fd,buf,len);
                }

            }
        }
    }

条件触发: epoll 的默认行为就是条件触发(select也是条件触发);code

经过修改代码得结论

先添加一个BUFF_SIZE

#define  BUFF_SIZE 4

把read(ep_events[i].data.fd,buf,BUFSIZ) 的BUFSIZ 修改为BUFF_SIZE

而后 telnet 此服务器 : telnet 127.0.0.1 9988 , 随意写一些数据给服务器
先看结果,这是我这里的输出:

fuck@ubuntu:~/sockettest$ ./epoll_serv 
server is running
1 events returned
accepted,client_fd:5,ip:127.0.0.1,port:54404
1 events returned
1 events returned
1 events returned
1 events returned
1 events returned
1 events returned

让read 最多只能读4个字节的惟一缘由是 ,证实什么是条件触发

经过结果获得结论: 只要此缓冲区内还有数据, epoll_wait 将不断的返回, 这个就是默认状况下epoll的条件触发;


边缘触发:这个须要先作个实验才能理解,纯文字估计不太好理解;
惟一须要修改的代码是在accept下面的一行:

event.events = EPOLLIN | EPOLLET; // EPOLLET 就是边缘触发

注意 read 那行代码,最多接受的字节最好在 1~4 之间 : read(...,..., 4);不然效果不明显
接着telnet ,尝试一下每次写给服务器超过 5个字节

我这里就不贴服务器输出了 , 能够看到这时 epoll_wait 不管怎么样只会返回一次了 ;

先给结论: 只有当客户端写入(write,send,...) , epoll_wait 才会返回且只返回一次;

注意与条件触发的不一样: 条件触发状况下只要接受缓冲区有数据即返回, 边缘触发不会;

因为这种只返回一次的特性 , EPOLLET 通常状况下都将采用非阻塞 O_NONBLOCK 的方式来读取;

下面修改上面代码:
对于上面的代码修改只修改几个地方:
1.全部的套接字全改为非阻塞;

static int setnonblock(int fd){
    int flag = fcntl(fd,F_GETFL,0);
    return fcntl(fd,F_SETFL,flag|O_NONBLOCK);
}

2.因为套接字是非阻塞的,因此在 read 时须要无限循环, 以及判断返回的错误,直到 errno == EAGAIN 才证实缓冲区已空;

echo.serv.c

static int setnonblock(int fd){
    int flag = fcntl(fd,F_GETFL,0);
    return fcntl(fd,F_SETFL,flag|O_NONBLOCK);
}

int main(){
    int listensock = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t  socklen = sizeof(serv_addr);
    memset(&serv_addr,0,socklen);
    memset(&cli_addr,0,socklen);
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_family = AF_INET;

    if(bind(listensock,(SA*)&serv_addr,sizeof(serv_addr)) < 0){
        perror("bind");
        return 0;
    }

    if(listen(listensock,BACKLOG) < 0){
        perror("listen");
        return 0;
    }

    int epfd = epoll_create(EPOLL_SIZE);
    if(epfd < 0){
        perror("epoll_create");
        return 0;
    }

    setnonblock(listensock);

    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = listensock;
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,listensock,&event) < 0)
    {
        perror("epoll ctl");
        return 0;
    }
    struct epoll_event  * ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
    if(ep_events == NULL){
        perror("malloc");
        return 0;
    }

    puts("server is running");
    int n = 0;
    int client_fd = 0;
    char buf[BUFSIZ];
    int len = 0,return_bytes = 0, tmp_fd = 0;

    while(1){
        n = epoll_wait(epfd,ep_events,EPOLL_SIZE,-1);
        if( n < 0){
            perror("epoll_wait");
            break;
        }
        printf("%d events returned\n", n);
        for ( int i = 0; i < n ; ++i){
            if( listensock == ep_events[i].data.fd){
                socklen = sizeof(cli_addr);
                client_fd = accept(listensock,(SA*)&cli_addr,&socklen);
                setnonblock(client_fd);
                //add in epoll
                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client_fd;
                if(epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&event) < 0){
                    perror("epoll add failed");
                    continue;
                }
                printf("accepted,client_fd:%d,ip:%s,port:%d\n",
                       client_fd,inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
            }
            else {
                tmp_fd =ep_events[i].data.fd;
                while(1) {
                    readagain:
                    len = read(tmp_fd, buf, BUFF_SIZE);
                    if (len < 0) {
                        if (errno == EINTR) {
                            puts("EINTR readagain");
                            goto readagain;
                        }
                        else if(errno == EAGAIN){
                            printf("no more data !! cliendfd:%d ,read return:%d\n",tmp_fd,len);
                            break;
                        }
                        else {
                            return_bytes = snprintf(buf, BUFSIZ - 1,
                                                    "clientfd:%d errorno:%d \t", tmp_fd, errno);
                            buf[return_bytes] = 0;
                            epoll_ctl(epfd, EPOLL_CTL_DEL, tmp_fd, NULL);
                            close(tmp_fd);
                            perror(buf);
                            break;
                        }
                    } else if (0 == len) {
                        return_bytes = snprintf(buf, BUFSIZ - 1,
                                                "clientfd:%d closed\n", tmp_fd);
                        buf[return_bytes] = 0;
                        epoll_ctl(epfd, EPOLL_CTL_DEL, tmp_fd, NULL);
                        close(tmp_fd);
                        puts(buf);
                        break;
                    } else {
                        return_bytes = write(tmp_fd, buf, len);
                        printf("write %d bytes , readnbytes:%d , buf:%s\n", return_bytes, len,buf);
                    }
                }
            }
        }
    }


    printf("serv down errno:%d\n",errno);
    close(epfd);
    close(listensock);
}
相关文章
相关标签/搜索