认识“协议”
文章目录:
- 什么是协议
- 结构化的数据传输
- 序列化和反序列化
- 网络版本计算器
什么是协议
在计算机网络中,协议是指在网络中进行通信和数据交换时,双方遵循的规则和约定集合。它定义了数据的传输格式、顺序、错误处理、认证和安全性等方面的规范。
协议的设计和实现是计算机网络能够正常运行的基础。它确保了不同设备和系统之间能够相互理解和协作。协议定义了数据的结构和编码方式,规定了数据传输的方式和顺序,以及双方之间进行通信的交互规则。协议的标准化和普遍采用,使得计算机网络得以互联互通,并支持各应用和服务的实现。
为了满足不同的应用场景和需求,已经存在许多成熟的应用层协议。这些协议定义了在特定应用中进行通信和数据交换的规则和格式。(http、https、DNS、ftp、smtp…)。
结构化的数据传输
协议是一种 “约定”,socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些 “结构化的数据” 怎么办呢?
例如,我们需要实现一个网络版本的简易计算器。此时,客户端给服务端发送的数据中,包含一个左操作数、操作符、右操作数。然后由服务端接收处理之后再将结果发送给客户端。
此时,就遇到了一个问题,客户端发送给服务端的不是一个简单的字符串,而是一组结构化的数据。如果客户端将这些结构化的数据单独的通过网络发送给服务端,那么服务端很难将收到的数据进行排列形成正确的数据。因此,客户端最好将这些数据一次性进行发出,此时服务端获取到的就是一个完整的数据请求,客户端常见的方案有以下两种。
约定方案一:将结构化的数据组合成为一个字符串
- 客户端发送一个形如 “5+7” 的字符串;
- 这个字符串中有两个操作数,都是整形;
- 两个数字之间会有一个字符是运算符;
- 数字和运算符之间没有空格;
- …
这种将结构化的数据组合成一个字符串的方式可以是一个简单的方法,适用于简单的数据结构和通信需求。通过约定字符串的格式和规则,可以将结构化数据转换为字符串进行传输。
约定方案二:将结构数据序列化和反序列化
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 这个过程叫做 “序列化” 和 “反序列化”。
通过序列化和反序列化可以更灵活地处理结构化数据,并支持复杂的数据类型和结构。通过定义一个结构体或对象来表示需要交互的信息,可以使用序列化算法将结构体转换为字符串形式进行传输。接收端则可以使用相同的序列化算法进行反序列化,将字符串转化为原始的数据结构。
序列化和反序列化
序列化和反序列化是将数据在不同表示形式之间进行转化的过程。
- 序列化是指将数据从内存中的对象或数据结构转化为可存储或传输的格式,例如字符串、字节流或二进制数据。
- 反序列化是将序列化之后的数据重新转化为内存中的对象或数据结构。
网络版本计算器
接下来我们使用自己制定的协议来写一个网络版本的计算器。
首先,将后续代码需要的日志文件引入,日志 log.hpp
代码如下:
#pragma once
#include <stdio.h>
#include <cstdarg>
#include <ctime>
#include <cassert>
#include <stdlib.h>
#include <cstring>
#include <cerrno>#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};void logMessage(int level,const char *format, ...)
{assert(level >= DEBUG);assert(level <= FATAL);char *name = getenv("USER");char logInfo[1024];va_list ap; // ap -> char*va_start(ap, format);vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);FILE *out = (level == FATAL) ? stderr : stdout;fprintf(out, "%s | %u | %s | %s\n",log_level[level],(unsigned int)time(nullptr),name == nullptr ? "unknow" : name,logInfo);va_end(ap); // ap = NULL
}
协议定制
要实现一个网络版本的计算器,就必须确保通信双方能够遵守某种协议约定。这里我们制定一套简单的协议约定。数据分为请求数据和响应数据。因此我们可以定义请求类和响应类来实现协议约定。
// 请求类
class Request {
public:int x_;int y_;char op_;
};// 响应类
class Response {
public:int exitCode_; // 状态字段int result_; // 计算结果
};
定义了一个名为 Request
的请求类和一个名为 Response
的响应类。这些类具有公共的成员变量来存储请求和响应的数据。
请求类中包括两个操作数和一个操作符,响应类中包含一个计算结果以及该次计算的状态字段。状态字段用于表示该次的计算是否符合计算要求。
约定状态字段对应的含义如下:
0
- 表示计算成功;-1
- 表示除0错误;-2
- 表示模0错误;-3
- 表示非法操作符;
注意,计算结果只有在状态码为0的时候才有意义,否则计算的结果是没有意义的。
序列化和反序列化
下面代码实现了一个简单的请求类和响应类,并提供了序列化和反序列化的方法来将结构化的数据转换为字符串,并将字符串转换会结构化的数据。根据定义的宏 MY_SELF
的是否定义,可以使用自定义的序列化方法或者使用 JSON 库进行序列化。
#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <jsoncpp/json/json.h>
#include "util.hpp"#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF)
#define OPS "+-*/%"
#define BUFFER_SIZE 1024
// #define MY_SELF 1// encode,整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{std::string encodein = std::to_string(len);encodein += CRLF;encodein += in;encodein += CRLF;return encodein;
}// decode,这个序列化之后的字符串进行提取长度
// 1.必须具有完整的长度 2.必须具有和len相符合的有效载荷
// 3.具备上述两个长度才返回有效载荷和len,否则,就是一个检测函数
std::string decode(std::string &in, uint32_t *len)
{assert(len);// 1.确认是否是一个包含len的有效字符串*len = 0;std::size_t pos = in.find(CRLF);if (pos == std::string::npos)return "";// 2.提取长度std::string Len = in.substr(0, pos);int intLen = atoi(Len.c_str());// 3.确认有效载荷也是符合要求的int surplus = in.size() - 2 * CRLF_LEN - pos;if (surplus < intLen)return "";// 4.确认有完整的报文结构std::string package = in.substr(pos + CRLF_LEN, intLen);*len = intLen;// 5.将当前的报文完整的从in中移除掉int removeLen = Len.size() + package.size() + 2 * CRLF_LEN;in.erase(0, removeLen);// 6.正常返回return package;
}class Request
{
public:Request() {}~Request() {}// 序列化 - 结构化的数据 -> 字符串void serialize(std::string *out){
#ifdef MY_SELFstd::string xStr = std::to_string(x_);std::string yStr = std::to_string(y_);*out = xStr;*out += SPACE;*out += op_;*out += SPACE;*out += yStr;
#else// 1.Value对象,json基于KV,json是有两套操作方法的Json::Value root;root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::FastWriter fw;*out = fw.write(root);
#endif}// 反序列化 - 字符串 -> 结构化的数据bool deserialize(std::string &in){
#ifdef MY_SELF// 100 + 200std::size_t spaceOne = in.find(SPACE);if (std::string::npos == spaceOne)return false;std::size_t spaceTwo = in.rfind(SPACE);if (std::string::npos == spaceTwo)return false;std::string dataOne = in.substr(0, spaceOne);std::string dataTwo = in.substr(spaceTwo + SPACE_LEN);std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));if (oper.size() != 1)return false;x_ = atoi(dataOne.c_str());y_ = atoi(dataTwo.c_str());op_ = oper[0];return true;
#elseJson::Value root;Json::Reader rd;rd.parse(in, root);x_ = root["x"].asInt();y_ = root["y"].asInt();op_ = root["op"].asInt();return true;
#endif}void debug(){std::cout << "debug---------------------" << std::endl;std::cout << x_ << " " << op_ << " " << y_ << std::endl;std::cout << "debug---------------------" << std::endl;}public:int x_;int y_;char op_;
};class Response
{
public:Response() {}~Response() {}// 序列化void serialize(std::string *out){
#ifdef MY_SELF// "exitCode_ result_"std::string ec = std::to_string(exitCode_);std::string res = std::to_string(result_);*out = ec;*out += SPACE;*out += res;
#elseJson::Value root;root["exitcode"] = exitCode_;root["result"] = result_;Json::FastWriter fw;*out = fw.write(root);
#endif}// 反序列化 - 不仅仅是在网路中应用,本地也是可以直接使用的bool deserialize(std::string &in){
#ifdef MY_SELF// "0 300"std::size_t pos = in.find(SPACE);if (std::string::npos == pos)return false;std::string codeStr = in.substr(0, pos);std::string restStr = in.substr(pos + SPACE_LEN);exitCode_ = atoi(codeStr.c_str());result_ = atoi(restStr.c_str());return true;
#elseJson::Value root;Json::Reader rd;rd.parse(in, root);exitCode_ = root["exitcode"].asInt();result_ = root["result"].asInt();return true;
#endif}void debug(){std::cout << "debug---------------------" << std::endl;std::cout << "exitCode = " << exitCode_ << " result = " << result_ << std::endl;std::cout << "debug---------------------" << std::endl;}public:// 退出状态,0表示运算结果合法,非0表示表示运行结果是非法的,!0是几就表示是因为什么原因错了。int exitCode_;int result_;
};bool makeRequest(std::string &str, Request *req)
{// 1+1char strcmp[BUFFER_SIZE];snprintf(strcmp, sizeof strcmp, "%s", str.c_str());char *left = strtok(strcmp, OPS);if (!left)return false;char *right = strtok(nullptr, OPS);if (!right)return false;char mid = str[strlen(left)];req->x_ = atoi(left);req->y_ = atoi(right);req->op_ = mid;return true;
}
debug
方法用于调试,打印对象的成员变量值。makeRequese
函数用于解析字符串并生成请求对象。- 此外,代码还定义了一些变量和辅助函数来处理字符串的拼接和分割。
- 若要使用第三方库
jsoncpp
来处理JSON
数据的序列化和反序列化。需要确保项目中包含了这个库,并在编译时链接到正确的库文件。
服务端代码
TCP 服务器我们使用多线程版本的,当服务器初始化完成并启动之后。当有一个客户端来请求服务器时,服务器就为其创建一个新的线程用于服务该客户端。这里,我们为客户端提供的是简单的计算器功能,服务端完成客户端给出的计算并给客户端返回结果。
class ServerTcp; // 声明一下static Response calculator(const Request &req)
{Response resp;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_ = -1; // -1,除0elseresp.result_ = req.x_ / req.y_;}break;case '%':{if (req.y_ == 0)resp.exitCode_ = -2; // -2,模0elseresp.result_ = req.x_ % req.y_;}break;default:resp.exitCode_ = -3; // -3,非法操作符break;}return resp;
}void netCalc(int sock, const std::string &clientIp, uint16_t clientPort)
{assert(sock >= 0);assert(!clientIp.empty());assert(clientPort >= 1024);// 9\r\n100 + 200\r\n 9\r\n100 + 200\r\nstd::string inbuffer;while (true){// 定义一个请求对象Request req;char buffer[128];ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s == 0){logMessage(NOTICE, "client[%s:%d] close sock,service done.", clientIp.c_str(), clientPort);break;}else if (s < 0){logMessage(WARINING, "read client[%s:%d] error,errorCode: %d,errorMessage: %s", clientIp.c_str(), clientPort, errno, strerror(errno));break;}// read successbuffer[s] = 0;inbuffer += buffer;// 1.检查inbuffer是否已经具有了一个strPackageuint32_t packgeLen = 0;std::string package = decode(inbuffer, &packgeLen);if (packgeLen == 0)continue; // 无法提取一个完整的报文,继续提取// 2.已经获取了一个完整的packageif (req.deserialize(package)){// 3.处理逻辑,输入的是一个req,得到一个respResponse resp = calculator(req); // resp是一个结构化的数据// 4.对resp进行序列化std::string respPackage;resp.serialize(&respPackage);// 5.对报文进行encoderespPackage = encode(respPackage, respPackage.size());// 6.简单进行发送write(sock, respPackage.c_str(), respPackage.size());}}
}class ThreadData
{
public:uint16_t clientPort_;std::string clientIp_;int sock_;ServerTcp *this_;ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts): clientPort_(port), clientIp_(ip), sock_(sock), this_(ts) {}
};class ServerTcp
{
public:ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1) {}~ServerTcp() {}public:void init(){// 1. 创建socketlistenSock_ = socket(PF_INET, SOCK_STREAM, 0);if (listenSock_ < 0){logMessage(FATAL, "socket:%s", strerror(errno));exit(SOCKET_ERR);}logMessage(DEBUG, "socket:%s,%d", strerror(errno), listenSock_);// 2. bind// 2.1 填充服务器信息struct sockaddr_in local; // 用户栈memset(&local, 0, sizeof local);local.sin_family = PF_INET;local.sin_port = htons(port_);ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));// 2.2 本地socket信息,写入sock_对应的内核区域if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind:%s", strerror(errno));exit(BIND_ERR);}logMessage(DEBUG, "bind:%s,%d", strerror(errno), listenSock_);// 3. 监听socket,为何要监听呢?tcp是面向连接的!if (listen(listenSock_, 5) < 0){logMessage(FATAL, "bind:%s", strerror(errno));exit(LISTEN_ERR);}logMessage(DEBUG, "listen:%s,%d", strerror(errno), listenSock_);}void loop(){while (true){struct sockaddr_in peer;socklen_t len = sizeof(peer);// 4. 获取连接,accept的返回值是一个新的socket fdint serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);if (serviceSock < 0){// 获取连接失败,继续获取logMessage(WARINING, "accept:%s[%d]", strerror(errno), serviceSock);continue;}// 4.1 获取客户端基本信息uint16_t peerPort = ntohs(peer.sin_port);std::string peerIp = inet_ntoa(peer.sin_addr);logMessage(DEBUG, "accept:%s | %s[%d], socket fd:%d", strerror(errno), peerIp.c_str(), peerPort, serviceSock);// 5. 提供服务,echho -> 小写 -> 大写// v2版本 - 多线程// 多线程不需要关闭文件描述符,因为多线程会共享文件描述符表!ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)td);}}static void *threadRoutine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);netCalc(td->sock_, td->clientIp_, td->clientPort_);delete td;}
private:int listenSock_;uint16_t port_;std::string ip_;
};
代码解释:
- 定义了一个
calculator
函数,用于根据请求对象进行计算并返回响应对象。根据请求的操作符,执行相应的计算,并将结果储存在响应对象中。 netCalc
函数用于吃力与客户端的网路通信。在循环中,读取客户端发送的数据,并解析出完整的请求报文。然后,调用calculator
函数进行计算,并将计算结果序列化为响应报文发送给客户端。- 其余的 TCP 服务器相关的代码在 TCP网络程序 中有详细的解释。
客户端代码
接下来实现一个简单的客户端程序,可以向服务端发送请求并接收响应。其主要作用是与服务器进行通信,实现请求和响应的交互。
客户端代码如下:
volatile bool quit = false;static void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " prot ip" << std::endl;std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8080\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t serverPort = atoi(argv[2]);std::string serverIp = argv[1];// 1. 创建socket SOCK_STREAMint sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket: " << strerror(errno) << std::endl;exit(SOCKET_ERR);}// 2. connect,向服务器发起连接请求// 2.1 先填充需要连接的远端主机的基本信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverPort);inet_aton(serverIp.c_str(), &server.sin_addr);// 2.2 发送请求,connect 会自动帮我们进行bindif (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0){std::cerr << "connect: " << strerror(errno) << std::endl;exit(CONN_ERR);}std::string message;while (!quit){message.clear();std::cout << "Place Enter# ";std::getline(std::cin, message);if (strcasecmp(message.c_str(), "quit") == 0){quit = true;continue;}// message=trimStr(message);Request req;if (!makeRequest(message, &req))continue;std::string package;req.serialize(&package);std::cout << "debug->serialize-> " << package << std::endl;package = encode(package, package.size());std::cout << "debug->encode-> \n" << package << std::endl;ssize_t s = write(sock, package.c_str(), package.size());if (s > 0){char buffer[1024];ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0)buffer[s] = 0;std::string echoPackage = buffer;Response resp;uint32_t len = 0;std::string tmp = decode(echoPackage, &len);if (len > 0){echoPackage = tmp;resp.deserialize(echoPackage);printf("[exitcode: %d] %d\n", resp.exitCode_, resp.result_);}}else if (s <= 0){break;}}return 0;
}
运行测试:
编写 makefile
构建程序,如下所示,需要定义 MY_SELF
时将 Method=-DMY_SELF
写上,不需要定义时就不需要写。
.PHONY:all
all:clientTcp serverTcpd
Method=-DMY_SELFclientTcp:clientTcp.ccg++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp
serverTcpd:serverTcp.ccg++ -o $@ $^ $(Method) -std=c++11 -lpthread -ljsoncpp.PHONY:clean
clean:rm -f serverTcpd clientTcp
使用我们写的序列化与反序列化代码进行测试。
相关文章:

认识“协议”
文章目录: 什么是协议结构化的数据传输序列化和反序列化网络版本计算器 什么是协议 在计算机网络中,协议是指在网络中进行通信和数据交换时,双方遵循的规则和约定集合。它定义了数据的传输格式、顺序、错误处理、认证和安全性等方面的规范。 …...

GO语言的由来与发展历程
Go语言,也称为Golang,是由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明,并于2009年正式对外发布的开源编程语言。 三名初始人的目标是设计一种适应网络和多核时代的C语言,Go语言从C继承了…...

MPN – 制造零件号
S/4 1610 中的 MPN – 基于 NAST 的输出管理 我试图查找有关 MPN 设置的信息,但找不到详细的配置步骤。在浏览了一些信息和 help.sap 链接后,我能够在 S/4 1610 系统中配置 MPN 设置,这与使用旧输出类型(Nast 和输出类型 NEU&…...
Redis企业级问题及解决方案
1.1 缓存预热 场景:“宕机” 服务器启动后迅速宕机 问题排查: 1.请求数量较高,大量的请求过来之后都需要去从缓存中获取数据,但是缓存中又没有,此时从数据库中查找数据然后将数据再存入缓存,造成了短期…...

【2021集创赛】基于arm Cortex-M3处理器与深度学习加速器的实时人脸口罩检测 SoC
团队介绍 参赛单位:深圳大学 队伍名称:光之巨人队 指导老师:钟世达、袁涛 参赛队员:冯昊港、潘家豪、慕镐泽 图1 团队风采 1. 项目简介 新冠疫情席卷全球,有效佩戴口罩可以极大程度地减小病毒感染的风险。本项目开发…...
B码的相关知识点笔记
B码(B-Code)通常是指中国北斗卫星导航系统的坐标编码方式。北斗卫星导航系统使用的坐标系是WGS-84,而B码是针对WGS-84坐标系进行编码的一种方式。 B码的格式通常为18位或24位,其中包含以下信息: 前两位为国家码&…...

java“贪吃蛇”小游戏
基于java实现贪吃蛇小游戏,主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。 我是在javaSwing项目下创建了一个包 名字叫做:Snakes包 包下有一个启动类和一个设置代码的主界面两个类 代码主界面: 代码主界面主要讲解的是 …...

【面试经典150 | 位运算】数字范围按位与
文章目录 Tag题目来源题目解读解题思路方法一:公共前缀方法二:n & (n-1) 写在最后 Tag 【位运算】 题目来源 201. 数字范围按位与 题目解读 计算给定区间内所有整数的按位与的结果。 解题思路 本题朴素的方法是直接将区间内的所有整数按位与&…...

推介会如何做好媒体宣传
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 推介会是一种专为企业、社会组织和团体、政府等提供的展示自身特点、产品和政策的活动形式,旨在促进交流活动,形成合作,从而带来共同利益。推介会的本…...

【ROS导航Navigation】五 | 导航相关的消息 | 地图 | 里程计 | 坐标变换 | 定位 | 目标点和路径规划 | 激光雷达 | 相机
致谢:ROS赵虚左老师 Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 参考赵虚左老师的实战教程 一、地图 nav_msgs/MapMetaData 地图元数据,包括地图的宽度、高度、分辨率等。 nav_msgs/OccupancyGrid 地图栅格数据&#…...

什么是脏读、不可重复读、幻读讲解
数据库隔离级别是数据库管理系统中一个重要的概念,它定义了事务之间的可见性和影响。在多用户并发访问数据库时,隔离级别能够确保事务之间的相互独立性,避免数据不一致的问题。本文将深入探讨三种常见的并发问题:脏读、不可重复读…...

2018年五一杯数学建模C题江苏省本科教育质量综合评价解题全过程文档及程序
2019年五一杯数学建模 C题 江苏省本科教育质量综合评价 原题再现 随着中国的改革开放,国家的综合实力不断增强,中国高等教育发展整体已进入世界中上水平。作为一个教育大省,江苏省的本科教育发展在全国名列前茅,而江苏省13个地级…...

第四代智能井盖传感器:万宾科技助力城市安全
在繁华喧嚣的城市里人来人往,井盖作为基础设施的一个组成部分在路面上分布范围广。然而这些看似普通的井盖却存在着位移、水浸的风险,可能给我们的生活带来诸多不便,更会威胁到我们的人身安全。如何有效监测和管理井盖的状态,成为…...

[Jenkins] Docker 安装Jenkins及迁移流程
系统要求 最低推荐配置: 256MB可用内存1GB可用磁盘空间(作为一个Docker容器运行jenkins的话推荐10GB) 为小团队推荐的硬件配置: 1GB可用内存50 GB 可用磁盘空间 软件配置: Java 8—无论是Java运行时环境(JRE)还是Java开发工具包(JDKÿ…...

第七篇 基于JSP 技术的网上购书系统——新品上架、推荐产品、在线留言、搜索功能实现(网上商城、仿淘宝、当当、亚马逊)
目录 1.新品上架 1.1功能说明 1.2界面设计 1.3处理流程 1.4数据来源和算法 1.4.1数据来源 1.4.2查询条件 1.4.3表间关系 1.4.4相关sql实例 2.推荐产品 2.1功能说明 2.2界面设计 2.3处理流程 2.4数据来源和算法 2.4.1数据来源 2.4.2查询条件 2.4.3表间关…...

IntelliJ IDE 插件开发 |(一)快速入门
前言 IntelliJ IDEA 作为 Java 开发的首选 IDE,其强大、方便之处不必多说。不过,由于个人或者团队的个性化需求,我们或多或少会想对其功能进行拓展,这时就需要开发插件(在 IntelliJ 平台下的所有 IDE 均可运行&#x…...

【Ubuntu】Windows远程Ubuntu系统
步骤 开启ssh服务并开放22端口关闭防火墙ufw或iptables ;或者将远程端口添加到入站与出站规则安装xrdp并将xrdp用户添加到ssl-cert用户组mstsc 远程,输入账号密码 1、开启ssh服务 1.1. 查看ssh是否已经开启 sudo ps -e | grep ssh如果最后返回是sshd…...

pipeline jenkins流水线
Pipeline 是 Jenkins 中一种灵活且强大的工作流机制,它允许您以代码的形式来定义和管理持续集成和持续交付的流程。 Pipeline 的作用主要体现在以下几个方面: 可编排的构建流程:使用 Pipeline,您可以将一个或多个阶段(…...

软件工程理论与实践 (吕云翔) 第六章 面向对象分析课后习题及其解析
第六章 面向对象分析 知识点: 一个典型的软件系统通常包括的内容为:它使用数据结构(对象模型),执行操作(动态模型),并且完成数据值的变化(功能模型)。 3种模型之间的关…...

langchain(1):使用LangChain 调用 openai 的 text/chat model
文章目录 重要参考OPENAI API调用 Text 模型调用 Chat 模型消息角色 Chat 模型 vs Text 模型 通过 LangChain 调用 Text 和 Chat 模型调用 text 模型调用 chat 模型 重要参考 langchain 中文网 langchain api openai api 文档 huggingface LangChain 是一个全方位的、基于大…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...

工厂方法模式和抽象工厂方法模式的battle
1.案例直接上手 在这个案例里面,我们会实现这个普通的工厂方法,并且对比这个普通工厂方法和我们直接创建对象的差别在哪里,为什么需要一个工厂: 下面的这个是我们的这个案例里面涉及到的接口和对应的实现类: 两个发…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...