多路转接(使用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 <iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
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);
            }
        }
    };
}

3.2poll实现多路转接代码

#include "Sock.hpp"
#include <poll.h>
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;
        }
    }
}
posted @ 2022-09-24 20:28  卖寂寞的小男孩  阅读(64)  评论(0)    收藏  举报