IO多路复用--select和epoll详解

来源:互联网 发布:淘宝怎么联系不了卖家 编辑:程序博客网 时间:2024/05/24 02:16

1.基本概念

IO多路复用是指在一个进程或线程中监控多个文件描述符,可以用select和epoll函数实现


select函数声明:

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);


epoll函数声明:

int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);


2.select与epoll比较


select最多能同时监控1024个文件描述符

epoll可监控的文件描述符个数没有限制

epoll通过回调函数的机制,可以不用遍历所有的文件描述符

epoll通过mmap内存映射机制,可以不用在用户空间和内核空间来拷贝文件描述符




3.示例代码(代码来自网上的博客)

单线程select服务器代码:

#include <stdio.h>#include <string.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/select.h>const static int MAXLINE = 1024;const static int SERV_PORT = 10001;int main(){    int i , maxi , maxfd, listenfd , connfd , sockfd ;    /*nready 描述字的数量*/    int nready ,client[FD_SETSIZE];    int n ;    /*创建描述字集合,由于select函数会把未有事件发生的描述字清零,所以我们设置两个集合*/    fd_set rset , allset;    char buf[MAXLINE];    socklen_t clilen;    struct sockaddr_in cliaddr , servaddr;    /*创建socket*/    listenfd = socket(AF_INET , SOCK_STREAM , 0);    /*定义sockaddr_in*/    memset(&servaddr , 0 ,sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(SERV_PORT);    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    bind(listenfd, (struct sockaddr *) & servaddr , sizeof(servaddr));    listen(listenfd , 100);    /*listenfd 是第一个描述字*/    /*最大的描述字,用于select函数的第一个参数*/    maxfd = listenfd;    /*client的数量,用于轮询*/    maxi = -1;    /*init*/    for(i=0 ;i<FD_SETSIZE ; i++)        client[i] = -1;    FD_ZERO(&allset);    FD_SET(listenfd, &allset);    for (;;)    {        rset = allset;        /*只select出用于读的描述字,阻塞无timeout*/        nready = select(maxfd+1 , &rset , NULL , NULL , NULL);        if(FD_ISSET(listenfd,&rset))        {            clilen = sizeof(cliaddr);            connfd = accept(listenfd , (struct sockaddr *) & cliaddr , &clilen);            /*寻找第一个能放置新的描述字的位置*/            for (i=0;i<FD_SETSIZE;i++)            {                if(client[i]<0)                {                    client[i] = connfd;                    break;                }            }            /*找不到,说明client已经满了*/            if(i==FD_SETSIZE)            {                printf("Too many clients , over stack .\n");                return -1;            }            FD_SET(connfd,&allset);//设置fd            /*更新相关参数*/            if(connfd > maxfd) maxfd = connfd;            if(i>maxi) maxi = i;            if(nready<=1) continue;            else nready --;        }        for(i=0 ; i<=maxi ; i++)        {            if (client[i]<0) continue;            sockfd = client[i];            if(FD_ISSET(sockfd,&rset))            {                n = read(sockfd , buf , MAXLINE);                if (n==0)                {                    /*当对方关闭的时候,server关闭描述字,并将set的sockfd清空*/                    close(sockfd);                    FD_CLR(sockfd,&allset);                    client[i] = -1;                }                else                {                    buf[n]='\0';                    printf("Socket %d said : %s\n",sockfd,buf);                    write(sockfd,buf,n); //Write back to client                }                nready --;                if(nready<=0) break;            }        }    }    return 0;}


单线程epoll服务器代码:

#include <stdio.h>#include <sys/epoll.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#include <fcntl.h>#include <unistd.h>#include <netdb.h>#include <errno.h>#define MAX_EVENT 20#define READ_BUF_LEN 256/** * 设置 file describe 为非阻塞模式 * @param fd 文件描述 * @return 返回0成功,返回-1失败 */static int make_socket_non_blocking (int fd) {    int flags, s;    // 获取当前flag    flags = fcntl(fd, F_GETFL, 0);    if (-1 == flags) {        perror("Get fd status");        return -1;    }    flags |= O_NONBLOCK;    // 设置flag    s = fcntl(fd, F_SETFL, flags);    if (-1 == s) {        perror("Set fd status");        return -1;    }    return 0;}int main() {    // epoll 实例 file describe    int epfd = 0;    int listenfd = 0;    int result = 0;    struct epoll_event ev, event[MAX_EVENT];    // 绑定的地址    const char * const local_addr = "127.0.0.1";    struct sockaddr_in server_addr = { 0 };    listenfd = socket(AF_INET, SOCK_STREAM, 0);    if (-1 == listenfd) {        perror("Open listen socket");        return -1;    }    /* Enable address reuse */    int on = 1;    // 打开 socket 端口复用, 防止测试的时候出现 Address already in use    result = setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );    if (-1 == result) {        perror ("Set socket");        return 0;    }    server_addr.sin_family = AF_INET;    inet_aton (local_addr, &(server_addr.sin_addr));    server_addr.sin_port = htons(10001);    result = bind(listenfd, (const struct sockaddr *)&server_addr, sizeof (server_addr));    if (-1 == result) {        perror("Bind port");        return 0;    }    result = make_socket_non_blocking(listenfd);    if (-1 == result) {        return 0;    }    result = listen(listenfd, 200);    if (-1 == result) {        perror("Start listen");        return 0;    }    // 创建epoll实例    epfd = epoll_create1(0);    if (1 == epfd) {        perror("Create epoll instance");        return 0;    }    ev.data.fd = listenfd;    ev.events = EPOLLIN | EPOLLET /* 边缘触发选项。 */;    // 设置epoll的事件    result = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);    if(-1 == result) {        perror("Set epoll_ctl");        return 0;    }    for ( ; ; ) {        int wait_count;        // 等待事件        wait_count = epoll_wait(epfd, event, MAX_EVENT, -1);    int i=0;        for ( i = 0 ; i < wait_count; i++) {            uint32_t events = event[i].events;            // IP地址缓存            char host_buf[NI_MAXHOST];            // PORT缓存            char port_buf[NI_MAXSERV];            int __result;            // 判断epoll是否发生错误            if ( events & EPOLLERR || events & EPOLLHUP || (! events & EPOLLIN)) {                printf("Epoll has error\n");                close (event[i].data.fd);                continue;            } else if (listenfd == event[i].data.fd) {                // listen的 file describe 事件触发, accpet事件                for ( ; ; ) { // 由于采用了边缘触发模式,这里需要使用循环                    struct sockaddr in_addr = { 0 };                    socklen_t in_addr_len = sizeof (in_addr);                    int accp_fd = accept(listenfd, &in_addr, &in_addr_len);                    if (-1 == accp_fd) {                        perror("Accept");                        break;                    }                    __result = getnameinfo(&in_addr, sizeof (in_addr),                                           host_buf, sizeof (host_buf) / sizeof (host_buf[0]),                                           port_buf, sizeof (port_buf) / sizeof (port_buf[0]),                                           NI_NUMERICHOST | NI_NUMERICSERV);                    if (! __result) {                        printf("New connection: host = %s, port = %s\n", host_buf, port_buf);                    }                    __result = make_socket_non_blocking(accp_fd);                    if (-1 == __result) {                        return 0;                    }                    ev.data.fd = accp_fd;                    ev.events = EPOLLIN | EPOLLET;                    // 为新accept的 file describe 设置epoll事件                    __result = epoll_ctl(epfd, EPOLL_CTL_ADD, accp_fd, &ev);                    if (-1 == __result) {                        perror("epoll_ctl");                        return 0;                    }                }                continue;            } else {                // 其余事件为 file describe 可以读取                int done = 0;                // 因为采用边缘触发,所以这里需要使用循环。如果不使用循环,程序并不能完全读取到缓存区里面的数据。                for ( ; ;) {                    ssize_t result_len = 0;                    char buf[READ_BUF_LEN] = { 0 };                    result_len = read(event[i].data.fd, buf, sizeof (buf) / sizeof (buf[0]));                    if (-1 == result_len) {                        if (EAGAIN != errno) {                            perror ("Read data");                            done = 1;                        }                        break;                    } else if (! result_len) {                        done = 1;                        break;                    }                    write(STDOUT_FILENO, buf, result_len);                }                if (done) {                    printf("Closed connection\n");                    close (event[i].data.fd);                }            }        }    }    close (epfd);    return 0;}



客户端代码:

#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netdb.h>int main(int argc, char *argv[]){ int sockfd; int n; struct sockaddr_in serv_addr; char buffer[256];  sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) {  printf("opening socket error! \n");  exit(-1); } bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(10001); serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {  printf("connecting error! \n");  exit(-1); } printf("Please enter the message: "); bzero(buffer, 256); fgets(buffer, 255, stdin); n = write(sockfd, buffer, strlen(buffer)); if (n < 0)  printf("error writing to socket! \n"); bzero(buffer, 256); n = read(sockfd, buffer, 255); if (n < 0)  printf("error reading from socket! \n"); printf("%s \n",buffer); return 0;}


原创粉丝点击