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

仿RabbitMq简易消息队列基础篇(Muduo库的使用)

@TOC

Muduo库简介

Muduo由陈硕⼤佬开发,是⼀个基于⾮阻塞IO和事件驱动的C++⾼并发TCP⽹络编程库。他是一款基于主从Reactor模型的网络库,其使用的线程模型是one loop per thread, 所谓 one loop per thread 指的是:

  • 一个线程只能有一个事件循环(EventLoop),用于相应计时器和IO时间
  • 一个文件描述符只能由一个线程进行读写,换句话说就是一个TCP连接必须归属某个EventLoop管理

Muduo库常见接口介绍

muduo::net::TcpServer类基础介绍

typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
typedef std::function<void(const TcpConnectionPtr &)> ConnectionCallback;
typedef std::function<void(const TcpConnectionPtr &,Buffer *,Timestamp)>MessageCallback;
class InetAddress : public muduo::copyable
{
public:InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);
};
class TcpServer : noncopyable
{
public:enum Option{kNoReusePort,kReusePort,};TcpServer(EventLoop *loop,const InetAddress &listenAddr,const string &nameArg,Option option = kNoReusePort);void setThreadNum(int numThreads);void start();/// 当⼀个新连接建⽴成功的时候被调⽤void setConnectionCallback(const ConnectionCallback &cb){connectionCallback_ = cb;}/// 消息的业务处理回调函数---这是收到新连接消息的时候被调⽤的函数void setMessageCallback(const MessageCallback &cb){messageCallback_ = cb;}
};

muduo::net::EventLoop 类基础介绍

class EventLoop : noncopyable
{
public:/// 无限循环。/// 必须在与对象创建相同的线程中调用。void loop();/// 退出循环。/// 如果通过原始指针调用,这不是 100% 线程安全,/// 最好通过 shared_ptr<EventLoop> 调用以保证 100% 安全。void quit();TimerId runAt(Timestamp time, TimerCallback cb);/// 在 @c delay 秒后运行回调。/// 线程安全,可以从其他线程调用。TimerId runAfter(double delay, TimerCallback cb);/// 每隔 @c interval 秒运行一次回调。/// 线程安全,可以从其他线程调用。TimerId runEvery(double interval, TimerCallback cb);/// 取消定时器。/// 线程安全,可以从其他线程调用。void cancel(TimerId timerId);private:std::atomic<bool> quit_;std::unique_ptr<Poller> poller_;mutable MutexLock mutex_;std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);
};

muduo::net::TcpConnection 类基础介绍

class TcpConnection : noncopyable,public std::enable_shared_from_this<TcpConnection>
{
public:/// 使用已连接的 sockfd 构造 TcpConnection////// 用户不应创建此对象。TcpConnection(EventLoop *loop,const string &name,int sockfd,const InetAddress &localAddr,const InetAddress &peerAddr);/// 检查连接是否已建立bool connected() const { return state_ == kConnected; }/// 检查连接是否已断开bool disconnected() const { return state_ == kDisconnected; }/// 发送消息(右值引用,C++11)void send(string &&message); // C++11/// 发送消息(指针和长度)void send(const void *message, int len);/// 发送消息(StringPiece)void send(const StringPiece &message);/// 发送消息(Buffer 指针,交换数据)void send(Buffer *message); // this one will swap data/// 关闭连接(非线程安全,不能同时调用)void shutdown(); // NOT thread safe, no simultaneous calling/// 设置上下文void setContext(const boost::any &context){context_ = context;}/// 获取上下文(常量引用)const boost::any &getContext() const{return context_;}/// 获取可变的上下文指针boost::any *getMutableContext(){return &context_;}/// 设置连接回调函数void setConnectionCallback(const ConnectionCallback &cb){connectionCallback_ = cb;}/// 设置消息回调函数void setMessageCallback(const MessageCallback &cb){messageCallback_ = cb;}private:/// 连接状态枚举enum StateE{kDisconnected,kConnecting,kConnected,kDisconnecting};EventLoop *loop_;                             // 事件循环指针ConnectionCallback connectionCallback_;       // 连接回调函数MessageCallback messageCallback_;             // 消息回调函数WriteCompleteCallback writeCompleteCallback_; // 写完成回调函数boost::any context_;                          // 上下文数据
};

muduo::net::TcpClient类基础介绍

class TcpClient : noncopyable
{
public:/// 构造函数,通过服务器地址和客户端名称构造TcpClientTcpClient(EventLoop *loop,const InetAddress &serverAddr,const string &nameArg);/// 析构函数,强制外部析构,以便管理std::unique_ptr成员~TcpClient(); // force out-line dtor, for std::unique_ptr members./// 连接服务器void connect();/// 断开连接void disconnect();/// 停止客户端void stop();/// 获取客户端对应的通信连接Connection对象的接口/// 在调用connect后,有可能连接还没有建立成功TcpConnectionPtr connection() const{MutexLockGuard lock(mutex_);return connection_;}/// 设置连接服务器成功时的回调函数void setConnectionCallback(ConnectionCallback cb){connectionCallback_ = std::move(cb);}/// 设置收到服务器发送的消息时的回调函数void setMessageCallback(MessageCallback cb){messageCallback_ = std::move(cb);}private:EventLoop *loop_;                                // 事件循环指针ConnectionCallback connectionCallback_;          // 连接回调函数MessageCallback messageCallback_;                // 消息回调函数WriteCompleteCallback writeCompleteCallback_;    // 写完成回调函数TcpConnectionPtr connection_ GUARDED_BY(mutex_); // 连接对象,受mutex_保护
};/*
需要注意的是,因为muduo库不管是服务器端还是客户端都是异步操作,
对于客户端来说,如果我们在连接还没有完全建立成功的时候发送数据,这是不被允许的。
因此我们可以使用内置的CountDownLatch类进行同步控制。
*/
class CountDownLatch : noncopyable
{
public:/// 显式构造函数,初始化倒计时计数explicit CountDownLatch(int count);/// 等待倒计时完成void wait(){MutexLockGuard lock(mutex_);while (count_ > 0){condition_.wait();}}/// 倒计时减一void countDown(){MutexLockGuard lock(mutex_);--count_;if (count_ == 0){condition_.notifyAll();}}/// 获取当前倒计时计数int getCount() const;private:mutable MutexLock mutex_;                // 互斥锁,受保护的成员变量Condition condition_ GUARDED_BY(mutex_); // 条件变量,受mutex_保护int count_ GUARDED_BY(mutex_);           // 倒计时计数,受mutex_保护
};

muduo::net::Buffer 类基础介绍

class Buffer : public muduo::copyable
{
public:/// 常量:预留的前置空间大小static const size_t kCheapPrepend = 8;/// 常量:初始缓冲区大小static const size_t kInitialSize = 1024;/// 显式构造函数,初始化缓冲区大小explicit Buffer(size_t initialSize = kInitialSize): buffer_(kCheapPrepend + initialSize),readerIndex_(kCheapPrepend),writerIndex_(kCheapPrepend) {}/// 交换缓冲区内容void swap(Buffer &rhs);/// 可读字节数size_t readableBytes() const;/// 可写字节数size_t writableBytes() const;/// 返回指向可读数据的指针const char *peek() const;/// 查找换行符(CRLF)的指针const char *findEOL() const;/// 从指定位置开始查找换行符(CRLF)的指针const char *findEOL(const char *start) const;/// 从缓冲区中取出指定长度的数据void retrieve(size_t len);/// 从缓冲区中取出一个64位整数void retrieveInt64();/// 从缓冲区中取出一个32位整数void retrieveInt32();/// 从缓冲区中取出一个16位整数void retrieveInt16();/// 从缓冲区中取出一个8位整数void retrieveInt8();/// 将缓冲区中的所有数据取出并转换为字符串string retrieveAllAsString();/// 将缓冲区中指定长度的数据取出并转换为字符串string retrieveAsString(size_t len);/// 向缓冲区追加字符串void append(const StringPiece &str);/// 向缓冲区追加指定数据和长度void append(const char * /*restrict*/ data, size_t len);/// 向缓冲区追加指定数据和长度void append(const void * /*restrict*/ data, size_t len);/// 返回指向可写数据的指针char *beginWrite();/// 返回指向可写数据的常量指针const char *beginWrite() const;/// 更新已写入的长度void hasWritten(size_t len);/// 向缓冲区追加一个64位整数void appendInt64(int64_t x);/// 向缓冲区追加一个32位整数void appendInt32(int32_t x);/// 向缓冲区追加一个16位整数void appendInt16(int16_t x);/// 向缓冲区追加一个8位整数void appendInt8(int8_t x);/// 从缓冲区中读取一个64位整数int64_t readInt64();/// 从缓冲区中读取一个32位整数int32_t readInt32();/// 从缓冲区中读取一个16位整数int16_t readInt16();/// 从缓冲区中读取一个8位整数int8_t readInt8();/// 查看缓冲区中的一个64位整数int64_t peekInt64() const;/// 查看缓冲区中的一个32位整数int32_t peekInt32() const;/// 查看缓冲区中的一个16位整数int16_t peekInt16() const;/// 查看缓冲区中的一个8位整数int8_t peekInt8() const;/// 在缓冲区前面添加一个64位整数void prependInt64(int64_t x);/// 在缓冲区前面添加一个32位整数void prependInt32(int32_t x);/// 在缓冲区前面添加一个16位整数void prependInt16(int16_t x);/// 在缓冲区前面添加一个8位整数void prependInt8(int8_t x);/// 在缓冲区前面添加指定数据和长度void prepend(const void * /*restrict*/ data, size_t len);private:std::vector<char> buffer_; // 缓冲区size_t readerIndex_;       // 读索引size_t writerIndex_;       // 写索引static const char kCRLF[]; // 换行符常量
};

接下来就是通过上面的接口来使用muduo库通过protobuf实现网络通信,这里我们简单写一个计算器

客户端

#include "muduo/proto/dispatcher.h"
#include "muduo/proto/codec.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/base/CountDownLatch.h"#include "request.pb.h"
#include <memory>
#include <iostream>class Client
{
public:typedef std::shared_ptr<sslx::AddResponse> AddResponsePtr;typedef std::shared_ptr<sslx::SubResponse> SubResponsePtr;typedef std::shared_ptr<sslx::MulResponse> MulResponsePtr;typedef std::shared_ptr<sslx::DivResponse> DivResponsePtr;typedef std::shared_ptr<sslx::ErrorResponse> ErrorResponsePtr;typedef std::shared_ptr<google::protobuf::Message> MessagePtr;Client(const std::string& sip, int port):_latch(1),_client(_loopthread.startLoop(), muduo::net::InetAddress(sip, port), "Client"),_dispatcher(std::bind(&Client::onUnknownMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)),_codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)){// 注册业务回复处理函数_dispatcher.registerMessageCallback<sslx::AddResponse>(std::bind(&Client::onAdd, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::SubResponse>(std::bind(&Client::onSub, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::MulResponse>(std::bind(&Client::onMul, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::DivResponse>(std::bind(&Client::onDiv, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::ErrorResponse>(std::bind(&Client::onError, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_client.setConnectionCallback(std::bind(&Client::onConnection, this, std::placeholders::_1));Connect();}void Connect(){_client.connect();_latch.wait();}void Add(int num1 , int num2){sslx::AddRequest req;req.set_num1(num1);req.set_num2(num2);send(req);}void Sub(int num1 , int num2){sslx::SubRequest req;req.set_num1(num1);req.set_num2(num2);send(req);}void Mul(int num1 , int num2){sslx::MulRequest req;req.set_num1(num1);req.set_num2(num2);send(req);}void Div(int num1 , int num2){sslx::DivRequest req;req.set_num1(num1);req.set_num2(num2);send(req);}
private:bool send(const google::protobuf::Message& msg){//连接状态正常,再发送,否则就返回falseif(_conn->connected()){_codec.send(_conn, msg);return true;}return false;}void onConnection(const muduo::net::TcpConnectionPtr& conn){if(conn->connected()){_conn = conn;_latch.countDown();}else{_conn.reset();  // 连接关闭时的操作}}void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& msg, muduo::Timestamp){LOG_INFO << "onUnknowMessage" << msg->GetTypeName();conn->shutdown();}void onAdd(const muduo::net::TcpConnectionPtr& conn, const AddResponsePtr& msg, muduo::Timestamp){std::cout << "加法结果 : " << msg->result() << std::endl; }void onSub(const muduo::net::TcpConnectionPtr& conn, const SubResponsePtr& msg, muduo::Timestamp){std::cout << "减法结果 : " << msg->result() << std::endl;}void onMul(const muduo::net::TcpConnectionPtr& conn, const MulResponsePtr& msg, muduo::Timestamp){std::cout << "乘法结果 : " << msg->result() << std::endl;}void onDiv(const muduo::net::TcpConnectionPtr& conn, const DivResponsePtr& msg, muduo::Timestamp){std::cout << "除法结果 : " << msg->result() << std::endl;}void onError(const muduo::net::TcpConnectionPtr& conn, const ErrorResponsePtr& msg, muduo::Timestamp){std::cout << "出现除零错误" << std::endl;}private:muduo::CountDownLatch _latch;  //主要同步主线程和网络线程,确保在连接建立之前不会发送请求muduo::net::EventLoopThread _loopthread; // 提供一个独立的事件循环,用于处理网络事件muduo::net::TcpConnectionPtr _conn;  // 保存当前的TcpConnectionPtr, 用于在连接建立之后发送消息muduo::net::TcpClient _client; // 管理客户端的连接和重连ProtobufDispatcher _dispatcher; // 用于根据消息的类型调用相应的处理函数ProtobufCodec _codec; // 负责Protobuf消息的编解码};int main()
{Client client("127.0.0.1", 8085);client.Add(11, 11);client.Sub(11, 11);sleep(1);return 0; 
}

服务端

#include <muduo/proto/codec.h>
#include "muduo/proto/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"#include "request.pb.h"#include <memory>
#include <string>
#include <iostream>
#include <unordered_map>class Server
{
public:typedef std::shared_ptr<google::protobuf::Message> MessagePtr;typedef std::shared_ptr<sslx::AddRequest> AddRequestPtr;typedef std::shared_ptr<sslx::SubRequest> SubRequestPtr;typedef std::shared_ptr<sslx::MulRequest> MulRequestPtr;typedef std::shared_ptr<sslx::DivRequest> DivRequestPtr;Server(int port):_server(&_baseLoop, muduo::net::InetAddress("0.0.0.0", port), "Server", muduo::net::TcpServer::kReusePort),_dispatcher(std::bind(&Server::onUnknownMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)),_codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)){_dispatcher.registerMessageCallback<sslx::AddRequest>(std::bind(&Server::onAdd, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::SubRequest>(std::bind(&Server::onSub, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::MulRequest>(std::bind(&Server::onMul, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<sslx::DivRequest>(std::bind(&Server::onDiv, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_server.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_server.setConnectionCallback(std::bind(&Server::onConnection, this, std::placeholders::_1));}void start(){_server.start();_baseLoop.loop();}
private:int Add(int num1, int num2){return num1 + num2;}int Sub(int num1, int num2){return num1 - num2;}int Mul(int num1, int num2){return num1 * num2;}int Div(int num1, int num2){if(num2 == 0){return INT_MIN;}return num1 / num2;}void onAdd(const muduo::net::TcpConnectionPtr& conn, const AddRequestPtr& msg, muduo::Timestamp){sslx::AddResponse resp;int ret = Add(msg->num1(), msg->num2());resp.set_result(ret);_codec.send(conn, resp);}void onSub(const muduo::net::TcpConnectionPtr& conn, const SubRequestPtr& msg, muduo::Timestamp){sslx::SubResponse resp;int ret = Sub(msg->num1(), msg->num2());resp.set_result(ret);_codec.send(conn, resp);}void onMul(const muduo::net::TcpConnectionPtr& conn, const MulRequestPtr& msg, muduo::Timestamp){sslx::MulResponse resp;int ret = Mul(msg->num1(), msg->num2());resp.set_result(ret);_codec.send(conn, resp);}void onDiv(const muduo::net::TcpConnectionPtr& conn, const DivRequestPtr& msg, muduo::Timestamp){sslx::DivResponse resp1;sslx::ErrorResponse resp2;int ret = Div(msg->num1(), msg->num2());if(ret ==INT_MIN){resp2.set_msg(std::to_string(ret));_codec.send(conn, resp2);}else{resp1.set_result(ret);_codec.send(conn, resp1);}}void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp){LOG_INFO << "onUNknownMessage : " << message->GetTypeName();conn->shutdown();}void onConnection(const muduo::net::TcpConnectionPtr& conn){if(conn->connected()){LOG_INFO << "新连接建立成功";}else{LOG_INFO << "连接已关闭";}}private:muduo::net::EventLoop _baseLoop;muduo::net::TcpServer _server; //服务器对象ProtobufDispatcher _dispatcher; // 请求分发对象 --要向其中注册请求处理函数ProtobufCodec _codec;   // protobuf协议处理器--针对收到的请求数据进行protobuf协议处理};int main()
{Server server(8085);server.start();return 0;
}

        这里简单梳理一下网路的流程:在进行初始化的时候,客户端和服务端之间会对_codec和_dispatcher还有事件监控循环线程,在建立连接的时候,异步线程会通过setConnectionCallback的回调函数,onConnection函数中的countDown() 函数唤醒主线程开始进行业务处理,在进行业务处理的时候,会将请求通过send发到缓冲区中,响应报文回来之后会给setMessageCallback函数传递信号,然后由loop线程来接收到响应,调用相关的函数来对结果进行处理。

protobuf

syntax = "proto3";package sslx;message AddRequest
{int32 num1 = 1;int32 num2 = 2;
}message AddResponse
{int32 result = 1;
}message SubRequest
{int32 num1 = 1;int32 num2 = 2;
}message SubResponse
{int32 result = 1;
}message MulRequest
{int32 num1 = 1;int32 num2 = 2;
}message MulResponse
{int32 result = 1;
}message DivRequest
{int32 num1 = 1;int32 num2 = 2;
}message DivResponse
{int32 result = 1;
}message ErrorResponse
{string msg = 1;
}

makefile

all: client server
client: client.cc request.pb.cc mqthird/include/muduo/proto/codec.ccg++ -g -o $@ $^ -std=c++11 -I./mqthird/include -L./mqthird/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz
server: server.cc request.pb.cc mqthird/include/muduo/proto/codec.ccg++ -g -o $@ $^ -std=c++11 -I./mqthird/include -L./mqthird/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz
.PHONY:clean
clean:rm -rf server client

gitee

https://gitee.com/pu-mingbo/master.git

相关文章:

仿RabbitMq简易消息队列基础篇(Muduo库的使用)

TOC Muduo库简介 Muduo由陈硕⼤佬开发&#xff0c;是⼀个基于⾮阻塞IO和事件驱动的C⾼并发TCP⽹络编程库。他是一款基于主从Reactor模型的网络库&#xff0c;其使用的线程模型是one loop per thread, 所谓 one loop per thread 指的是&#xff1a; 一个线程只能有一个事件循…...

.net SqlSugarHelper

NuGet安装&#xff1a; SqlSugarCore using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace Namespace {public class SqlSugarHelper{public string _connectionString Custom…...

“AI能不能代替某某职业”,到底谁在破防?

前几天&#xff0c;公司在午间分享时谈到一个有趣的辩题&#xff1a;“AI能不能代替产品经理”&#xff0c;不仅双方辩手打了个你来我往&#xff0c;就连下面的吃瓜群众也进入红温状态。 “AI能不能代替xx”已经成为一个普遍的话题&#xff0c;在某乎上随手一刷就是不同的职业…...

智慧图书馆:构建高效视频智能管理方案,提升图书馆个性化服务

一、背景分析 随着信息技术的飞速发展&#xff0c;智慧图书馆作为现代公共文化服务的重要载体&#xff0c;正逐步从传统的纸质阅读空间向数字化、智能化方向转型。其中&#xff0c;视频智能管理方案作为智慧图书馆安全管理体系的重要组成部分&#xff0c;不仅能够有效提升图书…...

React快速开发框架

本框架主要用于快速搭建项目 使用的基本库&#xff1a;webpackreactreact-routertypescript ps&#xff1a;有不足之处请多多包涵&#xff0c;提出意见或者建议 目的&#xff1a; 前端开发大多数时间是基于市面上比较流行的成品框架开始进行开发&#xff0c;途中遇到的问题大…...

【前端】记录各种控制台警告/bug

一、Element Plus 1、控制台警告&#xff1a;“Runtime directive used on component with non-element root node. The directives will not function as intended.” 错误原因&#xff1a;在 Vue 组件上使用了运行时指令&#xff08;指那些在运行时动态绑定到 DOM 元素上的指…...

猫咪掉毛严重怎么办?铲屎官家庭必备清理工具——宠物空气净化器

“毛&#xff0c;毛&#xff0c;毛&#xff0c;还是毛&#xff01;”铲屎官们每天都离不开和猫毛斗智斗勇&#xff0c;家里的每个角落都成了“战场”&#xff0c;掉毛的严重程度超乎想象。有时也在后悔当初怎么不养只无毛猫&#xff0c;而是把毛孩子接了回来&#xff0c;世上没…...

顺序表的实现——数据结构

线性表 文章目录 线性表线性表的定义和基本操作线性表的定义线性表的基本操作 线性表的顺序表示顺序表的定义顺序表的实现——静态分配顺序表的实现——动态分配顺序表的特点 线性表的定义和基本操作 线性表的定义 线性表&#xff08;Linear List&#xff09;的定义 ​ 线性…...

【模块化】CommonJS,AMD规范,CMD规范,ES6模块化

1. CommonJS Node.js基于CommonJS规范应运而生 1.1 commonjs规范语法导出模块 module.exports { a, b }1.2 commonjs规范语法引入模块 const mod require(./导出模块name)2. AMD 规范 RequireJS 是AMD规范的实现。是js文件和模块的加载器。 在没有单页应用&#xff08;angu…...

3.js - 顶点着色器、片元着色器的联系

1、定义与功能 顶点着色器 顶点着色器&#xff0c;是图形渲染管线中的第一个可编程阶段&#xff0c;它的主要任务是&#xff0c;处理从CPU发送到GPU的顶点数据&#xff0c;包括&#xff1a;1、顶点位置的变换&#xff08;如&#xff1a;模型空间 -> 世界空间 -> 视图控件…...

kotlin简介

Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言&#xff0c;被称之为 Android 世界的Swift&#xff0c;由 JetBrains 设计开发并开源。 Kotlin 可以编译成Java字节码&#xff0c;也可以编译成 JavaScript&#xff0c;方便在没有 JVM 的设备上运行。 在Google I/O 2017…...

Mintegral出海系列:解锁全球应用商店新增长路径

在全球化竞争的浪潮中&#xff0c;面对打法各异的应用和游戏品类&#xff0c;以及全球数百个环境不同的国家和地区&#xff0c;开发者们正面临着前所未有的挑战。Mintegral「出海ing」系列专题内容&#xff0c;助力出海开发者选准赛道探索新的增长路径。 据近期数据显示&#x…...

Qt 哈希加密之 QCryptographicHash

【写在前面】 QCryptographicHash 是 Qt 框架中提供的一个类&#xff0c;它用于实现加密散列函数&#xff0c;也就是我们常说的哈希函数。哈希函数能够将任意长度的数据转换为固定长度的哈希值&#xff0c;这个哈希值通常用于数据的完整性校验、密码存储等场景。 什么是哈希函数…...

渗透第二次作业

目录 简述rce漏洞 可能产生rce漏洞的函数 RCE代码执行漏洞示例 贷齐乐系统多处SQL注入漏洞 ​编辑 爆出库名 爆出表名 爆出表下的列名 查flag数据 简述rce漏洞 ​ rce漏洞&#xff0c;即远程代码执行和远程命令执行漏洞。这种漏洞允许攻击者在后台服务器上远程注入操作…...

42.【C语言】冒泡排序

目录&#xff1a; 冒泡排序 *核心思想 *分析 *代码 *优化 15.冒泡排序(bubble sort) *核心思想&#xff1a;两两相邻的元素进行比较&#xff0c;满足条件则两者交换 *分析 现要求升序排序 输入: 9 8 7 6 5 4 3 2 1 0 输出&#xff1a;0 1 2 3 4 5 6 7 8 9 下面展示一趟冒泡排…...

Linux安全与高级应用(七)深入Linux Shell脚本编程:循环与分支结构的高级应用

文章目录 深入Linux Shell脚本编程&#xff1a;循环与分支结构的高级应用一、循环结构详解1. for循环1.1 应用示例&#xff1a;检查主机状态 2. while循环2.1 应用示例&#xff1a;猜价格游戏 二、分支结构详解1. if语句1.1 单分支结构1.2 双分支结构1.3 多分支结构 2. case语句…...

python爬虫滑块验证及各种加密函数(基于ddddocr进行的一层封装)

git链接: https://github.com/JOUUUSKA/spider_toolsbox 这里写目录标题 一.识别验证码1、识别英文&#xff0b;数字验证码2、识别滑块验证码3、识别点选验证码 一.识别验证码 git链接: https://github.com/JOUUUSKA/spider_toolsbox 创作不易记得stars 1、识别英文&#xf…...

pytorch学习一(扩展篇):miniconda下载、安装、配置环境变量。miniconda创建多版本python环境。整理常用命令(亲测ok)

文章目录 前言一、miniconda和anaconda的关系1、Anaconda2、Miniconda3、总结 二、下载miniconda&#xff08;清华镜像链接&#xff09;三、安装miniconda1、安装2、或许要手动加载 ~/.bashrc 四、配置 命令1、查看anaconda安装博文2、取消默认进入conda&#xff08;base&#…...

说一下Android中的IdleHandler

IdleHandler 是 Android 中的一个接口&#xff0c;常用于在主线程空闲时执行一些低优先级的任务。 作用&#xff1a; 它提供了一种在主线程空闲时执行额外操作的机制&#xff0c;能够优化应用的性能和资源利用。 工作原理&#xff1a; 当主线程没有其他任务需要处理&#xff…...

Flake8 和 Autopep8 使用指南

Flake8 和 Autopep8 集成到 CI/CD 流程中&#xff0c;确保在代码提交和合并时自动进行检查和格式化&#xff0c;如果Autopep8格式化检查无法通过Flake8校验&#xff0c;说明pycodestyle版本依赖不兼容&#xff0c;参考文章&#xff1a;Flake8 与 Autopep8 兼容性指南 Flake8 使…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

Python 包管理器 uv 介绍

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

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...