Json-Rpc框架(Muduo库快速上手)

阅读导航

  • 引言
  • 一、Muduo库简介
  • 二、Muduo库常见接口
    • 1. TcpServer类基础介绍
    • 2. EventLoop类基础介绍
    • 3. TcpConnection类基础介绍
    • 4. TcpClient类基础介绍
    • 5. Buffer类基础介绍
  • 三、Muduo库使用示例
    • ⭕英译汉服务器
    • ⭕英译汉客户端

引言

在上一篇文章中,我们简要介绍了在项目中使用的JsonCpp库,这是一个广泛使用的C++ JSON解析和生成库,它为我们的项目提供了高效、灵活的数据序列化与反序列化能力。然而,在构建服务架构时,仅依靠数据序列化是远远不够的。高效的网络通信是确保系统稳定运行、提升用户体验的关键环节。为此,在本篇文章中,我们将聚焦于项目中用到的另一个重要库——Muduo网络库

Muduo是一个用于Linux多线程服务器的C++非阻塞网络库,它基于Reactor模式设计,提供了高性能的网络通信能力。它支持TCP、UDP等多种协议,并且拥有良好的可扩展性和灵活性。

一、Muduo库简介

Muduo,由陈硕大佬精心开发,是一个基于非阻塞IO和事件驱动的高性能C++ TCP网络编程库。它采用了主从Reactor模型,这种模型特别适用于构建高并发的网络服务器。在Muduo中,使用的线程模型被称为“one loop per thread”,这一理念的核心在于:

  • 一个线程对应一个事件循环(EventLoop):这意味着每个线程都维护着它自己的事件循环,该循环专门用于响应和处理该线程内的计时器事件以及IO事件。这样的设计有助于减少线程间的竞争和同步开销,提高系统的并发处理能力。

  • 一个文件描述符(或TCP连接)由单一线程处理:在Muduo中,每个TCP连接(或更一般地说,每个文件描述符)都被绑定到特定的EventLoop上,并由该EventLoop所在的线程负责其读写操作。这种绑定关系确保了数据的一致性和线程安全,避免了多线程同时操作同一资源可能导致的竞态条件和数据不一致问题。
    在这里插入图片描述

二、Muduo库常见接口

1. TcpServer类基础介绍

#include <memory>  
#include <functional>  
#include <string>  
#include "muduo/net/EventLoop.h"  
#include "muduo/net/InetAddress.h"  
#include "muduo/net/Timestamp.h"  
#include "muduo/net/Buffer.h"  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 std::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;  }  private:  ConnectionCallback connectionCallback_;  MessageCallback messageCallback_;  
};  

2. EventLoop类基础介绍

class EventLoop : noncopyable  
{  
public:  // 开始事件循环,直到调用quit()方法为止  void loop();  // 请求退出事件循环  void quit();  // 在指定的时间运行回调函数一次  // 参数time是回调应该被调用的时间戳  // 参数cb是当时间到达时应该被调用的回调函数  // 返回TimerId,可用于取消定时器  TimerId runAt(Timestamp time, TimerCallback cb);  // 在当前时间加上指定的延迟后运行回调函数一次  // 参数delay是延迟时间(秒)  // 参数cb是当延迟时间过后应该被调用的回调函数  // 返回TimerId,可用于取消定时器  TimerId runAfter(double delay, TimerCallback cb);  // 每隔指定的时间间隔重复运行回调函数  // 参数interval是时间间隔(秒)  // 参数cb是每隔interval秒应该被调用的回调函数  // 返回TimerId,可用于取消定时器  TimerId runEvery(double interval, TimerCallback cb);  // 取消指定的定时器  // 参数timerId是之前通过runAt、runAfter或runEvery方法返回的定时器ID  void cancel(TimerId timerId);  private:  // 原子变量,用于指示事件循环是否应该退出  std::atomic<bool> quit_;// 指向Poller对象的智能指针,Poller负责轮询I/O事件std::unique_ptr<Poller> poller_;  // 互斥锁,用于保护多线程访问共享数据  mutable MutexLock mutex_;  // 存储待执行函数的向量,这些函数将在事件循环的某个点被执行std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);  
};  

3. TcpConnection类基础介绍

class TcpConnection : noncopyable,  public std::enable_shared_from_this<TcpConnection>  
{  
public:  // 构造函数,用于创建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);  // 发送原始数据  void send(const void* message, int len);  // 使用StringPiece发送消息(StringPiece是Google的字符串切片类,用于高效处理字符串片段)  void send(const StringPiece& message);  // 发送Buffer对象中的数据  void send(Buffer* message);  // 关闭连接  void shutdown();  // 设置连接上下文,上下文可以是任意类型的数据,通过boost::any存储  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存储  boost::any context_;  // 连接的状态StateE state_;  };

4. TcpClient类基础介绍

class TcpClient : noncopyable  
{  
public:  // 构造函数,用于创建 TcpClient 对象。  // 需要提供事件循环指针、服务器地址和客户端名称。  TcpClient(EventLoop* loop,  const InetAddress& serverAddr,  const string& nameArg);  // 析构函数,声明为 out-of-line(在类定义外部实现),  // 以便处理 std::unique_ptr 成员(尽管在这个类的定义中没有直接显示)。  ~TcpClient();  // 连接到服务器。  void connect();  // 关闭连接。  void disconnect();  // 停止客户端操作,可能包括关闭连接和清理资源。  void stop();  // 获取客户端对应的通信连接 TcpConnection 对象的接口。  // 注意:在发起 connect 后,连接可能尚未建立成功。  TcpConnectionPtr connection() const  {  MutexLockGuard lock(mutex_); // 加锁以保护 connection_  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 在此类的定义中没有直接出现,但可能在其他地方使用  TcpConnectionPtr connection_ GUARDED_BY(mutex_); // 当前连接(受 mutex_ 保护)  mutable MutexLock mutex_; // 用于保护 connection_ 的互斥锁  
};  // CountDownLatch 类是一个同步辅助类,用于让一个或多个线程等待直到其他线程的一系列操作完成。  
// 它继承自 noncopyable 以防止被复制。  
class CountDownLatch : noncopyable  
{  
public:  // 构造函数,初始化计数器。  explicit CountDownLatch(int count);  // 等待计数器变为零。如果计数器不为零,则当前线程将阻塞。  void wait()  {  MutexLockGuard lock(mutex_); // 加锁以保护 count_ 和 condition_  while (count_ > 0) // 如果计数器大于零,则等待  {  condition_.wait(); // 释放锁并进入等待状态,直到被唤醒  }  }  // 将计数器减一。如果计数器变为零,则唤醒所有等待的线程。  void countDown()  {  MutexLockGuard lock(mutex_); // 加锁以保护 count_ 和 condition_  --count_; // 计数器减一  if (count_ == 0) // 如果计数器变为零  {  condition_.notifyAll(); // 唤醒所有等待的线程  }  }  // 获取当前计数器的值(主要用于调试)。  int getCount() const;  private:  mutable MutexLock mutex_; // 用于保护 count_ 和 condition_ 的互斥锁  Condition condition_ GUARDED_BY(mutex_); // 条件变量,与 mutex_ 一起使用以实现等待/通知机制  int count_ GUARDED_BY(mutex_); // 计数器,表示需要等待的操作数量  
};

5. Buffer类基础介绍

// Buffer 类是一个字节缓冲区类,支持从两端读写数据,以及处理整数和基本字符串。  
// 它继承自 muduo::copyable,表明这个类是可以被拷贝的。  
class Buffer : public muduo::copyable  
{  
public:  // 定义了一个便宜的前置空间大小,用于优化读操作。  static const size_t kCheapPrepend = 8;  // 定义了缓冲区的初始大小。  static const size_t kInitialSize = 1024;  // 构造函数,接受一个可选的初始大小参数。  // 缓冲区实际大小为 kCheapPrepend + initialSize,其中 kCheapPrepend 用于优化读操作。  explicit Buffer(size_t initialSize = kInitialSize)  : buffer_(kCheapPrepend + initialSize),  readerIndex_(kCheapPrepend),  writerIndex_(kCheapPrepend)  {}  // 与另一个Buffer对象交换内容。  void swap(Buffer& rhs);  // 返回可读字节数,即 writerIndex_ - readerIndex_。  size_t readableBytes() const;  // 返回可写字节数,即 buffer_.size() - writerIndex_。  size_t writableBytes() const;  // 返回一个指向可读数据的指针,但不移动读写索引。  const char* peek() const;  // 查找并返回指向缓冲区中第一个EOL(如"\r\n")的指针,从头开始搜索。  const char* findEOL() const;  // 查找并返回指向缓冲区中从指定位置开始的第一个EOL的指针。  const char* findEOL(const char* start) const;  // 从缓冲区中移除指定长度的数据。  void retrieve(size_t len);  // 移除并返回缓冲区中下一个 int64_t 类型的数据。  void retrieveInt64();  // 移除并返回缓冲区中下一个 int32_t 类型的数据。  void retrieveInt32();  // 移除并返回缓冲区中下一个 int16_t 类型的数据。  void retrieveInt16();  // 移除并返回缓冲区中下一个 int8_t 类型的数据。  void retrieveInt8();  // 移除并返回缓冲区中所有可读数据作为字符串。  string retrieveAllAsString();  // 移除并返回缓冲区中指定长度的数据作为字符串。  string retrieveAsString(size_t len);  // 向缓冲区末尾追加 StringPiece 对象。  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);  // 向缓冲区末尾追加一个 int64_t 类型的数据。  void appendInt64(int64_t x);  // 向缓冲区末尾追加一个 int32_t 类型的数据。  void appendInt32(int32_t x);  // 向缓冲区末尾追加一个 int16_t 类型的数据。  void appendInt16(int16_t x);  // 向缓冲区末尾追加一个 int8_t 类型的数据。  void appendInt8(int8_t x);  // 从缓冲区读取一个 int64_t 类型的数据,并移动读索引。  int64_t readInt64();  // 从缓冲区读取一个 int32_t 类型的数据,并移动读索引。  int32_t readInt32();  // 从缓冲区读取一个 int16_t 类型的数据,并移动读索引。  int16_t readInt16();  // 从缓冲区读取一个 int8_t 类型的数据,并移动读索引。  int8_t readInt8();  // 从缓冲区中查看(不移动读索引)下一个 int64_t 类型的数据。  int64_t peekInt64() const;  // 从缓冲区中查看(不移动读索引)下一个 int32_t 类型的数据。  int32_t peekInt32() const;  // 从缓冲区中查看(不移动读索引)下一个 int16_t 类型的数据。  int16_t peekInt16() const;  // 从缓冲区中查看(不移动读索引)下一个 int8_t 类型的数据。  int8_t peekInt8() const;  // 在缓冲区开头(readerIndex_ 之前)追加一个 int64_t 类型的数据。  void prependInt64(int64_t x);  // 在缓冲区开头(readerIndex_ 之前)追加一个 int32_t 类型的数据。  void prependInt32(int32_t x);  // 在缓冲区开头(readerIndex_ 之前)追加一个 int16_t 类型的数据。  void prependInt16(int16_t x);  // 在缓冲区开头(readerIndex_ 之前)追加一个 int8_t 类型的数据。  void prependInt8(int8_t x);  // 在缓冲区开头(readerIndex_ 之前)追加指定长度的数据。  void prepend(const void* /*restrict*/ data, size_t len);  private:  std::vector<char> buffer_; // 存储字节数据的向量。  size_t readerIndex_; // 读索引,指向下一个可读字节的位置。  size_t writerIndex_; // 写索引,指向下一个可写字节的位置。  static const char kCRLF[]; // 可能的行结束符,如 "\r\n"。  
};

三、Muduo库使用示例

接下来,我们将利用Muduo网络库来构建一个基础的英译汉翻译服务器及其对应的客户端。

⭕英译汉服务器

#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, // 使用_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";  } else {  std::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",  "世界"},  {"apple",  "苹果"}  };  // 从缓冲区中检索所有消息作为字符串  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;  // TCP服务器对象,用于监听和接受连接  muduo::net::TcpServer _server;  
};  int main()  
{  // 创建并初始化翻译服务器,监听9090端口  DictServer server(9090);  // 启动服务器  server.start();  return 0;  
}

⭕英译汉客户端

#include <muduo/net/TcpClient.h>  
#include <muduo/net/EventLoop.h>  
#include <muduo/net/EventLoopThread.h>  
#include <muduo/net/TcpConnection.h>  
#include <muduo/net/Buffer.h>  
#include <muduo/base/CountDownLatch.h>  
#include <iostream>  
#include <string>  // 定义一个字典客户端类  
class DictClient {  
public:  // 构造函数,初始化客户端  DictClient(const std::string &sip, int sport)  : _loopthread(), // 创建EventLoopThread对象,但此时不启动  _baseloop(_loopthread.startLoop()), // 启动EventLoopThread并获取其EventLoop  _downlatch(1), // 初始化CountDownLatch,计数为1,用于等待连接建立  _client(_baseloop, muduo::net::InetAddress(sip, sport), "DictClient") // 初始化TcpClient  {  // 设置连接事件(连接建立/断开)的回调函数  _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();  // 等待连接建立,这里使用CountDownLatch来阻塞当前线程  _downlatch.wait();  }  // 发送消息到服务器  bool send(const std::string &msg) {  if (!_conn->connected()) { // 检查连接是否仍然有效  std::cout << "连接已经断开,发送数据失败!\n";  return false;  }  _conn->send(msg); // 发送消息  return true;  }  private:  // 连接事件回调,处理连接建立或断开的情况  void onConnection(const muduo::net::TcpConnectionPtr &conn) {  if (conn->connected()) {  std::cout << "连接建立!\n";  _downlatch.countDown(); // 连接建立后,减少CountDownLatch的计数  _conn = conn; // 保存TcpConnectionPtr对象  } else {  std::cout << "连接断开!\n";  _conn.reset(); // 清除TcpConnectionPtr对象  }  }  // 消息接收回调,处理从服务器接收到的消息  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; // TcpConnection的智能指针,用于存储连接对象  muduo::CountDownLatch _downlatch; // 计数器,用于等待连接建立  muduo::net::EventLoopThread _loopthread; // 事件循环线程  muduo::net::EventLoop *_baseloop; // 指向EventLoopThread中EventLoop的指针  muduo::net::TcpClient _client; // TcpClient对象,用于连接和发送消息  
};  int main()  
{  // 创建字典客户端实例,连接到本地9090端口的服务器  DictClient client("127.0.0.1", 9090);  while(1) {  std::string msg;  std::cin >> msg; // 读取用户输入的消息  client.send(msg); // 发送消息到服务器  }  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/53851.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot教程(安装篇) | Docker Desktop的安装(Windows下的Docker环境)

SpringBoot教程&#xff08;安装篇&#xff09; | Docker Desktop的安装&#xff08;Windows下的Docker环境&#xff09; 前言如何安装Docker Desktop资源下载安装启动&#xff08;重点&#xff09;加入汉化包 设置加速镜像 前言 如果你在 Windows 上&#xff0c;确保 Docker …

Java实现找色和找图功能

某天&#xff0c;张三接到一个任务需求&#xff0c;将一个Excel表格里面的员工信息&#xff0c;录入到员工系统里面&#xff0c;由于数据量非常大&#xff0c;操作起来巨慢。经过一段时间的操作和观察&#xff0c;他发现这种操作&#xff0c;非常有规律&#xff0c;基本就是一些…

huggingface的transformers与datatsets的安装与使用

目录 1.安装 2.分词 2.1tokenizer.encode&#xff08;&#xff09; 2.2tokenizer.encode_plus &#xff08;&#xff09; 2.3tokenizer.batch_encode_plus&#xff08;&#xff09; 3.添加新词或特殊字符 3.1tokenizer.add_tokens&#xff08;&#xff09; 3.2 token…

第L4周:机器学习-KNN总结-分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 概念&#xff1a; 在第L4周&#xff1a;机器学习-K-邻近算法模型&#xff08;KNN&#xff09;-CSDN博客中学习了KNN的基本概念&#xff0c;本次主要加深印象&a…

锐捷 NBR 1300G路由器 越权CLI命令执行漏洞

漏洞描述 锐捷NBR 1300G路由器 越权CLI命令执行漏洞&#xff0c;guest账户可以越权获取管理员账号密码 漏洞复现 FOFA title"锐捷网络 --NBR路由器--登录界面" 请求包 POST /WEB_VMS/LEVEL15/ HTTP/1.1 Host: Connection: keep-alive Content-Length: 73 Autho…

硬件设计-噪声的学习

目录 LDO 噪声和 PSRR ​编辑 噪声类型 数据表中的噪声规格 哪种规格适合您的应用 如何降低 LDO 噪声&#xff1f; LDO 噪声的影响 LDO 噪声和 PSRR 低压差线性稳压器 (LDO) 为调节由较高电压输入产生的输出电压提供了一种简单方法。虽然操作简单&#xff0c;但其自生噪…

如何禁止电脑上某个软件运行?电脑设置禁止运行软件的4个方法速成

在日常使用电脑的过程中&#xff0c;可能会遇到需要禁止某些软件运行的情况。 无论是为了防止员工随意使用与工作无关的软件&#xff0c;还是为了管理孩子的电脑使用时间&#xff0c;禁止特定软件运行都是有效的解决方案。 下面介绍4个速成方法&#xff0c;帮你轻松禁止电脑上…

QQ机器人搭建

使用QQ官方机器人Python SDK和三方框架搭建QQ群聊机器人 文章目录 使用QQ官方机器人Python SDK和三方框架搭建QQ群聊机器人前言编写机器人代码机器人监听群聊进行文字回复机器人监听群聊进行图片回复机器人监听群聊进行文件发送机器人监听群聊进行视频发送机器人监听群聊进行语…

3.js - 运动曲线

这个球&#xff0c;绕着这个红色的线圈转 代码 import * as THREE from three import { OrbitControls } from three/examples/jsm/controls/OrbitControlslet scene,camera,renderer,controls nulllet moon,earth null// 根据&#xff0c;一系列的点&#xff0c;创建曲线 le…

【全新课程】正点原子《基于GD32 ARM32单片机项目实战入门》培训课程上线!

正点原子《基于GD32 ARM32单片机项目实战入门》全新培训课程上线啦&#xff01;正点原子工程师手把手教你学&#xff01;彻底解决ARM32单片机项目入门难的问题&#xff01; 一、课程介绍 本课程专为ARM32单片机的入门学习者设计&#xff0c;涵盖了环境搭建、编程软件使用、模…

ML 系列:机器学习和深度学习的深层次总结(08)—欠拟合、过拟合,正确拟合

ML 系列赛&#xff1a;第 9 天 — Under、Over 和 Good Fit 文章目录 一、说明二、了解欠拟合、过拟合和实现正确的平衡三、关于泛化四、欠拟合五、过拟合六、适度拟合七、结论 一、说明 在有监督学习过程中&#xff0c;对于指定数据集进行训练&#xff0c;训练结果存在欠拟合…

微软Win11 22H2/23H2 九月可选更新KB5043145发布!

系统之家于9月27日发出最新报道&#xff0c;微软针对Windows11系统&#xff0c;发布了九月最新可选更新补丁KB5043145&#xff0c;22H2用户安装后&#xff0c;系统版本号升至22621.4249&#xff0c;23H2用户安装后升至22631.4249。本次更新修复了Edge使用IE模式有时会停止响应等…

本地部署开源在线PPT制作与演示应用PPTist并实现异地远程使用

文章目录 前言1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统环境本地部署开源在线演示文稿应用PPTist&#xff0c;并结合cpolar内网穿透工具实现随时随地远程访问与使用该项目。 PPTist …

远程访问软路由

远程访问软路由主要涉及通过互联网从远程位置访问和控制基于软件的路由器系统。以下是远程访问软路由的一般方法&#xff1a; 一、远程访问软路由的方法 通过Web管理界面访问&#xff1a; 适用于大多数支持Web管理的软路由系统。用户只需在浏览器中输入软路由的公网IP地址或域…

[Linux#58][HTTP] 自己构建服务器 | 实现网页分离 | 设计思路

目录 一. 最简单的HTTP服务器 二.服务器 2.0 Protocol.hpp httpServer.hpp 子进程的创建和退出 子进程退出的意义 父进程关闭连接套接字 httpServer.cc argc (argument count) argv (argument vector) 三.服务器和网页分离 思考与补充&#xff1a; 一. 最简单的HTT…

ONFI 5.1:定义、缩写语和约定

address 该地址由一个行地址和一个列地址组成。行地址标识要访问的page、block和LUN。列地址标识要访问的page中的byte或word。 asynchronous 异步是指数据用WE_n信号进行写&#xff0c;RE_n信号进行读。 block 由多个page组成&#xff0c;是擦除操作的最小可寻址单元。 column…

安卓开发板_MTK开发板_联发科开发评估套件Demo板接口介绍

开发板是一种功能丰富的电路平台&#xff0c;专为开发人员设计&#xff0c;集成了多种传感器、扩展接口和通信模块。这使得开发者能够高效进行原型设计和功能验证&#xff0c;极大地简化了软硬件开发的过程。 此次介绍的安卓开发板由MT8788核心板与底板构成&#xff0c;特别之处…

OpenCV视频I/O(5)视频采集类VideoCapture之从视频流中获取下一帧的函数grab()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 从视频文件或捕获设备中抓取下一帧。 grab() 函数是 OpenCV 中 VideoCapture 类的一个成员函数&#xff0c;用于从视频流中获取下一帧而不立即检…

AB plc设备数据 转profinet IO项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 网关采集AB PLC数据 2 5 用PROFINET IO协议转发数据 4 6 案例总结 7 1 案例说明 设置网关采集AB PLC数据把采集的数据转成profinet IO协议转发给其他系统。 2 VFBOX网关工作原理 VFBOX网关是协议转换网关&#xff0…

Vue.js与Flask/Django全栈开发实战:从零搭建前后端分离的高效Web应用,打造现代化全栈开发体验!

将Vue.js与Flask或Django等后端框架配合使用&#xff0c;可以构建一个全栈的Web应用。以下是一个简要的指南&#xff0c;介绍如何将Vue.js与Flask或Django结合使用。 1. 准备工作 确保你已经安装了Node.js、npm&#xff08;或yarn&#xff09;以及Python和相应的包管理工具&am…