160 Linux C++ 通讯架构实战14,epoll 反应堆模型

到这里,我们需要整理一下之前学习的epoll模型,并根据之前的epoll模型,提出弊端,进而整理epoll反应堆模型,进一步深刻理解,这是因为epoll实在是太重要了。

复习之前的epoll的整体流程以及思路。

参考之前写的epoll的代码

第一步,socket,创建套接字。

int listenfd = Socket(AF_INET, SOCK_STREAM,0);

第二步,setsockopt 设定端口复用

int opt = 1;
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

第三步, bind函数,将socket和地址结构绑定

Bind(listenfd, (struct sockaddr *)&servaddr,sizeof(servaddr));

第四步,Listen 设置可以同时监听的最大的数量为1024

Listen(listenfd, 1024);

第五步,Epoll_create 创建一个红黑树结点,建议的节点>0

int epfd = Epoll_create(OPEN_MAX);

第六步,Epoll_ctl 先将listenfd添加到 epfd这个红黑树上,EPOLL_CTL_ADD表示是添加节点.EPOLLIN表示监听读事件

    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.events = EPOLLIN; 
    event.data.fd = listenfd;
    //epoll_ct 函数的目的是给这颗红黑树上添加节点,删除节点,修改节点
    Epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd, &event);

第七步,epoll_wait,这时候就需要弄一个循环监听了, 使用 epoll_wait函数等待连接

while (1) {
        //epoll_wait返回值nready为满足监听的总个数。realevents是传出参数,传出满足监听条件的所有的结构体
        nready = epoll_wait(epfd, realevents, OPEN_MAX, -1);

}

第7.1步:

epoll_wait返回后,如果realevents 中的是listenfd,说明有客户端第一次连接过来,需要使用accpet去接受这个链接,生成clientfd ,

int clientfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);

然后将这个clientfd添加到红黑树上。

                Epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &clientevent);

第7.2步:

epoll_wait返回后,如果realevents 中的是clientfd,那么说明客户端有发真正的数据过来,需要使用read 函数去读取这些数据,完成我们对于数据的处理,然后再使用write发给客户端

realreadnum = Read(socketfd, buf, MAXLINE);

//将读到的数据做处理,我们这里只是小写转大写。
for (int j = 0; j < realreadnum; ++j) {
        buf[j] = toupper(buf[j]);
}
Write(socketfd, buf, realreadnum);

这里会有问题,这是因为在网络环境下,环境是很复杂的,例如:客户端的 滑动窗口 已经满了

fix 方案:epoll 反应堆模型

接上述条件,因此在这里我们需要判断客户端是否能写,如果能写,也就是说:我们需要将 clientfd 的 “写事件” 加入到红黑树上。写完后,将 clientfd 的“写事件” 从红黑树上摘下。

还有一个问题,当我们接受的这些数据还没有处理的时候,我们不希望从 clientfd 再 “读取数据”,因此还需要将 clientfd 的“读事件” 从 红黑树上摘下,等待 给客户端 写数据完成后,再将 读取数据事件  添加回 红黑树上。

整个整理如下:

如果realevents中是clientfd--------> read 数据

-------->将clientfd 从红黑树上摘下(将clientfd 的 读事件通过epoll_del  从 红黑树上摘下),

-------->将clientfd 的写事件 通过epoll_ctl上添加到红黑树上

-------->当epoll_wait的返回时,如果返回中有 写事件 ,再使用write /send 发送数据给客户端

-------->当发送数据完成后,则将clientfd 的写事件从红黑树上摘下

-------->将clientfd 的读事件再次添加到红黑树上,从而形成循环

epoll反应堆模型的再次说明:

在epoll反应堆模型之前,我们需要使用到 epoll_event 中的 data 中的 fd来决定具体是那个连接

struct epoll_event
{
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

在epoll反应堆的时候,我们需要使用到 epoll_event 中的 data 中的 ptr

因为ptr是一个万能指针,可以指向任何东西,因此一般使用的时候,会有一个struct,将相关的信息都放在这个struct中,且这个ptr就指向这个ptr,这样就能保存所有的信息,且会将 epoll_wait成立需要调用的方法,通过回调函数的形式添加到这个struct中,

实际上是多了个参数的使用::   ET + NONBLOCK  轮询 + void *ptr

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/epoll.h>



#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 5000

int main() {

	int ret = 0;

	//第一步,socket,创建套接字。On  success,  a  file  descriptor  for  the new socket is returned
	int listenfd = Socket(AF_INET, SOCK_STREAM,0);

	//第二步,setsockopt 设定端口复用,代码是固定的,当opt是1的时候,说明可以复用端口。 On success, zero is returned for the standard options.  On error, -1 is returned, and errno is set appropriately.

	int opt = 1;
	Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	//第三步, bind函数,将socket和地址结构绑定
	//int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	Bind(listenfd, (struct sockaddr *)&servaddr,sizeof(servaddr));


	//第四步,设置可以同时监听的最大的数量为1024,如果改成5000会不会错呢?
	Listen(listenfd, 1024);
	//Listen(listenfd, OPEN_MAX);


	//第五步,创建一个红黑树结点,我们建议这个红黑树的节点为5000
	int epfd = Epoll_create(OPEN_MAX);

	//第六步,先将listenfd添加到 epfd这个红黑树上,EPOLL_CTL_ADD表示是添加节点.EPOLLIN表示监听读事件
	struct epoll_event event;
	bzero(&event, sizeof(event));
	event.events = EPOLLIN; 
	event.data.fd = listenfd;
	//epoll_ct 函数的目的是给这颗红黑树上添加节点,删除节点,修改节点
	Epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd, &event);

	//第七步,这时候就需要弄一个循环监听了, 使用 epoll_wait函数等待连接
	struct epoll_event realevents[OPEN_MAX];
	int nready = 0;
	while (1) {
		//epoll_wait返回值nready为满足监听的总个数。realevents是传出参数,传出满足监听条件的所有的结构体
		nready = epoll_wait(epfd, realevents, OPEN_MAX, -1);
		if (nready == -1) {
			perr_exit("epoll_wait error");
		}
		for (int i = 0; i < nready;++i) {
			if (!(realevents[i].events & EPOLLIN)) {
				//如果不是"读"事件, 继续循环
				continue;
			}
			int socketfd = realevents[i].data.fd;
			if (socketfd == listenfd) {
				//如果是listenfd 的读事件,说明有新的链接过来了,这时候要调用accpet
				struct sockaddr_in cliaddr;
				int cliaddrlen = sizeof(cliaddr);
				bzero(&cliaddr, cliaddrlen);
				printf("aaa\n");
				int clientfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
				char str[INET_ADDRSTRLEN] = {0};//#define INET_ADDRSTRLEN 16
				printf("received from %s at PORT %d\n",
					inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
					ntohs(cliaddr.sin_port));

				//然后将connfd,添加到红黑树上
				struct epoll_event clientevent;
				bzero(&clientevent, sizeof(clientevent));
				clientevent.events = EPOLLIN;
				clientevent.data.fd = clientfd;
				Epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &clientevent);

			}
			else {
				//如果不是listenfd,那么就是connectfd了,说明这时候客户端有东西写过来,我们要从客户端读取数据
				char buf[MAXLINE] = {0};
				int realreadnum;
				REREADPOINT:
				realreadnum = Read(socketfd, buf, MAXLINE);
				
				if (realreadnum == 0) {//在网络环境下,read函数返回0,说明是对端关闭了,也就是说,客户端关闭了
	//那么就应该关闭当前的connect端,并将该监听从 红黑树上 移除
					printf("read done\n");
					Epoll_ctl(epfd, EPOLL_CTL_DEL, socketfd, NULL);
					Close(socketfd);
				}
				else if (realreadnum == -1) {

					if (errno == EINTR) {
						//说明是被信号打断的,一般要重新read
						printf("信号打断\n");
						goto REREADPOINT;
					}
					else if (errno == EAGAIN || errno == EWOULDBLOCK)
					{
						printf(" WOULDBLOCK \n");
						//说明在打开文件的时候是使用的O_NONBLOCK方式打开的,但是没有读取到数据
						//当前代码是不会走到这里的,因为前面代码select的最后一个参数用的NULL,是阻塞的
						//一般在这里 也要重新读,但是这里有个问题,如果一直都读取不到,会不会死循环?
						goto REREADPOINT;
					}
					else if (errno == ECONNRESET) {
						//ECONNRESET 说明连接被重置了,因此要将该cfd关闭,并重新移除监听队列
						printf("read done\n");
						Epoll_ctl(epfd, EPOLL_CTL_DEL, socketfd, NULL);
						Close(socketfd);
					}
					else {
						//这就是真正的有问题了,注意这里不要exit程序,应该只是让打印log
						//不退出程序是因为,这时候还有其他的链接连上的
						perror("read num <0");
					}
				}
				else if (realreadnum > 0) {
					//真正的读取到了客户端发送过来的数据
					for (int j = 0; j < realreadnum; ++j) {
						buf[j] = toupper(buf[j]);
					}
					Write(socketfd, buf, realreadnum);
					Write(STDOUT_FILENO, buf, realreadnum);
				}
			}
		}
	}

	Close(listenfd);
	Close(epfd);
	return ret;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

虚幻UE5智慧城市全流程开发教学

一、背景 这几年&#xff0c;智慧城市/智慧交通/智慧水利等飞速发展&#xff0c;骑士特意为大家做了一个这块的学习路线。 二、这是学习大纲 1.给虚幻UE5初学者准备的智慧城市/数字孪生蓝图开发教程 https://www.bilibili.com/video/BV1894y1u78G 2.UE5数字孪生蓝图开发教学…

【软件工程】测试规格

1. 引言 1.1简介 本次的测试用例是基于核心代码基本开发完毕&#xff0c;在第一代系统基本正常运行后编写的&#xff0c;主要目的是为了后续开发与维护的便利性。 该文档主要受众为该系统后续开发人员&#xff0c;并且在阅读此文档前最后先阅读本系统的需求文档、概要设计文…

海外视频网站推广实战需掌握的10个关键性数据指标-华媒舍

在海外视频网站推广实战中&#xff0c;了解和掌握一些关键性数据指标是非常重要的。这些指标可以帮助我们评估视频网站的推广效果&#xff0c;优化推广策略&#xff0c;提升用户体验。以下是推广人员在实战中应该了解和关注的十个关键性数据指标&#xff1a; 1. 视频创意点击率…

PS入门|规规矩矩的图形怎么抠出来?

前言 上一次讲解到用魔棒工具蒙版可以把需要的区域抠出来&#xff0c;但仅适用于边缘锐利的类型。 但魔棒工具并不适用于边缘区域有过渡色的内容&#xff0c;比如下面这张照片&#xff1a; 如果直接使用魔棒工具进行选择&#xff0c;就会出现下面这种情况&#xff1a; 在边界…

数据挖掘入门项目二手交易车价格预测之建模调参

文章目录 目标步骤1. 调整数据类型&#xff0c;减少数据在内存中占用的空间2. 使用线性回归来简单建模3. 五折交叉验证4. 模拟真实业务情况5. 绘制学习率曲线与验证曲线6. 嵌入式特征选择6. 非线性模型7. 模型调参&#xff08;1&#xff09; 贪心调参&#xff08;2&#xff09;…

内表GROUP BY

内表GROUP BY REPORT z_test_table_lhy. DATA: price TYPE sflight-price. SELECT MIN( price ) AS m,carridINTO DATA(t_temp)FROM sflightGROUP BY carridHAVING MAX( price ) > 10. "Having从句中比较统计结果时&#xff0c;需要将统计函数重写一遍&#xff0c;而不…

Android数据存储技术

一、文件存储 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"android:layout_height"match_parent" ><EditTextandroid:id&qu…

树莓派安装Windows搭建网盘和下载机

0 需求分析 在同一个局域网内&#xff0c;同时有多种设备&#xff08;Windows&#xff0c;Linux&#xff0c;Android&#xff09;需要进行大量的数据共享。另外&#xff0c;还时常需要从百度网盘/夸克网盘等网盘下载文件。不难看出&#xff0c;我的需求很简单&#xff0c;就是…

异常的处理

异常处理概述 在编写程序时&#xff0c;经常要在可能出现错误的地方加上检测的代码&#xff0c;如进行x/y运算时&#xff0c;要检测分母为0&#xff0c;数据为空&#xff0c;输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿&#xff0c;可读性差&…

论文笔记:Large Language Models as Analogical Reasoners

iclr 2024 reviewer打分5558 1 intro 基于CoT prompt的大模型能够更好地解决复杂推理问题 然而传统CoT需要提供相关的例子作为指导&#xff0c;这就增加了人工标注的成本——>Zero-shot CoT避免了人工标注来引导推理 但是对于一些复杂的任务难以完成推理&#xff0c;例如c…

Ubuntu22.04中基于Qt开发Android App

文章目录 前言在Ubuntu22.04中配置开发环境案例测试参考 前言 使用Qt开发手机应用程序是一种高效且灵活的选择。Qt作为一个跨平台的开发框架&#xff0c;为开发者提供了统一的开发体验和丰富的功能库。首先&#xff0c;Qt的跨平台性让开发者可以使用相同的代码库在不同的操作系…

SSM项目实战——哈哈音乐(四)前台模块开发

1、项目准备 ①导入依赖和前端资源 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.x…

路由策略与路由控制之双点双向重发布(OSPF-ISIS)实验

双点双向重发布在路由协议中&#xff0c;特别是在OSPF&#xff08;开放式最短路径优先&#xff09;与IS-IS&#xff08;中间系统到中间系统&#xff09;等协议之间&#xff0c;指的是在两个协议间或者两个进程间进行路由信息共享的机制。这种机制涉及到在两个不同的协议区域使用…

微软推出GPT-4 Turbo优先使用权:Copilot for Microsoft 365商业用户享受无限制对话及增强图像生成能力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

电脑上音频太多,播放速度又不一致,如何批量调节音频播放速度?

批量调节音频速度是现代音频处理中的一个重要环节&#xff0c;尤其在音乐制作、电影剪辑、有声书制作等领域&#xff0c;它能够帮助制作者快速高效地调整音频的播放速度&#xff0c;从而满足特定的制作需求。本文将详细介绍批量调节音频速度的方法、技巧和注意事项&#xff0c;…

Docker 安装 Linux 系统可视化监控 Netdata

docker 安装 netdata 前提准备Docker 两种方式部署 Netdata1、使用 docker run 命令运行 netdata 服务2、使用 docker compose 运行 netdata 服务 Netdata 服务可视化界面Netdata 汉化处理 前提准备 说明&#xff1a;此处使用 windows11 安装的 docker desktop & wsl2/apli…

【Rust】环境搭建

Rust 支持很多的集成开发环境&#xff08;IDE&#xff09;或开发专用的文本编辑器。 官方网站公布支持的工具如下&#xff08;工具 - Rust 程序设计语言&#xff09; 本课程将使用 Visual Studio Code 作为我们的开发环境&#xff08;Eclipse 有专用于 Rust 开发的版本&#…

政安晨:【Keras机器学习实践要点】(十七)—— 利用 EfficientNet 通过微调进行图像分类

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本文目标&#xff1a; 使用 EfficientNet 和在图…

比nestjs更优雅的ts控制反转策略-依赖查找

一、Cabloy5.0内测预告 Cabloy5.0采用TS对整个全栈框架进行了脱胎换骨般的大重构&#xff0c;并且提供了更加优雅的ts控制反转策略&#xff0c;让我们的业务开发更加快捷顺畅 1. 新旧技术栈对比&#xff1a; 后端前端旧版js、egg2.0、mysqljs、vue2、framework7新版ts、egg3…

Git 如何去使用

目录 1. Git暂存区的使用 1.1. 暂存区的作用 1.2. 暂存区覆盖工作区&#xff08;注意&#xff1a;完全确认覆盖时使用&#xff09; 1.3. 暂存区移除文件 1.4. 练习 2. Git回退版本 2.1. 概念 2.2. 查看提交历史 2.3. 回退命令 2.4. 注意 3. Git删除文件 3.1. 需求 …