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

目录

前言

一,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;//3sfor (;;){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;//3sfor (;;){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;//3sfor (;;){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.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf poll_server

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

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

相关文章

奥维互动地图经纬度导入,再导出ovjsn再转化为kml格式

一、使用python将excel表中的经纬度换算成小数格式。 在文件上看到的经纬度是东经 1165′27.78″&#xff0c;北纬 2310′57.18″&#xff0c;要转化为116.09105,23.182550000000003 格式。如果要用vba编写函数&#xff0c;可能比较麻烦&#xff0c;为此我使用python来转化 i…

【例题】lanqiao3412 最小化战斗力差距

样例输入 3 1 2 3样例输出 1说明 样例中&#xff0c;当 a[1,3]&#xff0c;b[2]&#xff0c;此时战斗力差距为 1&#xff0c;无法得到比 1 更小的安排方式。 解题思路 目标是|max(a)-min(b)|最小&#xff0c;希望a里的最大值和b里的最小值能差距最小。 转化成&#xff1a;…

2024/9/16 英语每日一段

Stark argues that, in their gummies, at least,“The total sugar in a serving is less than in half a cherry.”Of course, cherries also provide fibre, vitamin C, and antioxidants--and 14 of them would count as one of your five-a-day. Artificial sweeteners to…

Linux操作系统文件权限管理

Linux操作系统下文件的权限分为当前用户权限、用户组权限和其他用户权限&#xff0c;然后每一类用户或组又分为读权限(r)、写权限(w)和可执行权限(x)。 如图1&#xff0c;打开任一目录&#xff0c;右键单击文件&#xff0c;在弹出菜单选择“属性”&#xff0c;在弹出的属性选项…

2022高教社杯全国大学生数学建模竞赛C题 问题一(1) Python代码演示

目录 问题 11.1 对这些玻璃文物的表面风化与其玻璃类型、纹饰和颜色的关系进行分析数据探索 -- 单个分类变量的绘图树形图条形图扇形图雷达图Cramer’s V 相关分析统计检验列联表分析卡方检验Fisher检验绘图堆积条形图分组条形图分类模型Logistic回归随机森林import matplotlib…

1.3 计算机网络的分类

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言一、按分布范围分类二、按传输技术分类三、按拓扑结构分类四、按使用者分类五、按传输介质分类 前言 计算机网络根据不同的标准可以被分为多种类型&#xff0c;本章从分布…

C语言刷题日记(附详解)(5)

一、选填部分 第一题: 下面代码在64位系统下的输出为( ) void print_array(int arr[]) {int n sizeof(arr) / sizeof(arr[0]);for (int i 0; i < n; i)printf("%d", arr[i]); } int main() {int arr[] { 1,2,3,4,5 };print_array(arr);return 0; } A . 1…

HarmonyOS使用LocationButton获取地理位置

LocationButton LocationKit getAddressesFromLocation方法 步骤&#xff1a; 整合 LocationButton并获取经纬度通过 LocationKit 将经纬度转为地址信息将地址信息渲染到页面上处理异常情况&#xff08;闪退&#xff09; LocationButton({ icon: LocationIconStyle.LINE…

robomimic基础教程(一)——基本概念

robosuite和robomimic都是由ARISE Initiative开发的开源工具&#xff0c;旨在推进机器人学习和机器人操作领域的研究。 一、基本概念 robomimic是一个用于机器人示范学习的框架。它提供了在机器人操作领域收集的大量示范数据集&#xff0c;以及用于从这些数据集中学习的离线学…

初始爬虫6

数据提取 数据提取总结 响应分类 结构化 json数据&#xff08;高频出现&#xff09; json模块 jsonpath模块 xml数据&#xff08;低频出现&#xff09; re模块 …

基于Python DoIPClient库的DoIP上位机开发手顺

代码 address, announcement DoIPClient.await_vehicle_announcement()logical_address announcement.logical_addressip, port addressprint(ip, port, logical_address) 效果 代码 address, announcement DoIPClient.get_entity(ecu_ip_addresssIp, protocol_version3…

重生归来之挖掘stm32底层知识(1)——寄存器

概念理解 要使用stm32首先要知道什么是引脚和寄存器。 如下图所示&#xff0c;芯片通过这些金属丝与电路板连接&#xff0c;这些金属丝叫做引脚。一般做软件开发是不需要了解芯片是怎么焊的&#xff0c;只要会使用就行。我们平常通过编程来控制这些引脚的输入和输出&#xff0c…

CefSharp_Vue交互(Element UI)_WinFormWeb应用(3)---通过页面锁屏和关机(含示例代码)

一、预览 实现功能:通过vue标题栏按钮锁屏和关机 1.1 预览 1.2 代码 锁屏代码csharp LockWorkStation() 关机代码chsharp 注意vue代码参数和此参数一致(0/1/2) 方法ExitWindowsEx()

Docker部署ddns-go教程(包含完整的配置过程)

本章教程教程,主要介绍如何用Docker部署ddns-go。 一、拉取容器 docker pull jeessy/ddns-go:v6.7.0二、运行容器 docker run -d \--name ddns-go \--restart unless-stopped \

鲲鹏云-docker安装mysql8.0-并设置参数(--lower-case-table-names=1)

前言&#xff1a; 由于鲲鹏云是arm架构&#xff0c;公司现有的镜像就用不了&#xff0c;为了搭建个测试环境&#xff0c;记录一下搭建过程 注意在mysql8.0里面lower-case-table-names必须在第一次安装时设置。 ①镜像的获取 鲲鹏镜像pull下来是不能跑的&#xff0c;会提示内…

Pycharm打印区打印数据不全问题

问题&#xff1a;pycharm底下的打印区打印一个比较大的数据&#xff0c;发现数据不全&#xff0c;只显示一板 解析&#xff1a;可能是打印区域的缓存不足导致的 解决办法&#xff1a; 找到pycharm的idea.properties文件&#xff0c;具体路径以下显示&#xff0c;前面路径看个人…

【十一,是忆】往事如风,回忆如诗,且行且歌

【十一&#xff0c;是忆】往事如风&#xff0c;回忆如诗&#xff0c;且行且歌 十一天的入职培训之所见、所得、所思、所感 820 启程 时隔两月&#xff0c;回到了熟悉的北京&#xff0c;我的脚又一次踏在这片土地上&#xff0c;遗憾的是周转的北京西站&#xff0c;未曾停留多久…

C语言中数据类型

一、C 语言中数据类型 基本数据类型&#xff1a; 整型&#xff08;int&#xff09;&#xff1a;用于存储整数&#xff0c;如&#xff1a;1、2、3等。字符型&#xff08;char&#xff09;&#xff1a;用于存储单个字符&#xff0c;如&#xff1a;‘a’、‘b’、c’等。浮点型&a…

Docker安装SVN,搭建自己的本地版本仓库

一、拉取镜像并运行容器 docker pull garethflowers/svn-server docker run --restart always --name svn -d -v /root/dockers/svn:/var/opt/svn -p 3690:3690 garethflowers/svn-server 1、解析 /root/dockers/svn为宿主机的文件目录&#xff0c;/var/opt/svn为容器内的文件…

线程池是啥有啥用,怎么用,如何自己实现一个

目录 一、线程池是啥&#xff0c;有啥用 二、线程池怎么用 1.构造方法 2.如何使用Java的线程池 三、简单实现一个线程池 假设我是一个&#xff08;好看有才华&#xff09; 的妹子&#xff0c;那么我就会有很多追求者&#xff0c;这些也叫备胎们&#xff0c;我们若把他…