六安市网站制作/微信营销管理软件

六安市网站制作,微信营销管理软件,长春火车站咨询电话号码是多少,深圳手机网站制作朋友们、伙计们,我们又见面了,本期来给大家带来应用层自定义协议相关的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言:从入门到精通…

朋友们、伙计们,我们又见面了,本期来给大家带来应用层自定义协议相关的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 协议

2. 自定义协议

2.1 预备工作 

3. 序列与反序列化

3.1 报头的添加与解析

3.2 计算业务

4. 功能完善

4.1 服务端

4.2 客户端 

5. 成熟的序列反序列化方案


1. 协议

前面说过,协议其实就是一种约定,我们实现的tcp通信时,都是按照字符串的方式进行发送的,那么对方收到的也是字符串,那么如果我们需要发送一些具体化的数据呢?

就比如:现在使用的这些聊天软件,我们在发送数据时,有昵称、时间、具体的消息内容,因此,在发送数据时,不仅仅是将消息内容发送过去,而是将这三样东西发送过去了,这是一种结构化的数据;

  • 所以在发送类似与这种结构化字段的数据就要制定一种协议;
  • 协议其实就是双方在通信时约定好的一种结构化字段;

在应用层这里我们发送时并不是直接将这个结构化的字段发送给对方:

  • 因为在应用层很可能双方系统有所差异,对于结构体的计算不统一,导致数据的不准确;
  • 所以,在应用层这里,我们要发送结构化的字段,必须要将结构化字段进行序列化成为字节流(“字符串”),将字节流发送给对方,对方通过反序列化将字节流转化为结构化字段;
  • 序列化的目的是为了更好的网络发送,反序列化的目的是为了上层更好的对数据进行有效字段的提取;
  • 序列化和反序列化的方式双方可以进行统一的约定;

2. 自定义协议

在自定协议这里我们直接实现一个网络版本的计算器来提现一下自定义协议的过程;

我们采用分模块来实现:

  • Socket.hpp:对网络套接字进行封装
  • TcpServer.hpp:实现Tcp的服务器
  • TcpServerMain.cc:测试Tcp服务器
  • TcpClientMain.cc:完成客户端
  • Protocol.hpp:自定义协议
  • Calculate.hpp:实现计算的业务

2.1 预备工作 

既然要进行网络通信,那么就少不了需要套接字接口,前面已经写过好多次套接字的接口了,这里对套接字进行封装,将服务器和客户端各自使用的接口整合在一起,我们对封装好的套接字提供一些我们需要的接口接口;

我们之前发送数据使用的read和write,其实还有两个接口:


Socket.hpp:

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#define Convert(addrptr) ((struct sockaddr *)addrptr)  // 套接字中对于地址类型强转的宏namespace Net_Work
{const static int defaultsockfd = -1;const int backlog = 5;enum // 对于一些错误码的设置{SocketError = 1,BindError,ListenError,};// 封装一个基类,Socket接口类// 设计模式:模版方法类class Socket{public:virtual ~Socket() {}virtual void CreateSocketorDie() = 0;      // 创建套接字virtual void BindSocketorDie(uint16_t port) = 0;  // 绑定virtual void ListenSocketorDie(int backlog) = 0;  // 监听virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0; // 获取连接virtual bool ConnectServer(std::string &serverip, uint16_t &serverport) = 0; // 建立连接virtual int GetSockFd() = 0;   // 获取套接字virtual void SetSockFd(int sockfd) = 0;  // 设置套接字virtual void CloseSocket() = 0;   // 关闭套接字virtual bool Recv(std::string *buffer, int size) = 0;  // 读取信息virtual void Send(std::string &send_str) = 0;    // 发送信息public:// 创建监听套接字----Servervoid BuildListenSocketMethod(uint16_t port, int blacklog){CreateSocketorDie();BindSocketorDie(port);ListenSocketorDie(blacklog);}// 创建连接套接字---Clientbool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport){CreateSocketorDie();return ConnectServer(serverip, serverport);}void BuildNormalSocketMethod(int sockfd){SetSockFd(sockfd);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd){}~TcpSocket() {}void CreateSocketorDie() override{_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0)exit(SocketError);}void BindSocketorDie(uint16_t port) override{struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_sockfd, Convert(&local), sizeof(local));if (n < 0)exit(BindError);}void ListenSocketorDie(int backlog) override{int n = ::listen(_sockfd, backlog);if (n < 0)exit(ListenError);}Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);uint16_t newsockfd = ::accept(_sockfd, Convert(&peer), &len);if (newsockfd < 0)return nullptr;*peerip = inet_ntoa(peer.sin_addr);*peerport = ntohs(peer.sin_port);Socket *s = new TcpSocket(newsockfd);return s;}bool ConnectServer(std::string &serverip, uint16_t &serverport) override{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str());server.sin_port = htons(serverport);int n = ::connect(_sockfd, Convert(&server), sizeof(server));if (n == 0)return true;elsereturn false;}int GetSockFd() override{return _sockfd;}void SetSockFd(int sockfd) override{_sockfd = sockfd;}void CloseSocket() override{if (_sockfd > defaultsockfd)::close(_sockfd);}bool Recv(std::string *buffer, int size) override{char inbuffer[size];ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);if(n > 0){inbuffer[n] = 0;*buffer += inbuffer;return true;}else if (n == 0) return false;else return false;}void Send(std::string &send_str) override{// 发送信息这里我们简略的写了;send(_sockfd, send_str.c_str(), send_str.size(), 0);}private:int _sockfd;};}

为了先测试一下客户端与服务器,我们先来简单的定制一下协议:

我们想要实现一个网络版本的计算器,来自己实现一下自定义协议;

协议方法:

  • 请求:参数1 运算符号 参数2
  • 响应:运算结果 运算状态(结果是否可靠)

请求和相应是两个结构化的字段,因为今天在同一台主机上进行测试,所以我们先直接发送结构化的字段,未来客户端和服务器分别include这个协议,至此双方都可以看到同一份结构化字段,这就是一种自定义的协议;

另外,我们定制好的协议我也想给他们设置一个工厂模式,为了后面方便使用;

接下来我们对客户端和服务器进行简单的实现,因为服务器与客户端在前面UDP和TCP通信那里细致的说多了,这里就直接展示代码了:

TcpServer.hpp:

 对于服务器我们想采用多线程的方式对任务进行处理;

TcpServerMain.cc:

这里对与任务函数的编写就简单一点,为了测试能否正常通信,以及直接传递结构化字段;

客户端:

TcpClientMain.cc:

客户端这里我们也是直接发送结构化字段;

网络版本计算器的基本流程:

服务器启动之后,先创建连接套接字,构建一个计算的请求,然后向服务器发起请求;

因为是本地测试,所以我们可以直接传递结构体,先完成基本的通信,后面再实现序列化的过程;

服务器启动之后,先创建监听套接字设置回调方法,然后获取新连接,使用多线程执行任务;

基本测试:

上面的代码通信时直接发送的是结构体字段,这种发送方式只在限定情况下可以使用,就比如我们上面的测试代码是在本地上演示的,所以不会有什么问题,为了考虑更全面,接下来就需要进行序列、反序列操作; 

在进行序列化之前再来对TCP协议进行一下深入的概念性了解:

  • 其实我们在进行TCP通信的时候,我们使用的发送(write/send)和接收(read/recv)的接口,并不是直接将数据通过网络发送给对方,因为这些接口是用户层接口,在内核层双方还会存在两个缓冲区:一个是发送缓冲区,一个是接收缓冲区;

  • write/send和read/recv接口只是将数据从用户缓冲区拷贝到内核缓冲区,本质就是一个拷贝函数;
  • 那么至于什么时候发送,发多少,怎么发,发送出错了怎么办等等,这些都不需要用户去考虑,这是由内核决定的,换句话说是由TCP协议决定的!
  • TCP协议在进行通信的时候,将发送缓冲区中的数据通过网络拷贝至对方接收缓冲区中,其实,是双方的OS之间进行通信
  • 这也就解释了,write或者read在某些条件下会发生阻塞的问题;当接收缓冲区中没有数据时,read就会阻塞,因为他不具备接收条件;当发送缓冲区写满的时候write就会阻塞,因为他不具备发送条件;
  • 像上面这种有人写就有人拿的模型其实就是一种生产者消费者模型
  • 因为双方在接收和发送时是两个独立的模块,所以可以进行同时通信,所以说TCP协议是全双工协议;

在TCP通信中,在发送时,对方发送了多少数据,并不意味着我就要接收多少数据,这完全由TCP协议来决定;

那么这就存在一种问题:我要读取对方发送的数据,怎么保证我就能读到对方发送的一个完整的数据报文呢?

此时就需要明确报文与报文之前的边界(代码中体现)

3. 序列与反序列化

为了完整我们的代码,我们就需要在发送数据与解析数据时进行序列与反序列化的工作;

序列反序列化也是双方进行的一种约定,也就是自定义的一种协议;

在这里我们想定制的协议是:

  • 未来要发送数据时将结构化字段全部转化为一个字符串“_data_x op _data_y”;
  • 这里需要注意op的长度是固定的(+ - * /)但是两边的操作数的长度是不固定的,所以为了反序列化更方便,我们需要添加报头,其中报头表示的含义就是这个字符串有效内容的长度“len_data_x op _data_y”,这个报头就叫做报文的自描述字段;
  • 为了让报头和有效载荷易于区分,并且为了让报文与报文之间易于区分,我们需要在报头和有效载荷的中间添加特殊字符(\n),在报文末尾添加特殊字符(\n)
  • “len\n_data_x op _data_y\n”;
  • 未来在读取报文的时候,首先读到的就是报头,读到\n时就知道前面的是报头,根据报头所表示的有效内容的长度,再向后读取指定大小的字符即可;
  • 这里添加\n是为了应付多种场景,我们现在的场景是四则运算,有效载荷中不可能出现\n,但是如果场景是一个聊天信息呢,里面可能会出现\n,但是这个消息的长度不可能有\n,所以用\n来区分报头与有效载荷的边界,当然也可以使用其他特殊字符;
  • 另外,我们在报文最后添加的\n不仅仅用于区分报文和报文之间的边界,还可以帮助我们在写代码的时候打印调试;
  • 上面是对请求进行的序列化,对于响应也是一样的“len\n_result _code”。

因为请求和相应都需要添加报头,所以序列化与报头我们分开处理;

Request的序列化与反序列化:

未来的客户端与服务器都需要遵守这样的约定来进行数据的交互与处理,这就是一种自定义的协议,有用户来决定的;

Response序列化与反序列化:

3.1 报头的添加与解析

添加报头:

未来我们相对这种类型"_data_x op _data_y"的字符串添加报头,所以我们依旧采用字符串的操作,这里就不详细解释了;

拼接特殊字符即可;

解析报头:

因为我们不确定报文的完整性,所以在使用解析报头时我们采用循环调用的方式;

首先我们需要找到区分报头和有效载荷的特殊字符;

然后截取特殊字符前面的报头来确定有效载荷的长度;

因为报文的不确定性,所以我们需要通过前面对有效载荷的长度以及报文长度的已知值来确定出一个完整报文的总长度;

然后根据传入的package与这个长度比较,想要至少有一个完整的报文那么就必须大于或者等于这个总长度;

然后通过特殊字符的位置进行截取到有效载荷的信息;

然后将我们已经截取到的完整报文丢弃掉,继续处理下一个报文;

3.2 计算业务

有了序列化与反序列化,接下来就需要对数据进行业务处理了,我们拿到数据先对数据进行处理,获取到其中的运算符(+ - * / %),然后根据不同的运算符来截取对应的操作数执行运算,然后将结果返回,所以在使用计算业务的时候,传入的是一个请求,返回的是一个相应;

4. 功能完善

4.1 服务端

上面实现的添加报头与解包分用其实就可以进行通信了,但是,我们想实现的是,把发送数据和接收数据放在TcpServer底层,此时我只负责发送和接收,不管发送和接收的数据是什么,将网络和业务进行解耦;

此时就需要对执行任务的函数进行简单的调整,未来我们发送一个字节流,对字节流进行业务处理,然后将处理完成的结果再序列化为字节流再返回给我即可,还可以再带一个参数,表示的是业务执行过程中是否出错;

首先我们来实现一下这个业务处理的函数HandlerRequest,在调用时,传入一个待处理的字节流,我们需要对这个字节流进行处理,获取到一个完整的报文,然后对报文反序列化,将有效载荷进行业务处理,处理完成之后的结果我们需要再进行序列化以及添加报头,然后返回出去;

紧接着我们需要在ThreadRun函数中对数据进行接收和发送的操作:

4.2 客户端 

客户端这里的代码就不封装了,直接编写实现通信;

在客户端这里我们首先需要构建一些需要计算的请求,然后对其进行序列化并添加报头,然后发送给服务器:

我们可以来梳理一下这个解耦的逻辑:

 我们既然能发送,当然也可以进行读取我们发送之后计算完成的结果,所以需要对返回的响应进行解析并反序列化得到最终的结构化字段的Response:

#include "Protocol.hpp"
#include "Socket.hpp"#include <iostream>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>using namespace Protocol;int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage: " << " serverip serverport" << std::endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 创建套接字Net_Work::Socket *conn = new Net_Work::TcpSocket();if (!conn->BuildConnectSocketMethod(serverip, serverport)){std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;}std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;// 使用工厂模式std::unique_ptr<Protocol::Factory> factory = std::make_unique<Protocol::Factory>();srand(time(nullptr) ^ getpid()); // 建立随机数的种子const std::string opers = "+-*/%";while (true){// 构建请求,遵守协议int x = rand() % 100; // [0, 99)usleep(rand() % 1234);int y = rand() % 100; // [0, 99)char op = opers[rand() % opers.size()];// 创建请求std::shared_ptr<Protocol::Request> req = factory->BuildRequest(x, y, op);// 对请求序列化std::string request_str;req->Serialize(&request_str);std::cout << request_str << std::endl;// for teststd::string testreq = request_str;testreq += " ";testreq += "= ";// 添加报头request_str = Encode(request_str);// 发送请求conn->Send(request_str);std::string response_str;while(true){// 读取响应if(!conn->Recv(&response_str, 1024)) break;// 解析响应报文std::string response;if(!Decode(response_str, &response))continue;// 反序列化auto resp = factory->BuildResponse();resp->Deserialize(response);// 得到了结果std::cout << testreq << resp->GetResult() << "[" << resp->GetCode() << "]" << std::endl;break;}sleep(1);}conn->CloseSocket();return 0;
}

5. 成熟的序列反序列化方案

上面我们是手写的序列反序列化,这样子写也可以,但是毕竟是我们手写的,我们可以使用一下成熟的方案,比如:json、protobuf、xml;

我们想使用一下json来替换我们手写的序列和反序列化;

想细致了解json的使用可以去搜一些博客看一下,这里我们先使用json进行简单的演示:

// ubuntu 安装jsoncppsudo apt-get install libjsoncpp-dev

演示代码:

#include <iostream>
#include <string>#include "jsoncpp/json/json.h"int main()
{// 创建Json对象// Json::Value 万能的类型Json::Value root;// 添加kv映射数据root["k1"] = 100;root["k2"] = 100;root["hello"] = "world";root["bit"] = 8;// 序列化Json::FastWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;// 反序列化int k1, k2, bit;std::string hello;Json::Value _root;Json::Reader reader;if (reader.parse(s, _root)){k1 = _root["k1"].asInt();k2 = _root["k2"].asInt();hello = _root["hello"].asCString();bit = _root["bit"].asInt();}std::cout << k1 << " " << k2 << " " << hello << " " << bit << std::endl;return 0;
}

接下来我们就将json引入到我们的代码中:

我们使用条件编译,也可以将我们自己实现的序列反序列化的过程保留下来

这到这里我们的代码已经完结了,其实也可以将服务器变成守护进程;

源码链接: https://gitee.com/yue-sir-bit/linux/tree/master/Network_version_calculator

当我们自己手写协议之后,再回头看一下OSI定义的七层网络协议栈,就可以与我们本节实现的代码可以结合起来了:

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

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

相关文章

【C++】二叉树和堆的链式结构

本篇博客给大家带来的是用C语言来实现堆链式结构和二叉树的实现&#xff01; &#x1f41f;&#x1f41f;文章专栏&#xff1a;数据结构 &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享&#xff01; 今日思想&#xff…

八股学习-JUC java并发编程

本文仅供个人学习使用&#xff0c;参考资料&#xff1a;JMM&#xff08;Java 内存模型&#xff09;详解 | JavaGuide 线程基础概念 用户线程&#xff1a;由用户空间程序管理和调度的线程&#xff0c;运行在用户空间。 内核线程&#xff1a;由操作系统内核管理和调度的线程&…

遗传算法+四模型+双向网络!GA-CNN-BiLSTM-Attention系列四模型多变量时序预测

遗传算法四模型双向网络&#xff01;GA-CNN-BiLSTM-Attention系列四模型多变量时序预测 目录 遗传算法四模型双向网络&#xff01;GA-CNN-BiLSTM-Attention系列四模型多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 基于GA-CNN-BiLSTM-Attention、CNN-BiL…

【新能源汽车“心脏”赋能:三电系统研发、测试与应用匹配的恒压恒流源技术秘籍】

新能源汽车“心脏”赋能&#xff1a;三电系统研发、测试与应用匹配的恒压恒流源技术秘籍 在新能源汽车蓬勃发展的浪潮中&#xff0c;三电系统&#xff08;电池、电机、电控&#xff09;无疑是其核心驱动力。而恒压源与恒流源&#xff0c;作为电源管理的关键要素&#xff0c;在…

在线JSON格式校验工具站

在线JSON校验格式化工具&#xff08;Be JSON&#xff09;在线,JSON,JSON 校验,格式化,xml转json 工具,在线工具,json视图,可视化,程序,服务器,域名注册,正则表达式,测试,在线json格式化工具,json 格式化,json格式化工具,json字符串格式化,json 在线查看器,json在线,json 在线验…

图片黑白处理软件推荐

图片黑白二值化是一款小巧实用的图片处理软件&#xff0c;软件大小仅268K。 它的操作极其简单&#xff0c;用户只需将需要处理的图片直接拖入软件&#xff0c;就能实现图片漂白效果。 从原图和处理后的图片对比来看&#xff0c;效果显著。这种图片漂白处理在打印时能节省墨水&a…

【AI知识】常见的优化器及其原理:梯度下降、动量梯度下降、AdaGrad、RMSProp、Adam、AdamW

常见的优化器 梯度下降&#xff08;Gradient Descent, GD&#xff09;局部最小值、全局最小值和鞍点凸函数和非凸函数动量梯度下降&#xff08;Momentum&#xff09;自适应学习率优化器AdaGrad&#xff08;Adaptive Gradient Algorithm&#xff09;​RMSProp&#xff08;Root M…

1.5.5 掌握Scala内建控制结构 - 异常处理

本次实战聚焦于Scala内建控制结构中的异常处理机制。通过具体案例演示了如何使用try-catch-finally结构来处理程序运行中可能出现的异常情况。在try块中调用可能抛出异常的方法&#xff0c;catch块则根据不同异常类型进行捕获并处理&#xff0c;finally块则无论是否发生异常都会…

齿轮热处理学习笔记分享

对于一个做冷加工的人来说&#xff0c;热处理是一个神秘的话题&#xff0c;但是一点都不去了解的话&#xff0c;工作也无法进行。所以抽点时间来学习一下齿轮热处理相关的内容&#xff0c;做成笔记分享给爱学习的小伙伴们&#xff0c;文章较长&#xff0c;需要一些耐心去阅读&a…

Linux中vscode编程,小白入门喂饭级教程

确保Ubuntu联网 因为后面安装VScode需要从互联网下载。 安装GCC 在桌面空白处右键->打开终端 执行命令&#xff1a;gcc -v 在最后一行可以看到gcc version 7.5.0 如果提示Command ‘gcc’ not found&#xff0c;就查一下如何安装gcc&#xff0c;先把gcc安装好。 安装VS…

蓝桥杯真题——洛谷Day13 找规律(修建灌木)、字符串(乘法表)、队列(球票)

目录 找规律 P8781 [蓝桥杯 2022 省 B] 修剪灌木 字符串 P8723 [蓝桥杯 2020 省 AB3] 乘法表 队列 P8641 [蓝桥杯 2016 国 C] 赢球票 找规律 P8781 [蓝桥杯 2022 省 B] 修剪灌木 思路&#xff1a;对某个特定的点来说有向前和向后的情况&#xff0c;即有向前再返回到该位置…

matrix-breakout-2-morpheus 靶机----练习攻略 【仅获取shell】

【此练习仅做到反弹shell】 1.靶机下载地址 https://download.vulnhub.com/matrix-breakout/matrix-breakout-2-morpheus.ova 2. 打开靶机&#xff0c;kali使用nmap扫描同C段的主机 找到靶机ip 确保靶机和kali网卡均为NAT模式 先查看kali的ip nmap 192.168.182.1/24 …

Flutter中Align的使用说明

又失业了&#xff0c;作为一个高龄Android程序员今年找工作真难呀。现在Flutter是必需技能了&#xff0c;所以最近在自学。所用书籍叫《Flutter实战》&#xff0c;如下 如今已看了100多页&#xff0c;发现这本书写得……有点赶吧&#xff0c;好几处讲得不清不楚&#xff0c;而关…

用ASCII字符转化图片

代码 from PIL import Image# 定义 ASCII 字符集&#xff0c;从最暗到最亮 ASCII_CHARS "%#*-:. "def resize_image(image, new_width100):width, height image.sizeratio height / widthnew_height int(new_width * ratio)resized_image image.resize((new_wi…

详解Sympy:符号计算利器

Sympy是一个专注于符号数学计算的数学工具&#xff0c;使得用户可以轻松地进行复杂的符号运算&#xff0c;如求解方程、求导数、积分、级数展开、矩阵运算等。其中比较流行的深度学习框架pytorch的用到了Sympy,主要用于将模型的计算图转换为符号化表达式&#xff0c;以便进行分…

高频SQL 50 题(持续更新)

SQL的编写与运用 0. 写在前面 最近学习了数据库系统概论&#xff0c;其中涉及到了关于SQL语句的编写&#xff0c;感觉理论知识不足以让我掌握相关的编写方式&#xff0c;因此选择刷力扣上的题目进行复习巩固。 时间不是很多&#xff0c;可能不会经常更新&#xff0c;有时间写…

Unity插件-适用于画面传输的FMETP STREAM使用方法(三)基础使用

目录 一、插件介绍 二、组件介绍 三、Game View Streaming 1、使用 FM Network UDP 的基本设置 Server Scene Client Scene 2、使用使用 FM WebSocket 的基本设置 四、Audio Streaming 五、Microphone Streaming 一、插件介绍 ​​​​​​Unity插件-适用于画面传输的…

UI设计中的加载动画:优化用户体验的细节

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字产品泛滥的今天&#xff0c;用户对体验的要求早已超越功能本身。一个看似简单的加载动画&…

SpringBoot3+Vue3实战(Vue3快速开发登录注册页面并对接后端接口)(4)

目录 一、SpringBoot3Vue3实现基本增删改查。前后端通信交互、配置后端跨域请求。数据批量删除。(博客链接) 二、SpringBoot3Vue3快速开发登录、注册页面并实现对接。 &#xff08;1&#xff09;操作数据表employee(员工信息表)。 <1>修改employee表的字段组成。 <2&g…

Excel(函数篇):IF函数、FREQUNCY函数、截取函数、文本处理函数、日期函数、常用函数详解

目录 IF函数等于判断区间判断与AND函数、OR函数一同使用IFNA函数和IFERROR函数 FREQUNCY函数、分断统计LEFT、RIGHT、MID截取函数FIND函数、LEN函数SUBSTITUTE函数ASC函数、WIDECHAR函数实战&#xff1a;如何获取到表中所有工作簿名称文本处理函数TEXT函数TEXTJOIN函数 日期函数…