• 多路转接(使用poll实现)


    1. poll的优缺点

    如果说select开辟了多路转接的道路,那么poll就是select的升级版。

    1.1 poll的优点

    不同于select使用三个位图来表示三个fd_set的方式,poll使用一个pollfd的指针实现。

    1.pollfd结构包含了要监视的event和就绪的event,不再使用select"参数-值"传递的方式,接口使用比select更加方便。
    2.poll没有最大的数量限制(但是数量过大后性能也会下降)。

    1.2 poll的缺点

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

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

    2. poll函数原型

    2.1 poll与pollfd内容

    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 */
    };

    2.2 参数与返回值

    2.2.1 参数

    在select中需要我们自己维护一个数组来存储需要关心的fd,在poll中通过将数组首元素地址与数组长度结合的方式来表示这个数组。
    fds是指向数组首元素地址的指针,而nfds表示的就是数组的长度。最后一个参数timeout指的是超时等待时间,与select不同的是,它是以毫秒为单位的。

    2.2.2 pollfd

    数组中的每一个元素都是一个pollfd类型,因此,这个类型中一定包括文件描述符fd,以及关于该文件描述符的事件。
    在select中,需要关心的事件与已经就绪的事件使用的是同一个fd_set位图结构,而在poll中,events表示的是OS需要关心该fd中的事件;revents表示的是OS要告诉用户哪些事件已经就绪了。

    2.2.3 事件的添加

    在select中是根据位置来进行事件的添加的,比如函数select的第三个位置不是空,就表示有文件描述符的读操作需要关心。poll的升级在于它可以将事件进行具体的划分,即增加多个事件种类。注意,这里的events是short类型,但是我们也可以将它看成一个位图结构,方便我们进行事件的添加。添加事件的本质就在于按位与或上一个表示某一个事件的宏。
    下面给出这些宏的含义,以及它们的使用范围(在events中使用还是在revents中使用)

    事件含义eventsrevents
    POLLIN数据(包括普通数据和优先数据)
    POLLRDNORM普通数据可读
    POLLRDBAND优先级带数据可读(Linux不支持)
    POLLPRI高优先级数据可读,比如TCP带外数据
    POLLOUT数据(包括普通数据和优先数据)可写
    POLLWRNORM普通数据可写
    POLLWRBAND优先级带数据可写
    POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作,它由GNU引入
    POLLERR错误
    POLLHUP挂起。比如管道的写端被关闭了之后,读端描述符上将收到POLLHUP事件
    POLLNVAL文件描述符没有打开

    2.2.4 返回值

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

    3.poll实现多路转接

    3.1实现思路

    1.实现思路上和select是完全一样的,首先定义一个pollfd类型的数组,并将其进行初始化,并将第一个数组的位置交给listen套接字。
    2.使用poll函数等待,当有事件就绪的时候它的返回值会大于0,然后遍历整个数组,将不关心的位置以及没有就绪的排除掉。
    3.剩下的就是就绪的文件描述符,当文件描述符时listen_sock的时候,说明有新的链接到来了,此时需要进行accept新的文件描述符,并在数组中为其找到对应的位置,如果没有那么说明已经满载了,此时直接执行四次挥手,即将文件描述符关闭。
    如果有空位置,那么就将其填入进去,并设置好对应的监听选项。
    4.如果是普通的套接字就绪了,那么就进行读操作,如果对端关闭或者读失败,则关闭对应的文件描述符。

    3.1通信代码

    #include 
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    namespace ns_Sock
    {
        class Sock
        {
        private:
        public:
            static int Socket()
            {
                int sock = socket(AF_INET, SOCK_STREAM, 0);
                if (sock < 0)
                {
                    cerr << "创建套接字失败" << endl;
                    exit(-2);
                }
                else
                {
                    return sock;
                }
            }
            static int Listen(int sock)
            {
                if(listen(sock,5)<0)
                {
                    cerr<<"listen error"<<endl;
                    exit(-3);
                }
            }
            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
                {
                    exit(-5);
                }
            }
            static void Bind(int sock,uint16_t port)
            {
                sockaddr_in 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(-4);
                }
            }
            static void Connect(int sock,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 fail"<<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

    3.2poll实现多路转接代码

    #include "Sock.hpp"
    #include 
    using namespace ns_Sock;
    #define NUM 128
    struct pollfd nfds[NUM];
    void Usage(char *proc)
    {
        cout << proc << " port" << endl;
    }
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            exit(-1);
        }
        uint16_t port = (uint16_t)atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
        for (int i = 0; i < NUM; i++)
        {
            nfds[i].fd = -1;
        }
        nfds[0].fd = listen_sock; //将数组中第一个元素设为listen套接字
        nfds[0].events = POLLIN;
        nfds[0].revents = 0;
        int timeout = 1000;
        int count=0;
        for (;;)
        {
            count++;
            int n = poll(nfds, NUM, -1);
            switch (n)
            {
            case 0:
                cout << "timeout......" << endl;
                break;
            case -1:
                cout << "error" << endl;
                break;
            default:
                cout << "有数据就绪了,可以进行读取了" << endl;
                for (int i = 0; i < NUM; i++)
                {
                    if (nfds[i].fd == -1)
                    {
                        continue; //不关心的文件描述符
                    }
                    if (nfds[i].revents != 0)
                    {
                        //当有监听事件就绪时
                        cout << "有监听的事件就绪了,可以进行读取了" << endl;
                        if (nfds[i].fd == listen_sock)
                        {
                            cout << "有新的链接到来了" << endl;
                            int sock = Sock::Accept(listen_sock);
                            if (sock >= 0)
                            {
                                cout << "新链接创建成功" << endl;
                                int pos = 1;
                                for (pos = 1; pos < NUM; pos++)
                                {
                                    if (nfds[pos].fd == -1)
                                    {
                                        break;
                                    }
                                }
                                //找到了一个位置还没有被使用
                                if (pos < NUM)
                                {
                                    cout << "新链接" << sock << "已经被填到了数组[" << pos << "]的位置" << endl;
                                    nfds[pos].fd = sock;
                                    nfds[pos].events = POLLIN;
                                }
                                else
                                {
                                    cout << "服务器满载了" << endl;
                                    close(sock);
                                }
                            }
                        }
                        else
                        {
                            cout << nfds[i].fd << "上面有数据读取" << endl;
                            char recvbuffer[1024] = {0};
                            ssize_t s = read(nfds[i].fd, recvbuffer, sizeof(recvbuffer) - 1);
                            if (s > 0)
                            {
                                recvbuffer[s] = '\0';
                                cout << "读取成功了" << endl;
                                cout << "client " << nfds[i].fd << "#" << recvbuffer << endl;
                            }
                            else if (s == 0)
                            {
                                cout << "客户端退出啦" << endl;
                                close(nfds[i].fd);
                                cout << "客户端" << nfds[i].fd << "退出了"
                                     << "已经将其数组位置的fd置为-1" << endl;
                                nfds[i].fd = -1;
                            }
                            else
                            {
                                cout << "出现读写失败" << endl;
                                close(nfds[i].fd);
                                cout << "读出错" << nfds[i].fd << "断开连接,已经将其数组位置的fd置为-1" << endl;
                                nfds[i].fd = -1;
                            }
                        }
                    }
                    //有数据就绪了
                }
                break;
            }
        }
    }
    
    • 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
  • 相关阅读:
    Unicode与UTF-8
    深入浅出Java多线程(四):线程状态
    Springboot专利申请服务平台 毕业设计-附源码260839
    C++:写ini文件(附完整源码)
    2024年阿里云2核4G配置服务器测评_ECS和轻量性能测评
    C/C++ 字符串问题总结
    【FastAPI】实现服务器向客户端发送SSE(Server-Sent Events)广播
    Apache-DBUtils的查询和DML
    设计模式之策略模式(行为型)
    springboot嘉应房地产公司质量管理系统毕业设计源码453100
  • 原文地址:https://blog.csdn.net/qq_51492202/article/details/126799954