Linux系统编程:自定义协议(序列化和反序列化)

1. 协议

        在之前我们谈到,协议就是一种"约定",socket api接口,在读写数据时,都是按照"字符串"的方式来发送接收的,那么我们要传输一些"结构化"数据时怎么办呢?,比如说一个结构体 eg:

struct message{string url;string time;string id;string msg;
};

我们可以将数据,变为一个字符串(有效载荷),并为其添加报头(包含数据的一些属性),最后形成一个报文,这个过程就是序列化的过程 再将这个报文发送到网络中;另一台主机从网络中接收到该数据,将其取报头,并且将字符串转换为我们上面的结构化数据,这个过程就是反序列化。

业务结构化数据在发送到玩过中时先序列化再发送,收到的数据一定是序列字节流,要先进行反序列化,然后才能使用。

2. 自定义协议

下面我们实现一个网络版本的服务端和客户端,你并且自定义一个协议,实现序列化反序列化的过程。

makefile文件:

cc=g++
LD=-DMYSELF
.PHONY:all
all:calServer calClientcalServer:calServer.cc$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
calClient:calClient.cc$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}.PHONY:clean
clean:rm -f calClient calServer

log.hpp(日志):

#pragma once#include <iostream>
#include <cstring>
#include <string>
#include <stdarg.h>
#include <ctime>
#include <unistd.h>using namespace std;#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3  // 出错可运行
#define FATAL   4  // 致命错误const char* to_levelStr(int level)
{switch ((level)){case DEBUG: return "DEBUG";case NORMAL: return "NORMAL";case WARNING: return "WARNING";case ERROR: return "ERROR";case FATAL: return "FATAL";default: return nullptr;}
}void logMessage(int level, const char* format, ...) // ... 可变参数列表 
{
#define NUM 1024char logPreFix[NUM];snprintf(logPreFix, sizeof(logPreFix), "[%s][%ld][pid: %d]", to_levelStr(level), (long int)time(nullptr), getpid());char logContent[NUM];va_list arg;va_start(arg, format);vsnprintf(logContent, sizeof(logContent), format, arg);cout << logPreFix << logContent << endl;}

 protocol.hpp:

在这个文件中,定义了请求和响应类,并在类中实现了请求和响应的序列化(serialize)和反序列化(deserialize)操作;并且实现了添加报头(enLength)和去报头的操作(deLength)

#pragma once#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <sys/socket.h>
#include <sys/types.h>
#include <jsoncpp/json/json.h>#define SEP " "
#define SEP_LEN strlen(SEP)  // 不能使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)enum { OK = 0, DIV_ZERO, MOD_ZERO, OP_ERROR };  // "_exitcode _result" -> "content_len"\r\n"_exitcode _result"\r\n
// "_x _op _y\r\n" -> "content_len"\r\n"__x _op _y"\r\n
std::string enLength(const std::string& text)
{std::string send_string = std::to_string(text.size());send_string += LINE_SEP;send_string += text; send_string += LINE_SEP;return send_string;
}// "content_len"\r\n"_x _op _y"\r\n -> "_x _op _y"
bool deLength(const std::string& package, std::string* text)
{auto pos = package.find(LINE_SEP);if(pos == std::string::npos) return false;std::string text_len_string = package.substr(0, pos);int text_len = std::stoi(text_len_string);*text = package.substr(pos+LINE_SEP_LEN, text_len);return true;
}// 没有人规定我们的网络通信的时候 只能有一种协议
// 我们如何让系统知道我们用的哪一种协议?
// "协议编号"\r\n"content_len"\r\n"_exitcode _result"\r\nclass Request
{
public:Request():_x(0),_y(0),_op(0){}Request(int x, int y, char op):_x(x),_y(y),_op(op){}// 序列化 自己写、现成的bool serialize(std::string* out){*out = "";// 结构化 -> "_x _op _y\r\n" 一个请求就一行std::string x_string = std::to_string(_x);std::string y_string = std::to_string(_y);*out = x_string;*out += SEP;*out += _op;*out += SEP;*out += y_string;return true;}// 反序列化bool deserialize(const std::string& in){// "_x _op _y\r\n" -> 结构化数据auto left = in.find(SEP);auto right = in.rfind(SEP); // 从右往前if(left == std::string::npos || right == std::string::npos) return false;if(left == right) return false;std::string x_string = in.substr(0, left); // 前闭后开区间std::string y_string = in.substr(right+SEP_LEN, strlen(in.c_str())-LINE_SEP_LEN); // 前闭后开区间if( right-(left+SEP_LEN) != 1) return false;_op = in[left+SEP_LEN];      if(x_string.empty()) return false;if(y_string.empty()) return false;_x = std::stoi(x_string);_y = std::stoi(y_string);return true;}public:// "_x _op _y" 约定int _x;int _y;char _op;
};class Response
{
public:Response():_exitcode(0),_result(0){}// 序列化bool serialize(std::string* out){*out = "";std::string ec_string = std::to_string(_exitcode);std::string res_string = std::to_string(_result);*out += ec_string;*out += SEP;*out += res_string;return true;}// 反序列化bool deserialize(const std::string& in){// "_exitcode result"auto mid = in.find(SEP);if(mid == std::string::npos) return false;std::string ec_string = in.substr(0, mid);std::string res_string = in.substr(mid+SEP_LEN);if(ec_string.empty() || res_string.empty())return false;_exitcode = std::stoi(ec_string);_result = std::stoi(res_string);}public:int _exitcode; // 0计算成功 !0表示计算失败,具体是多少,定好标准int _result;   // 计算结果
};bool recvPackge(int sock, std::string &inbuffer, std::string *text)
{text->clear();char buffer[1024];while(true){ssize_t n = recv(sock, buffer, sizeof(buffer)-1, 0);if(n > 0){buffer[n] = 0;inbuffer += buffer;// 分析处理auto pos = inbuffer.find(LINE_SEP);if(pos == std::string::npos) continue;std::string text_len_string = inbuffer.substr(0, pos);int text_len = std::stoi(text_len_string); // 得到有效载荷的长度int total_len = text_len_string.size() + 2*LINE_SEP_LEN + text_len;if(total_len > inbuffer.size()) // 缓冲区还没有读到一个完整的报文{std::cout << "你输入的消息没有严格按照我们的协议, 正在等待后续的内容, continue!" << std::endl;continue;}std::cout << "处理前#inbuffer: \n" << inbuffer << std::endl;// 至少有一个完整的报文*text = inbuffer.substr(0, total_len);   // "content_len"\r\n"_exitcode _result"\r\ninbuffer.erase(0, total_len);std::cout << "处理后#inbuffer: \n" << inbuffer << std::endl;break;}else return false;}return true;
}

server.hpp:

服务端,在接收到来自客户端的请求后,将其进行反序列化、去报头得到数据,在对数据进行计算,得到结果后,加报头序列化,组成一个响应发送给客户端。

#pragma once#include "log.hpp"
#include "protocol.hpp"#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>namespace server
{using namespace std;enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5;typedef std::function<bool(const Request &req, Response &resp)> func_t;// 解耦void handlerEntery(int sock, func_t func){std::string inbuffer;while (true){// 1.读取,content_len"\r\n"_exitcode _result"\r\n// 1.1 如何保证独到的消息是 一个!完整的请求呢?std::string req_text, req_str;// 1.2 读取成功,req_text是一个完整的请求:content_len"\r\n"_exitcode _result"\r\nif (!recvPackge(sock, inbuffer, &req_text))return; // 读取失败std::cout << "带报头的请求: \n" << req_text << std::endl;if (!deLength(req_text, &req_str))return;std::cout << "去报头的正文: \n" << req_str << std::endl;// 2.对请求request反序列化// 2.1 得到一个结构化的请求对象Request req;if (!req.deserialize(req_str))return;// 3.计算处理业务 req._x, req._op, req._y --- 业务逻辑// 3.1 得到一个结构化的响应Response resp;func(req, resp); // 调用的为.cc中的cal// 4.对相应Response进行序列化// 4.1 得到了一个序列化的数据std::string resp_str;resp.serialize(&resp_str);std::cout << "计算完成,序列化响应:" << req_str << std::endl;// 5.然后发送响应给客户端// 5.1构建成为一个完整的报文std::string send_string = enLength(resp_str);std::cout << "构建完整的响应:\n" << send_string << std::endl;send(sock, send_string.c_str(), send_string.size(), 0); // 这里的发送也是有问题的}}class CalServer{public:CalServer(const uint16_t &port = gport): _port(port), _listenSockfd(-1){}void initServer(){// 1.创建socket文件套接字对象_listenSockfd = socket(AF_INET, SOCK_STREAM, 0); // 第二个参数与UDP不同if (_listenSockfd < 0){// 创建套接字失败logMessage(FATAL, "created socket error!");exit(SOCKET_ERR);}logMessage(NORMAL, "created socket success: %d!", _listenSockfd);// 2.bind绑定自己的网络信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_listenSockfd, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind socket error!");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success!");// 3.设置socket 为监听状态if (listen(_listenSockfd, gbacklog) < 0) // 第二个参数backlog后面会讲 5的倍数{logMessage(FATAL, "listen socket error!");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success!");}void start(func_t func){for (;;){// 4.server 获取新连接 不能直接接收数据/发送数据struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listenSockfd, (struct sockaddr *)&peer, &len); // sock 和client进行通信if (sock < 0){logMessage(ERROR, "accept error, next!");continue;}logMessage(NORMAL, "accept a new link success, get new sock: %d!", sock); // ?// version 2, 多进程版,pid_t id = fork();if (id == 0){// 子进程 向外提供服务 不需要监听 关闭这个文件描述符close(_listenSockfd);// if(fork() > 0) exit(0); // 让子进程的子进程执行下面代码 子进程退出// serviceIO(sock);handlerEntery(sock, func); // 读取请求close(sock);               // 关闭父进程的exit(0);                   // 最后变成孤儿进程 交给OS回收这个进程}close(sock); // 关闭子进程的// 父进程pid_t ret = waitpid(id, nullptr, 0); // 阻塞式等待if (ret > 0){logMessage(NORMAL, "wait child process seccess");}}}~CalServer(){}private:int _listenSockfd; // 套接字 -- 不是用来通信的 是用来监听链接到来,获取新链接的!uint16_t _port;    // 端口号};
}

server.cc:

# include "calServer.hpp"
# include "protocol.hpp"
#include <memory>using namespace server;
using namespace std;static void Usage(string proc)
{cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}// req一定是我们的处理好的完整的请求对象
// resp:根据req进行业务处理,填充resp,不用管任何读取和写入,序列化和反序列化等任何细节
bool cal(const Request& req, Response& resp)
{// req已经有结构化完成的数据了 可以直接使用resp._exitcode = 0;resp._result = OK;switch(req._op){case '+':resp._result = req._x + req._y;break;case '-':resp._result = req._x - req._y;break;case '*':resp._result = req._x * req._y;break;case '/':{   if(req._y == 0) resp._exitcode = DIV_ZERO; // else resp._result = req._x / req._y;}break;case '%':{   if(req._y == 0) resp._exitcode = MOD_ZERO; // else resp._result = req._x % req._y;}break;default:resp._exitcode = OP_ERROR;break;}return true;
}// tcp服务器,在启动上与之前的udp server一模一样
// ./tcpServer localport
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);unique_ptr<CalServer> tsvr(new CalServer(port));tsvr->initServer();tsvr->start(cal);return 0;
}

client.hpp:

客户端从键盘输入要计算的数据和运算符和,将其加包头、序列化组成一个请求字符串,发送给服务端,然后阻塞等待,直到接收到服务端的响应后,将其反序列化去报头就是得到的计算结果打印到屏幕上。

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#include "protocol.hpp"using namespace std;#define NUM 1024class CalClient
{
public:CalClient(const string& serverIp, const uint16_t& serverPort):_sock(-1),_serverIp(serverIp),_serverPort(serverPort){}void initClient(){// 1.创建socket_sock = socket(AF_INET, SOCK_STREAM, 0);if(_sock < 0){cerr << "socket creat error!" << endl;exit(2);}// 2.TCP的客户端要bind 但不需要显式的bind,OS自动完成// 3.要不要listen? 不要// 4.要不要accept? 不要// 5.要发起链接}void start(){struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(_serverPort);server.sin_addr.s_addr = inet_addr(_serverIp.c_str());if(connect(_sock, (struct sockaddr*)&server, sizeof(server)) != 0){cerr << "socket connect error" << endl;}else{string line;std::string inbuffer;while(true){cout << "Mycal>>> ";getline(cin, line);Request req = ParseLine(line);std::string content;req.serialize(&content); // 序列化std::string send_string = enLength(content);send(_sock, send_string.c_str(), send_string.size(), 0); // BUG?std::string package, text;if(!recvPackge(_sock, inbuffer, &package)) continue;if(!deLength(package, &text)) continue;Response resp;resp.deserialize(text);std::cout << "exitCode: " << resp._exitcode << std::endl;std::cout << "result: " << resp._result << std::endl;}}}const Request& ParseLine(const std::string& line){// "1+1" "123*123" "21/0"int status = 0; // 0:开始 1:碰到操作符 2:操作符之后int i = 0;int cnt = line.size();std::string left, right; // 左右操作数char op;while(i < cnt){switch(status){case 0:{if(!isdigit(line[i])) {op = line[i];status = 1;}else left.push_back(line[i++]);}break;  case 1:i++;status = 2;break;case 2:right.push_back(line[i++]); break;}}cout << std::stoi(left) << op << std::stoi(right) << std::endl;return Request(std::stoi(left), std::stoi(right), op);}~CalClient(){if(_sock >= 0) close(_sock);}private:int _sock;string _serverIp;uint16_t _serverPort;
};

 client.cc:

# include "calClient.hpp"#include <memory>using namespace std;static void Usage(string proc)
{cout << "Usage:\n\t" << proc << " server_ip server_port\n\n";
}int main(int argc, char *argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);unique_ptr<CalClient> tcli(new CalClient(serverIp, serverPort));tcli->initClient();tcli->start();return 0;
}

上面的代码我们实现了一个简单的网络计算器代码,并且在protocol.hpp文件中实现了自定义协议,自己实现了服务端客户端响应与请求的序列化反序列化的操作,下面为一个测试用例的代码:

客户端和服务端还分别打印出了报文的形状。

在tcp中,客户端和服务端发送的本质都是将数据从自定义的缓冲区中拷贝到他们的发送缓冲区,在由OS决定在合适的时间发送到网络中。接受的本质就是从网络中拷贝数据到接收缓冲区,再拷贝到自定义的字符串或者变量中。发送缓冲区和接收缓冲区都是独立的。

TCP是如何保证收到一个完整的报文的? -- tcp是面向字节流的,所以可以明确报文和报文的边界:定长、特殊符号、子描述方式。在我们上面的代码中,采取的是特殊符号来确定报文的边界。

在这里再介绍两个接口:

# include <sys/types.h>

# include <sys/socket.h>

ssize_t send(int sockfd, const void* buff, size_t len, int flags);

ssize_t recv(int sockfd, void* buff, size_t len, int flags);

  1. send 和 sendto

    • send: 用于在已连接的TCP套接字上发送数据。在使用send时,操作系统知道要发送数据的套接字,并且已经建立了与远程主机的连接。因此,send不需要指定目标地址,因为操作系统已经知道数据将被发送到哪里。
    • sendto: 用于无连接的UDP套接字,也可以用于TCP套接字。在使用sendto时,需要指定目标地址和端口号,因为它没有依赖于之前的连接。在TCP中,尽管可以使用sendto发送数据,但通常更常见的是使用send,因为TCP是面向连接的协议,连接已经被建立,操作系统已经知道目标地址。
  2. recv 和 recvfrom

    • recv: 用于从已连接的TCP套接字接收数据。类似于send,recv操作系统知道从哪个套接字接收数据,因为连接已经建立。recv不需要指定源地址,因为操作系统已经知道要从哪里接收数据。
    • recvfrom: 用于无连接的UDP套接字,也可以用于TCP套接字。在使用recvfrom时,需要指定一个缓冲区来存储接收到的数据,以及一个指向结构体的指针,该结构体用于存储发送方的地址和端口号。在TCP中,虽然可以使用recvfrom接收数据,但通常更常见的是使用recv,因为TCP是面向连接的,连接已经建立。

总结来说,send和recv适用于TCP套接字,而sendto和recvfrom主要用于UDP套接字,但它们也可以在TCP套接字上使用。在TCP中,通常使用send和recv,因为连接已经建立,操作系统已经知道目标地址和源地址。

对于序列化和反序列化,有现成的方案可以使用:1.json、2.protobud、3.xml 

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

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

相关文章

前端-04-VScode敲击键盘有键入音效,怎么关闭

目录 问题解决办法 问题 今天正在VScode敲项目&#xff0c;不知道是按了什么快捷键还是什么的&#xff0c;敲击键盘有声音&#xff0c;超级烦人啊&#xff01;&#xff01;于是我上网查了一下&#xff0c;应该是开启了VScode的键入音效&#xff0c;下面是关闭键入音效的办法。…

分布式服务框架zookeeper+消息队列kafka

一、zookeeper概述 zookeeper是一个分布式服务框架&#xff0c;它主要是用来解决分布式应用中经常遇到的一些数据管理问题&#xff0c;如&#xff1a;命名服务&#xff0c;状态同步&#xff0c;配置中心&#xff0c;集群管理等。 在分布式环境下&#xff0c;经常需要对应用/服…

云计算数据中心(三)

目录 四、自动化管理&#xff08;一&#xff09;自动化管理的特征&#xff08;二&#xff09;自动化管理实现阶段&#xff08;三&#xff09;Facebook自动化管理 五、容灾备份&#xff08;一&#xff09;容灾系统的等级标准&#xff08;二&#xff09;容灾备份的关键技术&#…

Flink CDC 同步表至Paimon 写数据流程,write算子和commit算子。

Flink CDC 同步表至Paimon 写数据流程,write算子和commit算子。(未吃透版) 流程图 一般基本flink cdc 任务同步数据至paimon表时包含3个算子,source、write、global commit。 source端一般是flink connector实现的连接源端进行获取数据的过程,本文探究的是 source算子获…

Haproxy服务

目录 一.haproxy介绍 1.主要特点和功能 2.haproxy 调度算法 3.haproxy 与nginx 和lvs的区别 二.安装 haproxy 服务 1. yum安装 2.第三方rpm 安装 3.编译安装haproxy 三.配置文件详解 1.官方地址配置文件官方帮助文档 2.HAProxy 的配置文件haproxy.cfg由两大部分组成&…

Synchronized升级到重量级锁会发生什么?

我们从网上看到很多&#xff0c;升级到重量锁的时候不会降级&#xff0c;再来线程都是重量级锁 今天我们来实验一把真的是这样的吗 1.首选导入Java对象内存布局的工具库&#xff1a; <dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-…

什么是内网穿透?

前言 我们常常会听到“内网穿透”这个术语&#xff0c;但对于很多人来说&#xff0c;它可能还比较陌生。作为一个在网络世界中摸索了一段时间的使用者&#xff0c;我来和大家分享一下我对内网穿透的理解。 目录 一、内网穿透介绍 二、发现 三、特点 四、优势 简单来说&am…

初识godot游戏引擎并安装

简介 Godot是一款自由开源、由社区驱动的2D和3D游戏引擎。游戏开发虽复杂&#xff0c;却蕴含一定的通用规律&#xff0c;正是为了简化这些通用化的工作&#xff0c;游戏引擎应运而生。Godot引擎作为一款功能丰富的跨平台游戏引擎&#xff0c;通过统一的界面支持创建2D和3D游戏。…

Git使用技巧

rebase & merge 先说结论&#xff0c;rebase比较适用于私人分支&#xff0c;可以把主干分支上其他人开发的功能拉到自己的分支上&#xff0c;并且是一条线&#xff1b;merge则主要适用于主分支&#xff0c;可以将其他人的代码合并上去&#xff0c;因为要保留主分支的完整历…

JavaWeb笔记_Cookie

一.会话技术概述 在日常生活中,A和B之间在打电话过程中一连串的你问我答就是一个会话 在BS模型中,会话可以理解为通过浏览器访问服务端的资源,点击超链接可以进行资源的跳转,直到浏览器关闭过程叫做会话 我们使用会话技术可以解决的是整个会话过程中(通过浏览器浏览服务…

css-01-如何实现“文本过长(文本在一行排),超出部分被省略号代替”

目录 需求代码代码解释 需求 最近写一个项目&#xff0c;遇到了一个问题&#xff0c;就是希望实现下面这种文字效果&#xff1a;文字在一行排&#xff0c;超出的部分用省略号代替 代码 <!DOCTYPE html> <html lang"en"><head><meta charset…

【芯片设计- RTL 数字逻辑设计入门 番外篇 12 -- SoC 设计中的 ECO】

请阅读【ARM AMBA AXI 总线 文章专栏导读】 请阅读【芯片设计 RTL 数字逻辑设计扫盲 】 转自&#xff1a;简单了解SoC设计中的ECO — 快乐的芯片工程师 文章目录 ECO 概述Pre-Mask ECO&#xff08;预掩模ECO&#xff09;芯片设计前端与后端的区别 Post-Mask ECO&#xff08;后…

IAR环境下STM32+IAP方案的实现

--基于STM32F103ZET6的UART通讯实现 一、什么是IAP&#xff0c;为什么要IAP IAP即为In Application Programming(在应用中编程)&#xff0c;一般情况下&#xff0c;以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了&#xff0c;如果在设备使…

BM34 判断是不是二叉搜索树

1.题目描述 给定一个二叉树根节点&#xff0c;请你判断这棵树是不是二叉搜索树。 二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。 例&#xff1a; 图1 图2 数据范围&#xff1a;节点数量满足 1≤&#x1d45b;≤104 1≤n≤104…

SpringMVC的底层工作原理?

1.用户发送请求至前端控制器DispatcherServlet. 2.DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器 3.HandlerMapping找到具体的处理器(可以根据 xml 配置、注解进行查找&#xff09;&#xff0c;生成处理器及处理器拦截器(如果有则生成)一并返回给DispatcherSe…

AI赋能下的人体摔倒识别技术:深度解析与应用前景

引言 随着人工智能技术的快速发展&#xff0c;AI赋能的解决方案在各行各业中展现出巨大的潜力。特别是在安全监控和健康护理领域&#xff0c;AI技术的应用不仅提高了效率&#xff0c;还极大地提升了安全性。本文将深入探讨思通数科&#xff08;南京&#xff09;信息技术有限公…

【Matlab】RBF径向基神经网络回归预测算法(附代码)

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/89564332 资源合集&#xff1a; https://download.csdn.net/download/vvoennvv/89564427 目录 【Matlab】BP 神经网络回归预测算法 【Matlab】CNN-LSTM回归预测 卷积神经网络-长短期记忆神经网络组合模型 …

昇思学习打卡-23-生成式/CycleGAN图像风格迁移互换

文章目录 模型介绍网络结构数据集可视化网络的其他细节模型推理 模型介绍 CycleGAN(Cycle Generative Adversarial Network) 即循环对抗生成网络&#xff0c;实现了一种在没有配对示例的情况下学习将图像从源域 X 转换到目标域 Y 的方法。 该模型一个重要应用领域是域迁移(Do…

vue 侧边锚点外圆角

环境&#xff1a;uniapp、vue3、unocss、vant4 效果&#xff1a; 代码 主要是&#xff1a;pointTop 、pointCentent 、pointBottom&#xff0c;这三个样式 html <div v-show"!showPoint" class"fixedLeftDiv"><div><div class"pointT…

XXE:XML外部实体引入

XXE漏洞 如果服务器没有对客户端的xml数据进行限制&#xff0c;且版本较低的情况下&#xff0c;就可能会产生xxe漏洞 漏洞利用流程 1.客户端发送xml文件&#xff0c;其中dtd存在恶意的外部实体引用 2.服务器进行解析 3.服务器返回实体引用内容 危害&#xff1a;任意文件读…