当前位置: 首页 > news >正文

【网络】协议的定制与Json序列化和反序列化

文章目录

  • 应用层
  • 初识TCP协议通讯流程
  • 定制协议
    • 再谈协议
    • 网络版本计算器
      • Protocal.hpp
      • CalServer
      • CalClient
  • Json的安装

应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层

初识TCP协议通讯流程

  • 建立链接和断开链接

基于TCP协议,我们需要知道写代码时对应的接口大概在TCP通讯的过程中属于什么样的时间点角色,在TCP协议时详谈。三次握手,四次挥手

listen状态:准备好了,可以进行链接,accept:获取链接,不是创造链接,链接已经在底层创建好了,在应用层调用accept把链接拿上来

connect:1.发起链接请求2.绑定套接字;建立链接,在底层向服务端建立链接请求,在TCP中,采用链接的方案是三次握手的方案,connect会发起三次握手,发起链接请求和真正的建立链接是两码事,建立链接由双方OS自动完成的,为什么自动完成?网络分层中,下三层是OS内部的,用户感知不到。通过客户端调用connect让OS来帮我们把三次握手的工作做完。

而accept是获取链接,链接是已经建立好了的,所以accept并不参与三次握手的任何细节,accept一定是在获取链接前别人把链接做完,既链接建立完。三次握手是OS自己完成的,connect只是发起,accept只是收尾。即使上层不调用accept,三次握手也是能够建立好的。

TCP保证可靠性不是write和read有关系的,由双方OS完成的,后面详谈。

建立链接后面就要断开链接,所以UDP由于不需要建立链接,自然不需要谈论断开链接

而四次挥手的工作都是由双方的OS完成,而我们决定什么时候分手一旦调用系统调用close,用户层就不用管了。

  • 理解链接

谈男女朋友时,都会表达自己的爱意,一定有一方主动发起链接,无论如何表达,双方看对眼的概率是极低的。而主动发起链接,是怎么发起的呢?首先,男方先表白,然后女方在做表态,什么时候在一起?男方回答就现在。这就是双方三次握手成功。(虽然现实生活中被拒绝是常态)

建立链接究竟在干什么:记下一些东西

  • 什么是建立链接

所谓的建立链接,三次握手根本就是手段,不是目的,为了达到让双方都能记住这一套,一个服务端链接客户端,很多客户端来链接了,意味着很多的客户端来了,OS应该区分清楚,需要把链接管理起来,先描述在组织,需要创建对应的链接数据结构,把所有的链接描述起来,在对其进行管理。所谓的链接就是OS内部创建的链接结构体,包含了在建立链接时对应的属性信息。当有新的链接进来时,每到来一个链接,服务端会构建一个链接对象 ,将所有的链接对象在内部中用特定的数据结构管理起来。这就是链接的建模过程。维护链接是需要成本的。占用内存资源,要用对象进行管理。

断开链接需要四次挥手,断开链接的最终目的毫无疑问就是把建立好的链接信息释放。四次挥手理解:

男女朋友处的非常好,走到了婚姻的殿堂,但是被现实打败了,过不下去啦。然后一方提出离婚,但是你自己说了不算,另一方说好啊,过了一会,对象又说离就离,那我也要离,那么你一看,我也OK。所以断开链接是双方的事情,必须得征求双方的意见。双方在协商,TCP要保证可靠性,你说的话要保证你也听到了,我也知道了,反之也一样。这就是传说中的四次挥手

TCP与UDP对比

可靠传输VS不可靠传输

有连接VS无连接

字节流VS数据报

定制协议

应用层协议的定制

再谈协议

协议是一种约定,socket api的接口,在读写数据时,都是按照字符串的方式来接收的,如果要传输一些”结构化的数据“怎么办呢?

结构化的数据:群里说话的时候除了消息本身,还有头像,昵称时间等等信息 。但是不是一个一个独立的个体,你需要做的把这些消息形成一个报文——打包成一个字符串。

由多变一这个过程就是序列化。经过网络传输后,收到的是一个报文,收到一个报文要的是什么?把一个字符串变成多个字符串,这个过程是反序列化

业务数据发送到网络的时候,先序列化发送,收到的是序列字节流,要先进行反序列化, 然后才能使用

业务协议就是结构体,这样说还是不够的,所以我们要手写一个协议。

应用场景:形成字符串对方收到,收到之后上层来不及接收,对方又发一个,有可能一次全读的,上层如何保证收到的是一个报文?

tcp这里怎么保证收到一个完整的报文

理解业务协议,理解序列化和反序列化。

网络版本计算器

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加减乘除数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端

TCP是面向字节流的,所以明确报文和报文的边界:

image-20230521235914835

TCP是全双工的,如果接收方来不及读,那接收缓冲区就会存在很多数据,读的时候怎么怎么保证读到一个完整的报文:

1.定长2.特殊符号3.自描述方式

序列化、反序列化与定制协议是两码事,是不同阶段的事情,定制协议:报头+有效载荷

Protocal.hpp

自定义协议:

#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)

请求和响应:Request,Response

Request:x,y,op(“x op y”)x和y是数据,op是操作符,比如1+2

Response:设置了退出码exitcode和结果result()

对请求和响应添加报头,这里设置的报头是长度,enLength(即添加大小,转化成字符串),也就是封装了enLength函数:

//"x op y"->"content_len"\r\n"x op y"\r\n
//"exitcode result"->"cotent_len"\r\n"exitcode result"\r\n
const 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;
}

对请求和响应提取报文,只要报文,不要报头,也就是封装了deLength函数:

//"cotent_len"\r\n"exitcode result"\r\n
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;
}

对请求和响应进行序列化和反序列化:对于序列化和反序列化我们可以用Json来进行实现

序列化过程:结构化数据->“x op y”

反序列化过程:“x op y”->结构化数据

Protocal.hpp还提供了recvPackage函数

#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)
enum
{OK = 0,DIV_ZERO,MOD_ZERO,OP_ERROR
};//"x op y" --->"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"exitcode result"\r\n
// 去掉报头,得到"exitcode result"
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); // content_len:如“14”int text_len = std::stoi(text_len_string);*text = package.substr(pos + LINE_SEP_LEN, text_len);return true;
}class Request
{
public:Request() : x(0), y(0), op(0){}Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_){}// 序列化:// 结构化-> "x op y"bool serialize(std::string *out){
#ifdef MYSELF*out = "";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;
#elseJson::Value root;root["first"] = x;root["second"] = y;root["oper"] = op;Json::FastWriter writer;*out = writer.write(root);
#endifreturn true;}// 反序列化化://"x op y"->结构化bool deserialize(const std::string &in){
#ifdef MYSELFauto 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;if (right - (left + SEP_LEN) != 1)return false;std::string x_string = in.substr(0, left);std::string y_string = in.substr(right + SEP_LEN);if (x_string.empty())return false;if (y_string.empty())return false;x = std::stoi(x_string);y = std::stoi(y_string);op = in[left + SEP_LEN];
#elseJson::Value root;Json::Reader reader;reader.parse(in, root);x = root["first"].asInt();y = root["second"].asInt();op = root["oper"].asInt();
#endifreturn true;}public:int x;int y;char op;
};class Response
{
public:Response() : exitcode(0), result(0){}Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_){}bool serialize(std::string *out){
#ifdef MYSELF*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;
#elseJson::Value root;root["exitcode"] = exitcode;root["result"] = result;Json::FastWriter writer;*out = writer.write(root);
#endifreturn true;}bool deserialize(const std::string &in){
#ifdef MYSELFauto 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);
#elseJson::Value root;Json::Reader reader;reader.parse(in, root);exitcode = root["exitcode"].asInt();result = root["result"].asInt();
#endifreturn true;}public:int exitcode;int result;
};// 读取报文,保证读取的是一个完整的报文 ,inbuffer由外部传入
// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{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;std::cout<<"处理前#inbuffer:\n"<<inbuffer<<std::endl;if(inbuffer.size()< total_len){std::cout<<"你输入的消息,没有遵守所定制的协议,正在等待后续的内容,continue"<<std::endl;continue;}//至少是一个完整的报文*text = inbuffer.substr(0,total_len);inbuffer.erase(0,total_len);std::cout<<"处理后#inbuffer:\n"<<inbuffer<<std::endl;break;}elsereturn false;}return true;
}

对于recvPackage函数我们要保证读到的至少是一个完整的报文

CalServer

服务端代码

//CalServer.hpp
namespace server
{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"x op y"\r\n//保证读到的消息是一个完整的请求std::string req_text,req_str;if(!recvPackage(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.计算机处理————业务逻辑Response resp;func(req,resp);//4.对响应Response,进行序列化//4.1得到一个"字符串"std::string resp_str;resp.serialize(&resp_str);std::cout<<"计算完成,序列化响应:"<<resp_str<<std::endl;//5.发送响应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):_listensock(-1),_port(port){}void initServer(){_listensock = socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){logMessage(FATAL,"create socket error");exit(SOCKET_ERR);}logMessage(NORMAL,"create socket success:%d",_listensock);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(_listensock,(struct sockaddr*)&local,sizeof(local))<0){logMessage(FATAL,"bind socket error");exit(BIND_ERR);}logMessage(NORMAL,"bind socket success");if(listen(_listensock,gbacklog)<0){logMessage(FATAL,"listen socker error");exit(LISTEN_ERR);}logMessage(NORMAL,"listen socket success");}void start(func_t func){for(;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock,(struct sockaddr*)&peer,&len);if(sock<0){logMessage(ERROR,"accept error,next");continue;}logMessage(NORMAL,"accept a new link success,get new sock:%d",sock);pid_t id = fork();if(id == 0){close(_listensock);handlerEntery(sock,func);close(sock);exit(0);}close(sock);pid_t ret = waitpid(id,nullptr,0);if(ret>0){logMessage(NORMAL,"wait child success");}}}~CalServer() {}public:int _listensock;uint16_t _port;};
}//CalServer.cc
#include "calServer.hpp"
#include <memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}// req是处理好的完整的请求对象
// resp:根据req进行业务处理,填充resp,不需要管理任何IO,序列化和反序列化
bool cal(const Request &req, Response &resp)
{// req已经有结构化的数据resp.exitcode = OK;resp.result = 0;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 local_port
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;
}

CalClient

ParseLine:解析,构建一个请求:“1+1”

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Protocol.hpp"
#define NUM 1024class CalClient
{
public:CalClient(const std::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){std::cerr << "socket create error" << std::endl;exit(2);}}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){std::cerr << "socket connect error" << std::endl;}else{std::string line;std::string inbuffer;while (true){std::cout << "mycal>>> ";std::getline(std::cin, line);  // 1+1Request req = ParseLine(line); // "1+1"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;//  "content_len"\r\n"exitcode result"\r\nif (!recvPackage(_sock, inbuffer, &package))continue;if (!deLength(package, &text))continue;// "exitcode result"Response resp;resp.deserialize(text);std::cout << "exitCode: " << resp.exitcode << std::endl;std::cout << "result: " << resp.result << std::endl;}}}Request ParseLine(const std::string &line){// 建议版本的状态机!//"1+1" "123*456" "12/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;}}std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;return Request(std::stoi(left), std::stoi(right), op);}~CalClient(){if (_sock >= 0)close(_sock);}private:int _sock;std::string _serverip;uint16_t _serverport;
};#include "calClient.hpp"
#include <memory>using namespace std;
static void Usage(string proc)
{cout<<"\nUasge:\n\t"<<proc<<" serverip serverport\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;
}

Json的安装

sudo yum install -y jsoncpp-devel

相关文章:

【网络】协议的定制与Json序列化和反序列化

文章目录 应用层初识TCP协议通讯流程定制协议再谈协议网络版本计算器Protocal.hppCalServerCalClient Json的安装 应用层 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层 初识TCP协议通讯流程 建立链接和断开链接 基于TCP协议&#xff0c…...

浙大数据结构第一周最大子列和问题

题目详情&#xff1a; 给定K个整数组成的序列{ N1​, N2​, ..., NK​ }&#xff0c;“连续子列”被定义为{ Ni​, Ni1​, ..., Nj​ }&#xff0c;其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 }&#xff…...

Selenium基础 — Selenium自动化测试框架介绍

1、什么是selenium Selenium是一个用于Web应用程序测试的工具。只要在测试用例中把预期的用户行为与结果都描述出来&#xff0c;我们就得到了一个可以自动化运行的功能测试套件。Selenium测试套件直接运行在浏览器中&#xff0c;就像真正的用户在操作浏览器一样。Selenium也是…...

力扣竞赛勋章 | 排名分数计算脚本

文章目录 力扣竞赛勋章介绍竞赛评分算法脚本&#xff08;本文的重点内容&#xff09;运行结果 代码修改自&#xff1a;https://leetcode.cn/circle/discuss/6gnvEj/ 原帖子的代码无法正常运行。 力扣竞赛勋章介绍 https://leetcode.cn/circle/discuss/0fKGDu/ 如果你想知道自…...

win10 远程 ubuntu 18.04 桌面

win10 远程 ubuntu 18.04 桌面 我们要在Windows 10上远程连接到Ubuntu 18.04&#xff0c;您需要按照以下步骤进行设置&#xff1a; 1. 在Ubuntu 18.04上安装并启用远程桌面服务。打开终端&#xff0c;并运行以下命令来安装xrdp&#xff1a; sudo apt update sudo apt instal…...

c++ -- STL

【C/C】STL详解_cstl_沉晓的博客-CSDN博客 Learning Record have done assignment class template An excellent programmer only needs to know how to use containers to improve program encapsulation and reduce coupling, without understanding the underlying pri…...

文字识别(OCR)介绍与开源方案对比

目录 文字识别&#xff08;OCR&#xff09;介绍与开源方案对比 一、OCR是什么 二、OCR基本原理说明 三、OCR基本实现流程 四、OCR开源项目调研 1、tesseract 2、PaddleOC 3、EasyOCR 4、chineseocr 5、chineseocr_lite 6、cnocr 7、商业付费OCR 1&#xff09;腾讯…...

Modbus tcp转ETHERCAT在Modbus软件中的配置方法

Modbus tcp和ETHERCAT是两种不同的协议&#xff0c;这给工业生产带来了很大的麻烦&#xff0c;因为这两种设备之间无法通讯。但是&#xff0c;远创智控YC-ECT-TCP网关的出现&#xff0c;却为这个难题提供了解决方案。 YC-ECT-TCP网关能够连接到Modbus tcp总线和ETHERCAT总线中…...

开源点云数据集整理汇总

目录 一、ModelNet401. 网址2. 模型 二、ShapeNet1. 网址2. 模型 三、S3DIS Dataset1. 网址2. 模型 四、ScanNet1. 网址2. 模型 五、RGB-D Object Dataset1. 网址2. 模型 六、 NYU Depth Dataset V2 &#xff08;纽约大学深度数据集&#xff09;1. 网址2. 模型 七、 The Stanfo…...

【全栈开发指南】VUE前端路由设计及配置

我们在使用Vue.js时&#xff0c;创建单页面应用一定会用到路由&#xff0c;Vue Router 是 Vue.js 官方的路由管理器&#xff0c;我们在开发框架中过程中&#xff0c;需要结合Vue Router路由管理器提供的功能&#xff0c;设计和实现系统中菜单的配置。 一、实现原理 一级菜单r…...

C语言程序环境和预处理

本章主要以图片和文字的形式给大家讲解 程序的翻译环境和程序的执行环境 在ANSI C的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境&#xff0c;它用于实际执行代码 2. 详解编译…...

为摸鱼助力:一份Vue3的生成式ElementPlus表单组件

目录 一、实现背景 二、简介 三、组织架构设计 四、实现方式 五、代码示例 六、示例代码效果预览 七、项目预览地址 & 项目源码地址 目前项目还有诸多待完善的地方&#xff0c;大家有好的想法、建议、意见等欢迎再次评论&#xff0c;或于github提交Issues 一、实现…...

数通工作中常见问题与解决方法

城域网&#xff0c;硬件&#xff0c;交换机开局 1、环路产生&#xff0c;现象&#xff0c;怎么解决 一般是物理拓扑存在环路&#xff0c;导致数据互传&#xff0c;Mac地址漂移&#xff0c;产生环路&#xff1b; Cpu利用率变高&#xff0c;端口流量接近100%&#xff0c;有mac…...

基于STM32+华为云IOT设计的智能浇花系统

一、前言 随着社会的不断发展和人们生活水平的逐渐提高,人们逐渐追求高质量的生活,很多人都会选择在家里或办公室种植一些花卉以净化家庭空气,陶冶情操,但是很多人忙于工作、学习、出差、旅游或者一些其他的原因,不能及时地对花卉进行照料,短时间内导致很多花卉因缺水分…...

回调函数(callback)是什么?

通俗易懂 你到一个商店买东西&#xff0c;刚好你要的东西没有货&#xff0c;于是你在店员那里留下了你的电话&#xff0c;过了几天店里有货了&#xff0c;店员就打了你的电话&#xff0c;然后你接到电话后就到店里去取了货。 在这个例子里&#xff0c;你的电话号码就叫回调函…...

零代码量化投资:用ChatGPT获取新浪财经上的股票实时行情

现在很多免费的股票数据库&#xff0c;比如akshare&#xff0c;其实是从新浪财经或者东方财富网站上爬取下来的。如果能直接从新浪财经或者东方财富网站上爬取数据&#xff0c;可以获取更全面更即时的信息。 可以在ChatGPT中输入提示词如下&#xff1a; 写一段Python代码&…...

从GitLab拉取并运行项目

从GitLab拉取并运行项目 序Git项目运行运行报错 总结教训 序 搭建好前端基础环境后&#xff0c;开始尝试从单位项目组拉取项目尝试本地运行。 Git Git相关配置&#xff1a;一篇学会Git版本管理 先申请Git账号&#xff0c;随后由上级分配权限拉入该项目组。 通过git clone ……...

AI绘画结合GPT 把Ai绘画与摄影玩明白

一、绘画与摄影有什么关系&#xff1f; 绘画和摄影是两种不同的艺术形式&#xff0c;它们都以其自身独特的方式捕捉和表达现实。在某些方面&#xff0c;它们是相互联系的&#xff0c;而在其他方面&#xff0c;它们又有所不同。​ 相似之处&#xff1a;绘画和摄影都是创造性的…...

哈工大计算机网络课程数据链路层协议详解之:多路访问控制(MAC)协议

哈工大计算机网络课程数据链路层协议详解之&#xff1a;多路访问控制&#xff08;MAC&#xff09;协议 在上一小节介绍完数据链路层功能和所提供的服务后&#xff0c;接下来我们介绍一个在数据链路层非常重要的一个协议&#xff1a;多路访问控制MAC协议。 多路访问控制主要是…...

docker基本概念和相关命令

!!! 前面都是概念东西&#xff0c;可以直接跳到Docker命令就可以了(直接搜吧“Docker命令”&#xff0c;页内无法跳转&#xff0c;还在研究中……) 容器和虚拟化 容器包含应用和其所有的依赖包&#xff0c;但是与其他容器共享内核。容器在宿主机操作系统中&#xff0c;在用户…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...