• Socket Select & Epoll Code


    之前看过Socket,一直比较懒,没有总结一下,趁着有兴致赶紧写一下

    Socket在Linux中被当作文件看待,对应的sock_fd也是一个文件符被操作,因为端口需要监听多个socket_fd,所以采用select机制来进行非阻塞监听。

    直接上一段源码说明原理

    //socket select example
    //源代码copied from http://blog.sina.com.cn/s/blog_5f84dc840100edej.html
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet.h>
    #include <arpa/inet.h>
    
    #define MYPORT 1234    //listening port
    #define BACKLOG 5     //max recieve client
    #define BUF_SIZE 200
    
    int fd_A[BACKLOG];        //connected FD array
    int conn_amount;        //connect number
    
    int main(int argc, char **argv) {
        int sock_fd, new_fd;    //fd for listening, new connected fd
        struct sockaddr_in server_addr;        //server addresss info
        struct sockaddr_in client_addr;        //client address info
        socklen_t    sin_size;
        int yes =1;
        char buf[BUF_SIZE];
        int ret, i;
    
        //create a listing socket
        if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("Create listening socket error
    ");
            exit(1);
        }
        //configure the listening socket
        //SO_REUSEADDR BOOL allow aport reuse
        if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt error!
    ");
            exit(1);
        }
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(MYPORT);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        memset(server_addr.sin_zero, '', sizeof(server_addr.sin_zero));
    
        //bind sock_fd and server_addr
        if(bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
            perror("bind error!
    ");
            exit(1);
        }
    
        //begin listen
        if(listen(sock_fd, BACKLOG) == -1) {
            perror("listen error!
    ");
            exit(1);
        }
    
        //monitor fd set
        fd_set fdsr;
        //max file number
        int maxsock;
        //Select TimeOut time
        struct tim tv;
        conn_amount = 0;
        sin_size = sizeof(client_addr);
        maxsock = sock_fd;
        while(1) {
            FD_ZERO(&fdsr);
            FD_SET(sock_fd, &fdsr);
            tv.tv_sec = 30;
            tv.tv_usec = 0;
            //join the active socket handle into fdset
            for(i = 0; i < BACKLOG; i++) {
                if(fd_A[i] != 0) {
                    FD_SET(fd_A[i], &fdsr);
                }
            }
            ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
            if(ret < 0) {
                perror("select error!");
                break;
            }
            else if (ret == 0)
            {
                printf("timeoutn");
                continue;
            }
            //monitor every sock_fd
            for(i = 0; i < conn_amount; i++) {
                if(FD_ISSET(fd_A[i], &fdsr) {
                    ret = recv(fd_A[i], buf, sizeo(buf), 0);
                    if(ret <= 0) {
                        close(fd_A[i]);
                        FD_CLR(fd_A[i], &fdsr);
                        fd_A[i] = 0;
                    }
                    else {
                        if(ret < BUF_SIZE) {
                            memset(&buf[ret], '', 1);
                        }
    
                    }
                }
            }
            if(FD_ISSET(sock_fd, &fdsr)) {
                new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
                if(new_fd <= 0) {
                    perror("accept socket error!");
                    continue;
                }
                if(conn_amount < BACKLOG) {
                    fd_A[conn_amount++] = new_fd;
                    if(new_fd > maxsock) {
                        maxsock = new_fd;
                    }
                }
                else {
                    send(new_fd, "bye", 4, 0);
                    close(new_fd);
                    break;
                }
            }
        }
        for(i = 0; i < BACKLOG; i++) {
            if(fd_A[i] != 0) {
                close(fd_A[i]);
            }
        }
        return 0;
    }

    上面的代码循环过程中不断地把用作listen 的sock_fd和用sock_fd监听到的new_fd加入到fdsr中,然后select去超时监听。如果返回值为正值证明监听到了可读信息,接着去检查对应的每一个fd是否在fdsr中,如果在就accept或读取信息。这种过程就可以进行非阻塞的监听,但是由于过程中select后需要将已经连接的fd循环一遍看是否有可读信息,在大规模的连接但数据偶发的情况下,这种循环检测会很慢且浪费资源。需要一种更好的解决方法——epoll

    Epoll所以能够比select更加高效是因为

    1.epoll不需要对每一个sock_fd做循环检测,避免了大量的连接时的循环延时.epoll将需要处理的文件符从内核态返回到用户态的一个链表中,当用户态需要处理时,只需要遍历链表中的事件即可。

    2.epoll在监听之前就已经把句柄存储在内核中,每次添加新的监听句柄时才会向内核写入少量数据。而Select则是每次调用select函数都会将所有的监听的socket全部写入到内核态一遍,上万计的句柄写入到内核态,可能会有几十KB的大小,非常低效。

    epoll有三个常用函数:

    int epoll_create(int 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);

    通过epoll_create创建一个epoll对象,size是最大的句柄数,系统允许的最大句柄数和机器内存有关,1G 大约时10亿的句柄

    epoll_ctl函数则用于将某句柄加入或移出epoll对象

    epoll_wait类似与select函数,用于监听超时控制

    Linux的手册上对于epoll有这样的例子:

    #define MAX_EVENTS 10
    struct epoll_event ev, events[MAX_EVENTS];
    int listen_sock, conn_sock, nfds, epollfd;
     /* Set up listening socket, 'listen_sock' (socket(), bind(), listen()) */
    
    epollfd = epoll_create(10);
    if (epollfd == -1) {
        perror("epoll_create");
        exit(EXIT_FAILURE);
    }
    
    ev.events = EPOLLIN;
    ev.data.fd = listen_sock;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        exit(EXIT_FAILURE);
    }
    
    for (;;) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_pwait");
            exit(EXIT_FAILURE);
        }
    
        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listen_sock) {
                conn_sock = accept(listen_sock,
                        (struct sockaddr *) &local, &addrlen);
                if (conn_sock == -1) {
                    perror("accept");
                    exit(EXIT_FAILURE);
                }
                setnonblocking(conn_sock);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = conn_sock;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                            &ev) == -1) {
                    perror("epoll_ctl: conn_sock");
                    exit(EXIT_FAILURE);
                }
            } else {
                do_use_fd(events[n].data.fd);
            }
        }
    }

    结构非常简洁明了

    epoll在内核初始化时,会开辟出epoll自己的内核高速cache区,这是一个slab层,申请了一块统一大小的内存区域,便于申请释放和赋值。

    这些socket在cache中以红黑数的形式被管理保存,提升了查找插入和删除的速度。

    Epoll有对应的两种模式, ET和LT。因为有事件发生时内核态会将事件句柄发送到用户态的list中,如果时ET 模式,用户态只会返回一次。而如果时LT模式,当用户态没有处理完该句柄时,下次epoll_wait后仍然会将该句柄提交到用户态。这个没有实践过,还不能体会这两种实际的应用场景区别。

    socket的总结大致就是这么多。之后在实践中有新的学习和体会再补充。

  • 相关阅读:
    mysql
    jQuery选择器
    使用JavaScript操作DOM节点元素的常用方法(创建/删除/替换/复制等)
    MVC2.0==>MVC3.0
    sql 邮件发送测试情况
    C#调用存储过程
    设计模式
    sql 分页
    SQL Server 存储过程(转载)
    sqlitehelper封装
  • 原文地址:https://www.cnblogs.com/noanswer/p/3656585.html
Copyright © 2020-2023  润新知