根据源码,模拟实现 RabbitMQ - 网络通讯设计,自定义应用层协议,实现 BrokerServer (8)
目录
一、网络通讯协议设计
1.1、交互模型
1.2、自定义应用层协议
1.2.1、请求和响应格式约定
编辑
1.2.2、参数说明
1.2.3、具体例子
1.2.4、特殊栗子
1.3、实现 BrokerServer
1.3.1、属性和构造
1.3.2、启动 BrokerServer
1.3.3、停止 BrokerServer
1.3.4、处理每一个客户端连接
1.3.5、读取请求和写响应
1.3.6、根据请求计算响应
1.3.7、清除 channel
一、网络通讯协议设计
1.1、交互模型
目前我们需要考虑的交互模型:生产者消费者都是客户端,都需要通过 网络 和 BrokerServer 进行通信
此处我们使⽤ TCP 协议, 来作为通信的底层协议. 同时在这个基础上⾃定义应⽤层协议, 完成客⼾端对服 务器这边功能的远程调⽤.
TCP 是有连接的(Connection),创建 / 断开 TCP 连接成本还是挺高的(需要三次握手啥的),那么这里就是用 Channel 来表示 Connection 内部的 “逻辑上” 的连接,使得 “一个管道,多个网线传输” 的效果,使得 TCP连接得到复用
Ps:要远程调用的功能就是在 VirtualHost 中 public 的方法.
1.2、自定义应用层协议
1.2.1、请求和响应格式约定
之前我们定义的 Message 对象,本体就是二进制的数据,因此这里不方便使用 JSON 这种文本协议 / 格式.
因此这里使用 二进制 的方式来设定协议.
请求如下:
/*** 表示一个网络通信中的请求对象,按照自定义协议的格式展开*/
public class Request {private int type;private int length;private byte[] payload;public int getType() {return type;}public void setType(int type) {this.type = type;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}public byte[] getPayload() {return payload;}public void setPayload(byte[] payload) {this.payload = payload;}
}
响应如下:
/*** 这个对象表示一个响应,是根据自定义应用层协议来的*/
public class Response {private int type;private int length;private byte[] payload;public int getType() {return type;}public void setType(int type) {this.type = type;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}public byte[] getPayload() {return payload;}public void setPayload(byte[] payload) {this.payload = payload;}
}
1.2.2、参数说明
1)type是一个整形,用来表示当前这个请求和响应是用来干啥的(对应 VirtualHost 中的核心 API),取值如下:
- 0x1 创建 channel
- 0x2 关闭 channel
- 0x3 创建 exchange
- 0x4 销毁 exchange
- 0x5 创建 queue
- 0x6 销毁 queue
- 0x7 创建 binding
- 0x8 销毁 binding
- 0x9 发送 message
- 0xa 订阅 message
- 0xb 返回 ack
- 0xc 服务器给客⼾端推送的消息.(被订阅的消息) 响应独有的.
2)length 就是用来描述 payload 长度(防止粘包问题)
3)payload 就是具体要传输的二进制数据。数据具体是什么,会根据当前是请求还是响应,以及当前的 type 的不同取值来确定。
比如 type 是 0x3(创建交换机),同时当前是一个请求,此时 payload 里的内容,就相当于 exchangeDeclare 的 参数 的序列化的结果.
比如 type 是 0x3(创建交换机),同时当前是一个响应,此时 payload 里的内容,就是 exchangDeclare 的 返回结果 的序列化内容.
1.2.3、具体例子
栗子如下:
1)请求
当前需要远程调用 exchangeDeclare 方法,那么我们就需要传递核心 API 以下参数
使用一个公共的父类包装每次 请求 中公共(每个请求都要传输)的参数
/*** 这个类用来表示方法的公共参数/辅助字段* 后续每个方法会有一些不同的参数,不同的参数再用不同的子类来表示*/
public class BasicArguments implements Serializable {// 表示一次 请求/响应 的身份标识,让请求和响应能对的上protected String rid;// 表示这次通信使用的 channel 的身份标识protected String channelId;public String getRid() {return rid;}public void setRid(String rid) {this.rid = rid;}public String getChannelId() {return channelId;}public void setChannelId(String channelId) {this.channelId = channelId;}}
创建 ExchangeDeclareArguments 类(当前这个类将来会被序列化成 request 类中的 payload),继承 BasicArguments(公共参数),实现 Serializable 接口(避免序列化问题),要传递的参数如下:
public class ExchangeDeclareArguments extends BasicArguments implements Serializable {private String exchangeName;private ExchangeType exchangeType;private boolean durable;private boolean autoDelete;private Map<String, Object> arguments;public String getExchangeName() {return exchangeName;}public void setExchangeName(String exchangeName) {this.exchangeName = exchangeName;}public ExchangeType getExchangeType() {return exchangeType;}public void setExchangeType(ExchangeType exchangeType) {this.exchangeType = exchangeType;}public boolean isDurable() {return durable;}public void setDurable(boolean durable) {this.durable = durable;}public boolean isAutoDelete() {return autoDelete;}public void setAutoDelete(boolean autoDelete) {this.autoDelete = autoDelete;}public Map<String, Object> getArguments() {return arguments;}public void setArguments(Map<String, Object> arguments) {this.arguments = arguments;}
}
2)响应
当前 VirtualHost 中的核心 API 返回值都是 Boolean 类型,因此我们使用一个公共类来封装响应(当前这个类将来会被序列化成 response 类中的 payload 参数)
public class BasicReturns implements Serializable {//用来标识唯一的请求和响应protected String rid;//标识一个 channelprotected String channelId;//标识当前这个远程调用方法的返回值protected boolean ok;public String getRid() {return rid;}public void setRid(String rid) {this.rid = rid;}public String getChannelId() {return channelId;}public void setChannelId(String channelId) {this.channelId = channelId;}public boolean isOk() {return ok;}public void setOk(boolean ok) {this.ok = ok;}
}
Ps:其他核心 API 自定义应用层协议也一样
1.2.4、特殊栗子
0xa 订阅 message ,这个核心 API 比较特殊,参数中有回调函数
1)请求
创建 BasicConsumeArguments 类(当前这个类将来会被序列化成 request 类中的 payload) 表示要传递的参数,需要注意的是 Consumer 这个回调,在发送的请求中不需要携带这个参数(实际上也携带不了)
Ps:因为服务器收到这个订阅消息请求之后,就直接取拿队列中的消息,接着直接反馈给客户端,客户端拿到消息后才执行回调方法(要拿这个消息干什么事)。
这就类似于你去商店订阅报纸,接着拿到报纸以后,你要对这个报纸做什么,商店是不知道的~~
public class BasicConsumeArguments extends BasicArguments implements Serializable {private String consumerTag;private String queueName;private boolean autoAck;//注意! 这里的 Consumer 回调函数不用发送给服务器(实际上也发送不了)//因为服务器收到这个订阅消息请求之后,就直接取拿队列中的消息,接着直接反馈给客户端//客户端拿到消息后才执行回调方法//这就类似于你去商店订阅报纸,接着拿到报纸以后,你要对这个报纸做什么,商店是不知道的~~public String getConsumerTag() {return consumerTag;}public void setConsumerTag(String consumerTag) {this.consumerTag = consumerTag;}public String getQueueName() {return queueName;}public void setQueueName(String queueName) {this.queueName = queueName;}public boolean isAutoAck() {return autoAck;}public void setAutoAck(boolean autoAck) {this.autoAck = autoAck;}
}
2)响应
创建 SubScribeReturns 类(当前这个类将来会被序列化成 response 类中的 payload 参数) 来描述响应, 这个响应中不光要携带 BasicReturns (返回的公共响应参数),还需要带上回调中消息的参数,如下:
public class SubScribeReturns extends BasicReturns implements Serializable {private String consumerTag;private BasicProperties basicProperties;private byte[] body;public String getConsumerTag() {return consumerTag;}public void setConsumerTag(String consumerTag) {this.consumerTag = consumerTag;}public BasicProperties getBasicProperties() {return basicProperties;}public void setBasicProperties(BasicProperties basicProperties) {this.basicProperties = basicProperties;}public byte[] getBody() {return body;}public void setBody(byte[] body) {this.body = body;}
}
1.3、实现 BrokerServer
这里的写法就和以前写过的 TCP 回显服务器很类似了,只是根据请求计算响应的方式不同
1.3.1、属性和构造
private ServerSocket serverSocket = null;//当前考虑一个 BrokerServer 上只有一个 虚拟主机private VirtualHost virtualHost = new VirtualHost("default");//使用 哈希表 来标识当前所有会话(哪个客户端正在和服务器进行通信)//key 是 channelId, value 为对应的 Socket 对象private ConcurrentHashMap<String, Socket> sessions = new ConcurrentHashMap<>();//用线程池来处理多个客户端请求private ExecutorService executorService = null;//引入一个 Boolean 变量控制服务器是否继续运行private volatile boolean runnable = true;public BrokerServer(int port) throws IOException {serverSocket = new ServerSocket(port);}
1.3.2、启动 BrokerServer
public void start() throws IOException {System.out.println("[BrokerServer] 启动!");executorService = Executors.newCachedThreadPool();while(runnable) {Socket clientSocket = serverSocket.accept();//处理连接的逻辑给线程池executorService.submit(() -> {processConnection(clientSocket);});}}
1.3.3、停止 BrokerServer
/*** 停止服务器,一般是直接 kill 就可以了* 此处这个单独的方法,主要是为了后续的单元测试*/public void stop() throws IOException {runnable = false;//放弃线程池中的任务,并销毁线程executorService.shutdown();serverSocket.close();}
1.3.4、处理每一个客户端连接
private void processConnection(Socket clientSocket) {try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 这里需要按照特定格式来读取并解析. 此时就需要用到 DataInputStream 和 DataOutputStreamtry (DataInputStream dataInputStream = new DataInputStream(inputStream);DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {while (true) {// 1. 读取请求并解析.Request request = readRequest(dataInputStream);// 2. 根据请求计算响应Response response = process(request, clientSocket);// 3. 把响应写回给客户端writeResponse(dataOutputStream, response);}}} catch (EOFException | SocketException e) {// 对于这个代码, DataInputStream 如果读到 EOF , 就会抛出一个 EOFException 异常.// 需要借助这个异常来结束循环System.out.println("[BrokerServer] connection 关闭! 客户端的地址: " + clientSocket.getInetAddress().toString()+ ":" + clientSocket.getPort());} catch (IOException | ClassNotFoundException | MqException e) {System.out.println("[BrokerServer] connection 出现异常!");e.printStackTrace();} finally {try {// 当连接处理完了, 就需要记得关闭 socketclientSocket.close();// 一个 TCP 连接中, 可能包含多个 channel. 需要把当前这个 socket 对应的所有 channel 也顺便清理掉.clearClosedSession(clientSocket);} catch (IOException e) {e.printStackTrace();}}}
1.3.5、读取请求和写响应
private Request readRequest(DataInputStream dataInputStream) throws IOException {Request request = new Request();request.setType(dataInputStream.readInt());request.setLength(dataInputStream.readInt());byte[] body = new byte[request.getLength()];int n = dataInputStream.read(body);if(n != request.getLength()) {throw new IOException("读出请求格式出错!");}request.setPayload(body);return request;}private void writeResponse(DataOutputStream dataOutputStream, Response response) throws IOException {dataOutputStream.write(response.getType());dataOutputStream.write(response.getLength());dataOutputStream.write(response.getPayload());dataOutputStream.flush();}
1.3.6、根据请求计算响应
这里就是根据不同的 type 类型,来远程调用 VirtualHost 中不同的核心 API(需要特别注意订阅消息功能的回调函数)
private Response process(Request request, Socket clientSocket) throws IOException, ClassNotFoundException, MqException {//1.将 request 初步解析成 BasicArgumentsBasicArguments basicArguments = (BasicArguments) BinaryTool.fromBytes(request.getPayload());System.out.println("[Request] rid=" + basicArguments.getRid() + ", channelId=" + basicArguments.getChannelId() +", type=" + request.getType() + ", length=" + request.getLength());//2.根据 type 的值,进一步区分接下来要干什么boolean ok = true;if (request.getType() == 0x1) {//创建 channelsessions.put(basicArguments.getChannelId(), clientSocket);System.out.println("[BrokerServer] 创建 channel 完成!channelId=" + basicArguments.getChannelId());} else if(request.getType() == 0x2) {//销毁 channelsessions.remove(basicArguments.getChannelId());System.out.println("[BrokerServer] 销毁 channel 完成!channelId=" + basicArguments.getChannelId());} else if(request.getType() == 0x3) {//创建交换机,此时 payLoad 就是 ExchangDeclareArguments 了ExchangeDeclareArguments arguments = (ExchangeDeclareArguments) basicArguments;ok = virtualHost.exchangeDeclare(arguments.getExchangeName(), arguments.getExchangeType(),arguments.isDurable(), arguments.isAutoDelete(), arguments.getArguments());} else if(request.getType() == 0x4) {ExchangeDeleteArguments arguments = (ExchangeDeleteArguments) basicArguments;ok = virtualHost.exchangeDelete(arguments.getExchangeName());} else if(request.getType() == 0x5) {QueueDeclareArguments arguments = (QueueDeclareArguments) basicArguments;ok = virtualHost.queueDeclare(arguments.getQueueName(), arguments.isDurable(),arguments.isExclusive(), arguments.isAutoDelete(), arguments.getArguments());} else if(request.getType() == 0x6) {QueueDeleteArguments arguments = (QueueDeleteArguments) basicArguments;ok = virtualHost.queueDelete(arguments.getQueueName());} else if(request.getType() == 0x7) {QueueBindArguments arguments = (QueueBindArguments) basicArguments;ok = virtualHost.queueBind(arguments.getQueueName(), arguments.getExchangeName(), arguments.getBindingKey());} else if(request.getType() == 0x8) {QueueUnBindArguments arguments = (QueueUnBindArguments) basicArguments;ok = virtualHost.queueUnBind(arguments.getQueueName(), arguments.getExchangeName());} else if(request.getType() == 0x9) {BasicPublishArguments arguments = (BasicPublishArguments) basicArguments;ok = virtualHost.basicPublish(arguments.getExchangeName(), arguments.getRoutingKey(), arguments.getBasicProperties(), arguments.getBody());} else if(request.getType() == 0xa) {BasicConsumeArguments arguments = (BasicConsumeArguments) basicArguments;ok = virtualHost.basicConsume(arguments.getConsumerTag(), arguments.getQueueName(), arguments.isAutoAck(), new Consumer() {//这个回调函数要做的就是,把服务器收到的消息可以直接推送回对应的消费者客户端@Overridepublic void handlerDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) throws MqException, IOException {//首先需要知道收到的消息要发给哪个客户端//此处 consumerTag 其实就是 channelId,根据 channelId 去 sessions 中查询,既可以得到对应的//socket 对象了,从而往里面发送数据//1.根据 channelId 找到 socket 对象Socket clientSocket = sessions.get(consumerTag);if(clientSocket == null || clientSocket.isClosed()) {throw new MqException("[BrokerServer] 订阅消息的客户端已经关闭!");}//2.构造响应数据SubScribeReturns subScribeReturns = new SubScribeReturns();subScribeReturns.setChannelId(consumerTag);subScribeReturns.setRid("");//由于这里只有响应,没有请求,不需要去对应,rid 暂时不需要subScribeReturns.setOk(true);subScribeReturns.setConsumerTag(consumerTag);subScribeReturns.setBasicProperties(basicProperties);subScribeReturns.setBody(body);byte[] payload = BinaryTool.toBytes(subScribeReturns);Response response = new Response();// 0xc 表示服务器给消费者客户端推送的消息数据response.setType(0xc);response.setLength(payload.length);response.setPayload(payload);//3.把数据写回给客户端// 注意!此处的 dataOutputStream 这个对象不能 close// 如果把 dataOutputStream 关闭, 就会直接把 clientSocket 里的 outputStream 也关了// 此时就无法继续往 socket 中写后续的数据了DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());writeResponse(dataOutputStream, response);}});} else if(request.getType() == 0xb) {//调用 basicAck 确认消息BasicAckArguments arguments = (BasicAckArguments) basicArguments;ok = virtualHost.basicAck(arguments.getQueueName(), arguments.getMessageId());} else {throw new MqException("[BrokerServer] 未知 type!type=" + request.getType());}//构造响应BasicReturns basicReturns = new BasicReturns();basicReturns.setRid(basicArguments.getRid());basicReturns.setChannelId(basicArguments.getChannelId());basicReturns.setOk(ok);byte[] payload = BinaryTool.toBytes(basicReturns);Response response = new Response();response.setType(request.getType());response.setLength(request.getLength());response.setPayload(payload);System.out.println("[Response] rid=" + basicReturns.getRid() + ", channelId=" + basicReturns.getChannelId()+ ", type=" + response.getType() + ", length=" + response.getLength());return response;}
1.3.7、清除 channel
清理 map 中对应的(clientSocket) session 信息
private void clearClosedSession(Socket clientSocket) {List<String> toDeleteChannelId = new ArrayList<>();for(Map.Entry<String, Socket> entry : sessions.entrySet()) {if(entry.getValue() == clientSocket) { //这里一个 key 可能对应多个相同的 Socket//在集合类中不能一边用迭代器一边删除,会破坏迭代器结构的!//sessions.remove(entry.getKey());//因此这里先记录下 keytoDeleteChannelId.add(entry.getKey());}}for(String channelId : toDeleteChannelId) {sessions.remove(channelId);}System.out.println("[BrokerServer] 清理 session 完毕!channelId=" + toDeleteChannelId);}
相关文章:

根据源码,模拟实现 RabbitMQ - 网络通讯设计,自定义应用层协议,实现 BrokerServer (8)
目录 一、网络通讯协议设计 1.1、交互模型 1.2、自定义应用层协议 1.2.1、请求和响应格式约定 编辑 1.2.2、参数说明 1.2.3、具体例子 1.2.4、特殊栗子 1.3、实现 BrokerServer 1.3.1、属性和构造 1.3.2、启动 BrokerServer 1.3.3、停止 BrokerServer 1.3.4、处…...

MongoDB入门
简介 MongoDB是一个开源、高性能、支持海量数据存储的文档型数据库 是NoSQL数据库产品中的一种,是最像关系型数据库(MySQL)的非关系型数据库 内部采用BSON(二进制JSON)格式来存储数据,并支持水平扩展。 MongoDB本身并不是完全免费的,它对于…...

vr智慧党建主题展厅赋予企业数字化内涵
现如今,VR全景技术的发展让我们动动手指就能在线上参观博物馆、纪念馆,不仅不用受时间和空间的限制,还能拥有身临其境般的体验,使得我们足不出户就能随时随地学习、传承红色文化。 很多党建展厅都是比较传统的,没有运用…...
go中mutex的sema信号量是什么?
先看下go的sync.mutex是什么 type Mutex struct {state int32sema uint32 } 这里面有个sema,这个就是信号量。 什么是信号量? 什么是信号量?_kina100的博客-CSDN博客 其实通俗的来说,信号量就是信号灯,但是他不是…...
LeetCode笔记:Weekly Contest 360
LeetCode笔记:Weekly Contest 360 0. 吐槽1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 1. 解题思路2. 代码实现 比赛链接:https://leetcode.com/contest/weekly-contest-360/ 0.…...

【树DP】2021ICPC南京 H
Problem - H - Codeforces 题意: 思路: 这题应该算是铜牌题 铜牌题 简单算法 基础思维 简单复盘一下思路 首先,我们发现有个很特殊的条件: ti < 3 然后看一下样例: 注意到,对于一个结点 u &#…...

Leedcode19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5] 示例 2: 输入:head [1], n 1 输出:[] 示例 3: 输入࿱…...

Mysql-索引查询相关
一、单表查询 1.1 二级索引为null 不论是普通的二级索引,还是唯一二级索引,它们的索引列对包含 NULL 值的数量并不限制,所以我们采用key IS NULL 这种形式的搜索条件最多只能使用 ref 的访问方法,而不是 const 的访问方法 1.2 c…...
C++ Pimpl
Pimpl(Pointer to implementation,指向实现的指针) 是一种减少代码依赖和编译时间的C编程技巧,其基本思想是将一个外部可见类(visible class)的实现细节(一般是所有私有的非虚成员)放在一个单独的实现类(implementation class)中&…...
rust学习-类型转换
基本类型转换 // 不显示类型转换产生的溢出警告。 #![allow(overflowing_literals)]fn main() {let decimal 65.4321_f32;// 错误!不提供隐式转换// let integer: u8 decimal;// 可以显式转换let integer decimal as u8;let character integer as char;println…...

算法通过村第四关-栈青铜笔记|手写栈操作
文章目录 前言1. 栈的基础概要1.1 栈的特征1.2 栈的操作1.3 Java中的栈 2. 栈的实现(手写栈)2.1 基于数组实现2.2 基于链表实现2.3 基于LinkedList实现 总结 前言 提示:我自己一个人的感觉很好 我并不想要拥有你 除非你比我的独处更加宜人 --…...

Python计算加速利器
迷途小书童的 Note 读完需要 6分钟 速读仅需 2 分钟 1 简介 Python 是一门应用非常广泛的高级语言,但是,长久以来,Python的运行速度一直被人诟病,相比 c/c、java、c#、javascript 等一众高级编程语言,完全没有优势。 那…...

PyTorch 深度学习实践 第10讲刘二大人
总结: 1.输入通道个数 等于 卷积核通道个数 2.卷积核个数 等于 输出通道个数 1.单通道卷积 以单通道卷积为例,输入为(1,5,5),分别表示1个通道,宽为5,高为5。假设卷积核大小为3x3,…...

Linux特殊指令
目录 1.dd命令 2.mkfs格式化 3.df命令 4.mount实现硬盘的挂载 5.unshare 1.dd命令 dd命令可以用来读取转换并输出数据。 示例一: if表示infile,of表示outfile。这里的/dev/zero是一个特殊文件,会不断产生空白数据。 bs表示复制一块的大…...
MPI之主从模式的一般编程示例
比如,我们可以选举0号进程为master进程,其余进程为slaver进程 #include "mpi.h" #include <unistd.h> #include <iostream>int main(int argc, char *argv[]) {int err MPI_Init(&argc,&argv);int rank,size;MPI_Comm_r…...

基于野狗算法优化的BP神经网络(预测应用) - 附代码
基于野狗算法优化的BP神经网络(预测应用) - 附代码 文章目录 基于野狗算法优化的BP神经网络(预测应用) - 附代码1.数据介绍2.野狗优化BP神经网络2.1 BP神经网络参数设置2.2 野狗算法应用 4.测试结果:5.Matlab代码 摘要…...

C语言面向对象的编程思想
面向对象编程 面向对象编程Object-Oriented Programming,OOP) 作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征࿰…...
MPI之非阻塞通信中通信完成检测接口简介
在之前的文章中,简单的写了一个非阻塞的通信代码介绍最最基本的使用: int main(int argc, char *argv[]) {int err MPI_Init(&argc,&argv);int rank,size;MPI_Comm_rank(MPI_COMM_WORLD,&rank);MPI_Comm_size(MPI_COMM_WORLD, &size);…...

Excel:如何实现分组内的升序和降序?
一、POWER 1、构建辅助列D列,在D2单元格输入公式: -POWER(10,COUNTA($A$2:A2)3)C2 2、选中B1:D10,注意不能宣导A列的合并单元格,进行以下操作: 3、删除辅助列即可 二、COUNTA 第一步,D2建立辅助列…...

深度学习论文: Segment Any Anomaly without Training via Hybrid Prompt Regularization
深度学习论文: Segment Any Anomaly without Training via Hybrid Prompt Regularization Segment Any Anomaly without Training via Hybrid Prompt Regularization PDF: https://arxiv.org/pdf/2305.10724.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch Py…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...