【Linux网络】应用层自定义协议与序列化及Socket模拟封装
📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨


文章目录
- 🏳️🌈一、应用层
- 1.1 再谈 "协议"
- 1.2 网络版计算器
- 1.3 序列化和反序列化
- 🏳️🌈二、什么是全双工
- 🏳️🌈三、Socket封装
- 3.1 整体结构
- 3.2 Socket 基类
- 3.3 TcpSocket 子类
- 3.3.1 基本结构
- 3.3.2 构造、析构函数
- 3.3.3 创建套接字
- 3.3.4 绑定套接字
- 3.3.5 监听套接字
- 3.3.6 获取连接
- 3.3.7 建立连接
- 3.3.8 其他方法
- 3.3.10 整体代码
- 👥总结
11111111
11111111
11111111
11111111
**** 11111111
🏳️🌈一、应用层
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.
1.1 再谈 “协议”
协议是一种 “约定”. socket api 的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些 “结构化的数据” 怎么办呢?
其实,协议就是双方约定好的结构化的数据!
1.2 网络版计算器
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
约定方案一(传结构体对象):
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
- …
不推荐直接传结构体对象,从技术和业务角度解释?
1、技术角度
-
1、跨平台与兼容性:
- 结构体的大小和内存布局可能因编译器、操作系统或硬件平台的不同而有所差异。这可能导致在一个平台上发送的结构体在另一个平台上无法正确解析。
-
2、内存对齐与填充:
- 为了优化内存访问速度,编译器可能会对结构体成员进行对齐和填充。这会导致结构体的实际大小大于其成员大小的总和。
- 直接传输结构体可能会因为内存对齐和填充的问题而导致数据解析错误。
-
3、指针与动态内存:
- 结构体中可能包含指针,这些指针指向动态分配的内存。直接传输结构体无法传递指针所指向的数据,而只能传递指针值,这可能导致数据丢失或内存泄漏。
2、业务角度
- 1、数据安全性:
- 直接传输结构体可能会暴露数据的内部结构和实现细节,从而增加数据被恶意攻击的风险。
- 2、数据版本控制:
- 随着业务的发展和变化,数据结构和格式可能需要进行调整和升级。
约定方案二(传字符串):
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 这个过程叫做 “
序列化” 和 “反序列化”
1.3 序列化和反序列化

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据,在另一端能够正确的进行解析, 就是 ok 的. 这种约定, 就是 应用层协议
但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。
- 我们采用方案 2,我们也要体现协议定制的细节
- 我们要引入序列化和反序列化,只不过我们课堂直接采用现成的方案 – jsoncpp库
- 我们要对
socket进行字节流的读取处理
🏳️🌈二、什么是全双工

所以:
- 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
- 这就是为什么一个 tcp sockfd 读写都是它的原因
- 实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以 TCP 叫做传输控制协议
1、read,write,send,recv本质是拷贝函数!
2、发送数据的本质:是从发送方的发送缓冲区把数据通过协议栈和网络拷贝给接收方大的接收缓冲区!
3、tcp支持全双工通信的原因(有发送和接收缓冲区)!
4、有两个缓冲区这种模式就是生产者消费者模型!
5、为什么IO函数要阻塞?本质是在维护同步关系!
TCP协议是面向字节流的,客户端发的,不一定是全部是服务端收的,怎么保证读到的是一个完整的请求呢?
- 分割完整的报文!
🏳️🌈三、Socket封装
Socket类以模板方法类的设计模式进行封装,将算法的不变部分封装在抽象基类中 ,而将可变部分延迟到子类中实现!
3.1 整体结构
-
抽象类(Abstract Class):
- 定义了多个抽象操作,这些操作在抽象类中不具体实现,由子类实现。
- 定义了两个模板方法,这个方法通常调用了上面提到的抽象操作。模板方法的算法骨架是固定的,但其中一些步骤的具体实现会延迟到子类中。
-
具体子类(Concrete Class):
- 实现抽象类中的抽象操作,提供具体的算法步骤实现。
- 可以重写父类中的模板方法,但通常情况下不需要这么做,因为模板方法已经在抽象类中定义好了算法的骨架。
3.2 Socket 基类
将套接字创建、绑定、监听等 通用流程 抽象为模板方法,如 BuildListenSocket,而将具体步骤(如 CreaterSocketOrDie)延迟到子类实现。
角色划分:
- 抽象类(Abstract Class):Socket 定义了纯虚函数(步骤方法)和模板方法(流程框架)。
- 具体子类(Concrete Class):由用户继承 Socket 并实现纯虚函数(例如 TcpSocket、UdpSocket)。
using SockPtr = std::shared_ptr<Socket>;class Socket {public:virtual void CreateSocketOrDie() = 0; // 创建套接字virtual void BindOrDie(uint16_t port) = 0; // 绑定套接字virtual void ListenOrDie(int backlog = gbacklog) = 0; // 监听套接字virtual SockPtr Accepter(InetAddr* cli) = 0; // 获取链接virtual bool Connector(const std::string& peerip,uint16_t peerport) = 0; // 简历连接virtual int Sockfd() = 0;virtual void Close() = 0;virtual ssize_t Recv(std::string* out) = 0; // 接收数据virtual ssize_t Send(const std::string& in) = 0; // 发送数据public:// 创建监听套接字void BuildListenSocket(uint16_t port) {CreateSocketOrDie(); // 创建BindOrDie(port); // 绑定ListenOrDie(); // 监听}// 创建客户端套接字void BuildConnectorSocket(const std::string& peerip, uint16_t peerport) {CreateSocketOrDie(); // 创建Connector(peerip, peerport); // 连接}
};
3.3 TcpSocket 子类
TcpSocket类继承Socket类,并具体实现父类的抽象操作!
3.3.1 基本结构
TcpSocket 子类就是具体实现父类的抽象操作,所以所有 TCP 可能会用到的父类方法都要具体实现
class TcpSocket : public Socket {
public:TcpSocket() {}TcpSocket(int sockfd) {}// 创建套接字void CreateSocketOrDie() {}// 绑定套接字void BindOrDie(uint16_t port) {}// 监听套接字void ListenOrDie(int backlog = gbacklog) {}// 获取链接SockPtr Accepter(InetAddr* cli) {}// 建立连接bool Connector(const std::string& peerip, uint16_t peerport) {}// 获取套接字描述符int Sockfd() {}// 关闭套接字void Close() {}~TcpSocket() {}private:int _sockfd;
};
3.3.2 构造、析构函数
- 构造函数 可以实现两个,一个无参构造,一个有参构造(传参sockfd),用于初始化成员变量
- 析构函数 可以不做处理,后面关闭套接字自己调用关闭函数即可!
TcpSocket() {}
TcpSocket(int sockfd) : _sockfd(sockfd) {}
~TcpSocket() {}
3.3.3 创建套接字
使用库函数 socket 按照格式创建套接字
// 创建套接字
void CreateSocketOrDie() override {_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0) {LOG(LogLevel::ERROR) << "create socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;
}
3.3.4 绑定套接字
这部分用于服务端,所以需要先构建服务端的网络字节序地址,然后绑定套接字和网络字节序地址
// 绑定套接字void BindOrDie(uint16_t port) override{// sockaddr_in 的头文件是 #include <netinet/in.h>struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = htonl(INADDR_ANY);int n = ::bind(_sockfd, CONV(&local), sizeof(local));if(n < 0){LOG(LogLevel::ERROR) << "bind socket error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";}
3.3.5 监听套接字
在绑定好网络字节序地址后,我们需要形成老板模式,设置最大连接数量,然后不断监听
// 监听套接字
void ListenOrDie(int backlog = gbacklog) override {int n = ::listen(_sockfd, backlog);if (n < 0) {LOG(LogLevel::ERROR) << "listen socket error";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";
}
3.3.6 获取连接
在为监听套接字设置好最大连接长度后,我们不断使用 accept 监听这个套接字,将获取到的客户端ip和端口号等信息 与 我们的服务端的连接 整合起来,形成一个整体套接字,实现面向对象连接
// 获取链接
SockPtr Accepter(InetAddr* cli) override {struct sockaddr_in client;socklen_t clientlen = sizeof(client);// accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)// 返回一个新的套接字,该套接字与调用进程间接地建立了连接。int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);if (sockfd < 0) {LOG(LogLevel::ERROR) << "accept socket error";return nullptr;}*cli = InetAddr(client);LOG(LogLevel::DEBUG) << "get a new connection from "<< cli->AddrStr().c_str() << ", sockfd : " << sockfd;return std::make_shared<TcpSocket>(sockfd);
}
3.3.7 建立连接
当客户端想要连接上服务端的时候,我们需要先为服务端创建一个网络字节序地址,再与客户端的套接字连接起来
// 建立连接
bool Connector(const std::string& serverip, uint16_t serverport) override {struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET; // IPv4协议server.sin_port = htons(serverport); // 端口号// 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址// inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址// 这里的AF_INET表示IPv4协议// 这里的serverip.c_str()表示IP地址的字符串形式// &server.sin_addr表示将IP地址存储到sin_addr成员变量中::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // IP地址int n = ::connect(_sockfd, CONV(&server), sizeof(server));if (n < 0) {LOG(LogLevel::ERROR) << "connect socket error";return false;}LOG(LogLevel::DEBUG) << "connect success";return true;
}
3.3.8 其他方法
还有一些其他的方法,难度不大,就不一一介绍了
// 获取套接字描述符
int Sockfd() override { return _sockfd; }// 关闭套接字
void Close() override {if (_sockfd >= 0)::close(_sockfd);
}// 接收数据
ssize_t Recv(std::string* out) override {char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (n > 0) {inbuffer[n] = 0;*out = inbuffer;}return n;
}// 发送数据
ssize_t Send(const std::string& in) override {return ::send(_sockfd, in.c_str(), in.size(), 0);
}
3.3.10 整体代码
#pragma once #include <iostream>
#include <cstring>
#include <Socket.h>
#include <memory>#include <netinet/in.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const int gbacklog = 8;// common.hpp
// #define Die(code) \
// do \
// { \
// exit(code); \
// } while (0)// #define CONV(v) (struct sockaddr *)(v)// enum
// {
// USAGE_ERR = 1,
// SOCKET_ERR,
// BIND_ERR,
// LISTEN_ERR,
// CONNECTION_ERR
// };namespace SocketModule{using SockPtr = std::shared_ptr<Socket>;class Socket{public:virtual void CreateSocketOrDie() = 0; // 创建套接字virtual void BindOrDie(uint16_t port) = 0; // 绑定套接字virtual void ListenOrDie(int backlog = gbacklog) = 0; // 监听套接字virtual SockPtr Accepter(InetAddr* cli) = 0; // 获取链接virtual bool Connector(const std::string& serverip, uint16_t serverport) = 0; // 简历连接virtual int Sockfd() = 0;virtual void Close() = 0;virtual ssize_t Recv(std::string* out) = 0; // 接收数据virtual ssize_t Send(const std::string& in) = 0; // 发送数据 public:// 创建监听套接字void BuildListenSocket(uint16_t port){CreateSocketOrDie(); // 创建BindOrDie(port); // 绑定ListenOrDie(); // 监听}// 创建客户端套接字void BuildConnectorSocket(const std::string& serverip, uint16_t serverport){CreateSocketOrDie(); // 创建Connector(serverip, serverport); // 连接}};class TcpSocket : public Socket{public:TcpSocket(){}TcpSocket(int sockfd) : _sockfd(sockfd){ }// 创建套接字void CreateSocketOrDie() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if(_sockfd < 0){LOG(LogLevel::ERROR) << "create socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;}// 绑定套接字void BindOrDie(uint16_t port) override{// sockaddr_in 的头文件是 #include <netinet/in.h>struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = htonl(INADDR_ANY);int n = ::bind(_sockfd, CONV(&local), sizeof(local));if(n < 0){LOG(LogLevel::ERROR) << "bind socket error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";} // 监听套接字void ListenOrDie(int backlog = gbacklog) override{int n = ::listen(_sockfd, backlog);if(n < 0){LOG(LogLevel::ERROR) << "listen socket error";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";}// 获取链接SockPtr Accepter(InetAddr* cli) override{struct sockaddr_in client;socklen_t clientlen = sizeof(client);// accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)// 返回一个新的套接字,该套接字与调用进程间接地建立了连接。int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);if(sockfd < 0){LOG(LogLevel::ERROR) << "accept socket error";return nullptr;}*cli = InetAddr(client);LOG(LogLevel::DEBUG) << "get a new connection from " << cli->AddrStr().c_str() << ", sockfd : " << sockfd;return std::make_shared<TcpSocket>(sockfd);}// 建立连接bool Connector(const std::string& serverip, uint16_t serverport) override{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET; // IPv4协议server.sin_port = htons(serverport); // 端口号// 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址// inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址// 这里的AF_INET表示IPv4协议// 这里的serverip.c_str()表示IP地址的字符串形式// &server.sin_addr表示将IP地址存储到sin_addr成员变量中::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // IP地址int n = ::connect(_sockfd, CONV(&server), sizeof(server));if(n < 0){LOG(LogLevel::ERROR) << "connect socket error" ;return false;}LOG(LogLevel::DEBUG) << "connect success";return true;}// 获取套接字描述符int Sockfd() override{ return _sockfd; }// 关闭套接字void Close() override{ if(_sockfd >= 0) ::close(_sockfd); }// 接收数据ssize_t Recv(std::string* out) override{char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if(n > 0){inbuffer[n] = 0;*out = inbuffer;}return n;} // 发送数据 ssize_t Send(const std::string& in) override{return ::send(_sockfd, in.c_str(), in.size(), 0);}~TcpSocket(){}private:int _sockfd;};
}
👥总结
本篇博文对 【Linux网络】应用层自定义协议与序列化及Socket模拟封装 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~
相关文章:
【Linux网络】应用层自定义协议与序列化及Socket模拟封装
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
客户案例:西范优选通过日事清实现流程与项目管理的优化
近几年来,新零售行业返璞归真,从线上销售重返线下发展,满足消费者更加多元化的需求,国内家居集合店如井喷式崛起。为在激烈的市场竞争中立于不败之地,西范优选专注于加强管理能力、优化协作效率的“内功修炼”…...
LabVIEW实现Voronoi图绘制功能
该 LabVIEW 虚拟仪器(VI)借助 MathScript 节点,实现基于手机信号塔位置计算 Voronoi 图的功能。通过操作演示,能直观展示 Voronoi 图在空间划分上的应用。 各部分功能详细说明 随机地形创建部分 功能:根据 “Maximum a…...
【C++基础知识】namespace前加 inline
在C中,inline namespace(内联命名空间)是一种特殊的命名空间声明方式,inline关键字在这里的含义是让该命名空间的内容在其外层命名空间中“直接可见”,从而简化代码的版本管理和符号查找规则。以下是详细解释ÿ…...
离线部署kubernetes
麒麟Linux服务器 AMR架构 🧰 离线部署 Kubernetes v1.25.9(麒麟系统 Docker) 一、验证Docker部署状态 检查Docker服务运行状态 systemctl status docker 预期输出应显示 Active: active (running),表明服务已启动18。 …...
【AI提示词】私人教练
提示说明 以专业且细致的方式帮助客户实现健康与健身目标,提升整体生活质量。 提示词 # Role: 私人教练## Profile - language: 中文 - description: 以专业且细致的方式帮助客户实现健康与健身目标,提升整体生活质量 - background: 具备丰富的健身经…...
爬虫学习——获取动态网页信息
对于静态网页可以直接研究html网页代码实现内容获取,对于动态网页绝大多数都是页面内容是通过JavaScript脚本动态生成(也就是json数据格式),而不是静态的,故需要使用一些新方法对其进行内容获取。凡是通过静态方法获取不到的内容,…...
第54讲:总结与前沿展望——农业智能化的未来趋势与研究方向
目录 一、本板块内容回顾:人工智能助力农业的多元化应用 ✅ 精准农业与AI ✅ 农业金融与AI ✅ AI与农业政策 ✅ 农业物联网与AI 二、前沿趋势与研究方向:迈向智能、可持续农业的未来 1. AIGC(生成式AI)在农业中的应用 2. 数字孪生农业:虚拟与现实的无缝对接 3. A…...
创新项目实训开发日志4
一、开发简介 核心工作内容:logo实现、注册实现、登录实现、上传gitee 工作时间:第十周 二、logo实现 1.设计logo 2.添加logo const logoUrl new URL(/assets/images/logo.png, import.meta.url).href <div class"aside-first">…...
常见接口测试常见面试题(JMeter)
JMeter 是 Apache 提供的开源性能测试工具,主要用于对 Web 应用、REST API、数据库、FTP 等进行性能、负载和功能测试。它支持多种协议,如 HTTP、HTTPS、JDBC、SOAP、FTP 等。 在一个线程组中,JMeter 的执行顺序通常为:配置元件…...
发布事件和Insert数据库先后顺序
代码解释 csharp await PublishCreatedAsync(entity).ConfigureAwait(false); await Repository.InsertAsync(entity).ConfigureAwait(false);PublishCreatedAsync(entity):这是一个异步方法,其功能是发布与实体创建相关的事件。此方法或许会通知其他组…...
函数重载(Function Overloading)
1. 函数重载的核心概念 函数重载允许在 同一作用域内定义多个同名函数,但它们的 参数列表(参数类型、顺序或数量)必须不同。编译器在编译时根据 调用时的实参类型和数量 静态选择最匹配的函数版本。 2. 源码示例:基础函数重载 示…...
CGAL 网格等高线计算
文章目录 一、简介二、实现代码三、实现效果一、简介 这里等高线的计算其实很简单,使用不同高度的水平面与网格进行相交,最后获取不同高度的相交线即可。 二、实现代码 #include <iostream> #include <iterator> #include <map>...
计算机组成与体系结构:缓存(Cache)
目录 为什么需要 Cache? 🧱 Cache 的分层设计 🔹 Level 1 Cache(L1 Cache)一级缓存 🔹 Level 2 Cache(L2 Cache)二级缓存 🔹 Level 3 Cache(L3 Cache&am…...
Flutter 在全新 Platform 和 UI 线程合并后,出现了什么大坑和变化?
Flutter 在全新 Platform 和 UI 线程合并后,出现了什么大坑和变化? 在两个月前,我们就聊过 3.29 上《Platform 和 UI 线程合并》的具体原因和实现方式,而事实上 Platform 和 UI 线程合并,确实为后续原生语言和 Dart 的…...
开发 MCP Proxy(代理)也可以用 Solon AI MCP 哟!
MCP 有三种通讯方式: 通道说明备注stdio本地进程内通讯现有sse http远程 http 通讯现有streamable http远程 http 通讯(MCP 官方刚通过决定,mcp-java-sdk 还没实现) 也可以按两大类分: 本地进程间通讯远程通讯&…...
JetBrains GoLang IDE无限重置试用期,适用最新2025版
注意本文仅用于学习使用!!! 本文在重置2024.3.5版本亲测有效,环境为window(mac下应该也一样奏效) 之前eval-reset插件只能在比较低的版本才能起作用。 总结起来就一句:卸载重装,额外要删掉旧安装文件和注册…...
python中socket(套接字)库详细解析
目录 1. 前言 2. socket 库基础 2.1 什么是 socket? 2.2 socket 的类型 3. 基于 TCP 的 socket 编程 3.1 TCP 服务器端代码示例 3.2 TCP 客户端代码示例 3.3 代码分析 4. 基于 UDP 的 socket 编程 4.1 UDP 服务器端代码示例 4.2 UDP 客户端代码示例 4.3…...
鸿蒙-状态管理V1和V2在ForEach循环渲染的表现
目录 前提遇到的问题换V2呗 状态管理V2已经出来好长时间了,移除GAP说明也有一段时间了,相信有一部分朋友已经开始着手从V1迁移到V2了,应该也踩了不少坑。 下面向大家分享一下我使用状态管理V1和Foreach时遇到的坑,以及状态管理V2在…...
深入了解递归、堆与栈:C#中的内存管理与函数调用
在编程中,理解如何有效地管理内存以及如何控制程序的执行流程是每个开发者必须掌握的基本概念。C#作为一种高级编程语言,其内存管理和函数调用机制包括递归、堆与栈。本文将详细讲解这三者的工作原理、用途以及它们在C#中的实现和应用。 1. 递归 (Recur…...
图论---Prim堆优化(稀疏图)
题目通常会提示数据范围: 若 V ≤ 500,两种方法均可(朴素Prim更稳)。 若 V ≤ 1e5,必须用优先队列Prim vector 存图。 #include <iostream> #include <vector> #include <queue> #include <…...
stm32之GPIO函数详解和上机实验
目录 1.LED和蜂鸣器1.1 LED1.2 蜂鸣器 2.实验2.1 库函数:RCC和GPIO2.1.1 RCC函数1. RCC_AHBPeriphClockCmd2. RCC_APB2PeriphClockCmd3. RCC_APB1PeriphClockCmd 2.1.2 GPIO函数1. GPIO_DeInit2. GPIO_AFIODeInit3. GPIO_Init4. GPIO_StructInit5. GPIO_ReadInputDa…...
用 PyQt5 和 asyncio 打造接口并发测试 GUI 工具
接口并发测试是测试工程师日常工作中的重要一环,而一个直观的 GUI 工具能有效提升工作效率和体验。本篇文章将带你用 PyQt5 和 asyncio 从零实现一个美观且功能实用的接口并发测试工具。 我们将实现以下功能: 请求方法选择器 添加了一个下拉框 QComboBo…...
OpenHarmony Camera开发指导(四):相机会话管理(ArkTS)
概述 相机在使用预览、拍照、录像、获取元数据等功能前,都需要先创建相机会话。 相机会话Session的功能如下: 配置相机的输入流和输出流。 配置输入流即添加设备输入,通俗来讲即选择某一个摄像头进行拍照录像;配置输出流&#x…...
深入探索RAG(检索增强生成)模型的优化技巧
📌 友情提示: 本文内容由银河易创AI(https://ai.eaigx.com)创作平台的gpt-4o-mini模型生成,旨在提供技术参考与灵感启发。文中观点或代码示例需结合实际情况验证,建议读者通过官方文档或实践进一步确认其准…...
Spring boot 中的IOC容器对Bean的管理
Spring Boot 中 IOC 容器对 Bean 的管理,涵盖从容器启动到 Bean 的生命周期管理的全流程。 步骤 1:理解 Spring Boot 的容器启动 Spring Boot 的 IOC 容器基于 ApplicationContext,在应用启动时自动初始化。 入口类:通过 SpringB…...
Qt实战之将自定义插件(minGW)显示到Qt Creator列表的方法
Qt以其强大的跨平台特性和丰富的功能,成为众多开发者构建图形用户界面(GUI)应用程序的首选框架。而在Qt开发的过程中,自定义插件能够极大地拓展应用程序的功能边界,让开发者实现各种独特的、个性化的交互效果。想象一下…...
【Vue】TypeScript与Vue3集成
个人主页:Guiat 归属专栏:Vue 文章目录 1. 前言2. 环境准备与基础搭建2.1. 安装 Node.js 与 npm/yarn/pnpm2.2. 创建 Vue3 TypeScript 项目2.2.1. 使用 Vue CLI2.2.2. 使用 Vite(推荐)2.2.3. 目录结构简述 3. Vue3 TS 基础语法整…...
Linux之七大难命令(The Seven Difficult Commands of Linux)
Linux之七大难命令 、背景 作为Linux的初学者,肯定要先掌握高频使用的指令,这样才能让Linux的学习在短时间内事半功倍。但是,有些指令虽然功能强大,但因参数多而让初学者们很害怕,今天介绍Linux中高频使用࿰…...
Spring Boot单元测试实战指南:从零到高效测试
在Spring Boot开发中,单元测试是保障代码质量的核心环节。本文将基于实际开发场景,手把手教你如何快速实现分层测试、模拟依赖、编写高效断言,并分享最佳实践! 一、5分钟环境搭建 添加依赖 在pom.xml中引入spring-boot-starter-te…...
