• Linux之高级IO相关内容整理分析


    五种IO

    五种IO

    IO=等+拷贝数据
    什么叫做高效IO?
    高效的IO本质:减少单位时间内,“等”的比重!
    IO话题:
    a.改变等待的方式(为什么)
    b.减少等的比重

    IO:讨论如何输入输出

    阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式
    在这里插入图片描述

    非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码.
    非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用
    在这里插入图片描述

    信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作
    在这里插入图片描述

    IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态
    在这里插入图片描述

    异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
    在这里插入图片描述
    小结
    任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

    高级IO相关重要概念

    同步通信 vs 异步通信(synchronous communication/ asynchronous communication)
    同步和异步关注的是消息通信机制.
    所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;
    异步则是相反, 调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后, 被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.
    另外, 我们回忆在讲多进程多线程的时候, 也提到同步和互斥. 这里的同步通信和进程之间的同步是完全不想干的概念.
    进程/线程同步也是进程/线程之间直接的制约关系
    是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系. 尤其是在访问临界资源的时候

    阻塞 vs 非阻塞
    阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
    阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回.
    非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

    补充知识:
    什么叫做 等事件 就绪?IO事件就绪
    读事件就绪
    写事件就绪

    非阻塞IO

    fcntl
    一个文件描述符, 默认都是阻塞IO.
    函数原型如下.

    #include 
    #include 
    int fcntl(int fd, int cmd, ... /* arg */ );
    
    • 1
    • 2
    • 3

    传入的cmd的值不同, 后面追加的参数也不相同.
    fcntl函数有5种功能:
    复制一个现有的描述符(cmd=F_DUPFD) .
    获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
    获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
    获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
    获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
    第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞

    在这里插入图片描述

    在非阻塞的情况下,我们读取数据,如果数据没有就绪,系统是以出错的形式返回的(不是错误),没有就绪和真正的出错,使用的是同样的方式标识,去和进一步区分呢? errno

    查看所有报错:

    vim /usr/include/asm-generic/errno-base.h 
    
    • 1

    在这里插入图片描述

    I/O多路转接之select

    初识select

    select定位:只负责等,得到fd就绪,通知上层进行读取或者写入
    select没有读取和写入数据的功能。
    read,write,recv,send本身也有等待的功能!(只能传入一个fd)
    select能够等待多个fd!

    系统提供select函数来实现多路复用输入/输出模型.
    select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
    程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

    select的核心功能:
    读为例:
    1.用户告知内核,OS要帮我关心那些fd上的读事件就绪
    2.内核告知用户,OS关心的那些fd上的读事件已经就绪

    select函数原型

    在这里插入图片描述

    参数解释:
    参数nfds是需要监视的最大的文件描述符值+1;
    rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
    参数timeout为结构timeval,用来设置select()的等待时间
    参数timeout取值:
    NULL:阻塞
    timeout={0,0};非阻塞
    timeout={5,0};五秒超时

    1.只要不就绪,我就不返回
    2.只要不就绪,立马返回
    3.设置好deadline(最晚时间),deadline之内遵守1,deadline之外遵守2;之内到之外这种行为叫做timeout
    三者统一,只要就绪,立马返回

    struct timeval{
    	long tv_sec;//秒
    	long tv_usec;//微秒
    };
    
    • 1
    • 2
    • 3
    • 4

    函数返回值:
    执行成功则返回文件描述词状态已改变的个数
    如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
    当有错误发生时则返回-1,错误原因存于errno,此时参数readfds, writefds, exceptfds和timeout的值变成不可预测。
    错误值可能为:
    EBADF 文件描述词为无效的或该文件已关闭
    EINTR 此调用被信号所中断
    EINVAL 参数n 为负值。
    ENOMEM 核心内存不足

    select执行过程

    理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节, fd_set中的每一bit可以对应一个文件描符fd。则1字节长的fd_set最大可以对应8个fd.
    (1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
    (2)若fd= 5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
    (3)若再加入fd= 2, fd=1,则set变为0001,0011
    (4)执行select(6,&set,0,0,0)阻塞等待
    (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空

    select因为使用输入输出型参数标识不同的含义,意味着后面每一次都需要对fd_set进行重新设置!
    ->你的程序怎么知道,你拥有的fd!用户必须定义数组或者其他容器结构,将历史fd保存

    代码基础框架

    client向server发送消息的服务
    Sock.hpp

    #pragma once
    
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    
    class Sock
    {
    public:
        static int Socket()
        {
            int sock = socket(AF_INET,SOCK_STREAM,0);
            if(sock < 0)
            {
                cerr<<"socket error"<<endl;
                exit(2);
            }
            return sock;
        }
    
        static void Bind(int sock,uint16_t port)
        {
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(port);
            local.sin_addr.s_addr = INADDR_ANY;
    
            if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                cerr<<"bind error!"<<endl;
                exit(3);
            }
            
        }
    
        static void Listen(int sock)
        {
            if(listen(sock,5)<0)
            {
                cerr<<"listen error!"<<endl;
                exit(4);
            }
        }
    
        static int Accept(int sock)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = accept(sock,(struct sockaddr*)&peer,&len);
            if(fd>0)
            {
                return fd;
            }
            else return -1;
        }
    
        static void Connect(int sock,std::string ip,uint16_t port)
        {
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
    
            server.sin_family = AF_INET;
            server.sin_port = htons(port);
            server.sin_addr.s_addr  = inet_addr(ip.c_str());
    
            if(connect(sock,(struct sockaddr*)&server,sizeof(server)) == 0)
            {
                cout<<"connect success!"<<endl;
            }
            else{
                cout<<"connect failed!"<<endl;
                exit(5);
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    select_server.cc

    #include 
    #include 
    #include 
    #include "Sock.hpp"
    
    #define NUM (sizeof(fd_set)*8)
    
    //内容>=0,合法的fd,如果是-1,该位置没有fd
    int fd_array[NUM];
    
    void Usage(std::string proc)
    {
        std::cout << "Usage" << proc << "port" << std::endl;
    }
    
    // ./Select_server 端口号
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            exit(1);
        }
    
        u_int16_t port = (u_int16_t)atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
        memset(fd_array,0,sizeof(fd_array));
    
        // accept本质是通过listen_sock获取新链接
        //前提是listen_sock上面有新链接,accept不知道有新链接
        // accept阻塞式等待
        //站在多路转接的视角,我们认为链接到来,对于listen_sock,就是读事件就绪!
        //对于所有的服务器,最开始的时候,只有listen_sock
    
        //事件循环
        fd_set rfds;
        fd_array[0]=listen_sock;
        for (;;)
        {
            FD_ZERO(&rfds);
            int max_fd=fd_array[0]; 
            for(int i = 0;i<NUM;i++)
            {
                if(fd_array[i]==-1)
                    continue;
                
                //下面的都是合法的fd
                FD_SET(fd_array[i],&rfds);//所有要关心读事件的fd,添加到rfds中
                if(max_fd<fd_array[i])
                {
                    max_fd=fd_array[i];//更新最大fd
                }
            }
    
    
            struct timeval timeout = {1,0}; 
            //服务器上的所有fd,包括listen_sock,都要交给select检测!
            //recv,read,write,send,accept,只负责自己最核心的工作:真正的读写(listen_sock:accept)!
            int n = select(max_fd+1,&rfds,nullptr,nullptr,&timeout);//第三个nullptr暂时阻塞方式 
            switch (n)
            {
            case -1:
                std::cerr << "select 失败!" << std::endl;
                break;
            case 0:
                std::cerr << "select timeout" << std::endl;
                break;
            default:
                std::cout << "有fd对应的事件就绪" << std::endl;
                break;
            }
        }
    
        // std::cout<
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    输出描述:
    timeout = {1,0};
    每间隔一秒打印一次
    在这里插入图片描述
    当timeout设为:nullptr
    在网页部分输入你的服务器ip+端口号,就会:
    在这里插入图片描述
    timeout = {0,0};
    就会无限循环的输出
    select timeout

    补充完善

    select_server.cc

    #include 
    #include 
    #include 
    #include "Sock.hpp"
    
    #define NUM (sizeof(fd_set) * 8)
    
    //内容>=0,合法的fd,如果是-1,该位置没有fd
    int fd_array[NUM];
    
    void Usage(std::string proc)
    {
        std::cout << "Usage" << proc << "port" << std::endl;
    }
    
    // ./Select_server 端口号
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            exit(1);
        }
    
        u_int16_t port = (u_int16_t)atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
        memset(fd_array, 0, sizeof(fd_array));//全部初始化为零,即:里面全部被占用
    
        // accept本质是通过listen_sock获取新链接
        //前提是listen_sock上面有新链接,accept不知道有新链接
        // accept阻塞式等待
        //站在多路转接的视角,我们认为链接到来,对于listen_sock,就是读事件就绪!
        //对于所有的服务器,最开始的时候,只有listen_sock
    
        //事件循环
        fd_set rfds;
        fd_array[0] = listen_sock;
        for (;;)
        {
            FD_ZERO(&rfds);
            int max_fd = fd_array[0];
            for (int i = 0; i < NUM; i++)
            {
                if (fd_array[i] == -1)
                    continue;
    
                //下面的都是合法的fd
                FD_SET(fd_array[i], &rfds); //所有要关心读事件的fd,添加到rfds中
                if (max_fd < fd_array[i])
                {
                    max_fd = fd_array[i]; //更新最大fd
                }
            }
    
            struct timeval timeout = {1, 0};
            //服务器上的所有fd,包括listen_sock,都要交给select检测!
            // recv,read,write,send,accept,只负责自己最核心的工作:真正的读写(listen_sock:accept)!
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr); //第三个nullptr暂时阻塞方式
            switch (n)
            {
            case -1:
                std::cerr << "select 失败!" << std::endl;
                break;
            case 0:
                std::cerr << "select timeout" << std::endl;
                break;
            default:
                std::cout << "有fd对应的事件就绪" << std::endl;
                for (int i = 0; i < NUM; i++)
                {
                    if (fd_array[i] == -1)
                        continue;
                    //下面的fd都是合法的fd,合法的fd不一定是就绪的fd
                    if (FD_ISSET(fd_array[i], &rfds))
                    {
                        std::cout << "sock:" << fd_array[i] << "上面有了读事件,可以读取了!" << std::endl;
                        //一定是读事件就绪了!
                        //该就绪的fd就在fd_array[i]保存!
                        // read,recv一定不会被阻塞
                        //读事件就绪,就一定可以recv,read吗?不一定,
                        //读时间就绪,listen_sock就绪,也是以读事件就绪返回的
                        if (fd_array[i] == listen_sock)
                        {
                            std::cout << "listen_sock:" << listen_sock << "有了新的链接到来" << std::endl;
                            // accept
                            int sock = Sock::Accept(listen_sock);
                            if (sock >= 0)
                            {
                                std::cout << "listen_sock:" << listen_sock << "获取新的链接成功" << std::endl;
                                //获取成功
                                //新链接到来,不意味着有数据到来!
                                // select知道那些fd可以被读取!
                                //无法将fd设置进select,但是,我们有fd_array[]!
                                int pos=1;
                                for (; pos < NUM; pos++)
                                {
                                    //这个位置没有被使用!
                                    if (fd_array[pos] == -1)
                                        break;
                                }
                                // 1.找到一个位置没有被使用
                                // 2.找完了所有的fd_array[],都没有找到没有被使用的位置
                                if (pos < NUM) // 1
                                {
                                    std::cout << "新链接:" << sock << "已经被添加到了数组[" << pos << "]的位置" << std::endl;
                                    fd_array[pos] = sock;
                                }
                                else // 2
                                {
                                    //说明服务器已经满载,无法处理新的请求
                                    std::cout<<"服务器满载,关闭新的链接"<<std::endl;
                                    close(sock);
                                }
                            }
                        }
                        else
                        {
                            // read,recv
                        }
                    }
                }
                break;
            }
        }
    
        // std::cout<
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130

    在这里插入图片描述

    修改代码:

        //memset(fd_array, 0, sizeof(fd_array));//全部初始化为零,即:里面全部被占用
        for(int j =0;j<NUM;j++)//全部初始化为-1
        {
            fd_array[j]=-1;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    完整代码与测试(select完整框架)

    select_server.cc

    #include 
    #include 
    #include 
    #include "Sock.hpp"
    
    #define NUM (sizeof(fd_set) * 8)
    
    //内容>=0,合法的fd,如果是-1,该位置没有fd
    int fd_array[NUM];
    
    void Usage(std::string proc)
    {
        std::cout << "Usage" << proc << "port" << std::endl;
    }
    
    // ./Select_server 端口号
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            exit(1);
        }
    
        u_int16_t port = (u_int16_t)atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
        //memset(fd_array, 0, sizeof(fd_array));//全部初始化为零,即:里面全部被占用
        for(int j =0;j<NUM;j++)//全部初始化为-1
        {
            fd_array[j]=-1;
        }
    
    
        // accept本质是通过listen_sock获取新链接
        //前提是listen_sock上面有新链接,accept不知道有新链接
        // accept阻塞式等待
        //站在多路转接的视角,我们认为链接到来,对于listen_sock,就是读事件就绪!
        //对于所有的服务器,最开始的时候,只有listen_sock
    
        //事件循环
        fd_set rfds;
        fd_array[0] = listen_sock;
        for (;;)
        {
            FD_ZERO(&rfds);
            int max_fd = fd_array[0];
            for (int i = 0; i < NUM; i++)
            {
                if (fd_array[i] == -1)
                    continue;
    
                //下面的都是合法的fd
                FD_SET(fd_array[i], &rfds); //所有要关心读事件的fd,添加到rfds中
                if (max_fd < fd_array[i])
                {
                    max_fd = fd_array[i]; //更新最大fd
                }
            }
    
            struct timeval timeout = {1, 0};
            //服务器上的所有fd,包括listen_sock,都要交给select检测!
            // recv,read,write,send,accept,只负责自己最核心的工作:真正的读写(listen_sock:accept)!
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr); //第三个nullptr暂时阻塞方式
            switch (n)
            {
            case -1:
                std::cerr << "select 失败!" << std::endl;
                break;
            case 0:
                std::cerr << "select timeout" << std::endl;
                break;
            default:
                std::cout << "有fd对应的事件就绪" << std::endl;
                for (int i = 0; i < NUM; i++)
                {
                    if (fd_array[i] == -1)
                        continue;
                    //下面的fd都是合法的fd,合法的fd不一定是就绪的fd
                    if (FD_ISSET(fd_array[i], &rfds))
                    {
                        std::cout << "sock:" << fd_array[i] << "上面有了读事件,可以读取了!" << std::endl;
                        //一定是读事件就绪了!
                        //该就绪的fd就在fd_array[i]保存!
                        // read,recv一定不会被阻塞
                        //读事件就绪,就一定可以recv,read吗?不一定,
                        //读时间就绪,listen_sock就绪,也是以读事件就绪返回的
                        if (fd_array[i] == listen_sock)
                        {
                            std::cout << "listen_sock:" << listen_sock << "有了新的链接到来" << std::endl;
                            // accept
                            int sock = Sock::Accept(listen_sock);
                            if (sock >= 0)
                            {
                                std::cout << "listen_sock:" << listen_sock << "获取新的链接成功" << std::endl;
                                //获取成功
                                //新链接到来,不意味着有数据到来!
                                // select知道那些fd可以被读取!
                                //无法将fd设置进select,但是,我们有fd_array[]!
                                int pos=1;
                                for (; pos < NUM; pos++)
                                {
                                    //这个位置没有被使用!
                                    if (fd_array[pos] == -1)
                                        break;
                                }
                                // 1.找到一个位置没有被使用
                                // 2.找完了所有的fd_array[],都没有找到没有被使用的位置
                                if (pos < NUM) // 1
                                {
                                    std::cout << "新链接:" << sock << "已经被添加到了数组[" << pos << "]的位置" << std::endl;
                                    fd_array[pos] = sock;
                                }
                                else // 2
                                {
                                    //说明服务器已经满载,无法处理新的请求
                                    std::cout<<"服务器满载,关闭新的链接"<<std::endl;
                                    close(sock);
                                }
                            }
                        }
                        else
                        {
                            //普通的sock事件就绪!可以进行读取recv,read。 
                            //本次读取不一定能读完,读完,也不一定没有所谓的数据包粘包问题
                            std::cout<<"sock:"<<fd_array[i]<<"上面有普通读取"<<std::endl;
                            char recv_buffer[1024]={0};
                            ssize_t s=recv(fd_array[i],recv_buffer,sizeof(recv_buffer)-1,0);
                            if(s>0)
                            {
                                recv_buffer[s]=0;
                                std::cout<<"client["<< fd_array[i]<<"]#"<<recv_buffer<<std::endl;
                            }
                            else if(s==0)//网络读取时,0代表对端链接关闭
                            {
                                std::cout<<"sock:"<<fd_array[i]<<"关闭了链接,客户端退出了!"<<std::endl;
                               
                                //四次挥手,对方关闭了,我们也要关闭
                                close(fd_array[i]);
                              
                                //从数组中将这个文件描述符去掉
                                fd_array[i]=-1;
                                std::cout<<"已经在数组中fd_array["<<i<<"]中去掉了sock:"<<fd_array[i]<<std::endl;
                            }
                            else{
                                //读取失败 
                                close(fd_array[i]);
                                fd_array[i]=-1;
                                std::cout<<"已经在数组中fd_array["<<i<<"]中去掉了sock:"<<fd_array[i]<<std::endl;
                            }
                        }
                    }
                }
                break;
            }
        }
    
        // std::cout<
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    在这里插入图片描述
    延伸补充:
    客户端如何关闭链接?
    ctrl+】
    quit

    总结

    优点:
    可以一次等待多个fd,可以让等待的时间重叠,在一定程度可以提高IO的效率。
    缺点:
    1.每次都要重新设置,每次完成之后,需要遍历检测
    2.fd_set,它能够让select同时检测的fd,是有上限的
    3.select底层需要轮询式的检测那些fd上的那些时间就绪了
    4.频繁就绪,会发生频繁拷贝,即:select可能会较为高频的进行用户到内核,内核到用户的频繁拷贝问题

    socket就绪条件

    读就绪
    socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
    socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
    监听的socket上有新的连接请求;
    socket上有未处理的错误

    写就绪
    socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
    socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
    socket使用非阻塞connect连接成功或失败之后;
    socket上有未读取的错误

    异常就绪
    socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(回忆TCP协议头中, 有一个紧急指针的字段)

    I/O多路转接之poll [选学]

    poll函数接口

    在这里插入图片描述

    参数说明
    fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
    nfds表示fds数组的长度.
    timeout表示poll函数的超时时间, 单位是毫秒(ms).
    0:永远等待
    timeout大于0:timeout以内阻塞等待,timeout以外的返回一次
    -1:永久阻塞

    events和revents的取值:
    在这里插入图片描述
    在这里插入图片描述
    返回结果
    返回值小于0, 表示出错;
    返回值等于0, 表示poll函数等待超时;
    返回值大于0, 表示poll由于监听的文件描述符就绪而返回

    poll测试代码:

    #include 
    #include 
    #include 
    
    int main()
    {
        struct pollfd rfds;
        rfds.fd = 0;
        rfds.events = POLLIN;
        rfds.revents = 0;
    
        while (true)
        {
            int n = poll(&rfds, 1, 1000);
            switch (n)
            {
            case 0:
                std::cout << "time out..." << std::endl;
                break;
            case -1:
                std::cerr << "poll error!" << std::endl;
                break;
            default:
                std::cout << "有事件发生!" << std::endl;
                if (rfds.revents & POLLIN)
                {
                    char buffer[128];
                    std::cout << rfds.fd << "上面的读时间发生了!" << std::endl;
                    ssize_t s = read(0, buffer, sizeof(buffer) - 1);
                    if (s > 0)
                    {
                        std::cout << rfds.fd << "说:" << buffer << std::endl;
                    }
                }
                break;
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在这里插入图片描述

    poll总结

    优点:
    不同与select使用三个位图来表示三个fdset的方式, poll使用一个pollfd的指针实现.
    pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更方便
    poll并没有最大数量限制 (但是数量过大后性能也是会下降)
    poll解决了select因为位图,而导致检测的文件描述符有上限的问题

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

    I/O多路转接之epoll

    epoll初识

    按照man手册的说法: 是为处理大批量句柄而作了改进的poll.
    它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)
    它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法.

    epoll是干什么的?
    等,等是手段,通过用户设置的某些fd及其事件,告知内核,让内核用户关心,一旦就绪,通知上层

    epoll的相关系统调用

    在这里插入图片描述

    epoll接口测试

    最基础框架

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "Sock.hpp"
    
    #define SIZE 128
    #define NUM 64
    
    void Usage(std::string proc)
    {
        std::cerr << "Usage:" << proc << "port" << std::endl;
    }
    // ./Epoll_server port
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            exit(1);
        }
        // 1.建立tcp监听socket
        u_int16_t port = (u_int16_t)atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
    
        // 2.创建epoll模型,获得epfd(文件描述符)
        int epfd = epoll_create(SIZE);
        std::cout << "fd:" << epfd << std::endl; // 3
    
        // 3.将listen_sock及其他所关心的事件添加到内核
        struct epoll_event ev;
        ev.events = EPOLLIN;
        // ev.data
        epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);
    
        // 4.事件循环
        volatile bool quit = false;
        struct epoll_event revs[NUM];
        while (!quit)
        {
            int timeout = 1000;
            //这里传入的数组,仅仅是尝试从内核中拿回来已经就绪的事件
            int n = epoll_wait(epfd, revs, NUM, timeout); // n:就绪事件个数,所有就绪事件按照下标有序放进revs中
            switch (n)
            {
            case 0:
                std::cout << "time out..." << std::endl;
                break;
            case -1:
                std::cerr << "epoll error..." << std::endl;
                break;
            default:
                std::cout<<"有事件就绪!"<<std::endl;
                break;
            }
        }
        close(epfd);
        close(listen_sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    测试结果poll select基础框架测试用例类似

    epoll原理

    创建epoll模型,需要创建:红黑树,就绪队列,回调机制

    epoll基础框架

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "Sock.hpp"
    
    #define SIZE 128
    #define NUM 64
    
    void Usage(std::string proc)
    {
        std::cerr << "Usage:" << proc << "port" << std::endl;
    }
    // ./Epoll_server port
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            exit(1);
        }
        // 1.建立tcp监听socket
        u_int16_t port = (u_int16_t)atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
    
        // 2.创建epoll模型,获得epfd(文件描述符)
        int epfd = epoll_create(SIZE);
        // std::cout << "fd:" << epfd << std::endl; // 3
    
        // 3.将listen_sock及其他所关心的事件添加到内核
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = listen_sock;
        epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);
    
        // 4.事件循环
        volatile bool quit = false;
        struct epoll_event revs[NUM];
        while (!quit)
        {
            int timeout = -1;
            //这里传入的数组,仅仅是尝试从内核中拿回来已经就绪的事件
            int n = epoll_wait(epfd, revs, NUM, timeout); // n:就绪事件个数,所有就绪事件按照下标有序放进revs中
            switch (n)
            {
            case 0:
                std::cout << "time out..." << std::endl;
                break;
            case -1:
                std::cerr << "epoll error..." << std::endl;
                break;
            default:
                std::cout << "有事件就绪!" << std::endl;
                // 5.处理就绪事件
                for (int i = 0; i < n; i++)
                {
                    int sock = revs[i].data.fd;
                    std::cout << "文件描述符:" << sock << "上面有事件就绪" << std::endl;
                    if (revs[i].events & EPOLLIN)
                    {
                        std::cout << "文件描述符:" << sock << "读事件就绪" << std::endl;
                        if (sock == listen_sock)
                        {
                            // 5.1处理链接事件
                            std::cout << "文件描述符:" << sock << "链接数据就绪" << std::endl;
                            int fd = Sock::Accept(listen_sock);
                            if (fd >= 0)
                            {
                                std::cout << "获取新链接" << fd << "成功!" << std::endl;
                                //不能立即读取!
                                struct epoll_event _ev;
                                _ev.events = EPOLLIN; //关心读和写EPOLLIN | EPOLLOUT
                                _ev.data.fd = fd;
                                epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &_ev); //新的fd托管给了epoll
                                std::cout << "已经将" << fd << "托管给epoll了!" << std::endl;
                                std::cout << "-----分隔符-----" << std::endl;
                            }
                            else
                            {
                                // do nothing!
                            }
                        }
                        else
                        {
                            // 5.2正常读取处理
                            std::cout << "文件描述符:" << sock << "正常数据就绪" << std::endl;
                            char buffer[1024];
                            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
                            if (s > 0)
                            {
                                buffer[s] = 0;
                                std::cout << "client[" << sock << "]:" << buffer << std::endl;
                            }
                            else if (s == 0)
                            {
                                //对端关闭链接
                                std::cout << "client quit:" << sock << std::endl;
                                close(sock);
                                epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr); //删除事件
                                std::cout << "sock:" << sock << "删除成功!" << std::endl;
                            }
                            else
                            {
                                //读取失败
                                std::cout << "读取失败:" << sock << std::endl;
                                close(sock);
                                epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr); //删除事件
                                std::cout << "sock:" << sock << "删除成功!" << std::endl;
                            }
                            std::cout << "-----分隔符-----" << std::endl;
                        }
                    }
                    else if (revs[i].events & EPOLLOUT)
                    {
                        // 5.3处理写事件
                    }
                    else
                    {
                    }
                }
                break;
            }
        }
        close(epfd);
        close(listen_sock);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    在这里插入图片描述

    epoll工作方式

    epoll有2种工作方式-水平触发(LT)和边缘触发(ET)

    **LT:**当内核区有你的数据,就会一直通知你。
    **ET:**当内核区有你的数据,这些数据从无到有,从有到多,就会只给你发送一次信息。
    优势:通过它的这种通知策略,倒逼程序员一旦开始读取数据,就要一直读完;
    两种通知方式,他们的通知次数是相同的,ET的通知面更多;

    select,poll,epoll默认通知方式:LT

    ET-》通知一次-》recv/accept/read-》准备读取-》如何保证将本次全部读取完毕-》循环读取-》可能会在读取的最后一次卡住(阻塞)-.》单进程-》解决?-》ET模式下的所有fd,将其设置为非阻塞!

    epoll版本计算器

    在这里插入图片描述

    1.我们需要给每一个fd,都要有自己专属的输入输出缓冲区!
    2.虽然已经对等和拷贝在接口层面已经进行了分离,但是在代码逻辑上依旧是耦合在一起的(通过回调的方式进行解耦)
    3.epoll最大的优势在于 就绪事件通知机制!

    详细代码

    Accepter.hpp

    #pragma once
    
    #include "Reactor.hpp"
    #include"Sock.hpp"
    #include"Service.hpp"
    #include"Util.hpp"
    
    int Accepter(Event* evp)
    {
        std::cout<<"有新的链接到来了,就绪的sock是:"<<evp->sock<<std::endl;
        while(true)
        {
            int sock=Sock::Accept(evp->sock);
            if(sock<0){
                std::cout<<"已经将所有的链接读完"<<std::endl;
                break;
            }
            SetNonBlock(sock);//设为非阻塞
            std::cout<<"获取成功"<<sock<<std::endl;
            //获取链接成功
            Event *other_ev= new Event();
            other_ev->sock=sock;
            other_ev->R=evp->R;
            //为什么要让所有的Event指向自己所属的Reactor?
            //recver,sender,errorer就是我们代码中的较顶层,只负责真正的读取!
            other_ev->RegisterCallBack(Recver,Sender,Errorer);
    
            evp->R->InsertEvent(other_ev,EPOLLIN|EPOLLET); 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    epoll_server.cc

    #include"Reactor.hpp"
    #include"Sock.hpp"
    #include"Accepter.hpp"
    #include"Util.hpp"
    
    
    static void Usage(std::string proc)
    {
        std::cerr<<"Usage:"<<proc<<"proc"<<std::endl;
    }
    
    int main(int agrc,char *argv[])
    {
        if(agrc!=2)
        {
            Usage(argv[0]);
            exit(1);
        }
        //1.创建socket,监听
        int listen_sock=Sock::Socket();
        SetNonBlock(listen_sock);//设为非阻塞
        Sock::Bind(listen_sock,(uint16_t)atoi(argv[1]));
        Sock::Listen(listen_sock);
    
        //2.创建Reactor对象
        // Reactor 反应堆模式:通过多路转接方案,被动的采用事件派发的方式,去反向的调用对应的回调函数
        // a.检测到事件--epoll
        // b.派发事件---Dispatcher(事件派发+IO)+业务处理 :半同步半异步的处理方式
        // c.链接--- accept
        // d.IO---recver,sender
        Reactor *R=new Reactor();
        R->InitReactor();
    
        //3.给Reactor反应堆中加柴火
        //3.1有柴火
        Event *evp=new Event;
        evp->sock=listen_sock;
        evp->R=R;
        //Accepter:链接管理器
        evp->RegisterCallBack(Accepter,nullptr,nullptr);
        //3.2将准好的柴火放进反应堆Reactor中
        R->InsertEvent(evp,EPOLLIN | EPOLLET);
    
        //4.开始进行事件派发
        int timeout =1000;
        for(;;)
        {
            R->Dispatcher(timeout);
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    Makefile

    Epoll_server:epoll_server.cc
    	g++ -o $@ $^
    
    .PHONY:clean
    clean:
    	rm -rf Epoll_server
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Reactor.hpp

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    // 一般处理IO的时候,我们只有三种接口需要处理
    // 处理读取
    // 处理写入
    // 处理异常
    #define SIZE 128
    #define NUM 64
    
    class Event;
    class Reactor;
    
    typedef int (*callback_t)(Event *ev); //函数指针
    
    //需要epoll管理的基本节点
    class Event
    {
    public:
        //对应的文件描述符
        int sock;
        //对应的sock,对应的输入输出缓冲区
        std::string inbuffer;
        //对应的sock,对应的输出缓冲区
        std::string outbuffer;
        // sock设置回调
        callback_t recver;
        callback_t sender;
        callback_t errorer;
    
        //试着Event回指Reactor的指针
        Reactor *R;
    
    public:
        Event()
        {
            sock = -1;
            recver = nullptr;
            sender = nullptr;
            errorer = nullptr;
            R = nullptr;
        }
    
        //设置回调
        void RegisterCallBack(callback_t _recver, callback_t _sender, callback_t _errorer)
        {
            recver = _recver;
            sender = _sender;
            errorer = _errorer;
        }
    
        ~Event() {}
    };
    
    //不需要关心任何sock的类型(listen,读,写)
    //如何进行使用该类,对Event进行管理
    // Reactor:Event=1:n
    class Reactor //反应堆模式
    {
    private:
        int epfd;
        std::unordered_map<int, Event *> events; // Epoll类管理的所有的Event的集合
    
    public:
        Reactor() : epfd(-1) {}
        void InitReactor()
        {
            epfd = epoll_create(SIZE);
            if (epfd < 0)
            {
                std::cerr << "epoll_create error!" << std::endl;
                exit(2);
            }
            std::cout << "InitReactor Success!" << std::endl;
        }
        bool InsertEvent(Event *evp, uint32_t evs)
        {
            // 1.将evp中的sock插入到epoll中
            struct epoll_event ev;
            ev.events = evs;
            ev.data.fd = evp->sock;
            if (epoll_ctl(epfd, EPOLL_CTL_ADD, evp->sock, &ev) < 0)
            {
                std::cerr << "EPOLL_CTL_ADD event failed!" << std::endl;
                return false;
            }
    
            // 2.将ev本身插入到unordered_map中
            events.insert({evp->sock, evp});
        }
        void DeleteEvent(Event *evp)
        {
            int sock = evp->sock;
            auto iter = events.find(sock);
            if (iter != events.end())
            {
                //将evp中的sock从epoll中删除
                epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr);
    
                events.erase(iter);
    
                close(sock);
    
                //删除event节点
                delete evp;
            }
        }
    
        //使能读写
        bool EnableRW(int sock, bool enread, bool enwrite)
        {
            struct epoll_event ev;
            ev.events = EPOLLET | (enread ? EPOLLIN : 0) | (enwrite ? EPOLLOUT : 0);
            ev.data.fd = sock;
            if (epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ev) < 0)
            {
                std::cerr << "修改失败" << std::endl;
                return false;
            }
            return true;
        }
    
        //判断当前的sock是否合法
        bool IsSockOK(int sock)
        {
            auto iter=events.find(sock);
            return iter==events.end()?false:true;
        }
    
        //就绪事件派发器
        void Dispatcher(int timeout)
        {
            struct epoll_event revs[NUM];
            int n = epoll_wait(epfd, revs, NUM, timeout);
            for (int i = 0; i < n; i++)
            {
                int sock = revs[i].data.fd;
                uint32_t revents = revs[i].events;
                //代表差错处理,将所有的错误问题全部转化成为让IO函数去解决
                if (revents & EPOLLERR)
                    revents |= (EPOLLIN | EPOLLOUT);
                if (revents & EPOLLHUP)
                    revents |= (EPOLLIN | EPOLLOUT);
    
                //读数据就绪
                if (revents & EPOLLIN)
                {
                    //直接调用回调方法,执行对应的读取
                    if (IsSockOK(sock) &&events[sock]->recver)
                        events[sock]->recver(events[sock]);
                }
                //写数据就绪
                if (revents & EPOLLOUT)
                {
                    if (IsSockOK(sock) && events[sock]->sender)
                        events[sock]->sender(events[sock]);
                }
            }
        }
        ~Reactor() {}
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169

    Service.hpp

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    // 一般处理IO的时候,我们只有三种接口需要处理
    // 处理读取
    // 处理写入
    // 处理异常
    #define SIZE 128
    #define NUM 64
    
    class Event;
    class Reactor;
    
    typedef int (*callback_t)(Event *ev); //函数指针
    
    //需要epoll管理的基本节点
    class Event
    {
    public:
        //对应的文件描述符
        int sock;
        //对应的sock,对应的输入输出缓冲区
        std::string inbuffer;
        //对应的sock,对应的输出缓冲区
        std::string outbuffer;
        // sock设置回调
        callback_t recver;
        callback_t sender;
        callback_t errorer;
    
        //试着Event回指Reactor的指针
        Reactor *R;
    
    public:
        Event()
        {
            sock = -1;
            recver = nullptr;
            sender = nullptr;
            errorer = nullptr;
            R = nullptr;
        }
    
        //设置回调
        void RegisterCallBack(callback_t _recver, callback_t _sender, callback_t _errorer)
        {
            recver = _recver;
            sender = _sender;
            errorer = _errorer;
        }
    
        ~Event() {}
    };
    
    //不需要关心任何sock的类型(listen,读,写)
    //如何进行使用该类,对Event进行管理
    // Reactor:Event=1:n
    class Reactor //反应堆模式
    {
    private:
        int epfd;
        std::unordered_map<int, Event *> events; // Epoll类管理的所有的Event的集合
    
    public:
        Reactor() : epfd(-1) {}
        void InitReactor()
        {
            epfd = epoll_create(SIZE);
            if (epfd < 0)
            {
                std::cerr << "epoll_create error!" << std::endl;
                exit(2);
            }
            std::cout << "InitReactor Success!" << std::endl;
        }
        bool InsertEvent(Event *evp, uint32_t evs)
        {
            // 1.将evp中的sock插入到epoll中
            struct epoll_event ev;
            ev.events = evs;
            ev.data.fd = evp->sock;
            if (epoll_ctl(epfd, EPOLL_CTL_ADD, evp->sock, &ev) < 0)
            {
                std::cerr << "EPOLL_CTL_ADD event failed!" << std::endl;
                return false;
            }
    
            // 2.将ev本身插入到unordered_map中
            events.insert({evp->sock, evp});
        }
        void DeleteEvent(Event *evp)
        {
            int sock = evp->sock;
            auto iter = events.find(sock);
            if (iter != events.end())
            {
                //将evp中的sock从epoll中删除
                epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr);
    
                events.erase(iter);
    
                close(sock);
    
                //删除event节点
                delete evp;
            }
        }
    
        //使能读写
        bool EnableRW(int sock, bool enread, bool enwrite)
        {
            struct epoll_event ev;
            ev.events = EPOLLET | (enread ? EPOLLIN : 0) | (enwrite ? EPOLLOUT : 0);
            ev.data.fd = sock;
            if (epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ev) < 0)
            {
                std::cerr << "修改失败" << std::endl;
                return false;
            }
            return true;
        }
    
        //判断当前的sock是否合法
        bool IsSockOK(int sock)
        {
            auto iter=events.find(sock);
            return iter==events.end()?false:true;
        }
    
        //就绪事件派发器
        void Dispatcher(int timeout)
        {
            struct epoll_event revs[NUM];
            int n = epoll_wait(epfd, revs, NUM, timeout);
            for (int i = 0; i < n; i++)
            {
                int sock = revs[i].data.fd;
                uint32_t revents = revs[i].events;
                //代表差错处理,将所有的错误问题全部转化成为让IO函数去解决
                if (revents & EPOLLERR)
                    revents |= (EPOLLIN | EPOLLOUT);
                if (revents & EPOLLHUP)
                    revents |= (EPOLLIN | EPOLLOUT);
    
                //读数据就绪
                if (revents & EPOLLIN)
                {
                    //直接调用回调方法,执行对应的读取
                    if (IsSockOK(sock) &&events[sock]->recver)
                        events[sock]->recver(events[sock]);
                }
                //写数据就绪
                if (revents & EPOLLOUT)
                {
                    if (IsSockOK(sock) && events[sock]->sender)
                        events[sock]->sender(events[sock]);
                }
            }
        }
        ~Reactor() {}
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169

    Sock.hpp

    #pragma once
    
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    
    class Sock
    {
    public:
        static int Socket()
        {
            int sock = socket(AF_INET,SOCK_STREAM,0);
            if(sock < 0)
            {
                cerr<<"socket error"<<endl;
                exit(2);
            }
            return sock;
        }
    
        static void Bind(int sock,uint16_t port)
        {
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(port);
            local.sin_addr.s_addr = INADDR_ANY;
    
            if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                cerr<<"bind error!"<<endl;
                exit(3);
            }
            
        }
    
        static void Listen(int sock)
        {
            if(listen(sock,5)<0)
            {
                cerr<<"listen error!"<<endl;
                exit(4);
            }
        }
    
        static int Accept(int sock)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = accept(sock,(struct sockaddr*)&peer,&len);
            if(fd>0)
            {
                return fd;
            }
            else return -1;
        }
    
        static void Connect(int sock,std::string ip,uint16_t port)
        {
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
    
            server.sin_family = AF_INET;
            server.sin_port = htons(port);
            server.sin_addr.s_addr  = inet_addr(ip.c_str());
    
            if(connect(sock,(struct sockaddr*)&server,sizeof(server)) == 0)
            {
                cout<<"connect success!"<<endl;
            }
            else{
                cout<<"connect failed!"<<endl;
                exit(5);
            }
        }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    Util.hpp

    #pragma once
    
    #include
    #include
    #include
    #include
    #include
    
    //工具类
    //设置一个sock成为非阻塞
    void SetNonBlock(int sock)
    {
        int fl=fcntl(sock,F_GETFL);
        if(fl<0)
        {
            std::cerr<<"fcntl failed!"<<std::endl;
            return;
        }
    
        fcntl(sock,F_SETFL,fl|O_NONBLOCK);
    }
    
    void SplitSegment(std::string &inbuffer,std::vector<std::string> *tokens,std::string sep)
    {
        while(true) 
        {
            std::cout<<"inbuffer:"<<inbuffer<<std::endl;
            auto pos=inbuffer.find(sep);
            if(pos==std::string::npos)//报文不完整
            {
                break;
            }
            std::string sub=inbuffer.substr(0,pos); 
            tokens->push_back(sub);
            inbuffer.erase(0,pos+sep.size());
        }
    }
    bool Deserialize(const std::string seg,std::string *out1,std::string *out2) //就是和业务强相关
    {
        std::string op="+";
        auto pos=seg.find("+");
        if(pos==std::string::npos) return false;
        *out1=seg.substr(0,pos);
        *out2=seg.substr(pos+op.size());
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    测试结果

    在这里插入图片描述

  • 相关阅读:
    (计算机组成原理)第五章中央处理器-第七节2:硬件多线程的基本概念
    【小白教程】网课查题公众号搭建教程-附带题库查题接口
    【算法集训暑期刷题营】7.3日题---前缀和
    四级词汇词根 联想记忆法
    Redis规范,让你的Redis原地起飞
    python 模块、原始字符串
    Rust - 所有权
    【论文笔记】—低照度图像增强—Supervised—基于频率的分解和增强模型—2020-CVPR
    MongoDB系列之Window环境部署配置
    【Python零基础入门篇 · 15】:内置函数二【min()和max函数、zip()拉链函数、map()映射函数、reduce()】
  • 原文地址:https://blog.csdn.net/sakeww/article/details/126148312