Linux学习笔记之高级IO相关接口(select、poll、epoll服务器)

对于一个文件描述符,默认都是阻塞IO

非阻塞IO

fcntl函数原型

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ..../* arg */ );

传入的cmd的值不同, 后⾯面追加的参数也不相同.

fcntl函数有5种功能:

  • 复制一个现有的描述符(cmd=F_DUPFD).
  • 获得/设置文件描述符标记(cmd=FGETFD 或 FSETFD).
  • 获得/设置文件状态标记(cmd=FGETFL 或 FSETFL).
  • 获得/设置异步I/O所有权(cmd=FGETOWN 或 FSETOWN).
  • 获得/设置记录锁(cmd=FGETLK,FSETLK或F_SETLKW). 

这里我们只是用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞

该段代码是使用轮询方式读取标准输入

 3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include <stdio.h>
  6 
  7 void SetNonBlock(int fd)
  8 {
  9     int fl = fcntl(fd, F_GETFL);
 10     if( fl < 0 ){
 11         perror("fcntl");
 12         return;
 13     }
 14     fcntl(fd, F_SETFL, fl | O_NONBLOCK);
 15 }
 16 
 17 int main()
 18 {
 19     SetNonBlock(0); //将标准输入文件描述符设置为非阻塞
 20     while(1){  //循环读取,即轮询
 21         char buf[1024];
 22         ssize_t read_size = read(0, buf, sizeof(buf)-1);
 23         if( read_size < 0 ){
 24             perror("read");
 25             sleep(1);
 26             continue;
 27         }
 28         printf("input:%s\n", buf);
 29     }
 30     return 0;
 31 }

那么此处我们可以引出一个概念,重定向

重定向

1.dup/dup2系统调用

函数原型

#include <unistd.h>

int dup(int oldfd);

int dup2(int oldfd, int newfd);

使用·dup将标准输出重定向到文件中

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4 
  5 int main()
  6 {   
  7     int fd = open("./log", O_CREAT | O_RDWR);
  8     if( fd < 0 ){
  9         perror("open");
 10         return 1;
 11     }
 12     close(1);
 13     int new_fd = dup(fd);
 14     if( new_fd != 1 ){
 15         perror("dup");
 16         return 2;
 17     }
 18     printf("new_fd: %d\n", new_fd);
 19     close(fd);
 20     
 21     for(;;){ 
 22         char buf[1024] = {0};
 23         ssize_t read_size = read(0, buf, sizeof(buf)-1);
 24         if(read_size < 0){
 25             perror("read");
 26             continue;
 27         }
 28         printf("%s", buf);
 29         fflush(stdout);
 30     }
 31     close(new_fd);
 32     return 0;
 33 }

运行结果如下:

使用dup2将标准输出重定向到文件中

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
        int fd = open("./log", O_CREAT | O_RDWR);
        if( fd < 0 ){
                perror("open");
                return 1;
        }
        close(1);

        dup2(fd, 1); //newfd为1,是fd的拷贝,所以1里面存放的是fd的内容
        for(;;){
                char buf[1024];
                ssize_t read_size = read(0, buf, sizeof(buf));
                if( read_size < 0 ){
                        perror("read");
                        break;
                }
                printf("%s", buf);
                fflush(stdout);
        }
        close(fd);
        return 0;
}

IO多路转接之select

首先我们要明确一点,无论它怎么工作,以何种方式工作,它的目的只有一个那就是等(等多个文件描述符),即它最根本的工作机制就是等待内核将数据准备好,直到被监视的多个文件描述符中的一个或者多个状态发生了改变

函数原型:

   #include <sys/select.h>
   int select(int nfds,   fd_set *readfds,   fd_set *writefds,  fd_set *exceptfds,   struct timeval *timeout);
 

参数解释:

  • 参数nfds是需要监视的最大的⽂文件描述符值+1;
  • rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;

 关于fd_set   

其实这个结构就是一个整数数组, 更严格的说, 是一个 "位图". 使用位图中对应的位来表示要监视的文件描述 符. 提供了一组操作fd_set的接口, 来比较方便的操作位图


 void FD_CLR(int fd, fd_set *set);      // 用来清除描述词组set中相关fd 的位  

 int  FD_ISSET(int fd, fd_set *set);    // 用来测试描述词组set中相关fd 的位是否为真  

void FD_SET(int fd, fd_set *set);      // 用来设置描述词组set中相关fd的位

 void FD_ZERO(fd_set *set);           // 用来清除描述词组set的全部位


 

  • 参数timeout为结构timeval,用来设置select()的等待时间 

timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

  • 执行成功则返回文件描述词状态已改变的个数
  • 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
  • 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout 的值变成不可预测。 

select使用实例1:检测标准输入

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>

int main()
{
        fd_set read_fds;
        FD_ZERO(&read_fds); //将所有的文件描述符集合清空
        FD_SET(0, &read_fds); //将文件描述符fd设置为0,表示关心标准输入上的读事件

        for(;;){
                printf(">");
                fflush(stdout);
                int ret = select(1, &read_fds, NULL, NULL, NULL);//其他为设置为空,表示只关心rfds上的读事件
                if( ret < 0 ){
                        perror("select");
                        continue;
                }
                if(FD_ISSET(0, &read_fds)){ //测试当rfds的位为真时,表示可以读取
                        char buf[1024];
                        read(0, buf, sizeof(buf)-1);
                        printf("input:%s", buf);
                }else{
                        printf("error! invalid fd\n");
                        continue;
                }
                FD_ZERO(&read_fds);
                FD_SET(0, &read_fds);
        }
        return 0;
}

select使用示例:使用select编写网络服务器

服务器:

#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void init(int* fd_list, int fd_list_size)
{
        int i = 0;
        for( ; i < fd_list_size; ++i){
                fd_list[i] = -1;
        }
}

void Reload(int sock,int*  connect_list, int connect_list_size, fd_set* read_fds, int* max_fd)
{
        int i = 0;
        FD_ZERO(read_fds);
        FD_SET(sock, read_fds);
        int max = sock; //初始时只有sock,所以max等于sock
        for(i = 0 ; i < connect_list_size; ++i){
                if(connect_list[i] != -1){
                        FD_SET(connect_list[i], read_fds);//将connect_list这个数组中该元素指向的内容也就是对应的文件描述符加入read_fds
                        if(connect_list[i] > max)
                                max = connect_list[i];  //同时记得比较后更新max
                }
        }
        *max_fd = max;
}

void Add(int connect_fd,int*  connect_list, int connect_list_size)
{
        int i = 0;
        for(i = 0 ; i < connect_list_size; ++i)
        {
                if(connect_list[i] != -1){
                        connect_list[i] = connect_fd;
                        break;
                }
        }
}


int main( int argc, char* argv[] )
{
        if(argc != 3){
                printf("Usage: ./server [ip] [port]\n");
                return 1;
        }
        struct sockaddr_in addr;
        addr.sin_port = htons(atoi(argv[2]));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(argv[1]);

        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if(sock < 0){
                perror("socket");
                return 1;
        }

        int bind_fd = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
        if(bind_fd < 0){
                perror("bind");
                return 2;
        }

        bind_fd = listen(sock, 5);
        if(bind_fd < 0){
                perror("listen");
                return 3;
        }
        fd_set read_fds;
        int fd_list[1024];
        init(fd_list, sizeof(fd_list)/sizeof(int));
        for(;;){
                int max_fd = sock;
                Reload(sock, fd_list, sizeof(fd_list)/sizeof(int), &read_fds, &max_fd);
                printf("before select: %d\n", FD_ISSET(sock, &read_fds)); //FD_ISSET测试read_fds中的sock是否为真
                                                                                                                    82,3-17       53%
                int ret = select(max_fd+1, &read_fds, NULL, NULL, NULL);
                printf("after select: %d\n", FD_ISSET(sock, &read_fds));
                if(ret < 0){
                        perror("select");
                        continue;
                }
                if(ret == 0){
                        printf("select timeout\n");
                }
                //处理sock
                if( FD_ISSET(sock, &read_fds)){
                        struct sockaddr_in client_addr;
                        socklen_t len = sizeof(client_addr);
                        int connect_fd = accept(sock, (struct sockaddr*)&client_addr, &len); //服务器端接受请求
                        if(connect_fd < 0){
                                perror("accept");
                                continue;
                        }
                        printf("client %s: %d connect\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                        Add(connect_fd, fd_list, sizeof(fd_list)/sizeof(int));
                }
                //处理connect_fd
                size_t i = 0;
                for(i = 0; i < sizeof(fd_list)/sizeof(int); ++i){
                        if(fd_list[i] == -1){
                                continue;
                        }
                        if(!FD_ISSET(fd_list[i], &read_fds))
                                continue;
                        char buf[1024];
                        ssize_t read_size = read(fd_list[i], buf, sizeof(buf)-1);
                        if(read_size < 0){
                                perror("read");
                                continue;
                        }
                        if(read_size == 0){
                                printf("client say: goodbuye!\n");
                                fd_list[i] = -1;
                        }
                        printf("client say: %s", buf);
                        write(fd_list[i], buf, strlen(buf));
                }
        }
        return 0;
}
                                                                                                                    

客户端

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void Usage()
{
        printf("Usage: ./client [ip] [port] \n");
}

int main(int argc, char* argv[])
{
        if(argc != 3){
                Usage();
                return 1;
        }
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(atoi(argv[2]));
        addr.sin_addr.s_addr = inet_addr(argv[1]);

        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if(sock < 0){
                perror("socket");
                return 2;
        }
        int ret =  connect(sock, (struct sockaddr*)&addr, sizeof(addr));
        if(ret < 0){
                perror("connect");
                return 3;
        }
        for(;;){
                printf(">");
                fflush(stdout);
                char buf[1024];
                read(0, buf, sizeof(buf)-1);
                ssize_t write_size = write(sock, buf, strlen(buf)-1);
                if(write_size < 0){
                        perror("wirte");
                        continue;
                }
                ssize_t read_size = read(sock, buf, sizeof(buf)-1);
                if(read_size < 0){
                        perror("read");
                        continue;
                }
                if(read_size == 0){
                        printf("server close\n");
                        break;
                }
                printf("server say: %s\n", buf);
        }
        close(sock);
        return 0;
}

IO多路转接之poll

poll函数接口

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构

struct pollfd {  

int   fd;         /* file descriptor */  

short events;     /* requested events */  

short revents;    /* returned events */

};
参数说明:

  • fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集 合, 返回的事件集合.
  • nfds表示fds数组的长度.
  • timeout表示poll函数的超时时间, 单位是毫秒(ms)

返回结果:

  • 返回值小于0, 表示出错;
  • 返回值等于0, 表示poll函数等待超时;
  • 返回值大于0, 表⽰示poll由于监听的文件描述符就绪而返回.
     

poll使用实例1:检测标准输入

#include <poll.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    struct pollfd poll_fd;
    poll_fd.fd = 0;  //将文件描述符设置为0,即标准输入
    poll_fd.events = POLLIN; //events事件设置为数据可读

    for(;;){
        int ret = poll(&poll_fd, 1, 1000);
        if(ret < 0){
            perror("poll");
            continue;
        }
        if(ret == 0){
            printf("poll timeour\n");
            continue;
        }
        else{ //代表poll监听的文件描述符就绪
            if(poll_fd.revents == POLLIN)
             {
                 char buf[1024];
                 read(0, buf, sizeof(buf)-1);
                 printf("stdin: %s", buf);
             }
        }
    }
                                    

pollt使用实例2:使用poll编写网络服务器

/*************************************************************************
        > File Name: server.c
        > Author: 
        > Mail: 
        > Created Time: Sun 22 Jul 2018 08:46:19 PM PDT
 ************************************************************************/

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct pollfd pollfd;

void Init(pollfd* fd_list, int size)
{
    int i = 0;
    for(i = 0 ; i < size; ++i )
    {
        fd_list[i].fd = -1;
        fd_list[i].events = 0;
        fd_list[i].revents = 0;
    }
}

void Add(int fd, pollfd* fd_list, int size)
{
    int i = 0;
    for(i = 0 ; i < size; ++i)
    {
        if(fd_list[i].fd != -1)
        {
            fd_list[i].fd = fd;
            fd_list[i].events = POLLIN;
            break;
        }
    }
}

int main( int argc, char* argv[] )
{
    if(argc != 3){
        printf("Usage: ./server [ip] [port]\n");
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
        perror("socket");
        return 1;
    }
    int ret = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0){
        perror("bind");
        return 2;
    }

    ret = listen(sock, 5);
    if(ret < 0){
        perror("listen");
        return 3;
    }
    pollfd fd_list[1024];
    Init(fd_list, sizeof(fd_list)/sizeof(pollfd));
    Add(sock, fd_list, sizeof(fd_list)/sizeof(pollfd));
    for(;;){
        int ret = poll(fd_list, sizeof(fd_list)/sizeof(pollfd), 1000);
        if(ret < 0){
            perror("poll");
            continue;
        }
        if(ret == 0){
            printf("poll timeout\n");
            continue;
        }
        else
        {
            size_t i = 0;
            for(i = 0; i < sizeof(fd_list)/sizeof(pollfd); ++i)
            {
                if(fd_list[i].fd == sock)
                {//处理sock的情况
                    struct sockaddr_in client_addr;
                    socklen_t len = sizeof(client_addr);
                    int connect_fd = accept(sock, (struct sockaddr*)&client_addr, &len);
                    if(connect_fd < 0)
                    {
                        perror("accept");
                        continue;
                    }
                    Add(connect_fd, fd_list, sizeof(fd_list)/sizeof(pollfd));
                }
                else
                { // 处理connect_fd的情况
                    char buf[1024];
                    ssize_t read_size = read(fd_list[i].fd, buf, sizeof(buf)-1);
                    if(read_size < 0)
                    {
                        perror("read");
                        continue;
                    }
                    if(read_size == 0)
                    {
                        printf("client say : goodbye\n");
                        close(fd_list[i].fd);
                        fd_list[i].fd = -1
                    }
                    printf("client say: %s\n", buf);
                   write(fd_list[i].fd, buf, strlen(buf)-1);
                }
            }
        }
    }
    return 0;
}

IO多路转接之epoll

  • 根据man手册说,epoll是为处理大批量句柄而作了改进的poll.

epoll有三个相关的系统调用

epoll_create

int epoll_create(int size); 

创建一个epoll的句柄.

  • 自从linux2.6.8之后,size参数是被忽略的.
  • ⽤完之后, 必须调用close()关闭.
     

epoll_ctl(epoll的事件注册函数. 

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

 

  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件 类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示.

   第二个参数的宏:

1.  EPOLL_CTL_ADD :注册新的fd到epfd中;

    2.EPOLL_CTL_MOD :修改已经注册的fd的监听事件;

    3..EPOLL_CTL_DEL :从epfd中删除⼀一个fd;             

  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事.

events可以是以下几个宏的集合:

  • EPOLLIN : 表⽰对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表⽰对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这⾥里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered) 来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的 话, 需要再次把这个socket加⼊入到EPOLL队列里. 

epoll_wait(收集在epoll监控的事件中已经发送的事件. 

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

  • 参数events是分配好的epoll_event结构体数组.
  • epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这 个events数组中,不会去帮助我们在用户态中分配内存).
  • maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  • 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
  • 如果函数调用成功,返回对应I/O上已准备好的⽂文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败.
     

epollt使用实例:使用epoll编写网络服务器

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>



void ProcessConnect(int sock,int  epoll_fd)
{
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int connect_fd = accept(sock, (struct sockaddr*)&client, &len);
    if(connect_fd < 0){
        perror("accept");
        return;
    }
    printf("client %s: %d connect\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    struct epoll_event ev;
    ev.data.fd = connect_fd;
    ev.events = EPOLLIN;
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, connect_fd, &ev);
    if(ret < 0){
        perror("epoll_ctl");
        return;
    }
    return;
}


void ProcessRequest(int connect_fd, int epoll_fd)
{
    char buf[1024];
    ssize_t read_size = read(connect_fd, buf, sizeof(buf)-1);
    if(read_size < 0){
        perror("read");
        return;
    }
    if(read_size == 0){
        close(connect_fd);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, connect_fd, NULL);
        printf("client say: goodbye\n");
        return;
    }
    else{
        printf("client say; %s", buf);
        write(connect_fd, buf, strlen(buf));
    }
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("Usage: ./server [ip] [port]\n");
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
        perror("socket");
        return 2;
    }

    int bind_fd = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
    if(bind_fd < 0){
        perror("bind");
        return 3;
    }

    bind_fd = listen(sock, 5);
    if(bind_fd < 0){
        perror("listen");
        return 4;
    }

    int epoll_fd = epoll_create(10);
    if(epoll_fd < 0){
        perror("epoll_create");
        return 5;
    }

    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = sock;
    bind_fd = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event);
    if(bind_fd < 0){
        perror("epoll_ctl");
        return 6;
    }

    for(;;){
        struct epoll_event events[10];
        int size = epoll_wait(epoll_fd, events, sizeof(events)/sizeof(events[0]), -1);
        if(size < 0){
            perror("epoll_wait");
            continue;
        }
        if(size == 0){
            printf("epoll timeout\n");
            continue;
        }
        else
        {
            int i = 0;
            for(i = 0; i < size; ++i)
            {
                if(!(events[i].events & EPOLLIN))
                    continue;
                if(events[i].data.fd == sock) //处理sock的情况
                    ProcessConnect(sock, epoll_fd);
                else
                     ProcessRequest(events[i].data.fd, epoll_fd);
            }
        }
    }
    return 0;
}

                                                                      
  • 实际上,epoll有2种工作方式-水平触发(LT)和边缘触发(ET) 
  • 假设我们有一个这样的例子:
  1. 我们已经把一个tcp socket添加到epoll描述符
  2. 这个时候socket的另一端被写入了2KB的数据
  3. 调用epoll_wait,并且它会返回. 说明它已经准备好读取操作 
  4. 调用read, 只读取了1KB的数据
  5. 继续调用epoll_wait...... 

水平触发Level Triggered 工作模式 

epoll默认状态下就是LT工作模式.

  • 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
  • 如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调⽤用 epoll_wait 时, epoll_wait 仍然会立刻返回并通知socket读事件就绪.
  • 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.
  • 支持阻塞读写和非阻塞读写

边缘触发Edge Triggered工作模式 

如果我们在第1步将socket添加到epoll描述符的时候使⽤用了EPOLLET标志, epoll进⼊入ET⼯工作模式.

  • 当epoll检测到socket上事件就绪时, 必须立刻处理.
  • 如上面的例⼦子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调⽤用 epoll_wait 的时 候, epoll_wait 不会再返回
  • 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会.
  • ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采⽤用ET模式使用epoll
  •  只支持非阻塞的读写 

epoll的ET网络服务器

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

void SetNoBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if(fl < 0){
        perror("fcntl");
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}


void ProcessConnect(int sock,int  epoll_fd)
{
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int connect_fd = accept(sock, (struct sockaddr*)&client, &len);
    if(connect_fd < 0){
        perror("accept");
        return;
    }
    printf("client %s: %d connect\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    struct epoll_event ev;
    ev.data.fd = connect_fd;
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, connect_fd, &ev);
    if(ret < 0){
        perror("epoll_ctl");
        return;
    }
    return;
}

ssize_t NoBlockRead(int fd, char* buf, int size)
{
    ssize_t total_size = 0;
    for(;;){
        ssize_t cur_size = read(fd, buf+total_size, 1024);
        total_size += cur_size;
        if(cur_size < 1024 || errno == EAGAIN)
            break;
    }
    buf[total_size] = '\0';
    return total_size;
}

void ProcessRequest(int connect_fd, int epoll_fd)
{
    char buf[1024] = {0};
    ssize_t read_size = NoBlockRead(connect_fd, buf, sizeof(buf)-1);
    if(read_size < 0){
        perror("read");
        return;
    }
    if(read_size == 0){
        close(connect_fd);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, connect_fd, NULL);
        printf("client say: goodbye\n");
        return;
    }
    else{
        printf("client say; %s", buf);
        write(connect_fd, buf, strlen(buf));
    }
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("Usage: ./server [ip] [port]\n");
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
        perror("socket");
        return 2;
    }

    int bind_fd = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
    if(bind_fd < 0){
        perror("bind");
        return 3;
    }

    bind_fd = listen(sock, 5);
    if(bind_fd < 0){
        perror("listen");
        return 4;
    }

    int epoll_fd = epoll_create(10);
    if(epoll_fd < 0){
        perror("epoll_create");
        return 5;
    }

    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = sock;
    bind_fd = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event);
    if(bind_fd < 0){
        perror("epoll_ctl");
        return 6;
    }

    for(;;){
        struct epoll_event events[10];
        int size = epoll_wait(epoll_fd, events, sizeof(events)/sizeof(events[0]), -1);
        if(size < 0){
            perror("epoll_wait");
            continue;
        }
        if(size == 0){
            printf("epoll timeout\n");
            continue;
        }
        else
        {
            int i = 0;
            for(i = 0; i < size; ++i)
            {
                if(!(events[i].events & EPOLLIN))
                    continue;
                if(events[i].data.fd == sock) //处理sock的情况
                    ProcessConnect(sock, epoll_fd);
                else
                     ProcessRequest(events[i].data.fd, epoll_fd);
            }
        }
    }
    return 0;
}

                                                                                                                    157,0-1       Bot

select、poll、epoll的的优缺点总结:

1.工作机制:

三者都是等,即都是就绪事件通知机制

2.默认工作方式

三者默认都是工作在LT模式下,只有poll既可以支持LT,也支持ET模式

select

  • select的文件描述符有上限
  • select代码编写比较麻烦
  • select的服务器性能随着用户的增加有可能降低
  • 每次调用select,都需要手动的设置fd集合,从接口使用角度来说非常不方便
  • 每次调用select,都需要在在内核遍历传递进来的fd,这个开销在fd很多时很大

poll

优点:

  • poll所关心的文件描述符没有上限
  • poll将输入输出的参数进行了分离,即Poll结构包含了要监视的event和发生的revents,不再使用select“参数-值”传达的方式

缺点:

poll中监听的文件描述符数目增多时

  • 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
  • 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.

epoll

  • ⽂文件描述符数目无上限: 通过epoll_ctl()来注册一个文件描述符, 内核中使用红黑树的数据结构来管理所有需要监控的文件描述符.
  • 基于事件的就绪通知方式: ,一旦被监听的某个文件描述符就绪, 内核会采用类似于callback的回调机制, 迅速激活这个文件描述符. 这样随着文件描述符数量的增加, 也不会影响判定就绪的性能;
  • 维护就绪队列: 当文件描述符就绪, 就会被放到内核中的一个就绪队列中. 这样调用epoll_wait获取 epoll的就绪文件描述符的时候, 只要取队列中的元素即可, 操作的时间复杂度是O(1);

epoll工作原理

 

 

 

 

原文链接:加载失败,请重新获取