Linux 多路转接 —— poll

news2024/12/25 9:25:03

目录

    • 传统艺能😎
    • poll🤣
      • struct pollfd🤣
    • poll 服务器😘
      • PollServer类😁
      • 运行服务器😒
      • 事件处理😁
    • 服务器测试😂

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

poll🤣

poll 也是系统提供的一个多路转接接口。poll 系统调用也可以让程序同时监视多个文件描述符上的事件是否就绪,和 select 定位是一样的,适用场景也是一样的。

p o l l 函数的函数原型如下: \color{red} {poll 函数的函数原型如下:} poll函数的函数原型如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

fds 为一个 poll 函数监视的结构列表,每一个元素包含三部分内容:文件描述符、监视的事件集合、就绪的事件集合nfds 为 fds 数组长度。timeout 为poll函数的超时时间,单位是毫秒(ms)。

timeout 可取值:

-1:poll 调用后阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
0:poll 调用后非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll 检测后都会立即返回。
特定的时间值:poll 调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后 poll 进行超时返回。

函数调用成功则返回有事件就绪的文件描述符个数。如果 timeout 时间耗尽,则返回 0。如果函数调用失败,则返回 -1,同时错误码会被设置。

在 poll 调用失败后会设置错误码,具体分为四种错误码:

EFAULT:fds数组不包含在调用程序的地址空间中。
EINTR:此调用被信号所中断。
EINVAL:nfds值超过RLIMIT_NOFILE值。
ENOMEM:核心内存不足。

struct pollfd🤣

struct pollfd 结构当中包含三个成员:

fd:特定的文件描述符,若设置为负值则忽略events字段并且revents字段返回0。
events:需要监视该文件描述符上的哪些事件。
revents:poll函数返回时告知用户该文件描述符上的哪些事件已经就绪。
在这里插入图片描述
events 和 revents 的取值:

在这里插入图片描述
这些取值实际都是以宏的方式进行定义的,它们的二进制序列中有且仅有一个比特位是1,且为 1 的比特位是各不相同的

在这里插入图片描述

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

poll 服务器😘

poll 工作流程和 select 是基本类似的,这里我们也实现一个简单poll服务器,该服务器也只是读取客户端发来的数据并进行打印。

PollServer类😁

PollServer类当中只需要包含监听套接字和端口号两个成员变量,在 poll 服务器绑定时直接将 IP 地址设置为INADDR_ANY即可。

在构造 PollServer 对象时,需要指明 poll 服务器端口号,当然也可以在初始化 poll 服务器的时候指明。
在初始化 poll 服务器的时候调用 Socket 类中的函数,依次进行套接字的创建、绑定和监听即可,这里的 Socket 类和之前实现的一样。
在析构函数中可以选择调用 close 函数将监听套接字进行关闭,但实际也可以不进行该动作,因为服务器运行后一般是不退出的。
代码如下:

#pragma once

#include "socket.hpp"
#include <poll.h>

#define BACK_LOG 5

class PollServer{
private:
	int _listen_sock; //监听套接字
	int _port; //端口号
public:
	PollServer(int port)
		: _port(port)
	{}
	void InitPollServer()
	{
		_listen_sock = Socket::SocketCreate();
		Socket::SocketBind(_listen_sock, _port);
		Socket::SocketListen(_listen_sock, BACK_LOG);
	}
	~PollServer()
	{
		if (_listen_sock >= 0){
			close(_listen_sock);
		}
	}
};

运行服务器😒

服务器初始化完后开始运行,而 poll 服务器要做的就是不断调用 poll 函数,当事件就绪时执行动作即可。

首先,在 poll 服务器开始死循环调用 poll 函数之前,需要定义一个 fds 数组,该数组当中的每个位置都是一个struct pollfd结构,后续调用 poll 函数时会作为参数进行传入。先将 fds 数组当中每个位置初始化为无效,并将监听套接字添加到 fds 数组当中,表示服务器刚开始运行时只需要监视监听套接字的读事件。

此后,poll 服务器就不断调用 poll 函数监视读事件是否就绪。如果返回值大于0,说明 poll 调用成功,此时已经有文件描述符的读事件就绪,接下来就应该对就绪事件进行处理。如果返回值等于0,则说明 timeout 时间耗尽,此时直接准备进行下一次 poll 调用即可。如果返回值为-1,则说明 poll 调用失败,此时也让服务器准备进行下一次 poll 调用,但实际应该进一步判断错误码,根据错误码来判断是否应该继续调用 poll 函数。

#pragma once

#include "socket.hpp"
#include <poll.h>

#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1

class PollServer{
private:
	int _listen_sock; //监听套接字
	int _port; //端口号
public:
	void Run()
	{
		struct pollfd fds[NUM];
		ClearPollfds(fds, NUM, DFL_FD); //清空数组中的所有位置
		SetPollfds(fds, NUM, _listen_sock); //将监听套接字添加到数组中,并关心其读事件
		for (;;){
			switch (poll(fds, NUM, -1)){
			case 0:
				std::cout << "timeout..." << std::endl;
				break;
			case -1:
				std::cerr << "poll error" << std::endl;
				break;
			default:
				//正常的事件处理
				//std::cout<<"有事件发生..."<<std::endl;
				HandlerEvent(fds, NUM);
				break;
			}
		}
	}
private:
	void ClearPollfds(struct pollfd fds[], int num, int default_fd)
	{
		for (int i = 0; i < num; i++){
			fds[i].fd = default_fd;
			fds[i].events = 0;
			fds[i].revents = 0;
		}
	}
	bool SetPollfds(struct pollfd fds[], int num, int fd)
	{
		for (int i = 0; i < num; i++){
			if (fds[i].fd == DFL_FD){ //该位置没有被使用
				fds[i].fd = fd;
				fds[i].events |= POLLIN; //添加读事件到events当中
			return true;
			}
		}
		return false; //fds数组已满
	}
};

事件处理😁

当 poll 检测到有文件描述符的读事件就绪,就会在其对应的 struct pollfd 结构中的 revents 成员中添加读事件并返回,接下来 poll 服务器就应该对就绪事件进行处理了,事件处理过程如下:

首先遍历 fds 数组中的每个 struct pollfd 结构,如果该结构当中的 fd 有效,且 revents 当中包含读事件,则说明该文件描述符的读事件就绪,接下来就需要进一步判断该文件描述符是监听套接字还是与客户端建立的套接字。

如果是监听套接字的读事件就绪,则调用 accept 函数将底层建立好的连接获取上来,并将获取到的套接字添加到 fds 数组当中,表示下一次调用 poll 函数时需要监视该套接字的读事件。

如果是与客户端建立的连接对应的读事件就绪,则调用 read 函数读取客户端发来的数据,并将读取到的数据在服务器端进行打印。如果在调用 read 时发现客户端将连接关闭或 read 调用失败,则服务器也应关闭对应的连接,并将该连接对应的文件描述符从 fds 数组当中清除,表示下一次调用 poll 时无需再监视该套接字的读事件。

#pragma once

#include "socket.hpp"
#include <poll.h>

#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1

class PollServer{
private:
	int _listen_sock; //监听套接字
	int _port; //端口号
public:
	void HandlerEvent(struct pollfd fds[], int num)
	{
		for (int i = 0; i < num; i++){
			if (fds[i].fd == DFL_FD){ //跳过无效的位置
				continue;
			}
			if (fds[i].fd == _listen_sock&&fds[i].revents&POLLIN){ //连接事件就绪
				struct sockaddr_in peer;
				memset(&peer, 0, sizeof(peer));
				socklen_t len = sizeof(peer);
				int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
				if (sock < 0){ //获取连接失败
					std::cerr << "accept error" << std::endl;
					continue;
				}
				std::string peer_ip = inet_ntoa(peer.sin_addr);
				int peer_port = ntohs(peer.sin_port);
				std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;
				
				if (!SetPollfds(fds, NUM, sock)){ //将获取到的套接字添加到fds数组中,并关心其读事件
					close(sock);
					std::cout << "poll server is full, close fd: " << sock << std::endl;
				}
			}
			else if (fds[i].revents&POLLIN){ //读事件就绪
				char buffer[1024];
				ssize_t size = read(fds[i].fd, buffer, sizeof(buffer)-1);
				if (size > 0){ //读取成功
					buffer[size] = '\0';
					std::cout << "echo# " << buffer << std::endl;
				}
				else if (size == 0){ //对端连接关闭
					std::cout << "client quit" << std::endl;
					close(fds[i].fd);
					UnSetPollfds(fds, i); //将该文件描述符从fds数组中清除
				}
				else{
					std::cerr << "read error" << std::endl;
					close(fds[i].fd);
					UnSetPollfds(fds, i); //将该文件描述符从fds数组中清除
				}
			}
		}
	}
private:
	bool SetPollfds(struct pollfd fds[], int num, int fd)
	{
		for (int i = 0; i < num; i++){
			if (fds[i].fd == DFL_FD){ //该位置没有被使用
				fds[i].fd = fd;
				fds[i].events |= POLLIN; //添加读事件到events当中
				return true;
			}
		}
		return false; //fds数组已满
	}
	void UnSetPollfds(struct pollfd fds[], int pos)
	{
		fds[pos].fd = DFL_FD;
		fds[pos].events = 0;
		fds[pos].revents = 0;
	}
};

因为这里将fds数组的大小是固定设置的,因此在将新获取连接对应的文件描述符添加到fds数组时,可能会因为fds数组已满而添加失败,这时poll服务器只能将刚刚获取上来的连接对应的套接字进行关闭。

服务器测试😂

运行 poll 服务器时也需要先实例化出一个 PollServer 对象,对 poll 服务器进行初始化后就可以运行服务器了:

#include "poll_server.hpp"
#include <string>

static void Usage(std::string proc)
{
	std::cerr << "Usage: " << proc << " port" << std::endl;
}

int main(int argc, char* argv[])
{
	if (argc != 2){
		Usage(argv[0]);
		exit(1);
	}
	int port = atoi(argv[1]);
	PollServer* svr = new PollServer(port);
	svr->InitPollServer();
	svr->Run();
	
	return 0;
}

p o l l 的优点: \color{red} {poll 的优点:} poll的优点:

  1. struct pollfd 结构当中包含了 events 和 revents,相当于将 select 的输入输出型参数进行分离,因此在每次调用 poll 之前,不需要像 select 一样重新对参数进行设置。
  2. poll可监控的文件描述符数量没有限制。
  3. poll也可以同时等待多个文件描述符,能够提高IO的效率。

说明一下:

虽然代码中将 fds 数组的元素个数定义为1024,但 fds 数组的大小是可以继续增大的,poll 函数能够帮你监视多少个文件描述符是由传入 poll 函数的第二个参数决定的。而 fd_set 类型只有1024个比特位,因此 select 函数最多只能监视1024个文件描述符。

p o l l 的缺点: \color{red} {poll 的缺点:} poll的缺点:

  1. 和 select 一样,当 poll 返回后,需要遍历 fds 数组来获取就绪的文件描述符。
  2. 每次调用 poll,都需要把大量的 struct pollfd 结构从用户态拷贝到内核态,这个开销也会随着 poll 监视的文件描述符数目的增多而增大。
  3. 同时每次调用 poll 都需要在内核遍历传递进来的所有fd,这个开销在 fd 很多时也很大。

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

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

相关文章

【初识C语言(4)】操作符

文章目录 1. 算术操作符2. 移位操作符3. 位操作符4. 赋值操作符5. 单目操作符6. 关系操作符7. 逻辑操作符8. 条件操作符9. 逗号表达式10. 下标引用、函数调用和结构成员 1. 算术操作符 - * / %加 减 乘 除 求余 加减乘 都很简单没啥可讲的&#xff0c;这里主要讲解 除 和 求余。…

nginx主配置文件及实操

文章目录 一、nginx主配置文件nginx.conf1.认识nginx服务的主配置文件2.全局配置3.I/O事件配置4.HTTP配置&#xff15;.检查配置文件是否正确&#xff16;.浏览器测试 二、实战操作1.nginx的访问状态统计2.基于授权的访问控制&#xff1a;3.基于客户端访问设置&#xff08;设置…

驱动开发:内核中进程与句柄互转

在内核开发中&#xff0c;经常需要进行进程和句柄之间的互相转换。进程通常由一个唯一的进程标识符&#xff08;PID&#xff09;来标识&#xff0c;而句柄是指对内核对象的引用。在Windows内核中&#xff0c;EProcess结构表示一个进程&#xff0c;而HANDLE是一个句柄。 为了实…

【跑实验04】CLIP安装报错,setup.py如何安装,图像编码器(image_encoder)的使用(含源代码)

文章目录 一、如何解决CLIP安装报错二、setup.py如何安装&#xff1f;三、图像编码器(image_encoder)的使用 一、如何解决CLIP安装报错 我第一次尝试安装CLIP&#xff0c;采用的方法是pip的方法&#xff1a; pip install clip但是安装后&#xff0c;无法使用&#xff1a; 明确…

代码随想录二刷 day31 | 贪心之 理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和

day31 理论基础什么是贪心贪心算法的套路一般解题步骤 455.分发饼干376. 摆动序列情况一&#xff1a;上下坡中有平坡情况二&#xff1a;数组首尾两端情况三&#xff1a;单调坡度有平坡 53. 最大子序和 理论基础 什么是贪心 贪心的本质是选择每一阶段的局部最优&#xff0c;从…

vue3基础 ---- 上

目录 一.vue3介绍 1. 官网初识 2.环境搭建 2-1 线上尝试 2-2 CDN使用 2-3 Vue CLI 2-4 Vite 二.vue3基础 1.模板语法 1-1 我的第一个vue应用 1-2 应用背后的真相 1-3 模板语法-新的皮肤来了 1-4 Todolist-来个案例 1-5 点击变心案例 - 是变色 1-6 v-html- 模板…

【libdatachannel】pycharm运行streamer的信令服务及streamer与js客户端本机联调2

后面与ws服务器一直有ping pong2023-06-23 10:30:27.915 VERB [24456] [rtc::impl::PollService::runLoop@178] Entering poll, timeout=10000ms 2023-06-23 10:30:27.916 VERB [24456] [rtc::impl::PollServ

有了电脑怎么搞?我有办法(第一部分)-涵子的个人想法

上一次会选电脑后&#xff0c;今天我们来学习一下电脑的整理方法。今天满满的都是干货&#xff0c;请仔细阅读&#xff01;注意粗体内容。 目录 一、磁盘太乱咋整&#xff1f; 1.1.磁盘是个啥&#xff1f;为啥乱&#xff1f; 1.2.分区怎么分&#xff1f; 1.3.还是分不清怎…

Java类加载机制:从字节码到对象的奇妙之旅

目录 什么是类加载机制&#xff1f; 类加载顺序 类加载顺序图 双亲委派模型 双亲委派模型示意图 如何打破双亲委派模型&#xff1f; 要想学好java&#xff0c;首先得知道它是什么&#xff0c;怎么运行的&#xff0c;怎么加载的&#xff0c;运行的是个什么东西&#xff0c…

JVM内存模型及JAVA程序运行原理

文章目录 JVM简介JVM的内存结构方法区堆栈程序计数器 JAVA程序在JVM内是如何执行的 JVM简介 JVM------Java Virtual Machine.JVM是Java平台的基础&#xff0c;与实际机器一样&#xff0c;它有自己的指令集&#xff08;类似CPU通过指令操作程序运行&#xff09;&#xff0c;并在…

Temu、shopee、Lazada、Newegg、美客多销量增长黑科技!

在跨境电商的激烈竞争环境下&#xff0c;2023年伴随着疫情的解封&#xff0c;电商业务重新焕发生机。卖家们不应该只在一个平台投放所有资源&#xff0c;而应多元化地进行平台选择。实际上&#xff0c;许多小型电商平台如Wish、敦煌等也能带来大量业务&#xff0c;而且竞争压力…

如何保证API接口的安全性

API接口的安全性是非常重要的&#xff0c;以下是一些保证API接口安全性的措施&#xff1a; 用户认证、授权&#xff1a;接口的调用者必须提供有效的身份认证信息&#xff0c;包括用户名、密码、密钥等&#xff0c;以保证接口的调用者的身份有效性。同时&#xff0c;需要在接口的…

亚马逊云科技中国峰会:Amazon DeepRacer——因构建 而可见

文章目录 一、前言二、 亚马逊云科技中国峰会大会亮点三、Amazon DeepRacer 赛事火热开启四、Amazon DeepRacer 深度体验五、2023亚马逊云科技中国峰会——Amazon DeepRacerAmazon DeepRacer 中国峰会总决赛Girls in Tech Show高校联动&#xff0c;寻找未来“生力军”Amazon De…

电赛汇总(三):常用传感器电路模块设计

电赛汇总(三)&#xff1a;常用传感器电路模块设计 这一章节主要详细记录各种常用的传感器的电子芯片型号、设计原理与思想&#xff0c;以便随时查看翻阅。这部分内容出自黄根春等学者著的《全国大学生电子设计竞赛教程》一书中&#xff0c;感兴趣的朋友可以购买翻阅。 文章目…

Debian12编译安装R软件

1.解压源码包 2.运行./configure --prefix/usr/local/R-4.3 报错&#xff0c;没有安装fortran编译器 3.运行./configure --prefix/usr/local/R-4.3 4.运行./configure --prefix/usr/local/R-4.3 5.运行./configure --prefix/usr/local/R-4.3 6.运行./configure --pref…

厦门大学计算机考研分析

关注我们的微信公众号 姚哥计算机考研 更多详情欢迎咨询 厦门大学&#xff08;B&#xff09;考研难度&#xff08;☆☆☆☆☆&#xff09; 厦门大学计算机考研主要招生学院有信息学院、人工智能研究院、医学院和电影学院。目前均已出拟录取名单。 厦门大学信息学院&#xff…

14. WebGPU 透视投影

在上一篇文章中&#xff0c;介绍了如何制作 3D &#xff0c;但 3D 没有任何透视效果。它使用的是所谓的“正交”视图&#xff0c;它有其用途&#xff0c;但通常不是人们说“3D”时想要的。 现在&#xff0c;需要添加透视图。究竟什么是透视&#xff1f;基本特征就是离得越远的…

牛客网基础语法71~80题

牛客网基础语法71~80题&#x1f618;&#x1f618;&#x1f618; &#x1f4ab;前言&#xff1a;今天是咱们第八期刷牛客网上的题目。 &#x1f4ab;目标&#xff1a;可以掌握循环嵌套&#xff0c;可以采用一些数组来解决问题&#xff0c;对循环知识掌握熟练&#xff0c;对数学…

H5套壳微信小程序跳转H5以及配置服务器接口域名和业务域名

一、H5套壳微信小程序跳转H5 基本语法&#xff1a; <web-view src"https://你的域名"></web-view> 1. 其中&#xff0c;url必须在管理后台加进业务域名&#xff0c;并且是https开头的。 使用了web-view&#xff0c;页面将不能放置其他元素&#xff0c…

【深度学习】RepVGG解析和学习体会,结构重参数化的后的速度比较,代码实现

文章目录 前言0. Vgg1.RepVGG Block 详解 前言 论文名称&#xff1a;RepVGG: Making VGG-style ConvNets Great Again 论文下载地址&#xff1a;https://arxiv.org/abs/2101.03697 官方源码&#xff08;Pytorch实现&#xff09;&#xff1a;https://github.com/DingXiaoH/RepV…