Zinx框架-游戏服务器开发002:框架学习-按照三层结构模式重构测试代码+Tcp数据适配+时间轮定时器

文章目录

  • 1 Zinx框架总览
  • 2 三层模式的分析
  • 3 三层重构原有的功能 - 头文件
    • 3.1 通道层Stdin和Stdout类
      • 3.1.2 StdInChannel
      • 3.1.2 StdOutChannel
    • 3.2 协议层CmdCheck和CmdMsg类
      • 3.2.1 CmdCheck单例模式
        • 3.2.1.1 单例模式
        • 3.2.1.2 * 命令识别类向业务层不同类别做分发
      • 3.2.2 CmdMsg自定义用户信息类,继承UserData
    • 3.3 业务层:回显类, 输出通道控制类, 日期前缀管理类
      • 3.3.1 回显对象EchoRole
      • 3.3.2 控制输入输出
      • 3.3.3 日期管理类
  • 4 Tcp数据适配
    • 4.1 工厂类 - 框架头文件分析
    • 4.2 tcp通道实现
      • 4.2.1 Tcp套接字通道通信类
      • 4.2.2 tcp数据套接字通道类的工厂类
  • 5 时间轮定时器
    • 5.1 timerfd产生超时事件
      • 5.1.1 测试代码
    • 5.2 时间轮设置
      • 5.2.1 时间轮的定义
      • 5.2.2 时间轮的移动
      • 5.2.3 添加和删除任务
        • 5.2.3.1 添加任务
        • 5.2.3.2 删除任务
    • 5.3 定时器设置
      • 5.3.1 定时器定义
      • 5.3.2 定时器初始化
      • 5.3.3 输出hello world

1 Zinx框架总览

在这里插入图片描述

2 三层模式的分析

在这里插入图片描述

3 三层重构原有的功能 - 头文件

三层结构重构原有功能

  1. 自定义消息类,继承UserData,添加一个成员变量szUserData
  2. 定义多个Role类继承Irole,重写ProcMsg函数,进行不同处理
  3. 定义protocol类,继承Iprotocol,重写四个函数,两个函数时原始
    数据和用户数据之间的转换;另两个用来找消息处理对象和消息发
    送对象。
  4. 定义channel类,继承Ichannel,在getnextinputstage函数中返回协
    议对象

3.1 通道层Stdin和Stdout类

通道类,派生自基础处理者类,提供基于系统调用的数据收发功能
一般地,用户应该根据处理的文件(信息源)不同而创建通道类的子类或选用合适的实用类(已经提供的通道类子类)来完成系统级文件IO
在这里插入图片描述

class StdInChannel :public Ichannel
{
public:StdInChannel();virtual ~StdInChannel();// 通过 Ichannel 继承virtual bool Init() override;virtual bool ReadFd(std::string& _input) override;virtual bool WriteFd(std::string& _output) override;virtual void Fini() override;virtual int GetFd() override;virtual std::string GetChannelInfo() override;virtual AZinxHandler* GetInputNextStage(BytesMsg& _oInput) override;
};class StdOutChannel :public Ichannel
{// 通过 Ichannel 继承virtual bool Init() override;virtual bool ReadFd(std::string& _input) override;virtual bool WriteFd(std::string& _output) override;virtual void Fini() override;virtual int GetFd() override;virtual std::string GetChannelInfo() override;virtual AZinxHandler* GetInputNextStage(BytesMsg& _oInput) override;
};

3.1.2 StdInChannel

bool StdInChannel::ReadFd(std::string& _input)
{cin >> _input;return true;
}bool StdInChannel::WriteFd(std::string& _output)
{return false;
}int StdInChannel::GetFd()
{return 0;
}std::string StdInChannel::GetChannelInfo()
{return "stdin";
}AZinxHandler* StdInChannel::GetInputNextStage(BytesMsg& _oInput)
{/*返回协议对象*/return CmdCheck::GetInstance();
}

3.1.2 StdOutChannel

bool StdOutChannel::ReadFd(std::string& _input)
{return false;
}bool StdOutChannel::WriteFd(std::string& _output)
{cout << _output << endl;return true;
}int StdOutChannel::GetFd()
{return 1;
}std::string StdOutChannel::GetChannelInfo()
{return "stdout";
}AZinxHandler* StdOutChannel::GetInputNextStage(BytesMsg& _oInput)
{return nullptr;
}

3.2 协议层CmdCheck和CmdMsg类

3.2.1 CmdCheck单例模式

  1. 原始数据和业务数据相互函数,开发者重写该函数,实现协议
  2. 获取处理角色对象函数,开发者应该重写该函数,用来指定当前产生的用户数据消
  3. 获取发送通道函数,开发者应该重写该函数,用来指定当前字节流应该由哪个通道对象发出
class CmdCheck :public Iprotocol
{CmdCheck();virtual ~CmdCheck();static CmdCheck *poSingle;
public:// 通过 Iprotocol 继承/*原始数据和业务数据相互函数,开发者重写该函数,实现协议*/virtual UserData * raw2request(std::string _szInput) override;virtual std::string * response2raw(UserData & _oUserData) override;/*获取处理角色对象函数,开发者应该重写该函数,用来指定当前产生的用户数据消息应该传递给哪个角色处理*/virtual Irole * GetMsgProcessor(UserDataMsg & _oUserDataMsg) override;/*获取发送通道函数,开发者应该重写该函数,用来指定当前字节流应该由哪个通道对象发出*/virtual Ichannel * GetMsgSender(BytesMsg & _oBytes) override;static CmdCheck *GetInstance() {return poSingle;}std::string szOutChannel;
};
3.2.1.1 单例模式

构造全局唯一的协议对象

#include "CmdCheck.h"
#include "CmdMsg.h"
#include "EchoRole.h"
using namespace std;CmdCheck *CmdCheck::poSingle = new CmdCheck();
3.2.1.2 * 命令识别类向业务层不同类别做分发

通过是不是命令来进行区分:if (isCmd)

Irole * CmdCheck::GetMsgProcessor(UserDataMsg & _oUserDataMsg)
{szOutChannel = _oUserDataMsg.szInfo;if ("stdin" == szOutChannel){szOutChannel = "stdout";}/*根据命令不同,交给不同的处理role对象*/auto rolelist = ZinxKernel::Zinx_GetAllRole();auto pCmdMsg = dynamic_cast<CmdMsg *>(_oUserDataMsg.poUserData);/*读取当前消息是否是命令*/bool isCmd = pCmdMsg->isCmd;Irole *pRetRole = NULL;for (Irole *prole : rolelist){if (isCmd){auto pOutCtrl = dynamic_cast<OutputCtrl *>(prole);if (NULL != pOutCtrl){pRetRole = pOutCtrl;break;}}else{auto pDate = dynamic_cast<DatePreRole *>(prole);if (NULL != pDate){pRetRole = pDate;break;}}}return pRetRole;
}

3.2.2 CmdMsg自定义用户信息类,继承UserData

class CmdMsg :public UserData
{
public:/*成员变量表示要回显的字符串*/std::string szUserData;/*开启输出标志*/bool isOpen = true;/*该消息是命令*/bool isCmd = false;/*要加前缀*/bool needDatePre = false;CmdMsg();virtual ~CmdMsg();
};

3.3 业务层:回显类, 输出通道控制类, 日期前缀管理类

3.3.1 回显对象EchoRole

主要有init, procmsg,fini三个函数

#pragma once
#include <zinx.h>class EchoRole :public Irole
{
public:EchoRole();virtual ~EchoRole();// 通过 Irole 继承virtual bool Init() override;virtual UserData * ProcMsg(UserData & _poUserData) override;virtual void Fini() override;
};
  • 容易出错的点:参数一必须是一个堆对象
UserData * EchoRole::ProcMsg(UserData & _poUserData)
{/*写出去*/GET_REF2DATA(CmdMsg, input, _poUserData);CmdMsg *pout = new CmdMsg(input);ZinxKernel::Zinx_SendOut(*pout, *(CmdCheck::GetInstance()));return nullptr;
}

3.3.2 控制输入输出

  1. 写一个关闭输出的角色类,摘除输出通道或添加输出通道
  2. 在CmdMsg用户数据类中添加开关标志,是否是命令标志
  3. 在协议类中,根据输入字符串,设置开关标志和是否是命令的标志
  4. 在协议类分发消息时,判断是否是命令,是命令则发给关闭输出角 色类,否则发给回显角色类
class OutputCtrl :public Irole {// 通过 Irole 继承virtual bool Init() override;virtual UserData * ProcMsg(UserData & _poUserData) override;virtual void Fini() override;Ichannel *pOut = NULL;
};

3.3.3 日期管理类

class DatePreRole :public Irole {// 通过 Irole 继承virtual bool Init() override;virtual UserData * ProcMsg(UserData & _poUserData) override;virtual void Fini() override;bool needAdd = false;
};

4 Tcp数据适配

4.1 工厂类 - 框架头文件分析

  • 产生tcp数据套接字通道类的抽象工厂类。
    • 开发者需要重写CreateTcpDataChannel函数,来返回一个tcp通道对象。
    • 般地,开发者应该同时创建一对tcp通道类和工厂类
class IZinxTcpConnFact {
public:virtual ZinxTcpData *CreateTcpDataChannel(int _fd) = 0;
};
  • tcp监听通道类,这是一个实体类(不建议继承该类)。
    • 开发者可以直接创建tcp监听通道对象,
    • 一般地,开发者应该在该类的构造函数中,指定一个tcp套接字通道类的工厂类,当有连接到来后,该工厂类的成员方法会被调用
class ZinxTCPListen :public Ichannel
{
private:unsigned short m_usPort = 0;int m_fd = -1;IZinxTcpConnFact *m_ConnFac = NULL;public:ZinxTCPListen(unsigned short _usPort, IZinxTcpConnFact *_pConnFac) :m_usPort(_usPort), m_ConnFac(_pConnFac){}virtual ~ZinxTCPListen();virtual bool Init() override;virtual bool ReadFd(std::string & _input) override;virtual bool WriteFd(std::string & _output) override;virtual void Fini() override;virtual int GetFd() override;virtual std::string GetChannelInfo() override;virtual AZinxHandler * GetInputNextStage(BytesMsg & _oInput);
};

4.2 tcp通道实现

在这里插入图片描述

4.2.1 Tcp套接字通道通信类

  • tcp数据套接字通道类,继承通道类,该类也是一个抽象类,需要开发者继承该类,重写GetInputNextStage函数以指定读取到的字节流的处理方式
// h
class myTcpData :public ZinxTcpData {
public:myTcpData(int _fd) :ZinxTcpData(_fd) {}// 通过 ZinxTcpData 继承virtual AZinxHandler* GetInputNextStage(BytesMsg& _oInput) override;
};
  • Q: Ichannel对象读取到的数据给谁了?
    • 给该对象调用GetInputNextStage函数返回的对象
AZinxHandler* myTcpData::GetInputNextStage(BytesMsg& _oInput)
{/*返回协议对象*/return CmdCheck::GetInstance();
}
  • Q: Iprotocol对象转换出的用户请求给谁了?
    • 给该对象调用GetMsgProcessor函数返回的对象

4.2.2 tcp数据套接字通道类的工厂类

  • 产生tcp数据套接字通道类的抽象工厂类,开发者需要重写CreateTcpDataChannel函数,来返回一个tcp通道对象
    一般地,开发者应该同时创建一对tcp通道类和工厂类
// h
class myFact :public IZinxTcpConnFact {// 通过 IZinxTcpConnFact 继承virtual ZinxTcpData* CreateTcpDataChannel(int _fd) override;
};
ZinxTcpData* myFact::CreateTcpDataChannel(int _fd)
{return new myTcpData(_fd);
}

5 时间轮定时器

5.1 timerfd产生超时事件

timerfd_create()返回定时器文件描述符
timerfd_settime()设置定时周期,立刻开始计时
read,读取当当前定时器超时的次数,没超时会阻塞.
一般地,会将定时器文件描述符结合IO多路复用使用

5.1.1 测试代码

#include<sys/timerfd.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{int iTimerfd = timerfd_create(CLOCK_MONOTONIC, 0);struct itimerspec period{{5, 0},{5, 0}};timerfd_settime(iTimerfd,0, &period,NULL);__uint64_t count = 0;while(1) {read(iTimerfd, &count, sizeof(count));puts("time out");}
}

在这里插入图片描述

5.2 时间轮设置

单例模式

AZinxHandler * ZinxTimerChannel::GetInputNextStage(BytesMsg & _oInput)
{return &TimerOutMng::GetInstance();
}TimerOutMng TimerOutMng::single;

5.2.1 时间轮的定义

// h
class TimerOutProc {
public:virtual void Proc() = 0;virtual int GetTimeSec() = 0;/*所剩圈数*/int iCount = -1;
};
  • vector存储轮的齿
  • 每个齿里用list存每个定时任务
  • 每个定时任务需要记录剩余圈数
  • 时间轮类中要有一个刻度,每秒进一步
TimerOutMng::TimerOutMng()
{/*创建10个齿*/for (int i = 0; i < 10; i++){list<TimerOutProc *> tmp;m_timer_wheel.push_back(tmp);}
}

5.2.2 时间轮的移动

// h
class TimerOutMng :public AZinxHandler {std::vector<std::list<TimerOutProc *> > m_timer_wheel;int cur_index = 0;static TimerOutMng single;TimerOutMng();
public:/*处理超时事件,遍历所有超时任务*/virtual IZinxMsg * InternelHandle(IZinxMsg & _oInput) override;virtual AZinxHandler * GetNextHandler(IZinxMsg & _oNextMsg) override;void AddTask(TimerOutProc *_ptask);void DelTask(TimerOutProc *_ptask);static TimerOutMng &GetInstance() {return single;}
};
  • 移动当前刻度
  • 遍历当前齿中的任务列表
  • 若圈数为0,则执行处理函数,摘除本节点,重新添加
  • 否则,圈数–
IZinxMsg * TimerOutMng::InternelHandle(IZinxMsg & _oInput)
{unsigned long iTimeoutCount = 0;GET_REF2DATA(BytesMsg, obytes, _oInput);obytes.szData.copy((char *)&iTimeoutCount, sizeof(iTimeoutCount), 0);while (iTimeoutCount-- > 0){/*移动刻度*/cur_index++;cur_index %= 10;list<TimerOutProc *> m_cache;/*遍历当前刻度所有节点,指向处理函数或圈数-1,*/for (auto itr = m_timer_wheel[cur_index].begin(); itr != m_timer_wheel[cur_index].end(); ){if ((*itr)->iCount <= 0){/*缓存待处理的超时节点*/m_cache.push_back(*itr);auto ptmp = *itr;itr = m_timer_wheel[cur_index].erase(itr);AddTask(ptmp);}else{(*itr)->iCount--;++itr;}}/*统一待处理超时任务*/for (auto task : m_cache){task->Proc();}}return nullptr;
}

5.2.3 添加和删除任务

5.2.3.1 添加任务
  • 计算当前任务在哪个齿上
  • 添加该任务到该齿对应的list里
  • 计算所需圈数记录到任务中
void TimerOutMng::AddTask(TimerOutProc * _ptask)
{/*计算当前任务需要放到哪个齿上*/int index = (_ptask->GetTimeSec() + cur_index) % 10;/*把任务存到该齿上*/m_timer_wheel[index].push_back(_ptask);/*计算所需圈数*/_ptask->iCount = _ptask->GetTimeSec() / 10;
}
5.2.3.2 删除任务
  • 遍历所有齿
  • 在每个齿中遍历所有节点
  • 若找到则删除并返回
void TimerOutMng::DelTask(TimerOutProc * _ptask)
{/*遍历时间轮所有齿,删掉任务*/for (list<TimerOutProc *> &chi : m_timer_wheel){for (auto task : chi){if (task == _ptask){chi.remove(_ptask);return;}}}
}

5.3 定时器设置

5.3.1 定时器定义

class ZinxTimerChannel :public Ichannel
{int m_TimerFd = -1;public:ZinxTimerChannel();virtual ~ZinxTimerChannel();// 通过 Ichannel 继承virtual bool Init() override;virtual bool ReadFd(std::string & _input) override;virtual bool WriteFd(std::string & _output) override;virtual void Fini() override;virtual int GetFd() override;virtual std::string GetChannelInfo() override;virtual AZinxHandler * GetInputNextStage(BytesMsg & _oInput) override;
};

5.3.2 定时器初始化

/*创建定时器文件描述符*/
bool ZinxTimerChannel::Init()
{bool bRet = false; //判断成功或者失败/*创建文件描述符*/int iFd = timerfd_create(CLOCK_MONOTONIC, 0);if (0 <= iFd){/*设置定时周期*/struct itimerspec period = {{1,0}, {1,0}};if (0 == timerfd_settime(iFd, 0, &period, NULL)){bRet = true;m_TimerFd = iFd;  }}return bRet;
}
/*读取超时次数*/
bool ZinxTimerChannel::ReadFd(std::string & _input)
{bool bRet = false;char buff[8] = { 0 };if (sizeof(buff) == read(m_TimerFd, buff, sizeof(buff))){bRet = true;_input.assign(buff, sizeof(buff));}return bRet;
}bool ZinxTimerChannel::WriteFd(std::string & _output)
{return false;
}/*关闭文件描述符*/
void ZinxTimerChannel::Fini()
{close(m_TimerFd);m_TimerFd = -1;
}/*返回当前的定时器文件描述符*/
int ZinxTimerChannel::GetFd()
{return m_TimerFd;
}std::string ZinxTimerChannel::GetChannelInfo()
{return "TimerFd"; // 名字随便起的
}

5.3.3 输出hello world

class output_hello :public AZinxHandler {// 通过 AZinxHandler 继承virtual IZinxMsg * InternelHandle(IZinxMsg & _oInput) override{auto pchannel = ZinxKernel::Zinx_GetChannel_ByInfo("stdout");std::string output = "hello world";ZinxKernel::Zinx_SendOut(output, *pchannel);return nullptr;}virtual AZinxHandler * GetNextHandler(IZinxMsg & _oNextMsg) override{return nullptr;}
} *pout_hello = new output_hello();

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

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

相关文章

【Mac开发环境搭建】JDK安装、多JDK安装与切换

文章目录 JDK下载与安装下载安装 配置环境变量安装多个JDK共存 JDK下载与安装 下载 Oracle官网提供了非常多个版本的JDK供下载&#xff0c;可以点击如下链接重定向到JDK下载页面 ORACLE官网JDK下载 安装 下面的官方文档可以点开收藏到浏览器的收藏夹&#xff0c;这样后续在开…

Ribbon讲解

一&#xff1a;Ribbon是什么&#xff1f; Ribbon其实就是一个软负载均衡的客户端组件。 二&#xff1a;负载均衡(LB)是什么&#xff1f; 用户的请求平摊的分配到多个服务上&#xff0c;从而达到系统的HA&#xff08;高可用&#xff09; 三&#xff1a;负载均衡分类&#xf…

Android Studio(控件常用属性)

通用属性 属性描述android:id用于为视图指定唯一的标识符。android:layout_width用于指定视图的宽度。android:layout_height用于指定视图的高度。android:layout_margin用于指定视图周围的空白区域。android:layout_padding用于指定视图内部的填充区域。android:background用…

思维模型 锚定效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。先入为主&#xff0c;决策易偏。 1 锚定效应的应用 1.1 定价策略中的锚定效应 黑珍珠的定价策略&#xff1a;在 20 世纪 70 年代&#xff0c;黑珍珠被视为一种廉价的珠宝。然而&#xff…

[算法日志]图论: 广度优先搜索(BFS)

[算法日志]图论&#xff1a; 广度优先搜索(BFS) 广度优先概论 ​ 广度优先遍历也是一种常用的遍历图的算法策略&#xff0c;其思想是将本节点相关联的节点都遍历一遍后才切换到相关联节点重复本操作。这种遍历方式类似于对二叉树节点的层序遍历&#xff0c;即先遍历完子节点后…

STM32存储左右互搏 SPI总线读写FLASH W25QXX

STM32存储左右互搏 SPI总线读写FLASH W25QXX FLASH是常用的一种非易失存储单元&#xff0c;W25QXX系列Flash有不同容量的型号&#xff0c;如W25Q64的容量为64Mbit&#xff0c;也就是8MByte。这里介绍STM32CUBEIDE开发平台HAL库操作W25Q各型号FLASH的例程。 W25QXX介绍 W25QX…

【java】实现自定义注解校验——方法一

自定义注解校验的实现步骤&#xff1a; 1.创建注解类&#xff0c;编写校验注解&#xff0c;即类似NotEmpty注解 2.编写自定义校验的逻辑实体类&#xff0c;编写具体的校验逻辑。(这个类可以实现ConstraintValidator这个接口&#xff0c;让注解用来校验) 3.开启使用自定义注解进…

linux日志

1.日志介绍 linux日志是linux最重要的系统信息文件&#xff0c;其记录了系统发生的各类信息&#xff0c;例如&#xff1a;用户登录信息、系统启动信息、系统安全信息、相关服务信息等等。 2.日志说明 2.1.日志存放目录 linux日志记录存放目录路径&#xff1a;/var/log [ro…

【深度学习】Yolov8 区域计数

git&#xff1a;https://github.com/ultralytics/ultralytics/blob/main/examples/YOLOv8-Region-Counter/readme.md 很长时间没有做yolov的项目了&#xff0c;最近一看yolov8有一个区域计数的功能&#xff0c;不得不说很实用啊。 b站&#xff1a;https://www.bilibili.com/vid…

【qemu逃逸】HWS2017-FastCP

前言 虚拟机用户名&#xff1a;root 虚拟机密码&#xff1a;无密码 本题有符号&#xff0c;所以对于设备定位啥的就不多说了&#xff0c;直接逆向设备吧。 设备逆向 在 realize 函数中设置一个时钟任务&#xff0c;并且可以看到只注册了 mmio&#xff0c;大小为 0x100000。…

民宿酒店服务预约小程序的作用

民宿往往是旅游者们前往某个城市感受风情常住的地方&#xff0c;也因此在景区或特定地方&#xff0c;总是不乏大小民宿品牌&#xff0c;但除了市场高需求外&#xff0c;商家们所遇的痛点也不少&#xff1a; 1、获客引流难 民宿生意虽然需求量高&#xff0c;但各家品牌众多&am…

2000-2022年上市公司供应链数字化示范名单匹配数据

2000-2022年上市公司供应链数字化示范名单匹配数据 1、时间&#xff1a;2000-2022年 2、来源&#xff1a;商务部 3、指标&#xff1a; 上市公司供应链数字化&#xff08;根据城市名单匹配&#xff09;&#xff1a;股票代码、年份、股票简称、中文全称、省份、城市、区县、上…

祝贺璞华大数据产品入选中国信通院“铸基计划”

武汉璞华大数据技术有限公司HawkEye设备数字化管理平台产品&#xff0c;凭借优秀的产品技术能力&#xff0c;通过评估后&#xff0c;入选中国信通院“铸基计划”《高质量数字化转型产品及服务全景图(2023&#xff09;》的工业数字化领域。 “铸基计划”是中国信通院推出的高质量…

leetcode_2586 统计范围元音字符串

1. 题意 元音字符串是首尾都是元音字母的字符串。 给字符数组&#xff0c;让你找出指定范围内的元音字符串的个数。 统计范围元音字符串 2. 题解 直接模拟即可 class Solution { public:int vowelStrings(vector<string>& words, int left, int right) {unordere…

【Linux网络编程_TCP/UDP_字节序_套接字 实现: FTP 项目_局域网聊天项目 (已开源) 】.md updata:23/11/05

文章目录 TCP/UDP对比端口号作用字节序字节序转换api套接字 socket实现网络通讯服务端 逻辑思路demo&#xff1a; 满血版双方通讯/残血版多方通讯 &#xff08;配合进程实现&#xff09;服务端 demo客户端 demo FTP 项目实现sever demo:client demo: 局域网多方通讯 &#xff0…

seo而生的WordPress主题RabbitV3.0主题分享

seo而生的WordPress主题RabbitV3.0主题分享&#xff0c;是一款专注于SEO优化用途的WordPress主题&#xff0c;专为博客、自媒体、资讯类等类型网站SEO优化设计开发&#xff0c;自适应兼容手机、平板设备&#xff0c;支持前端用户中心&#xff0c;可以前端发布/投稿文章&#xf…

Pytorch里面参数更新前为什么要梯度手动置为0?

因为在一般情况下&#xff0c;每次minibatch之后&#xff0c;都会计算得到一个loss&#xff0c;进而计算该loss关于全局参数的梯度。如果在下一次minibatch 进入模型&#xff0c;计算得到相应的loss和梯度之前&#xff0c;不对优化器的梯度进行置0操作&#xff0c;那么几次batc…

创建文件,删除文件(如何恢复),查看文件(ls,echo,cat)的原理和过程

目录 创建文件的过程 删除文件的过程 如果想要恢复删除的文件? 注意点 查看文件的过程 ls echo重定向写入 cat 创建文件的过程 首先,创建文件的时候,输入的命令中会有路径文件名的传入 在某一分区的某一块组中(由os分配),在其inode位图遍历,找到第一个为0的位,将它置…

C# Onnx Dense Face 3D人脸重建,人脸Mesh

效果 项目 代码 using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms;namespace Onnx_Demo {public partial class frmMain : Form{public frmMain(){InitializeComponent();}string fileFilter "*.…

kafka3.6.0集群部署

环境准备 机器环境 系统主机名IP地址centos7.9kafka01192.168.200.51centos7.9kafka02192.168.200.52centos7.9kafka03192.168.200.53 所需软件 jdk-8u171-linux-x64.tar.gzapache-zookeeper-3.8.3-bin.tar.gz https://dlcdn.apache.org/zookeeper/zookeeper-3.8.3/apache-zook…