【网络】高级IO——poll版本TCP服务器

news2024/12/26 8:08:48

目录

前言

一,poll函数

 1.1.参数一:fds

1.2.参数二,nfds

1.3.参数三,timeout

1.4.返回值

1.5.poll函数简单使用示例

二,poll版TCP服务器编写

2.1.编写

2.2.poll的优缺点

2.3.源代码


前言

由于select函数有下面几个特别明显的缺点,就推演出了改进版本——poll函数

  • 比如select监视的fd是有上限的,我的云服务器内核版本下最大上限是1024个fd,主要还是因为fd_set他是一个固定大小的位图结构,位图中的数组开辟之后不会在变化了,这是内核的数据结构,除非你修改内核参数,否则不会在变化了,所以一旦select监视的fd数量超过1024,则select会报错。
  • 除此之外,select大部分的参数都是输入输出型参数,用户和内核都会不断的修改这些参数的值,导致每次调用select前,都需要重新设置fd_set位图中的内容,这在用户层面上会带来很多不必要的遍历+拷贝的成本。

        poll接口主要解决了select接口的两个问题,一个是select监视的fd有上限,另一个是select每次调用前都需要借助第三方数组,向fd_set里面重新设置关心的fd。 

        select() 和 poll() 系统调用的本质一样,poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。

只要我们理解了我们的select,poll也就不在话下

        poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

一,poll函数

这个函数的功能和select一模一样。都是监视并等待多个文件描述符的属性变化

poll函数处理的还是IO过程里面的等待过程!!!

 1.1.参数一:fds

这个参数就需要我们知道struct pollfd结构体是什么东西!!!

struct pollfd{
	int fd;			//文件描述符
	short events;	//等待的事件
	short revents;	//实际发生的事件
};

         poll 函数是 Unix/Linux 系统中用于监视多个文件描述符(file descriptors)状态变化的一种机制。poll 函数能够同时监视多个文件描述符,以检查其上是否发生了感兴趣的事件(如可读、可写、错误等)。为了实现这一功能,poll 函数使用了一个 pollfd 结构体数组作为输入参数,每个 pollfd 结构体代表了一个被监视的文件描述符及其相关的事件。

下面是对 pollfd 结构体各成员的详细解释:

  • int fd;这个成员变量是一个整数,表示被监视的文件描述符。文件描述符是一个非负整数,它是一个索引值,指向内核中打开文件的表项。通过文件描述符,我们可以对打开的文件进行读写操作。在 poll 函数的上下文中,这个文件描述符可以是任何类型的文件、套接字(socket)或者管道(pipe)等。
  • short events;:这个成员变量是一个位掩码(bitmask),用于指定我们想要监视的、在该文件描述符上可能发生的事件。这些事件可以是以下几种之一(或它们的组合,通过位或操作符 | 实现):
  1. POLLIN:有数据可读。
  2. POLLOUT:写数据不会阻塞。
  3. POLLERR:发生错误。
  4. POLLHUP:挂起(hang up)。
  5. POLLNVAL:无效的文件描述符。
  6. 以及其他一些可能依赖于特定实现的标志。

相比于select,poll将输入和输出事件进行了分离 

比如我们希望内核帮我们关注 0号文件描述符上的读事件

struct pollfd rfds;    
rfds.fd = 0;              //希望内核帮我们关注0号文件描述符上的事件
rfds.events |= POLLIN;    //希望内核关注0号文件描述符上的读事件
//rfds.events = POLLIN | POLLOUT;    //希望既监听读事件又监听写事件
rfds.revents = 0;         //这个参数用于内核通知我们有事件就绪了,让我们赶紧来取

如果我们要判断读事件是否就绪

if(rfds.revents & POLLIN){
    std::cout << "读事件就绪了..." << std::endl;
}
  • short revents;这个成员变量在 poll 函数调用返回时被填充,它也是一个位掩码,表示在调用 poll 期间,实际发生在文件描述符上的事件。与 events 不同,revents 是由 poll 函数自动填写的,用户不需要(也不应该)在调用 poll 之前设置它。revents 的值可能包含 events 中指定的任何事件,或者由于某种原因(如错误或挂起)而包含其他事件。

events和revents的取值都是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。 

  • 在调用poll函数之前,可以通过“或”运算符将要检测的事件添加到events成员当中。
  • 在poll函数返回后,可以通过“与”运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪。

注意:每个结构体的 events 域是由用户来设置,告诉内核我们关注的是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件 

  • 使用 poll 函数时,你通常会创建一个 pollfd 结构体数组,每个元素代表一个你想要监视的文件描述符及其事件。
  • 然后,你调用 poll 函数,并将这个数组和数组的大小作为参数传递给它。
  • poll 函数会阻塞(或根据需要立即返回),直到一个或多个文件描述符上发生了请求的事件,或者超过了指定的超时时间。
  • 最后,你可以通过检查每个 pollfd 结构体的 revents 成员来确定哪些文件描述符上发生了哪些事件。

1.2.参数二,nfds

nfds_t类型是什么?

        nfds_t 类型是在 Unix/Linux 系统编程中,特别是在使用 poll、ppoll、select 等系统调用时,用来表示文件描述符数量的数据类型。这个类型的确切定义可能会根据不同的系统和库实现而有所不同,但通常它是一个足够大的整数类型,以容纳系统可能支持的最大文件描述符数量。

       在大多数现代 Unix-like 系统中,nfds_t 通常是 unsigned int 或 unsigned long 的别名,但这不是一个固定的规则,因此最好查看你的系统或库的文档以获取确切的定义。

例如,在 glibc(GNU C Library)中,nfds_t 的定义可能看起来像这样(尽管这取决于 glibc 的版本和配置):

 typedef unsigned long nfds_t; 

或者在某些情况下,它可能是:

 typedef unsigned int nfds_t; 

当你使用 poll 函数时,你需要将一个 nfds_t 类型的值作为第一个参数传递给函数,这个值表示 pollfd 结构体数组中的元素数量。这个值应该大于或等于数组中实际元素的数量,因为 poll 会检查这个范围内的所有 pollfd 结构体。

 #include <poll.h>  
   int main() {  
 struct pollfd fds[2];  
 // 初始化 fds 数组...  
   nfds_t nfds = sizeof(fds) / sizeof(fds[0]);  
 int timeout = -1; // 无限等待  
   int ret = poll(fds, nfds, timeout);  
 // 处理 poll 的返回值...  
   return 0;  
 } 

在这个例子中,nfds 被设置为 fds 数组的大小,这是调用 poll 时应该传递的正确值。注意,虽然在这个例子中 nfds 被显式地计算为数组的大小,但在许多情况下,你可能已经知道要监视的文件描述符数量,因此可以直接使用那个值。

1.3.参数三,timeout

这个参数是一个输入型参数,单位是毫秒,代表阻塞等待的时间,超过该时间就会变为非阻塞等待。

  1. timeout = -1,代表永久阻塞等待
  2. timeout = 0,代表永久非阻塞等待
  3. timeout > 0,代表先阻塞等待 timeout 毫秒,超过这个时间变为非阻塞等待,poll函数返回

1.4.返回值

成功时,poll() 返回结构体中 revents 域不为 0 的文件描述符个数;如果在超时前没有任何事件发生,poll()返回 0;

失败时,poll() 返回 -1,并设置 errno 为下列值之一:

  1. EBADF:一个或多个结构体中指定的文件描述符无效。
  2. EFAULT:fds 指针指向的地址超出进程的地址空间。
  3. EINTR:请求的事件之前产生一个信号,调用可以重新发起。
  4. EINVAL:nfds 参数超出 PLIMIT_NOFILE 值。
  5. ENOMEM:可用内存不足,无法完成请求。

1.5.poll函数简单使用示例

#include <stdio.h>  // 引入标准输入输出库,用于printf等函数  
#include <stdlib.h> // 引入标准库,用于EXIT_FAILURE等宏定义  
#include <unistd.h> // 引入POSIX操作系统API,用于open、close等函数  
#include <fcntl.h>  // 引入文件控制选项,如O_RDONLY  
#include <poll.h>   // 引入poll函数及其相关结构体pollfd  
  
int main() {    
    // 尝试以只读模式打开名为"test.txt"的文件  
    int fd = open("test.txt", O_RDONLY);    
    if (fd < 0) {    
        // 如果文件打开失败,打印错误信息并返回失败状态  
        perror("Failed to open file");    
        return EXIT_FAILURE;    
    }    
    
    // 定义一个pollfd结构体数组,用于存储要监视的文件描述符及其事件  
    struct pollfd fds[1];    
    // 设置数组的第一个元素,指定要监视的文件描述符及其感兴趣的事件(POLLIN,表示可读)  
    fds[0].fd = fd;    
    fds[0].events = POLLIN;    
    
    // 设置poll函数的超时时间为5000毫秒(5秒)  
    int timeout = 5000; // 5 seconds    
    // 调用poll函数,监视fds数组中的文件描述符,超时时间为timeout  
    int ret = poll(fds, 1, timeout);    
    
    // 检查poll函数的返回值  
    if (ret == -1) {    
        // 如果poll调用失败,打印错误信息,关闭文件描述符,并返回失败状态  
        perror("poll failed");    
        close(fd);    
        return EXIT_FAILURE;    
    }    
    
    // 如果poll超时,没有任何事件发生  
    if (ret == 0) {    
        printf("No data within five seconds\n");    
    } else if (fds[0].revents & POLLIN) {    
        // 如果在文件描述符上发生了POLLIN事件(即可读)  
        char buf[1024];  // 定义一个缓冲区,用于存储读取的数据  
        ssize_t bytes_read = read(fd, buf, sizeof(buf) - 1);  // 从文件描述符中读取数据到缓冲区  
        if (bytes_read > 0) {  // 如果成功读取到数据  
            buf[bytes_read] = '\0';  // 在读取到的数据末尾添加字符串结束符'\0'  
            printf("Read %zd bytes: %s\n", bytes_read, buf);  // 打印读取到的数据及其长度  
        }    
    }    
    
    // 关闭文件描述符,释放资源  
    close(fd);    
    // 程序正常结束  
    return EXIT_SUCCESS;    
}

这段代码展示了如何使用poll函数来监视一个文件描述符(在这个例子中是一个打开的文件)的状态,特别是检查文件是否有数据可读。如果文件在指定的超时时间内变得可读,程序将读取并打印文件内容;如果超时,则打印一条消息表示没有数据可读。

二,poll版TCP服务器编写

2.1.编写

我们这个poll版TCP服务器和那个select版本的差不多!!!所以我们基本就是在select版本上面的基础上进行修改

PollServer.hpp初始版本

#pragma once
#include <iostream>
#include "Socket.hpp"
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h> 

const uint16_t default_port = 8877;       // 默认端口号
const std::string default_ip = "0.0.0.0"; // 默认IP
const int default_fd = -1;
const int fd_num_max=64;
const int non_event=0;

class PollServer
{
public:
    PollServer(const uint16_t port = default_port, const std::string ip = default_ip)
        : ip_(ip), port_(port)
    {
        for(int i=0;i<fd_num_max;i++)
        {
            event_fds[i].fd=default_fd;
            event_fds[i].events=non_event;//暂时不关心
            event_fds[i].revents=non_event;//暂时不关心
        }
    }
    ~PollServer()
    {
        listensock_.Close();
    }

    bool Init()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();

        return true;
    }

    void Start()
    {
        
    }

private:
    uint16_t port_;          // 绑定的端口号
    Sock listensock_;        // 专门用来listen的
    std::string ip_;         // ip地址
    struct pollfd event_fds[fd_num_max];
};

上面这些就是最最基本的东西,接下来我们就很容易写出下面这些东西!!

 void HandlerEvent()
    {
        for (int n = 0; n < fd_num_max; n++)
        {
            int fd = event_fds[n].fd;
            if (fd == default_fd) // 无效的
                continue;

            if (event_fds[n].revents&POLLIN) // fd套接字就绪了
            {
                // 1.是listen套接字就绪了
                if (fd == listensock_.Fd()) // 如果是listen套接字就绪了!!!
                {
                    Accept();
                }
                // 2.是通信的套接字就绪了,fd不是listen套接字
                else // 读事件
                {
                    Receiver(fd,n);
                }
            }
        }
    }
    

    void Start()
    {
        int listensock = listensock_.Fd();
        event_fds[0].fd=listensock;//把listen放到首个数组下标里面
        event_fds[0].events=POLLIN;//只关心读事件
        //revent可以不设置
        int timeout=3000;//3s
        for (;;)
        {
            int n = poll(event_fds,fd_num_max,timeout);

            switch (n)
            {
            case 0:
                std::cout << "time out....." << std::endl;
                break;
            case -1:
                std::cout << "poll error" << std::endl;
                break;
            default:
                // 有事件就绪
                std::cout << "get a new link" << std::endl;
                HandlerEvent(); // 处理事件
                break;
            }
        }
    }

接下来就是修改 Accept()和Receive()了

void Accept()
    {
        // 我们的连接事件就绪了
        std::string clientip;
        uint16_t clientport;

        int sockfd = listensock_.Accept(&clientip, &clientport); // 这里会返回一个新的套接字
        // 请问进程会阻塞在Accept这里吗?答案是不会的,因为上层的select已经完成的了等待部分,accept只需要完成建立连接即可
        if (sockfd < 0)
            return;
        else // 把新fd加入位图
        {
            int i = 1;
            for (; i < fd_num_max; i++) // 为什么从1开始,因为我们0号下标对应的是listen套接字,我们不要修改
            {
                if (event_fds[i].fd != default_fd) // 没找到空位
                {
                    continue;
                }
                else
                { // 找到空位,但不能直接添加
                    break;
                }
            }
            if (i != fd_num_max) // 没有满
            {
                event_fds[i].fd = sockfd; // 把新连接加入数组
                 event_fds[i].events=POLLIN ;//关心读事件
                 event_fds[i].revents = non_event;//重置一下
                 
                Printfd();
            }
            else // 满了
            {
                close(sockfd); // 处理不了了,可以直接选择关闭连接
                //当然,如果想要连接更多链接,我们可以在这里进行扩容操作,不过我们这里就不做了
            }
        }
    }
     void Printfd()
    {
        std::cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (event_fds[i].fd == default_fd)
                continue;
            else
            {
                std::cout << event_fds[i].fd << " ";
            }
        }
        std::cout << std::endl;
    }


    void Receiver(int fd, int i)
    {
        char in_buff[1024];
        int n = read(fd, in_buff, sizeof(in_buff) - 1);
        if (n > 0)
        {
            in_buff[n] = 0;
            std::cout << "get message: " << in_buff << std::endl;
        }
        else if (n == 0) // 客户端关闭连接
        {
            close(fd);               // 我服务器也要关闭
            event_fds[i].fd = default_fd; // 重置数组内的值
        }
        else
        {
            close(fd);               // 我服务器也要关闭
            event_fds[i].fd = default_fd; // 重置数组内的值
        }
    }

 PollServer.hpp

#pragma once
#include <iostream>
#include "Socket.hpp"
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h> 

const uint16_t default_port = 8877;       // 默认端口号
const std::string default_ip = "0.0.0.0"; // 默认IP
const int default_fd = -1;
const int fd_num_max=64;
const int non_event=0;

class PollServer
{
public:
    PollServer(const uint16_t port = default_port, const std::string ip = default_ip)
        : ip_(ip), port_(port)
    {
        for(int i=0;i<fd_num_max;i++)
        {
            event_fds[i].fd=default_fd;
            event_fds[i].events=non_event;//暂时不关心
            event_fds[i].revents=non_event;//暂时不关心
        }
    }
    ~PollServer()
    {
        listensock_.Close();
    }

    bool Init()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();

        return true;
    }

    void Accept()
    {
        // 我们的连接事件就绪了
        std::string clientip;
        uint16_t clientport;

        int sockfd = listensock_.Accept(&clientip, &clientport); // 这里会返回一个新的套接字
        // 请问进程会阻塞在Accept这里吗?答案是不会的,因为上层的select已经完成的了等待部分,accept只需要完成建立连接即可
        if (sockfd < 0)
            return;
        else // 把新fd加入位图
        {
            int i = 1;
            for (; i < fd_num_max; i++) // 为什么从1开始,因为我们0号下标对应的是listen套接字,我们不要修改
            {
                if (event_fds[i].fd != default_fd) // 没找到空位
                {
                    continue;
                }
                else
                { // 找到空位,但不能直接添加
                    break;
                }
            }
            if (i != fd_num_max) // 没有满
            {
                event_fds[i].fd = sockfd; // 把新连接加入数组
                 event_fds[i].events=POLLIN ;//关心读事件
                 event_fds[i].revents = non_event;//重置一下
                 
                Printfd();
            }
            else // 满了
            {
                close(sockfd); // 处理不了了,可以直接选择关闭连接
                //当然,如果想要连接更多链接,我们可以在这里进行扩容操作,不过我们这里就不做了
            }
        }
    }
     void Printfd()
    {
        std::cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (event_fds[i].fd == default_fd)
                continue;
            else
            {
                std::cout << event_fds[i].fd << " ";
            }
        }
        std::cout << std::endl;
    }


    void Receiver(int fd, int i)
    {
        char in_buff[1024];
        int n = read(fd, in_buff, sizeof(in_buff) - 1);
        if (n > 0)
        {
            in_buff[n] = 0;
            std::cout << "get message: " << in_buff << std::endl;
        }
        else if (n == 0) // 客户端关闭连接
        {
            close(fd);               // 我服务器也要关闭
            event_fds[i].fd = default_fd; // 重置数组内的值
        }
        else
        {
            close(fd);               // 我服务器也要关闭
            event_fds[i].fd = default_fd; // 重置数组内的值
        }
    }

    void HandlerEvent()
    {
        for (int n = 0; n < fd_num_max; n++)
        {
            int fd = event_fds[n].fd;
            if (fd == default_fd) // 无效的
                continue;

            if (event_fds[n].revents&POLLIN) // fd套接字就绪了
            {
                // 1.是listen套接字就绪了
                if (fd == listensock_.Fd()) // 如果是listen套接字就绪了!!!
                {
                    Accept();
                }
                // 2.是通信的套接字就绪了,fd不是listen套接字
                else // 读事件
                {
                    Receiver(fd,n);
                }
            }
        }
    }
    

    void Start()
    {
        int listensock = listensock_.Fd();
        event_fds[0].fd=listensock;//把listen放到首个数组下标里面
        event_fds[0].events=POLLIN;//只关心读事件
        //revent可以不设置
        int timeout=3000;//3s
        for (;;)
        {
            int n = poll(event_fds,fd_num_max,timeout);

            switch (n)
            {
            case 0:
                std::cout << "time out....." << std::endl;
                break;
            case -1:
                std::cout << "poll error" << std::endl;
                break;
            default:
                // 有事件就绪
                std::cout << "get a new link" << std::endl;
                HandlerEvent(); // 处理事件
                break;
            }
        }
    }

private:
    uint16_t port_;          // 绑定的端口号
    Sock listensock_;        // 专门用来listen的
    std::string ip_;         // ip地址
    struct pollfd event_fds[fd_num_max];
};

 

我们再链接一个看看

非常完美了啊!!!

2.2.poll的优缺点

  • poll不是也用了一个数组吗?他是怎么解决了fd有上限的问题?

凭的是这个数组的大小是我们自己设置的,而select的那个数组是已经固定了的。

        其实poll的优点就是解决了select支持的fd有上限,以及用户输入信息和内核输出信息耦合的两个问题。

        但poll的缺点其实在上面的代码已经体现出来了一部分,内核在检测fd是否就绪时,需要遍历整个结构体数组检测events的值,同样用户在处理就绪的fd事件时,也需要遍历整个结构体数组检测revents的值,当rfds结构体数组越来越大时,每次遍历数组其实就会降低服务器的效率,为此,内核提供了epoll接口来解决这样的问题。

        与select相同的是,poll也需要用户自己维护一个第三方数组来存储用户需要关心的fd及事件,只不过poll不需要在每次调用前都重新设置关心的fd,因为用户的输入和内核的输出是分离的,分别在结构体的events和revents的两个字段,做到了输入和输出分离。

2.3.源代码

PollServer.hpp

#pragma once
#include <iostream>
#include "Socket.hpp"
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h> 

const uint16_t default_port = 8877;       // 默认端口号
const std::string default_ip = "0.0.0.0"; // 默认IP
const int default_fd = -1;
const int fd_num_max=64;
const int non_event=0;

class PollServer
{
public:
    PollServer(const uint16_t port = default_port, const std::string ip = default_ip)
        : ip_(ip), port_(port)
    {
        for(int i=0;i<fd_num_max;i++)
        {
            event_fds[i].fd=default_fd;
            event_fds[i].events=non_event;//暂时不关心
            event_fds[i].revents=non_event;//暂时不关心
        }
    }
    ~PollServer()
    {
        listensock_.Close();
    }

    bool Init()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();

        return true;
    }

    void Accept()
    {
        // 我们的连接事件就绪了
        std::string clientip;
        uint16_t clientport;

        int sockfd = listensock_.Accept(&clientip, &clientport); // 这里会返回一个新的套接字
        // 请问进程会阻塞在Accept这里吗?答案是不会的,因为上层的select已经完成的了等待部分,accept只需要完成建立连接即可
        if (sockfd < 0)
            return;
        else // 把新fd加入位图
        {
            int i = 1;
            for (; i < fd_num_max; i++) // 为什么从1开始,因为我们0号下标对应的是listen套接字,我们不要修改
            {
                if (event_fds[i].fd != default_fd) // 没找到空位
                {
                    continue;
                }
                else
                { // 找到空位,但不能直接添加
                    break;
                }
            }
            if (i != fd_num_max) // 没有满
            {
                event_fds[i].fd = sockfd; // 把新连接加入数组
                 event_fds[i].events=POLLIN ;//关心读事件
                 event_fds[i].revents = non_event;//重置一下
                 
                Printfd();
            }
            else // 满了
            {
                close(sockfd); // 处理不了了,可以直接选择关闭连接
                //当然,如果想要连接更多链接,我们可以在这里进行扩容操作,不过我们这里就不做了
            }
        }
    }
     void Printfd()
    {
        std::cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (event_fds[i].fd == default_fd)
                continue;
            else
            {
                std::cout << event_fds[i].fd << " ";
            }
        }
        std::cout << std::endl;
    }


    void Receiver(int fd, int i)
    {
        char in_buff[1024];
        int n = read(fd, in_buff, sizeof(in_buff) - 1);
        if (n > 0)
        {
            in_buff[n] = 0;
            std::cout << "get message: " << in_buff << std::endl;
        }
        else if (n == 0) // 客户端关闭连接
        {
            close(fd);               // 我服务器也要关闭
            event_fds[i].fd = default_fd; // 重置数组内的值
        }
        else
        {
            close(fd);               // 我服务器也要关闭
            event_fds[i].fd = default_fd; // 重置数组内的值
        }
    }

    void HandlerEvent()
    {
        for (int n = 0; n < fd_num_max; n++)
        {
            int fd = event_fds[n].fd;
            if (fd == default_fd) // 无效的
                continue;

            if (event_fds[n].revents&POLLIN) // fd套接字就绪了
            {
                // 1.是listen套接字就绪了
                if (fd == listensock_.Fd()) // 如果是listen套接字就绪了!!!
                {
                    Accept();
                }
                // 2.是通信的套接字就绪了,fd不是listen套接字
                else // 读事件
                {
                    Receiver(fd,n);
                }
            }
        }
    }
    

    void Start()
    {
        int listensock = listensock_.Fd();
        event_fds[0].fd=listensock;//把listen放到首个数组下标里面
        event_fds[0].events=POLLIN;//只关心读事件
        //revent可以不设置
        int timeout=3000;//3s
        for (;;)
        {
            int n = poll(event_fds,fd_num_max,timeout);

            switch (n)
            {
            case 0:
                std::cout << "time out....." << std::endl;
                break;
            case -1:
                std::cout << "poll error" << std::endl;
                break;
            default:
                // 有事件就绪
                std::cout << "get a new link" << std::endl;
                HandlerEvent(); // 处理事件
                break;
            }
        }
    }

private:
    uint16_t port_;          // 绑定的端口号
    Sock listensock_;        // 专门用来listen的
    std::string ip_;         // ip地址
    struct pollfd event_fds[fd_num_max];
};

Socket.hpp

#pragma once  
  
#include <iostream>  
#include <string>  
#include <unistd.h>  
#include <cstring>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <netinet/in.h>  
 
  
// 定义一些错误代码  
enum  
{  
    SocketErr = 2,    // 套接字创建错误  
    BindErr,          // 绑定错误  
    ListenErr,        // 监听错误  
};  
  
// 监听队列的长度  
const int backlog = 10;  
  
class Sock  //服务器专门使用
{  
public:  
    Sock() : sockfd_(-1) // 初始化时,将sockfd_设为-1,表示未初始化的套接字  
    {  
    }  
    ~Sock()  
    {  
        // 析构函数中可以关闭套接字,但这里选择不在析构函数中关闭,因为有时需要手动管理资源  
    }  
  
    // 创建套接字  
    void Socket()  
    {  
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);  
        if (sockfd_ < 0)  
        {  
            printf("socket error, %s: %d", strerror(errno), errno); //错误  
            exit(SocketErr); // 发生错误时退出程序  
        } 
        int opt=1;
        setsockopt(sockfd_,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //服务器主动关闭后快速重启
    }  
  
    // 将套接字绑定到指定的端口上  
    void Bind(uint16_t port)  
    {  
        //让服务器绑定IP地址与端口号
        struct sockaddr_in local;  
        memset(&local, 0, sizeof(local));//清零  
        local.sin_family = AF_INET;  // 网络
        local.sin_port = htons(port);  // 我设置为默认绑定任意可用IP地址
        local.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的网络接口  
  
        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)  //让自己绑定别人
        {  
            printf("bind error, %s: %d", strerror(errno), errno);  
            exit(BindErr);  
        }  
    }  
  
    // 监听端口上的连接请求  
    void Listen()  
    {  
        if (listen(sockfd_, backlog) < 0)  
        {  
            printf("listen error, %s: %d", strerror(errno), errno);  
            exit(ListenErr);  
        }  
    }  
  
    // 接受一个连接请求  
    int Accept(std::string *clientip, uint16_t *clientport)  
    {  
        struct sockaddr_in peer;  
        socklen_t len = sizeof(peer);  
        int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);  
        
        if(newfd < 0)  
        {  
            printf("accept error, %s: %d", strerror(errno), errno);  
            return -1;  
        }  
        
        char ipstr[64];  
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));  
        *clientip = ipstr;  
        *clientport = ntohs(peer.sin_port);  
  
        return newfd; // 返回新的套接字文件描述符  
    }  
  
    // 连接到指定的IP和端口——客户端才会用的  
    bool Connect(const std::string &ip, const uint16_t &port)  
    {  
        struct sockaddr_in peer;//服务器的信息  
        memset(&peer, 0, sizeof(peer));  
        peer.sin_family = AF_INET;  
        peer.sin_port = htons(port);
 
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));  
  
        int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));  
        if(n == -1)   
        {  
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;  
            return false;  
        }  
        return true;  
    }  
  
    // 关闭套接字  
    void Close()  
    {  
        close(sockfd_);  
    }  
  
    // 获取套接字的文件描述符  
    int Fd()  
    {  
        return sockfd_;  
    }  
  
private:  
    int sockfd_; // 套接字文件描述符  
};

main.cc

#include"PollServer.hpp"
#include<memory>

int main()
{
    std::unique_ptr<PollServer> svr(new PollServer());
    svr->Init();
    svr->Start();
}

makefile

poll_server:main.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf poll_server

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2141237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Python的自然语言处理系列(10):使用双向LSTM进行文本分类

在前一篇文章中&#xff0c;我们介绍了如何使用RNN进行文本分类。在这篇文章中&#xff0c;我们将进一步优化模型&#xff0c;使用双向多层LSTM来替代RNN&#xff0c;从而提高模型在序列数据上的表现。LSTM通过引入一个额外的记忆单元&#xff08;cell state&#xff09;来解决…

Linux:vim编辑技巧

命令模式 光标跳转 输入18&#xff0c;再输入G&#xff0c;可以跳转到18行。 复制、粘贴、删除 P是往上一行粘贴 小写u可以撤销 查找/撤销/保存 大写U可能失效&#xff0c;用CTRLr 末行模式 保存/退出/文件操作 字符串替换 开关参数的控制

基于python+django+vue的在线学习资源推送系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤pythondjangovue…

近乎实时的物联网数据管道架构

这篇论文的标题是《Near Real-Time IoT Data Pipeline Architectures》&#xff0c;作者是 Markus Multamki&#xff0c;完成于 2024 年&#xff0c;属于计算机科学与工程硕士学位论文。论文主要研究了物联网&#xff08;IoT&#xff09;数据分析的可扩展数据管道架构&#xff…

FloodFill算法【下】

417. 太平洋大西洋水流问题 题目链接&#xff1a;417. 太平洋大西洋水流问题 题目解析 题目给我们一个矩阵&#xff0c;这个矩阵相当于陆地&#xff0c;被两个洋包围&#xff0c;左和上代表太平洋&#xff0c;右和下代表大西洋。 矩阵里面的数字代表海拔&#xff0c;水可以…

STM32之FMC—扩展外部 SDRAM

文章目录 一、FMC外设介绍二、SDRAM 控制原理1、SDRAM关键参数a、容量、分区b、引脚SDRAM 使用 2、SDRAM芯片IS42S16400J3、SDRAM 控制引脚说明控制逻辑地址控制SDRAM 的存储阵列SDRAM 的命令预充电刷新 W9825G6KH&#xff1a;W9825G6KH引脚 三、STM32F429 FMC四、其他文章打开…

医学数据分析实训 项目四回归分析--预测帕金森病病情的严重程度

文章目录 项目四&#xff1a;回归分析实践目的实践平台实践内容 预测帕金森病病情的严重程度作业&#xff08;一&#xff09;数据读入及理解&#xff08;二&#xff09;数据准备&#xff08;三&#xff09;模型建立&#xff08;四&#xff09;模型预测&#xff08;五&#xff0…

神经网络通俗理解学习笔记(4) 深度生成模型VAE、GAN

深度生成模型 什么是生成式模型蒙特卡洛方法变分推断Variational Inference变分自编码器VAE生成对抗网络Generative Adversarial NetworkDiffusion 扩散模型VAE和GAN 代码实现 什么是生成式模型 判别式和生成式模型 判别式:CNN/RNN/transformer;生成式:AE/VAE/GAN 判别式模型学…

Linux:RPM软件包管理以及Yum软件包仓库

挂载光驱设备 RPM软件包管理 RPM软件包简介 区分软件名和软件包名 软件名&#xff1a;firefox 软件包名&#xff1a;firefox-52.7.0-1.el7.centos.x86_64.rpm 查询软件信息 查询软件&#xff08;参数为软件名&#xff09; ]# rpm -qa #当前系统中所有已安装的软件包 ]# r…

Unity实战案例全解析 :PVZ 植物脚本分析

植物都继承了Pants脚本&#xff0c;但是我因为没注意听讲&#xff0c;把Pants也挂在植物上了&#xff0c;所以子类的PlantEnableUpdate和PlantDisableUpdate抢不过父类&#xff0c;无法正确触发动画&#xff0c;我还找不到哪里出了问题&#xff0c;所以就使用了携程加while强行…

Navicat使用 笔记04

Navicat调用数据库 1.创建一个自己的链接&#xff08;文件-->新建连接-->MySQL&#xff09; 进入到这个界面中&#xff1a; 【注意&#xff1a;密码是下载登录软件时设定过的】 创建一个连接完成&#xff08;通过双击激活&#xff09;。 2.在创建好的连接中创建数据库…

神经网络通俗理解学习笔记(5) 自然语言处理

自然语言处理 词嵌入和word2vec词义搜索和句意表示预训练模型Hugging Face库介绍经典NLP数据集代码案例-电影评论情感分析 词嵌入和word2vec 词嵌入是一种 将高维的数据表示映射到低维空间的方法 word embedding 是将语言中的词编码成向量便于后续的分析和处理 词嵌入和词向量…

感知器神经网络

1、原理 感知器是一种前馈人工神经网络&#xff0c;是人工神经网络中的一种典型结构。感知器具有分层结构&#xff0c;信息从输入层进入网络&#xff0c;逐层向前传递至输出层。根据感知器神经元变换函数、隐层数以及权值调整规则的不同&#xff0c;可以形成具有各种功能特点的…

宿舍管理系统的设计与实现 (含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 宿舍管理系统拥有三个角色&#xff0c;分别为系统管理员、宿舍管理员以及学生。其功能如下&#xff1a; 管理员&#xff1a;宿舍管理员管理、学生管理、宿舍楼管理、缺勤记录管理、个人密…

django学习入门系列之第十点《A 案例: 员工管理系统8》

文章目录 10.6 重写样式10.7 判断数据是否合法10.8 保存内容至数据库10.9 修改入职时间10.10 错误提示10.11 重写错误信息往期回顾 10.6 重写样式 注意&#xff1a;因为他框架都已经给你写好了&#xff0c;所以如果要使用样式的话可能要自己重新定义框架来进行修改 他有两种方…

衣食住行的投资与消费

机器人工程课程与科研采取敏捷开发的弊端和反思_工业机器人适合敏捷开发吗-CSDN博客 →学历消费者←自我救赎↑2024↓(*Φ皿Φ*)-CSDN博客 大部分衣食住行相关的产品都是消费品&#xff0c;只有极少部分是能保值的资产。 物以稀为贵&#xff0c;量产供应的一般而言都是消费品…

第二百三十五节 JPA教程 - JPA Lob列示例

JPA教程 - JPA Lob列示例 以下代码显示了如何使用Lob注释将字节数组保存到数据库。 LOB在数据库中有两种类型&#xff1a;字符大对象&#xff08;称为CLOB&#xff09;和二进制大对象&#xff08;或BLOB&#xff09;。 CLOB列保存大字符序列&#xff0c;BLOB列可存储大字节序…

JDK的选择安装和下载

搭建Java开发环境 要使用Java首先必须搭建Java的开发环境&#xff1b;Java的产品叫JDK&#xff08;Java Development Kit&#xff1a;Java开发工具包&#xff09;&#xff0c;必须安装JDK才能使用Java。 JDK发展史 那么这么多JDK&#xff0c;应该使用哪个版本&#xff0c;此处…

C# 比较对象新思路,利用反射技术打造更灵活的比较工具

前言 嘿&#xff0c;大家好&#xff01;如果你之前看过我分享的文章《C# 7个方法比较两个对象是否相等》&#xff0c;你可能会意识到对象比较在实际业务中经常出现的场景。今天&#xff0c;我想继续与大家分享一个在实际项目中遇到的问题。 有一次&#xff0c;我接手了一个别…

LLVM PASS-PWN-前置

文章目录 参考环境搭建基础知识![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dced705dcbb045ceb8df2237c9b0fd71.png)LLVM IR实例1. **.ll 格式&#xff08;人类可读的文本格式&#xff09;**2. **.bc 格式&#xff08;二进制格式&#xff09;**3. **内存表示** …