C/C++轻量级并发TCP服务器框架Zinx-游戏服务器开发003:架构搭建-需求分析及TCP通信方式的实现

news2024/11/24 15:58:02

文章目录

  • 1 项目总体架构
  • 2 项目需求
    • 2.1 服务器职责
    • 2.2 消息的格式和定义
  • 3 基于Tcp连接的通信方式
    • 3.1 通道层实现GameChannel类
      • 3.1.1 TcpChannel类
      • 3.1.2 Tcp工厂类
      • 3.1.3 创建主函数,添加Tcp的监听套接字
      • 3.1.4 代码测试
    • 3.2 协议层与消息类
      • 3.2.1 消息的定义
      • 3.2.2 消息类-用户请求对象的创建
      • 3.2.3 protoc消息的创建
      • 3.2.4 消息对象的构造与解析
      • 3.2.5 代码测试-1
      • 3.2.6 报文里的多条请求
      • 3.2.7 Tcp报文粘包的处理
      • 3.2.8 数据包代码测试
        • 3.2.8.1 完整数据
        • 3.2.8.2 数据缺失和错误
      • 3.2.9 协议和通道相互绑定
        • 3.2.9.1 循环引用的问题
        • 3.2.9.1 相互绑定的实现
        • 3.2.9.3 代码测试
    • 3.3 业务层玩家类的创建
      • 3.3.1 在Role中绑定协议
      • 3.3.2 在协议中绑定一个role
      • 3.3.3 在tcp中绑定协议和玩家对象
      • 3.3.4 重写协议层获取角色处理对象
      • 3.3.5 修改角色Init函数
      • 3.3.6 测试代码

1 项目总体架构

在这里插入图片描述

2 项目需求

2.1 服务器职责

服务器职责(接收客户端数据,发送数据给客户端)

  • 新客户端连接后,向其发送ID和名称
  • 新客户端连接后,向其发送周围玩家的位置
  • 新客户端连接后,向周围玩家发送其位置
  • 收到客户端的移动信息后,向周围玩家发送其新位置
  • 收到客户端的移动信息后,向其发送周围新玩家位置
  • 收到客户端的聊天信息后,向所有玩家发送聊天内容
  • 客户端断开时,向周围玩家发送其断开的消息

2.2 消息的格式和定义

  • 消息定义

每一条服务器和客户端之前的消息都应该满足以下格式

消息内容的长度(4个字节,低字节在前)| 消息ID(4个字节,低字节在前)| 消息内容 |

消息以及其处理方式已经在客户端实现,本项目要实现的是服务器端的相关处理

  • 详细定义如下
消息ID消息内容发送方向客户端处理服务器处理
1玩家ID和玩家姓名S->C记录自己ID和姓名
2聊天内容C->S广播给所有玩家
3新位置C->S处理玩家位置更新后的信息同步
200玩家ID,聊天内容/初始位置/动作(预留)/新位置S->C根据子类型不通而不同
201玩家ID和玩家姓名S->C把该ID的玩家从画面中拿掉
202周围玩家们的位置S->C在画面中显示周围的玩家

3 基于Tcp连接的通信方式

3.1 通道层实现GameChannel类

  • GameChannel::GetInputNextStage 函数中直接返回成员变量中的协议对象
  • GameChannel 的析构函数中要一并从kernel中摘掉协议对象,玩家对象并析构之
  • GameChannelFac::CreateTcpDataChannel 函数要一并创建通道对象,协议对象,玩家对象,并将这三者绑定起来,添加到kernel中

3.1.1 TcpChannel类

  • 使用框架提供的Tcp通信类
  • 创建GameChannel类继承ZinxTcpData,重写GetInputNextStage函数,将tcp收到的数据交给协议对象解析

每个协议对象只处理本通道的协议数据

GameProtocol* m_proto = NULL; 

创建对象啊以后交给m_proto,通过该变量访问通道内的数据

AZinxHandler* GameChannel::GetInputNextStage(BytesMsg& _oInput)
{
	return m_proto;
}

3.1.2 Tcp工厂类

  • 创建GameChannelFac类用于创建基于连接的GameChannel对象
  • 因为玩家是通过tcp连接,所以tcp通道,协议对象,和玩家对象是一对一对一的绑定关系
  • 创建通道的时候,需要创建协议,并且绑定协议对象
ZinxTcpData* GameConnFact::CreateTcpDataChannel(int _fd)
{
/*创建tcp通道对象*/
	auto pChannel = new GameChannel(_fd);
/*创建协议对象*/
	auto pProtocol = new GameProtocol();
/*绑定协议对象*/
	pChannel->m_proto = pProtocol;
/*将协议对象添加到kernel, 注意参数需要为指针*/
	ZinxKernel::Zinx_Add_Proto(*pProtocol);
	return pChannel;
}

3.1.3 创建主函数,添加Tcp的监听套接字

#include "GameChannel.h"

int main()
{
	ZinxKernel::ZinxKernelInit();
	/*添加监听通道:需要端口号和连接*/
	ZinxKernel::Zinx_Add_Channel(*(new ZinxTCPListen(8899, new GameConnFact())));
	ZinxKernel::Zinx_Run();
	ZinxKernel::ZinxKernelFini();
}

3.1.4 代码测试

设置标准输入

UserData* GameProtocol::raw2request(std::string _szInput)
{
	cout << _szInput << endl;
	return nullptr;
}

在这里插入图片描述
在这里插入图片描述

3.2 协议层与消息类

  • GameProtocol::GetMsgProcessor 函数即返回绑定的玩家对象
  • GameProtocol::GetMsgSender 函数即返回绑定的通道对象
  • GameProtocol::response2raw 函数要返回消息内容编码后的字节流(将GameMsg 对象中每个消息对象序列化并结合长度消息ID一起粘合起来)
  • GameProtocol::raw2request 函数要将一串tcp数据流转换成游戏消息

3.2.1 消息的定义

//h
enum MSG_TYPE {
	MSG_TYPE_LOGIN_ID_NAME = 1,
	MSG_TYPE_CHAT_CONTENT = 2,
	MSG_TYPE_NEW_POSTION = 3,
	MSG_TYPE_BROADCAST = 200,
	MSG_TYPE_LOGOFF_ID_NAME = 201,
	MSG_TYPE_SRD_POSTION = 202
} enMsgType;

3.2.2 消息类-用户请求对象的创建

  • 一个类一个请求
//h
class GameMsg :
	public UserData
{
public:
	/*用户的请求信息*/
	google::protobuf::Message * pMsg = NULL;
	enum MSG_TYPE {
		MSG_TYPE_LOGIN_ID_NAME = 1,
		MSG_TYPE_CHAT_CONTENT = 2,
		MSG_TYPE_NEW_POSTION = 3,
		MSG_TYPE_BROADCAST = 200,
		MSG_TYPE_LOGOFF_ID_NAME = 201,
		MSG_TYPE_SRD_POSTION = 202
	} enMsgType;

	/*已知消息内容创建消息对象*/
	GameMsg(MSG_TYPE _type, google::protobuf::Message  * _pMsg);
	/*将字节流内容转换成消息结构*/
	GameMsg(MSG_TYPE _type, std::string _stream);

	/*序列化本消息*/
	std::string serialize();

	virtual ~GameMsg();
};
  • 一个消息类里应该要放多条请求,每个请求一条消息
class MultiMsg :public UserData {
public:
	std::list<GameMsg *> m_Msgs;
};

3.2.3 protoc消息的创建

protoc msg.proto --cpp_out=./
syntax="proto3";
package pb;

//无关选项,用于客户端
option csharp_namespace="Pb";

message SyncPid{
	int32 Pid=1;
	string Username=2;
}

message Player{
	int32 Pid=1;
	Position P=2;
	string Username=3;
}

message SyncPlayers{
	/*嵌套多个子消息类型Player的消息*/
	repeated Player ps=1;
}

message Position{
	float X=1;
	float Y=2;	
	float Z=3;	
	float V=4;
	int32 BloodValue=5;
}

message MovePackage{
	Position P=1;
	int32 ActionData=2;
}

message BroadCast{
	int32 Pid=1;
	int32 Tp=2;
	/*根据Tp不同,Broadcast消息会包含:
	  聊天内容(Content)或初始位置(P)或新位置P*/
	oneof Data{
		string Content=3;
		Position P=4;
		/*ActionData暂时预留*/
		int32 ActionData=5;
		}
	string Username=6;
}

message Talk{
	string Content=1;
}

3.2.4 消息对象的构造与解析

GameMsg::GameMsg(MSG_TYPE _type, std::string _stream) :enMsgType(_type)
{
	/*通过简单工厂构造具体的消息对象*/
	switch (_type)
	{
	case GameMsg::MSG_TYPE_LOGIN_ID_NAME:
		pMsg = new pb::SyncPid();
		break;
	case GameMsg::MSG_TYPE_CHAT_CONTENT:
		pMsg = new pb::Talk();
		break;
	case GameMsg::MSG_TYPE_NEW_POSTION:
		pMsg = new pb::Position();
		break;
	case GameMsg::MSG_TYPE_BROADCAST:
		pMsg = new pb::BroadCast();
		break;
	case GameMsg::MSG_TYPE_LOGOFF_ID_NAME:
		pMsg = new pb::SyncPid();
		break;
	case GameMsg::MSG_TYPE_SRD_POSTION:
		pMsg = new pb::SyncPlayers();
		break;
	default:
		break;
	}

	/*将参数解析成消息对象内容*/
	pMsg->ParseFromString(_stream);
}


std::string GameMsg::serialize()
{
	std::string ret;

	pMsg->SerializeToString(&ret);

	return ret;
}

3.2.5 代码测试-1

在这里插入图片描述

3.2.6 报文里的多条请求

//h
class MultiMsg :public UserData {
public:
	std::list<GameMsg*> m_Msgs; //注意此处要加命名空间
};
	MultiMsg* pRet = new MultiMsg(); //此时没有用户请求
	
	/*构造一条用户请求*/
	GameMsg* pMsg = new GameMsg((GameMsg::MSG_TYPE)id, szLast.substr(8, iLength)); // iLength是正文的长度
	pRet->m_Msgs.push_back(pMsg);

	//Debug打印每条请求
	for (auto single : pRet->m_Msgs)
	{
		cout << single->pMsg->Utf8DebugString() << endl;
	}

3.2.7 Tcp报文粘包的处理

问题:tcp或类似的流式文件无法保证收到的数据按照期望的格式分割。

举例:服务器期望接收2个字节的数据作为一个合理请求。客户端发送了两个请求(四个字节)后,由于网络拥塞,服务器收到了1个字节后,recv返回,1秒钟后,数据到来,再次调用recv会收到3个字节。

常规套路

  1. 设定报文边界,一般使用Tag Length Value的格式
  2. recv数据后,若接收缓冲区当前数据长度小于报文内规定长度,则保留当前缓冲区,下次recv数据后重新处理(缓存)
  3. 若接收缓冲区数据长度大于等于报文内规定长度,则循环生成生成请求并保留后续多余的数据等待下次recv数据后重新处理(滑窗)
UserData* GameProtocol::raw2request(std::string _szInput)
{
	MultiMsg* pRet = new MultiMsg(); //此时没有用户请求
	szLast.append(_szInput);

	while (1)
	{
		if (szLast.size() < 8)
		{
			break;
		}

		/*在前四个字节中读取消息内容长度*/
		int iLength = 0;
		iLength |= szLast[0] << 0;
		iLength |= szLast[1] << 8;
		iLength |= szLast[2] << 16;
		iLength |= szLast[3] << 24;
		/*中四个字节读类型id*/
		int id = 0;
		id |= szLast[4] << 0;
		id |= szLast[5] << 8;
		id |= szLast[6] << 16;
		id |= szLast[7] << 24;

		/*通过读到的长度判断后续报文是否合法*/
		if (szLast.size() - 8 < iLength)
		{
			/*本条报文还没够,啥都不干*/
			break;
		}

		/*构造一条用户请求*/
		GameMsg* pMsg = new GameMsg((GameMsg::MSG_TYPE)id, szLast.substr(8, iLength)); // iLength是正文的长度
		pRet->m_Msgs.push_back(pMsg);

		/*弹出已经处理成功的报文*/
		szLast.erase(0, 8 + iLength);
	}

	//Debug打印每条请求
	for (auto single : pRet->m_Msgs)
	{
		cout << single->pMsg->Utf8DebugString() << endl;
	}
	return pRet;
}


/*参数来自业务层,待发送的消息
返回值转换后的字节流*/
std::string * GameProtocol::response2raw(UserData & _oUserData)
{
	int iLength = 0;
	int id = 0;
	std::string MsgContent;

	GET_REF2DATA(GameMsg, oOutput, _oUserData);
	id = oOutput.enMsgType;
	MsgContent = oOutput.serialize();
	iLength = MsgContent.size();

	auto pret = new std::string();

	pret->push_back((iLength >> 0) & 0xff);
	pret->push_back((iLength >> 8) & 0xff);
	pret->push_back((iLength >> 16) & 0xff);
	pret->push_back((iLength >> 24) & 0xff);
	pret->push_back((id >> 0) & 0xff);
	pret->push_back((id >> 8) & 0xff);
	pret->push_back((id >> 16) & 0xff);
	pret->push_back((id >> 24) & 0xff);
	pret->append(MsgContent);

	return pret;
}

3.2.8 数据包代码测试

3.2.8.1 完整数据
08000000010000000801120474657374

08 00 00 00 - 前4个字节存储数据消息的长度,变量值是数据消息的长度为8个字节。
01 00 00 00 - 第5-8个字节存储的是用户的ID,变量值表示用户ID是1
08 01 12 04 74 65 73 74 - 末尾8个字节表示数据消息的全部内容
在这里插入图片描述

在这里插入图片描述

3.2.8.2 数据缺失和错误

收到数据以后,啥都不干

在这里插入图片描述

3.2.9 协议和通道相互绑定

3.2.9.1 循环引用的问题

一般地来说,不涉及使用类的成员的时候,尽量避免使用头文件,直接声明一个类即可。

GameChannel.h中引用了头文件"GameProtocol.h"

#pragma once
#include<ZinxTCP.h>
#include"GameProtocol.h"

class GameChannel :
    public ZinxTcpData
{
public:
    GameChannel(int _fd);
    virtual ~GameChannel();
    GameProtocol * m_proto = NULL; 

};

如果在GameProtocol.h中引用GameChannel.h,则会造成循环引用。
处理办法是,直接在前面声明相关的类。

#pragma once
#include <zinx.h>

class GameChannel;  //避免循环引用

class GameProtocol :
    public Iprotocol
{
    std::string szLast; //上次未来得及处理的报文
public:
    GameChannel* m_channel = NULL;
    GameProtocol() ;
    virtual ~GameProtocol();
};
3.2.9.1 相互绑定的实现

在这里插入图片描述

3.2.9.3 代码测试
	pb::SyncPid* pmsg = new pb::SyncPid();
	pmsg->set_pid(1);
	pmsg->set_username("test");

	GameMsg gm(GameMsg::MSG_TYPE_LOGIN_ID_NAME, pmsg);
	auto output = gm.serialize();

	for (auto byte : output)
	{
		printf("%02X ", byte);
	}
	puts("");

	
	char buff[] = { 0x08, 0x01, 0x12, 0x04 ,0x74, 0x65, 0x73, 0x74 };
	std::string input(buff, sizeof(buff));

	auto ingm = GameMsg(GameMsg::MSG_TYPE_LOGIN_ID_NAME, input);
	std::cout << dynamic_cast<pb::SyncPid*> (ingm.pMsg)->pid() << std::endl;
	std::cout << dynamic_cast<pb::SyncPid*> (ingm.pMsg)->username() << std::endl;
	

收到数据

07 00 00 00 02 00 00 00 0A 05 68 65 6C 6C 6F

07 00 00 00 - 数据消息的长度是7个字节
02 00 00 00 - 消息ID是2
0A 05 68 65 6C 6C 6F - 转换成string代表"hello"

在这里插入图片描述
在这里插入图片描述

3.3 业务层玩家类的创建

3.3.1 在Role中绑定协议

class GameProtocol;

class GameRole :
    public Irole
{
public:
    GameRole() ;
    virtual ~GameRole();

    // 通过 Irole 继承
    virtual bool Init() override;
    virtual UserData* ProcMsg(UserData& _poUserData) override;
    virtual void Fini() override;

    GameProtocol* m_pProto = NULL;
};

3.3.2 在协议中绑定一个role

class GameChannel;  //避免循环引用
class GameRole;
class GameProtocol :
    public Iprotocol
{
    std::string szLast; //上次未来得及处理的报文
public:
    GameChannel* m_channel = NULL;
    GameRole* m_Role = NULL;

    GameProtocol() ;
    virtual ~GameProtocol();

    // 通过 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;
};

3.3.3 在tcp中绑定协议和玩家对象

ZinxTcpData* GameConnFact::CreateTcpDataChannel(int _fd)
{
/*创建tcp通道对象*/
	auto pChannel = new GameChannel(_fd);
/*创建协议对象*/
	auto pProtocol = new GameProtocol();
	/*创建玩家对象*/
	auto pRole = new GameRole();

	/*绑定协议对象和通道对象*/
	pChannel->m_proto = pProtocol;
	pProtocol->m_channel = pChannel;

	/*绑定协议对象和玩家对象*/
	pProtocol->m_Role = pRole;
	pRole->m_pProto = pProtocol;

/*将协议对象添加到kernel, 注意参数需要为指针*/
	ZinxKernel::Zinx_Add_Proto(*pProtocol);

	/*将玩家对象添加到kernel*/
	ZinxKernel::Zinx_Add_Role(*pRole);
	return pChannel;
}

3.3.4 重写协议层获取角色处理对象

Irole* GameProtocol::GetMsgProcessor(UserDataMsg& _oUserDataMsg)
{
	return m_Role;
}

3.3.5 修改角色Init函数

bool GameRole::Init()
{
	return true;
}

3.3.6 测试代码

/*处理游戏相关的用户请求*/
UserData* GameRole::ProcMsg(UserData& _poUserData)
{
	/*测试:打印消息内容*/
	GET_REF2DATA(MultiMsg, input, _poUserData);

	for (auto single : input.m_Msgs)
	{
		cout << "type is" << single->enMsgType << endl;
		cout << single->pMsg->Utf8DebugString() << endl;
	}

	return nullptr;

08000000010000000801120474657374

在这里插入图片描述

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

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

相关文章

算法训练 第六周

一、用栈实现队列 本题要求我们使用栈这个数据结构来模拟实现队列的各种操作&#xff0c;我们的具体思路是使用两个栈&#xff0c;将一个栈当作输入栈&#xff0c;用于压入 push传入的数据&#xff1b;另一个栈当作输出栈&#xff0c;用于 pop和 peek 操作。每次 pop 或 peek 时…

CHOME、EDGE无法打开网页问题处理方法

最近有多位同时反馈CHOME/EDGE无法打开网页&#xff0c;如下提示 右键点CHOME/EDGE图标&#xff0c;在属性-兼容性&#xff0c;勾上以兼容模式运行这个程序&#xff0c;确定即可。

【Verilog 教程】7.4Verilog CIC 滤波器设计

积分梳状滤波器&#xff08;CIC&#xff0c;Cascaded Integrator Comb&#xff09;&#xff0c;一般用于数字下变频&#xff08;DDC&#xff09;和数字上变频&#xff08;DUC&#xff09;系统。CIC 滤波器结构简单&#xff0c;没有乘法器&#xff0c;只有加法器、积分器和寄存器…

方法----JAVA

方法 使用方法提高了代码的复用性 方法的定义和调用 方法概述:JAVA 方法的调用,不调用不执行,就只会在硬盘上待着 带参方法 无参方法 无参方法 调用格式: 方法名(); 方法格式: //最简单的方法格式 public static void 方法名(){ //方法体 } 带参方法 调用格式: 方…

C语言之extern关键字实例总结(八十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

HBase表数据的读、写操作与综合操作

文章目录 HBase表数据的读、写操作与综合操作一、实验目标二、实验要求及注意事项三、实验内容及步骤 附&#xff1a;系列文章 HBase表数据的读、写操作与综合操作 一、实验目标 熟练掌握通过HBase shell命令来设计HBase表结构实例掌握使用HBase编程创建HBase表、删除HBase表…

中国发表第一篇计算机顶会的人都怎么样了?中国ACL、AAAI、CVPR第一人是谁

夕小瑶科技说 整理 | 王二狗中国大陆发第一篇计算机顶会的人都怎么样了&#xff1f;相信AI从业者对这个话题都会非常感兴趣&#xff0c;本文对知乎上各位大佬的信息做一个整理&#xff0c;分享给大家。 注&#xff1a;信息由知乎网友整理&#xff0c;不保证100%准确&#xff0c…

主流新闻媒体有哪些,怎么邀约记者

主流新闻媒体是社会信息传播的重要渠道&#xff0c;它们拥有广泛的受众群体和影响力。邀请主流新闻媒体参与活动或报道&#xff0c;可以迅速扩大活动影响力&#xff0c;提升组织的知名度和公信力。下面介绍一些主流新闻媒体及邀约媒体的方法。 主流新闻媒体 1、电视台&#x…

算法通过村第十八关-回溯|白银笔记|经典问题

文章目录 前言组合总和问题分割回文串子集问题排序问题字母大小写全排列单词搜索总结 前言 提示&#xff1a;我不愿再给你写信了。因为我终于感到&#xff0c;我们的全部通信知识一个大大的幻影&#xff0c;我们每个人知识再给自己写信。 --安德烈纪德 回溯主要解决一些暴力枚举…

Find My拐杖|苹果Find My技术与拐杖结合,智能防丢,全球定位

拐杖是一种重要的医疗康复辅助用具&#xff0c;辅助腿脚不灵活的人群平稳的行走&#xff0c;避免出现摔倒等情况。目前&#xff0c;全球均已步入老龄化社会&#xff0c;那么老年人的生活质量和安全成为各国学者的关注点。由于年龄原因容易突发意外情况&#xff0c;如摔倒&#…

SSM大学生众筹平台-计算机毕设 附源码22506

SSM大学生众筹平台 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 大学生众筹平台&#xff0c;主要的模块包括管理员和用户及筹资用户&#xff0c;实现功能包括&#xff1a;首页、个人资料&a…

多媒体融合应急通信解决方案

近年来&#xff0c;随着经济社会快速发展和现代化进程加快&#xff0c;我国公共安全面临诸多新的挑战。面对大型安全事故发生后&#xff0c;救援队伍必须在恶劣的条件下迅速建立指挥调度中心&#xff0c;方能协调前后方救援力量&#xff0c;这对应急通信网络建设的可靠性、时效…

基于springboot实现智慧外贸平台系统【项目源码+论文说明】

基于springboot实现智慧外贸平台管理系统演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把智慧外贸管理与现在网络相结合&#xff0c;利用java技术建设智慧外贸平台&#xff0c;实现智慧外贸的信息化。则对于进一步提高智慧外贸管理发展&#xff0c;丰富智慧外贸管理经…

ICC2与PT端口时序上的差别

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 有星球成员遇到如下问题: 你好,我想问一下就是之前一直遇到一个情况:INtoReg的path_group的时序报告,ICC2里launch的clock network delay(propagated)会有一个值,skew就很小。 但是到PT里launc…

窗函数法设计FIR中,如何选择窗函数和滤波器阶数N

窗函数法设计FIR中&#xff0c;如何选择窗函数和滤波器阶数N 1、概述 在用窗函数法设计FIR滤波器时&#xff0c;给出了滤波器要求的具体指标&#xff0c;包括通带频率fp、阻带频率fs、通带波纹Rp 和阻带衰减As 等&#xff0c;有了这些指标后&#xff0c;是否什么窗函数都可以选…

辐射骚扰整改思路及方法:实地验证?|深圳比创达电子EMC

某产品首次EMC测试时&#xff0c;辐射、静电、浪涌均失败。本篇文章就“实地验证”问题进行详细讨论。 方案拟定好后&#xff0c;就该准备前往EMC检测机构进行测试了。出发前&#xff0c;先分析4个解决方案的易操作程度&#xff1a;方案2最容易验证&#xff0c;只要将磁环往电…

【机器学习】六、概率图模型

今天我们对概率图模型&#xff08;Probabilistic Graphical Model&#xff0c;PGM&#xff09;做一个总结。 模型表示 概率图模型&#xff0c;是指一种用图结构来描述多元随机变量之间条件独立关系的概率模型。 它提出的背景是为了更好研究复杂联合概率分布的数据特征&#x…

亚马逊合规,亚马逊涉及12个站点合规政策更新,需警惕合规要求!

最近&#xff0c;许多亚马逊站点的卖家陆续收到了合规政策更新的通知邮件&#xff0c;涵盖了美国站、加拿大站、英国站、法国站、意大利站、德国站以及西班牙站。 这些更新影响了不同品类的卖家&#xff0c;包括以下品类&#xff1a; 美国站&#xff08;US&#xff09;对于“发…

农林牧数据可视化监控平台 | 智慧农垦

数字农业是一种现代农业方式&#xff0c;它将信息作为农业生产的重要元素&#xff0c;并利用现代信息技术进行农业生产过程的实时可视化、数字化设计和信息化管理。能将信息技术与农业生产的各个环节有机融合&#xff0c;对于改造传统农业和改变农业生产方式具有重要意义。 图扑…

3 任务3 使用趋动云部署自己的stable-diffusion

使用趋动云部署自己的stable-diffusion 1 创建项目&#xff1a;2 初始化开发环境实例3 部署模型4 模型测试 1 创建项目&#xff1a; 1.进入趋动云用户工作台&#xff0c;选择&#xff1a;当前空间&#xff0c;请确保当前所在空间是注册时系统自动生成的空间。 a.非系统自动生成…