【C++】从零实现Json-Rpc框架(2)
目录
JsonCpp库
1.1- Json数据格式
1.2 - JsonCpp介绍
• 序列化接口
• 反序列化接口
1.3 - Json序列化实践
JsonCpp使用
Muduo库
2.1 - Muduo库是什么
2.2 - Muduo库常见接口介绍
TcpServer类基础介绍
EventLoop类基础介绍
TcpConnection类基础介绍
TcpClient类基础介绍
Buffer类基础介绍
2.3 - Muduo库快速上手
英译汉TCP服务器
编辑
英译汉客户端
Makefile
项目汇总:uyeonashi的博客-CSDN博客
项目源码:https://gitee.com/uyeonashi/project
本篇文章是对第三方库的介绍和使用(JsonCpp库,Muduo库)!
JsonCpp库
1.1- Json数据格式
Json 是一种数据交换格式,它采用完全独立于编程语言的文本格式来存储和表示数据。
例如: 我们想表示一个同学的学生信息
C 代码表示:
char *name = "xx";
int age = 18;
float score[3] = {88.5, 99, 58};
Json 表示:
{"姓名" : "xx","年龄" : 18,"成绩" : [88.5, 99, 58],"爱好" :{"书籍" : "西游记","运动" : "打篮球"}
}
Json 的数据类型包括对象,数组,字符串,数字等。
- 对象:使用花括号{ } 括起来的表示一个对象
- 数组:使用中括号[ ] 括起来的表示一个数组
- 字符串:使用常规双引号" " 括起来的表示一个字符串
- 数字:包括整形和浮点型,直接使用
1.2 - JsonCpp介绍
Jsoncpp 库主要是用于实现Json 格式数据的序列化和反序列化,它实现了将多个数据对象组织成为json 格式字符串,以及将Json 格式字符串解析得到多个数据对象的功能。
Jsoncpp的介绍:3个类
Json::Value类:中间数据存储类
如果要将数据进行序列化,就需要先存储到Json::Value对象当中
如果要将数据进行反序列化,就是解析后,将数据对象放入到Json::Value对象当中
先看一下Json 数据对象类的表示
class Json::Value
{Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过Value& operator[](const std::string& key);//简单的方式完成 val["name"] ="xx";Value& operator[](const char* key);Value removeMember(const char* key);//移除元素const Value& operator[](ArrayIndex index) const; //val["score"][0]Value& append(const Value& value);//添加数组元素val["score"].append(88);ArrayIndex size() const;//获取数组元素个数 val["score"].size();std::string asString() const;//转string string name = val["name"].asString();const char* asCString() const;//转char* char *name = val["name"].asCString();Int asInt() const;//转int int age = val["age"].asInt();float asFloat() const;//转float float weight = val["weight"].asFloat();bool asBool() const;//转 bool bool ok = val["ok"].asBool();
};
Jsoncpp 库主要借助三个类以及其对应的少量成员函数完成序列化及反序列化
• 序列化接口
Json::StreamWriter类:用于进行数控序列化
Json::StreamWriter::write() 序列化函数
Json::StreamWriterBuilder类:Json::StreamWriter工厂类 — 用于生成Json::StreamWriter对象
class JSON_API StreamWriter
{virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory
{virtual StreamWriter* newStreamWriter() const;
}
• 反序列化接口
Json::CharReader类:反序列化类
Json::CharReader::parse() 反序列化函数
Json::CharReaderBuilder类:Json::CharReader工厂类 — 用于生产Json::CharReader对象
class JSON_API CharReader
{virtual bool parse(char const* beginDoc, char const* endDoc,Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory
{virtual CharReader* newCharReader() const;
}
1.3 - Json序列化实践
JsonCpp使用
#include<iostream>
#include<memory>
#include<string>
#include<sstream>
#include<jsoncpp/json/json.h>//实现数据的序列化
bool serialize(const Json::Value &val,std::string &body)
{std::stringstream ss;//先实例化一个工厂类对象Json::StreamWriterBuilder swb;//通过工厂生产派生类对象std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());int ret = sw->write(val,&ss);if(ret != 0){std::cout << "json serialize failed\n";return false;}body = ss.str();return true;
}//实现json的反序列化
bool unserialize(const std::string &body,Json::Value &val)
{//实例化工厂对象Json::CharReaderBuilder crb;//生产Charreader对象std::string errs;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());bool ret = cr->parse(body.c_str(),body.c_str()+body.size(),&val,&errs);if(ret == false){std::cout << "Json unserialize failed: " << errs << std::endl;return false;}return true;
}int main()
{const char *name = "小明";int age = 18;const char *sex = "男";float score[3] = {88,77.5,66};Json::Value student;student["姓名"] = name;student["年龄"] = age;student["性别"] = sex;student["成绩"].append(score[0]);student["成绩"].append(score[1]);student["成绩"].append(score[2]);Json::Value fav;fav["书籍"] = "西游记";fav["运动"] = "打篮球";student["爱好"] = fav;std::string body;serialize(student,body);std::cout << body << std::endl;std::string str = R"({"姓名":"小黑","年龄":19,"成绩":[32,45.5,56]})";Json::Value stu;bool ret = unserialize(str,stu);if(ret == false)return -1;std::cout << "姓名: " << stu["姓名"].asString() << std::endl;std::cout << "年龄:" << stu["年龄"].asInt() << std::endl;int sz = stu["成绩"].size();for(int i = 0; i < sz; i++){std::cout << "成绩:" << stu["成绩"][i].asFloat() << std::endl;} return 0;
}
编译运行程序查看序列化和反序列化结果
并在在这将其封装了一下,方便我们后边的使用
Muduo库
2.1 - Muduo库是什么
Muduo由陈硕大佬开发,是一个基于非阻塞IO和事件驱动的C++高并发TCP网络编程库。 它是一款基于主从Reactor模型的网络库,其使用的线程模型是one loop per thread, 所谓one loop per thread指的是:
- 一个线程只能有一个事件循环(EventLoop), 用于响应计时器和IO事件
- 一个文件描述符只能由一个线程进行读写,换句话说就是一个TCP连接必须归属于某EventLoop管理
2.2 - Muduo库常见接口介绍
TcpServer类基础介绍
TcpServer{
void start(); //启动服务
setConnectionCallback(); //设置连接建立 / 关闭时的回调函数
setMessageCallback();//设置消息处理回调函数
};
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; }
};
EventLoop类基础介绍
EvenLoop{
void loop(); //开始事件监控事件循环
void quit(); //停止循环
Timerld runAfter(delay,cb); //定时任务
};
class EventLoop : noncopyable
{
public:// Loops forever.// Must be called in the same thread as creation of the object.void loop();// Quits loop.// This is not 100% thread safe, if you call through a raw pointer,///better to call through shared_ptr<EventLoop> for 100% safety.void quit();TimerId runAt(Timestamp time, TimerCallback cb);// Runs callback after @c delay seconds.// Safe to call from other threads.TimerId runAfter(double delay, TimerCallback cb);// Runs callback every @c interval seconds.// Safe to call from other threads.TimerId runEvery(double interval, TimerCallback cb);// Cancels the timer.// Safe to call from other threads.void cancel(TimerId timerId);
private:std::atomic<bool> quit_;std::unique_ptr<Poller> poller_;mutable MutexLock mutex_;std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);
};
TcpConnection类基础介绍
TcpConnection{
void send(std::string &msg); //发送数据
bool connected(); //判断当前连接是否连接正常
shutdown down(); //关闭连接
}
class TcpConnection : noncopyable,public std::enable_shared_from_this<TcpConnection>
{
public:
// Constructs a TcpConnection with a connected sockfd
//
// User should not create this object.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; }void send(string&& message); // C++11void send(const void* message, int len);void send(const StringPiece& message);// void send(Buffer&& message); // C++11void send(Buffer* message); // this one will swap datavoid shutdown(); // NOT thread safe, no simultaneous callingvoid 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_;
};
TcpClient类基础介绍
TcpClient{
void connect(); //连接服务器
void disconnect(); //关闭连接
TcpConnectionPtr connection() const; // 获取客户端对应的TcpConnection连接
Muduo库的客户端也是通过Eventloop进行IO事件监控IO处理的
void setConnectionCallback(ConnectionCallback cb); //连接建立成功 / 关闭的回调处理
void setMessageCallback(MessageCallback cb); //收到消息的回调处理
}
CountDownLatch{ //做计数同步操作的类
void wait(); //计数大于0则阻塞
countDown(); //计数--,为0时唤醒wait
}
因为Client的connect接口是一个非阻塞操作,所以可能出现移走以外情况:connect连接还没有完全建立的情况下,调用connection接口获取连接,send发送数据
class TcpClient : noncopyable
{
public:// TcpClient(EventLoop* loop);// TcpClient(EventLoop* loop, const string& host, uint16_t port);TcpClient(EventLoop* loop,const InetAddress& serverAddr,const string& nameArg);~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_);
};/*需要注意的是,因为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_);int count_ GUARDED_BY(mutex_);};
Buffer类基础介绍
Buffer {
size_t readableBytes(); //获取缓冲区可读数据大小
const char* peek(); // 获取缓冲区的起始地址
int32_t peeklnt32() const; //尝试从缓冲区获取4字节数据,进行网络字节序转换为整形,但数据并不从缓冲区删除
void retrieventlnt32(); //数据读取位置向后偏移4字节,本质上就是删除起始位置的4字节数据
int32_t readlnt32();
string retrieveAllAsString(); //从缓冲区取出所有数据,当做string返回,并删除缓冲区中的数据
string retrieveAsString(size_t len),从缓冲区中取出len长度的数据,当做string返回,并删除缓冲区中的数据
}
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() constsize_t writableBytes() constconst char* peek() constconst char* findEOL() constconst char* findEOL(const char* start) constvoid retrieve(size_t len)void retrieveInt64()void retrieveInt32()void retrieveInt16()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() constvoid hasWritten(size_t len)void appendInt64(int64_t x)void appendInt32(int32_t x)void appendInt16(int16_t x)void appendInt8(int8_t x)int64_t readInt64()int32_t readInt32()int16_t readInt16()int8_t readInt8()int64_t peekInt64() constint16_t peekInt16() constint8_t peekInt8() constvoid prependInt64(int64_t x)void prependInt32(int32_t x)void prependInt16(int16_t x)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[];
};
2.3 - Muduo库快速上手
我们使用Muduo网络库来实现一个简单英译汉服务器和客户端 快速上手Muduo库
英译汉TCP服务器
包含头文件时指定路径
// 实现一个翻译服务器,客户端发来一个英语单词,返回一个汉语词典#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/net/Buffer.h>
#include <iostream>
#include <string>
#include <unordered_map>class DictServer
{
public:DictServer(int port): _server(&_baseloop, muduo::net::InetAddress("0.0.0.0", port), "DictServer", muduo::net::TcpServer::kReusePort){//设置连接事件(连接建立/管理)的回调_server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));//设置连接消息的回调_server.setMessageCallback(std::bind(&DictServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));}void start(){_server.start(); //先开始监听_baseloop.loop();//开始死循环执行事件监控}private:void onConnection(const muduo::net::TcpConnectionPtr &conn){if (conn->connected())std::cout << "连接建立!\n";elsestd::cout << "连接断开!\n";}void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp){static std::unordered_map<std::string, std::string> dict_map = {{"hello", "你好"},{"world", "世界"},{"bite", "比特"}};std::string msg = buf->retrieveAllAsString();std::string res;auto it = dict_map.find(msg);if (it != dict_map.end()){res = it->second;}else{res = "未知单词";}conn->send(res);}private:muduo::net::EventLoop _baseloop;muduo::net::TcpServer _server;
};int main()
{DictServer server(9090);server.start();return 0;
}
英译汉客户端
#include <muduo/net/TcpClient.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/net/EventLoopThread.h>
#include <muduo/net/Buffer.h>
#include <muduo/base/CountDownLatch.h>
#include <memory>
#include <iostream>
#include <string>class DictClient
{
public:DictClient(const std::string &sip, int sport):_baseloop(_loopthread.startLoop()) ,_downlatch(1), _client(_baseloop, muduo::net::InetAddress(sip, sport), "DictClient"){// 设置连接事件(连接建立/管理)的回调_client.setConnectionCallback(std::bind(&DictClient::onConnection, this, std::placeholders::_1));// 设置连接消息的回调_client.setMessageCallback(std::bind(&DictClient::onMessage, this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));//连接服务器_client.connect();_downlatch.wait();}bool send(const std::string &msg){if(_conn->connected() == false){std::cout << "链接已断开,发送数据失败!\n";return false;}_conn->send(msg);//_baseloop.loop(); //开始事件循环监控 -- 内部是个死循环,客户端不能直接使用return true;}private:void onConnection(const muduo::net::TcpConnectionPtr &conn){if (conn->connected()){std::cout << "连接建立!\n";_downlatch.countDown(); // 计数--,为0时唤醒阻塞_conn = conn;}else{std::cout << "连接断开!\n";_conn.reset();}}void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp){std::string res = buf->retrieveAllAsString();std::cout << res << std::endl;}private:muduo::net::TcpConnectionPtr _conn;muduo::CountDownLatch _downlatch;muduo::net::EventLoopThread _loopthread;muduo::net::EventLoop *_baseloop;muduo::net::TcpClient _client;
};int main()
{DictClient client("127.0.0.1",9090);while(1){std::string msg;std::cin >> msg;client.send(msg);}return 0;
}
Makefile
CFLAG=-I ../../build/release-install-cpp11/include/
LFLAF=-L ../../build/release-install-cpp11/lib -lmuduo_net -lmuduo_base -pthread
all: server client
server:server.cppg++ $(CFLAG) -o $@ $^ $(LFLAF) -std=c++11
client:client.cppg++ $(CFLAG) -o $@ $^ $(LFLAF) -std=c++11
C++11 异步操作
3.1 - std::future
介绍
std::future是C++11标准库中的一个模板类,它表示一个异步操作的结果。当我们在多线程编程中使用异步任务时,std::future可以帮助我们在需要的时候获取任务的执行结果。std::future的一个重要特性是能够阻塞当前线程,直到异步操作完成,从而确保我们在获取结果时不会遇到未完成的操作。
应用场景
- 异步任务: 当我们需要在后台执行一些耗时操作时,如网络请求或计算密集型任务等,std::future可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率
- 并发控制: 在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作
- 结果获取:std::future提供了一种安全的方式来获取异步任务的结果。我们可以使用std::future::get()函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。这样,在调用get()函数时,我们可以确保已经获取到了所需的结果
用法示例
使用 std::async关联异步任务
std::async是一种将任务与std::future关联的简单方法。它创建并运行一个异步任务,并返回一个与该任务结果关联的std::future对象。默认情况下,std::async是否启动一个新线程,或者在等待future时,任务是否同步运行都取决于你给的 参数。这个参数为std::launch类型:
std::launch::deferred 表明该函数会被延迟调用,直到在future上调用get()或者wait()才会开始执行任务
std::launch::async 表明函数会在自己创建的线程上运行
std::launch::deferred | std::launch::async 内部通过系统等条件自动选择策略
#include <iostream>
#include <thread>
#include <future>int Add(int num1, int num2)
{std::cout << "into add!\n";return num1 + num2;
}int main()
{//std::launch::async策略:内部创建一个线程执行函数,函数运行结果通过future获取//std::launch::deferred策略:同步策略,获取结果的时候再去执行任务//std::future<int> res = std::async(std::launch::deferred, Add, 11, 22);std::future<int> res = std::async(std::launch::async, Add, 11, 22);//进行了一个异步非阻塞调用std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "--------------------------\n";//std::future<int>::get() 用于获取异步任务的结果,如果还没有结果就会阻塞std::cout << res.get() << std::endl;return 0;
}
使用std::packaged_task和std::future配合
std::packaged_task就是将任务和 std::feature 绑定在一起的模板,是一种对任务的封装。我们可以通过std::packaged_task对象获取任务相关联的std::feature对象,通过调用get_future()方法获得。
std::packaged_task的模板参数是函数签名。
可以把std::future和std::async看成是分开的, 而 std::packaged_task则是一个整体。
#include<iostream>
#include<future>
#include<thread>
#include<memory>int Add(int num1, int num2)
{std::cout << "into add!\n";return num1 + num2;
}int main()
{//1. 封装任务auto task = std::make_shared<std::packaged_task<int(int, int)>>(Add);//2. 获取任务包关联的future对象std::future<int> res = task->get_future();std::thread thr([&task](){(*task)(11,22);});//4. 获取结果std::cout << res.get() << std::endl;thr.join();return 0;
}
使用std::promise和std::future配合
std::promise提供了一种设置值的方式,它可以在设置之后通过相关联的std::future对象进行读取。换种说法就是之前说过std::future可以读取一个异步函数的返回值了, 但是要等待就绪, 而std::promise就提供一种 方式手动让 std::future就绪
#include<iostream>
#include<future>
#include<thread>
#include<memory>int Add(int num1,int num2)
{std::cout << "into add!\n";return num1+num2;
}int main()
{//1.在使用的时候,先实例化一个指定结果的promise对象std::promise<int> pro;//2. 通过promise对象获取关联future对象std::future<int> res = pro.get_future();//3. 在任意位置给promise设置数据,就可以通过关联的future获取到这个设置的数据了std::thread thr([&pro](){int sum = Add(11,22);pro.set_value(sum);});std::cout << res.get() << std::endl;thr.join();return 0;
}
std::future本质上不是一个异步任务,而是一个辅助我们获取异步任务结果的东西
std::future并不能单独使用,需要搭配一些能够执行异步任务的模版类或函数一起使用
异步任务搭配使用
- std::async函数模版:异步执行一个函数,返回一个future对象用于获取函数结果
- std::packaged_task类模版:为一个函数生成一个异步任务结果(可调用对象),用于在其他线程中执行
- std::promise类模版:实例化的对象可以返回一个future,在其他线程中相promise对象设置数据,其他线程的关联future就可以获取数据
std::async是一个模版函数,内部会创建线程执行异步操作
std::packaged_task是一个模版类,是一个任务包,是对一个函数进行二次封装,封装乘一个课调用对象作为任务放到其他线程执行的
任务包封装好了以后,可以在任意位置进行调用,通过关联的future来获取执行结果
本篇完,下篇见!
相关文章:
【C++】从零实现Json-Rpc框架(2)
目录 JsonCpp库 1.1- Json数据格式 1.2 - JsonCpp介绍 • 序列化接口 • 反序列化接口 1.3 - Json序列化实践 JsonCpp使用 Muduo库 2.1 - Muduo库是什么 2.2 - Muduo库常见接口介绍 TcpServer类基础介绍 EventLoop类基础介绍 TcpConnection类基础介绍 TcpClient…...
JVM虚拟机篇(二):深入剖析Java与元空间(MetaSpace)
这里写目录标题 JVM虚拟机篇(二):深入剖析Java与元空间(MetaSpace)一、引言二、全面认识Java2.1 Java的起源与发展历程2.2 Java的特性2.2.1 简单性2.2.2 面向对象2.2.3 平台无关性2.2.4 健壮性2.2.5 安全性2.2.6 多线程…...
NDK开发:音视频处理基础
音视频处理基础 一、音视频基础 1.1 音视频基本概念 视频编码格式 H.264/AVCH.265/HEVCVP8/VP9AV1音频编码格式 AACMP3PCMOPUS封装格式 MP4FLVMKVTS1.2 音视频处理流程 视频处理流程 采集(Camera/Screen)预处理(美颜/滤镜)编码(H.264/H.265)封装传输/存储音频处理流程 …...
【数字电路】第一章 数制和码制
一、数码的基本概念 1.数制 2.码制 二、几种常用的数制 三、不同数制间的转换 八进制和十六进制间通常不直接进行转换,而是先转换成二进制或十进制然后再进行转换。 1.任意进制→十进制(N—十转换) 2.十进制→任意进制(十—N转换…...
软件工程面试题(二十九)
1、Internet的最顶级的商业域名叫什么? 答: .com 2、GC是什么,为什么要使用它? 垃圾回收 (garbage collection, GC) 一个跟踪过程,它传递性地跟踪指向当前使用的对象的所有指针,以便找到可以引用的所有对象,然后重新使用在此跟踪过程中未找到的任何堆内存。公共语言运行…...
6.第二阶段x64游戏实战-分析人物状态
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 上一个内容:5.第二阶段x64游戏实战-动态模块地址 人物状态是与角色相关的,如果…...
Synopsys:设计对象
相关阅读 Synopsyshttps://blog.csdn.net/weixin_45791458/category_12812219.html?spm1001.2014.3001.5482 对于Synopsys的EDA工具(如Design Compiler、PrimeTime、IC Compiler)等,设计对象(Design Objects)是组成整个设计的抽象表示&…...
Redis数据结构之Hash
目录 1.概述2.常见操作2.1 H(M)SET/H(M)GET2.2 HGETALL2.3 HDEL2.4 HLEN2.5 HEXISTS2.6 HKEYS/HVALS2.7 HINCRBY2.8 HSETNX 3.总结 1.概述 Hash是一个String类型的field(字段)和value(值)的映射表,而且value是一个键值对集合,类似Map<String, Map<…...
【VUE】RuoYi-Vue3项目结构的分析
【VUE】RuoYi-Vue3项目结构的分析 1. 项目地址2. RuoYi-Vue3项目结构2.1 整体结构2.2 package.json2.2.1 🧾 基本信息2.2.2 🔧 脚本命令(scripts)2.2.3 🌍 仓库信息2.2.4 📦 项目依赖(dependenc…...
libreoffice-help-common` 的版本(`24.8.5`)与官方源要求的版本(`24.2.7`)不一致
出现此错误的原因主要是软件包依赖冲突,具体分析如下: ### 主要原因 1. **软件源版本不匹配(国内和官方服务器版本有差距) 系统中可能启用了第三方软件源(如 PPA 或 backports 源),导致 lib…...
5.数据手册解读——共模电感
目录 1 共模电感的工作原理 2 核心参数解读 2.1 电气参数 2.2 阻抗特性 共模电感(Common mode Choke),也叫共模扼流圈,是在一个闭合磁环上对称绕制方向相反、匝数相同的线圈。理想的共模扼流圈对L(或N)与E之间的共模干扰具有抑…...
easy-poi 一对多导出
1. 需求: 某一列上下两行单元格A,B值一样且这两个单元格, 前面所有列对应单元格值一样的话, 就对A,B 两个单元格进行纵向合并单元格 1. 核心思路: 先对数据集的国家,省份,城市...... id 身份证进行排序…...
用C语言控制键盘上的方向键
各位同学,大家好!相信大家在学习C语言的过程中,都和我一样,经常使用scanf函数来接受字符,数字,这些标准输入信息,来实现自己设计的程序效果。 而我突然有一天(对就是今天)…...
第3课:状态管理与事件处理
第3课:状态管理与事件处理 学习目标 掌握useState Hook的使用理解组件事件处理机制实现表单输入与状态绑定完成任务添加功能原型 一、useState基础 1. 创建第一个状态 新建src/Counter.js: import { useState } from react;function Counter() {co…...
硬件工程师面试问题(五):蓝牙面试问题与详解
蓝牙技术作为物联网与智能设备的核心无线协议,其硬件设计能力直接影响产品连接稳定性、功耗及兼容性。面试是评估候选人射频电路设计、天线优化、协议栈调试等综合技能的关键环节,尤其在BLE低功耗设计、共存抗干扰等场景中,硬件工程师的实践经…...
leetcode4.寻找两个正序数组中的中位数
思路源于 LeetCode004-两个有序数组的中位数-最优算法代码讲解 基本思路是将两个数组看成一个数组,然后划分为两个部分,若为奇数左边部分个数多1,若为偶数左边部分等于右边部分个数。i表示数组1划分位置(i为4是索引4也表示i的左半…...
20250405在荣品的PRO-RK3566开发板使用Rockchip原厂的buildroot系统来适配gmac1
【暂时还没有解决让PRO-RK3566的eth0/gmac1开机就启动】 PRO-RK3566作为iperf服务器: rootrk3566-buildroot:/# ifconfig rootrk3566-buildroot:/# ifconfig -a rootrk3566-buildroot:/# ifconfig eth0 up rootrk3566-buildroot:/# ifconfig rootrk3566-buildroot:/…...
7. 记忆(Memory)机制:让AI拥有“短期记忆”与“长期记忆”
引言:当AI学会"记住你" 2025年某银行智能客服因无法记住用户身份,每次对话都要求重复验证,引发大量投诉。引入LangChain 记忆系统后,客户满意度提升62%。本文将基于MemorySaver与FAISS本地存储,教你构建符合…...
Chapter07_图像压缩编码
文章目录 图像压缩编码图像压缩编码基础图像压缩的基本概念信息相关信息冗余信源编码及其分类 图像编码模型信源编码器模型信源解码器模型 数字图像的信息熵信源符号码字平均长度信息熵信息量 变长编码费诺码霍夫曼编码 位平面编码格雷码 图像压缩编码 数字图像的压缩是指在满…...
网络安全之前端学习(css终章)
如大家所见,今天的文章就是css的最后一篇文章。那么话不多说,我们开始吧。本章内容比较杂,就是补充之前几章没讲到的。 关系选择器 之前我们讲到了很多选择器,这里补充一个关系选择器。 1.1后代选择器 后代选择器,…...
多线程代码案例 - 2
阻塞队列 阻塞队列,我们熟悉的概念是队列,即一种先进先出的数据结构。阻塞队列,就是基于普通队列做出的扩展。 特点 1. 线程安全的 2. 具有阻塞特性 (a)如果针对一个已经满了的队列进行入队列,此时入队操…...
Qt实现鼠标右键弹出弹窗退出
Qt鼠标右键弹出弹窗退出 1、鼠标右键实现1.1 重写鼠标点击事件1.2 添加头文件1.3 添加定义2、添加菜单2.1添加菜单头文件2.2创建菜单对象2.3 显示菜单 3、添加动作3.1添加动作资源文件3.2 添加头文件3.3 创建退出动作对象3.4菜单添加动作对象 4、在当前鼠标位置显示菜单4.1当前…...
AI绘画中的LoRa是什么?
Lora是一个多义词,根据不同的上下文可以指代多种事物。以下将详细介绍几种主要的含义: LoRa技术 LoRa(Long Range Radio)是一种低功耗广域网(LPWAN)无线通信技术,以其远距离、低功耗和低成本的特…...
LaTeX、KaTeX、Markdown 的用法
文章目录 1. LaTeX 用法概述1.1 LaTeX简介1.2 优点与应用场景2. LaTeX 基础语法2.1 文档结构2.2 文本格式化2.3 数学公式3. KaTeX 用法3.1 KaTeX简介3.2 基本使用方法3.2.1 引入KaTeX3.2.2 渲染数学公式3.2.3 自定义配置3.3 与LaTeX的兼容性4. Markdown 用法4.1 Markdown简介4.…...
Python 如何高效实现 PDF 内容差异对比
Python 如何高效实现 PDF 内容差异对比 1. 安装 PyMuPDF 库2. 获取 PDF 内容通过文件路径获取通过 URL 获取 3. 提取 PDF 每页信息4. 内容对比metadata 差异文本对比可视化对比 5. 提升对比效率通过哈希值快速判断页面是否相同早停机制多进程机制 6. 其他 最近有接触到 PDF 内容…...
JJJ:generic netlink例程分析
接嵌入式毕设、课设辅导、技术咨询,欢迎私信 完整代码:github代码仓链接 若想要和指定的generic netlink family通信,如: 994 static struct genl_family genl_ctrl __ro_after_init { // generic netlink子协议995 .module THIS_MODU…...
3D图像重建中Bundle Adjustment的推导与实现
介绍 捆集调整(Bundle Adjustment),也称为光束平差法,是一种利用来自多台相机的图像数据同时优化相机位置和姿态以及 3D 点位置的技术。该技术历史相当悠久,于 1958 年由 DC Brown1 首次提出。 最初这是美国空军正在进行的从航拍照片中恢复环境的研究,随着视觉SLAM和Sf…...
【代码模板】C语言如何修改文件权限?读写执行权限对应值是多少?(chmod(“./a.out“, 0741);bit 2 1 0表示 读 写 执行)
#include "stdio.h" #include "unistd.h"int main(int argc, char *argv[]) {if (chmod("./a.out", 0741) ! 0) {perror("Failed to set exec permission");return -1;}return 1; }0741中0是8进制,7是 0111, 4是…...
新版pycharm如何实现debug调试需要参数的python文件
在最顶上有这个选项 把鼠标移上去 点击号 选择python 具体长这样 名字随便取 script选择你要调试的python文件 脚本形参填入参数,如:--arg1 value1 --arg2 value2 点击应用确定 最后给文件打上断点,再点击调试按键,就可以调试了…...
赚钱模拟器-百宝库v0.1.1
#include<bits/stdc.h> #include<windows.h> using namespace std; int n,i,j; void welcome(); void zhuye(); void GAME(); int main(){welcome();zhuye();return 0; }void welcome(){cout<<"欢迎您使用更多资源-百宝库v0.1.1"<<endl;sys…...
实战打靶集锦-38-inclusiveness
文章目录 1. 主机发现2. 端口扫描&服务枚举3. 服务探查4.系统提权 靶机地址:https://download.vulnhub.com/inclusiveness/Inclusiveness.ova 1. 主机发现 目前只知道目标靶机在192.168.56.xx网段,通过如下的命令,看看这个网段上在线的主…...
01人工智能基础入门
一、AI应用场景和发展历程 1.1行业应用 1、deepdream图像生成、yolo目标检测 2、知识图谱、画风迁移 3、语音识别、计算机视觉 4、用户画像 5、百度人工智能布局 1.2发展历程 人工智能的发展经历了 3 个阶段: 1980年代是正式成形期,尚不具备影响力。 …...
SortedSet结构之用户积分实时榜单实战
Redis 中的SortedSet结构非常适合用于实现实时榜单的场景,它根据成员的分数自动进行排序,支持高效的添加、更新和查询操作。 SortedSet实时榜单的一些典型应用场景: 游戏中的玩家排行榜:在多人在线游戏中,使用 Sorte…...
C++_类和对象(上)
【本节目标】 面向过程和面向对象初步认识类的引入类的定义类的访问限定符及封装类的作用域.类的实例化类的对象大小的计算类成员函数的this指针 1. 面向过程和面向对象初步认识 C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过…...
vllm作为服务启动,无需额外编写sh文件,一步到位【Ubuntu】
看到网上有的vllm写法,需要额外建立一个.sh文件,还是不够简捷。这里提供一种直接编写service文件一步到位的写法: vi /etc/systemd/system/vllm.service [Unit] DescriptionvLLM Service Afternetwork.target[Service] Typesimple Userroot…...
Mathematics | Branch
注:本文为“遇见数学”翻译的 “数学分支概览” 两篇文章合辑。 数学世界的版图:主要分支概览(上) 原创 遇见数学 2025 年 04 月 03 日 12:02 河南 数学的分支(Areas of Mathematics) 在文艺复兴之前&am…...
8.5/Q1,Charls最新文章解读
文章题目:Associations of estimated glucose disposal rate with frailty progression: results from two prospective cohorts DOI:10.1186/s12933-025-02650-7 中文标题:估计葡萄糖处理率与虚弱进展的关系:两个前瞻性队列的结果…...
PCL学习(5)随机采样一致性算法RANSAC
一、RANSAC概念及作用 1.1 基本概念 RANSAC是一种鲁棒的参数估计方法,用于从包含大量异常值的数据中拟合数学模型。其核心思想是通过随机采样和迭代验证,找到最优的模型参数,避免异常值的干扰。 1.2 核心思想 随机采样:每次从数…...
app逆向专题一:如何下载app
app逆向专题一:如何下载app 一、打开豌豆荚官网 一、打开豌豆荚官网 打开豌豆荚官网豌豆荚,在右上角搜索框中输入要下载的app名称 依次点击搜索–查看–普通下载,即可将apk文件下载到本地电脑上。...
将 DataFrame 中某一列的列表拆分成多个独立的列的方式
要将 DataFrame 中某一列的列表拆分成多个独立的列,可以使用以下方法,具体取决于数据结构和需求: 场景示例 假设 DataFrame 中 genres 列存储的是列表(如 [drama, action]),目标是将每个列表元素拆分成多列…...
VUE+SPRINGBOOT+语音技术实现智能语音歌曲管理系统
语音控制歌曲的播放、暂停、增删改查 <template><div class"Music-container"><div style"margin: 10px 0"><!--检索部分--><el-input style"width: 200px;" placeholder"请输入歌曲名称"v-model"sen…...
安卓开发工程师-自定义 View
1. 如何创建一个简单的自定义 View? 创建自定义 View 的基本步骤如下: 继承 View 或其子类:自定义 View 可以继承自 View 或其他更具体的视图类(如 ImageView、Button 等)。重写构造方法:通常需要重写三个…...
Vue中权限控制的方案
文章目录 源码:一、页面级1.1、路由守卫1.2、动态路由 二、按钮级别2.1、通过v-if来判断2.2、通过组件包裹的方式来判断2.3、通过自定义指令的方式 三、接口级别 源码: https://gitee.com/liu-qiang-yyds/sysPermission 一、页面级 1.1、路由守卫 前端…...
磁盘分析工具合集:告别C盘焦虑!
今天李师傅带大家盘点五款硬盘空间分析利器,帮你精准定位那些"吃空间"的元凶,让C盘告别臃肿烦恼! 一、WizTree 这款NTFS磁盘的"透视眼"堪称效率典范。它通过直接读取硬盘主文件表(MFT)实现秒级扫描,1TB机械…...
硬件学习之器件篇-蜂鸣器
根据工作原理的不同,可以分为电磁式蜂鸣器和压电式蜂鸣器。 1、电磁式蜂鸣器 电磁式蜂鸣器根据内部是否有震荡源,又可以分为有源电磁式蜂鸣器和无源电磁式蜂鸣器。 1.1 外观区别 有源电磁式蜂鸣器从底部看是,是黑胶密封的。 无源电磁式蜂…...
紫檀博物馆一游与软件开发
今天去逛了中国紫檀博物馆,里边很多层展品,也有一些清代的古物,檀木,黄花梨木家具和各种摆件,馆主陈丽华女士也是发心复原、保留和弘扬中国的传统文化,和西游记唐僧扮演者迟成瑞先生一家。 每一件展品都精…...
Cribl 新建Datatype
Cribl 数据dataset 有个很重要的就是datatype, 下面来新建一下: 先看一下原来的datatype : 再点击Add Datatype: Rule...
开源 LLM 应用开发平台 Dify 全栈部署指南(Docker Compose 方案)
开源 LLM 应用开发平台 Dify 全栈部署指南(Docker Compose 方案) 一、部署环境要求与前置检查 1.1 硬件最低配置 组件要求CPU双核及以上内存4GB 及以上磁盘空间20GB 可用空间 1.2 系统兼容性验证 ✅ 官方支持系统: Ubuntu 20.04/22.04 L…...
医药档案区块链系统
1. 医生用户模块 目标用户:医护人员 核心功能: 检索档案:通过关键词或筛选条件快速定位患者健康档案。请求授权:向个人用户发起档案访问权限申请,需经对方确认。查看档案…...
Redis常见命令
(一)常见命令① 一、数据结构 二、通用命令 ①KEYS查询语句 pattern代表模板,有点像匹配表达式(不是正则),是redis的一种内置表达式,可以在里面使用通配符 底层存在一种模糊查询机制,效率并不高。当redis的数据量达到一定规模时(数百万上千万甚至更多),使用这种模糊查询机制会…...