select和epoll网络模型

1.select模型

select所需要的头文件:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

select函数定义:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);

fd_set操作函数:
FD_CLR(int fd, fd_set *set);    // 用于在文件描述符集合中删除一个文件描述符
FD_ISSET(int fd, fd_set *set);  // 用于检查指定的文件描述符是否在该集合中
FD_SET(int fd, fd_set *set);    // 用于在文件描述符集合中增加一个新的文件描述符。
FD_ZERO(fd_set *set);          // 将指定的文件描述符集清空

timeval的定义如下:
struct timeval {
     long tv_sec; /* seconds */
     long tv_usec; /* 10E-6 second */
};

fd_set   readfds;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);

struct timeval timeout;
timeout.tv_sec  =  2;
timeout.tv_usec =  0;
int ret = select(sock + 1, &readfds, NULL, NULL, &timeout);
// select函数返回以后会修改readfds和timeout内的值
if (ret > 0)
{
     if (FD_ISSET(sock, &readfds))
    {
         // 可读
    }
}

 select模式的特点:
1.单个进程监测的fd受限制,默认下是1024个文件描述符;
2.轮询式检查文件描述符集合中的每个fd可读可写状态,IO效率会随着描述符集合增大而降低;
3.可以采用一个父进程专门accept,父进程均衡的分配多个子进程分别处理一部分的链接,子进程采用select模型监测自己负责的fd的可读可写。 

2.epoll模型

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

LT(level triggered)水平触发,是缺省的工作方式,并且同时支持block和no-block socket。只要buffer里有未被处理的事件,内核会不断通知你就绪未处理的FD。

ET(edge-triggered)边缘触发,是高速工作方式,只支持no-block socket。只在buffer大小发生变化时通知,只通知一次。

复制代码
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h> 

#define MAX_EVENTS 10
#define PORT 8080

//设置socket连接为非阻塞模式
void setnonblocking(int sockfd) {
    int opts;

    opts = fcntl(sockfd, F_GETFL);
    if(opts < 0) {
        perror("fcntl(F_GETFL)\n");
        exit(1);
    }
    opts = (opts | O_NONBLOCK);
    if(fcntl(sockfd, F_SETFL, opts) < 0) {
        perror("fcntl(F_SETFL)\n");
        exit(1);
    }
}

int main(){
    struct epoll_event ev, events[MAX_EVENTS];
    int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;
    struct sockaddr_in local, remote;
    char buf[BUFSIZ];

    //创建listen socket
    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("sockfd\n");
        exit(1);
    }
    setnonblocking(listenfd);
    bzero(&local, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY);;
    local.sin_port = htons(PORT);
    if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {
        perror("bind\n");
        exit(1);
    }
    listen(listenfd, 20);

    epfd = epoll_create(MAX_EVENTS);
    if (epfd == -1) {
        perror("epoll_create");
        exit(EXIT_FAILURE);
    }

    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        exit(EXIT_FAILURE);
    }

    for (;;) {
        nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_pwait");
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < nfds; ++i) {
            fd = events[i].data.fd;
            if (fd == listenfd) {
                while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, 
                    (size_t *)&addrlen)) > 0) {
                        setnonblocking(conn_sock);
                        ev.events = EPOLLIN | EPOLLET;
                        ev.data.fd = conn_sock;
                        if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,
                            &ev) == -1) {
                                perror("epoll_ctl: add");
                                exit(EXIT_FAILURE);
                        }
                }
                if (conn_sock == -1) {
                    if (errno != EAGAIN && errno != ECONNABORTED 
                        && errno != EPROTO && errno != EINTR) 
                        perror("accept");
                }
                continue;
            }  
            if (events[i].events & EPOLLIN) {
                n = 0;
                while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
                    n += nread;
                }
                if (nread == -1 && errno != EAGAIN) {
                    perror("read error");
                }
                ev.data.fd = fd;
                ev.events = events[i].events | EPOLLOUT;
                if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {
                    perror("epoll_ctl: mod");
                }
            }
            if (events[i].events & EPOLLOUT) {
                sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);
                int nwrite, data_size = strlen(buf);
                n = data_size;
                while (n > 0) {
                    nwrite = write(fd, buf + data_size - n, n);
                    if (nwrite < n) {
                        if (nwrite == -1 && errno != EAGAIN) {
                            perror("write error");
                        }
                        break;
                    }
                    n -= nwrite;
                }
                close(fd);
            }
        }
    }

    return 0;
}
复制代码

  epoll模式的特点: 
1.支持进程打开的最大文件描述符,很好的解决了C10K问题;
2.IO效率不随FD数目增加而线性下降,epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间;
3.使用mmap加速内核与用户空间的消息传递。
4.内核微调

3.总结

      在连接少而且都活跃的情况下,使用select模型效果很好;而epoll在高并发量的处理上表现更优秀。

你可能感兴趣的:(select和epoll网络模型)