基于muduo库函数实现protobuf协议的通信

news2024/10/13 10:34:10

文章目录

  • 先定义具体的业务请求类型
  • 2. 实现服务端提供的服务
  • protobuf_server.cpp
  • protobuf_client.cpp

建议先去了解muduo库和protobuf协议:

  1. Protobuf库的使用
  2. Muduo库介绍及使用

先定义具体的业务请求类型

先使用protobuf库创建我们所要完成的业务请求类型,英译汉和加法服务器和客⼾端

创建request.proto

syntax = "proto3";
package nzq;
//接下来定义rpc翻译请求信息结构 
message TranslateRequest {
 string msg = 1;
}
//接下来定义rpc翻译响应信息结构 
message TranslateResponse {
 string msg = 1;
}
//定义rpc加法请求信息结构 
message AddRequest {
 uint32 num1 = 1;
 uint32 num2 = 2;
}
//定义rpc加法响应信息结构 
message AddResponse {
 uint32 result = 1;
}

protoc --cpp_out=. request.proto

在这里插入图片描述

2. 实现服务端提供的服务

在实现具体服务前,先介绍⼀下muduo库中内部实现的关于简单的基于protobuf的接⼝类

ProtobufCodec类是muduo库中对于protobuf协议的处理类,其内部实现了onMessage回调接,对于接收到的数据进⾏基于protobuf协议的请求处理,然后将解析出的信息,存放到对应请求的protobuf请求类对象中,然后最终调⽤设置进去的消息处理回调函数进⾏对应请求的处理。

/*muduo-master/examples/protobuf/codec.h*/
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
//
// FIXME: merge with RpcCodec
//
class ProtobufCodec : muduo::noncopyable
{
public:
    enum ErrorCode
    {
        kNoError = 0,
        kInvalidLength,
        kCheckSumError,
        kInvalidNameLen,
        kUnknownMessageType,
        kParseError,
    };
    typedef std::function<void(const muduo::net::TcpConnectionPtr &,
                               const MessagePtr &,
                               muduo::Timestamp)>
        ProtobufMessageCallback;
    // 这⾥的messageCb是针对protobuf请求进⾏处理的函数,它声明在dispatcher.h中的
    ProtobufDispatcher类 explicit ProtobufCodec(const ProtobufMessageCallback &messageCb)
        : messageCallback_(messageCb), // 这就是设置的请求处理回调函数
          errorCallback_(defaultErrorCallback)
    {
    }
    // 它的功能就是接收消息,进⾏解析,得到了proto中定义的请求后调⽤设置的messageCallback_
    进⾏处理
    void onMessage(const muduo::net::TcpConnectionPtr &conn,
                   muduo::net::Buffer *buf,
                   muduo::Timestamp receiveTime);
    // 通过conn对象发送响应的接⼝
    void send(const muduo::net::TcpConnectionPtr &conn,
              const google::protobuf::Message &message)
    {
        // FIXME: serialize to TcpConnection::outputBuffer()
        muduo::net::Buffer buf;
        fillEmptyBuffer(&buf, message);
        conn->send(&buf);
    }
    static const muduo::string &errorCodeToString(ErrorCode errorCode);
    static void fillEmptyBuffer(muduo::net::Buffer *buf, const google::protobuf::Message &message);
    static google::protobuf::Message *createMessage(const std::string &
                                                        type_name);
    static MessagePtr parse(const char *buf, int len, ErrorCode *errorCode);

private:
    static void defaultErrorCallback(const muduo::net::TcpConnectionPtr &,
                                     muduo::net::Buffer *,
                                     muduo::Timestamp,
                                     ErrorCode);
    ProtobufMessageCallback messageCallback_;
    ErrorCallback errorCallback_;
    const static int kHeaderLen = sizeof(int32_t);
    const static int kMinMessageLen = 2 * kHeaderLen + 2;        // nameLen + typeName +
    checkSum const static int kMaxMessageLen = 64 * 1024 * 1024; // same as codec_stream.h
    kDefaultTotalBytesLimit
};
};

ProtobufDispatcher类,这个类就⽐较重要了,这是⼀个protobuf请求的分发处理类,我们⽤⼾在使⽤的时候,就是在这个类对象中注册哪个请求应该⽤哪个业务函数进⾏处理。

它内部的onProtobufMessage接⼝就是给上边ProtobufCodec::messageCallback_设置的回调函数,相当于ProtobufCodec中onMessage接⼝会设置给服务器作为消息回调函数,其内部对于接收到的数据进⾏基于protobuf协议的解析,得到请求后,通过ProtobufDispatcher::onProtobufMessage接⼝进⾏请求分发处理,也就是确定当前请求应该⽤哪⼀个注册的业务函数进⾏处理。

typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
class Callback : muduo::noncopyable
{
public:
    virtual ~Callback() = default;
    virtual void onMessage(const muduo::net::TcpConnectionPtr &,
                           const MessagePtr &message,
                           muduo::Timestamp) const = 0;
};
// 这是⼀个对函数接⼝进⾏⼆次封装⽣成⼀个统⼀类型对象的类
template <typename T>
class CallbackT : public Callback
{
    static_assert(std::is_base_of<google::protobuf::Message, T>::value,
                  "T must be derived from gpb::Message.");

public:
    typedef std::function<void(const muduo::net::TcpConnectionPtr &,
                               const std::shared_ptr<T> &message,
                               muduo::Timestamp)>
        ProtobufMessageTCallback;
    CallbackT(const ProtobufMessageTCallback &callback)
        : callback_(callback)
    {
    }
    void onMessage(const muduo::net::TcpConnectionPtr &conn,
                   const MessagePtr &message,
                   muduo::Timestamp receiveTime) const override
    {
        std::shared_ptr<T> concrete = muduo::down_pointer_cast<T>(message);
        assert(concrete != NULL);
        callback_(conn, concrete, receiveTime);
    }

private:
    ProtobufMessageTCallback callback_;
};
// 这是⼀个protobuf请求分发器类,需要⽤⼾注册不同请求的不同处理函数,
// 注册完毕后,服务器收到指定请求就会使⽤对应接⼝进⾏处理
class ProtobufDispatcher
{
public:
    typedef std::function<void(const muduo::net::TcpConnectionPtr &,
                               const MessagePtr &message,
                               muduo::Timestamp)>
        ProtobufMessageCallback;
    // 构造对象时需要传⼊⼀个默认的业务处理函数,以便于找不到对应请求的处理函数时调⽤。
    explicit ProtobufDispatcher(const ProtobufMessageCallback &defaultCb)
        : defaultCallback_(defaultCb)
    {
    }
    // 这个是⼈家实现的针对proto中定义的类型请求进⾏处理的函数,内部会调⽤我们⾃⼰传⼊的业务
    处理函数
    void onProtobufMessage(const muduo::net::TcpConnectionPtr &conn,
                           const MessagePtr &message,
                           muduo::Timestamp receiveTime) const
    {
        CallbackMap::const_iterator it = callbacks_.find(message->GetDescriptor());
        if (it != callbacks_.end())
        {
            it->second->onMessage(conn, message, receiveTime);
        }
        else
        {
            defaultCallback_(conn, message, receiveTime);
        }
    }
    /*
    这个接⼝⾮常巧妙,基于proto中的请求类型将我们⾃⼰的业务处理函数与对应的请求给关联起来
   了
    相当于通过这个成员变量中的CallbackMap能够知道收到什么请求后应该⽤什么处理函数进⾏处理
    简单理解就是注册针对哪种请求--应该⽤哪个我们⾃⼰的函数进⾏处理的映射关系

    但是我们⾃⼰实现的函数中,参数类型都是不⼀样的⽐如翻译有翻译的请求类型,加法有加法请求
   类型
    ⽽map需要统⼀的类型,这样就不好整了,所以⽤CallbackT对我们传⼊的接⼝进⾏了⼆次封装。
    */
    template <typename T>
    void registerMessageCallback(const typename CallbackT<T>::ProtobufMessageTCallback &callback)
    {
        std::shared_ptr<CallbackT<T>> pd(new CallbackT<T>(callback));
        callbacks_[T::descriptor()] = pd;
    }

private:
    typedef std::map<const google::protobuf::Descriptor *,
                     std::shared_ptr<Callback>>
        CallbackMap;
    CallbackMap callbacks_;
    ProtobufMessageCallback defaultCallback_;
};

⽽能实现请求与函数之间的映射,还有⼀个⾮常重要的元素:那就是应⽤层协议

在这里插入图片描述

protobuf根据我们的proto⽂件⽣成的代码中,会⽣成对应类型的类,⽐如TranslateRequest对应了⼀个TranslateRequest类,⽽不仅仅如此,protobuf⽐我们想象中做的事情更多,每个对应的类中,都包含有⼀个描述结构的指针:

在这里插入图片描述
这个描述结构⾮常重要,其内部可以获取到当前对应类类型名称,以及各项成员的名称,因此通过这些名称,加上协议中的typename字段,就可以实现完美的对应关系了.

在这里插入图片描述

protobuf_server.cpp

服务端同之前实现的muduo库的翻译服务器区别不大,加上protobuf协议后实际上就多了两个类成员:

  1. 请求分发器对象–要向其中注册请求处理函数 ProtobufDispatcher _dispatcher;
  2. protobuf协议处理器–针对收到的请求数据进行protobuf协议处理 rotobufCodec _codec;
#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 <iostream>
#include <unordered_map>

class Server {
    public:
        typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
        typedef std::shared_ptr<nzq::TranslateRequest> TranslateRequestPtr;
        typedef std::shared_ptr<nzq::AddRequest> AddRequestPtr;
        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)){
            
            //注册业务请求处理函数
            //messagecallback有两个是因为要完成翻译和加法两个业务
            _dispatcher.registerMessageCallback<nzq::TranslateRequest>(std::bind(&Server::onTranslate, this, 
                std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
                
            _dispatcher.registerMessageCallback<nzq::AddRequest>(std::bind(&Server::onAdd, 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:
        std::string translate(const std::string &str) {
            static std::unordered_map<std::string, std::string> dict_map = {
                {"hello", "你好"},
                {"Hello", "你好"},
                {"你好", "Hello"},
                {"吃了吗", "油泼面"}
            };
            auto it = dict_map.find(str);
            if (it == dict_map.end()) {
                return "没听懂!!";
            }
            return it->second;
        }
        void onTranslate(const muduo::net::TcpConnectionPtr& conn, const TranslateRequestPtr& message, muduo::Timestamp) {
            //1. 提取message中的有效消息,也就是需要翻译的内容
            std::string req_msg = message->msg();
            //2. 进行翻译,得到结果
            std::string rsp_msg = translate(req_msg);
            //3. 组织protobuf的响应
            nzq::TranslateResponse resp;
            resp.set_msg(rsp_msg);
            //4. 发送响应
            _codec.send(conn, resp);
        }
        void onAdd(const muduo::net::TcpConnectionPtr& conn, const AddRequestPtr& message, muduo::Timestamp) {
            int num1 = message->num1();
            int num2 = message->num2();
            int result = num1 + num2;
            nzq::AddResponse resp;
            resp.set_result(result);
            _codec.send(conn, resp);
        }
        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;
}

protobuf_client.cpp

服务端同之前实现的muduo库的翻译服务器区别不大,加上protobuf协议后实际上就多了两个类成员:

  1. 请求分发器对象–要向其中注册请求处理函数 ProtobufDispatcher _dispatcher;
  2. protobuf协议处理器–针对收到的请求数据进行protobuf协议处理 rotobufCodec _codec;
#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 <iostream>

class Client {
    public:
        typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
        typedef std::shared_ptr<nzq::AddResponse> AddResponsePtr;
        typedef std::shared_ptr<nzq::TranslateResponse> TranslateResponsePtr;
        Client(const std::string &sip, int sport):
            _latch(1), _client(_loopthread.startLoop(), muduo::net::InetAddress(sip, sport), "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<nzq::TranslateResponse>(std::bind(&Client::onTranslate, this, 
                std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
                
            _dispatcher.registerMessageCallback<nzq::AddResponse>(std::bind(&Client::onAdd, 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));      
        }
        void connect() {
            _client.connect();
            _latch.wait();//阻塞等待,直到连接建立成功
        }
        void Translate(const std::string &msg){
            bit::TranslateRequest req;
            req.set_msg(msg);
            send(&req);
        }
        void Add(int num1, int num2) {
            bit::AddRequest req;
            req.set_num1(num1);
            req.set_num2(num2);
            send(&req);
        }
    private:
        bool send(const google::protobuf::Message *message) {
            if (_conn->connected()) {//连接状态正常,再发送,否则就返回false
                _codec.send(_conn, *message);
                return true;
            }
            return false;
        }  
        void onTranslate(const muduo::net::TcpConnectionPtr& conn, const TranslateResponsePtr& message, muduo::Timestamp) {
            std::cout << "翻译结果:" << message->msg() << std::endl;
        }
        void onAdd(const muduo::net::TcpConnectionPtr& conn, const AddResponsePtr& message, muduo::Timestamp) {
            std::cout << "加法结果:" << message->result() << std::endl;
        }
        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()) {
                _latch.countDown();//唤醒主线程中的阻塞
                _conn = conn;
            }else {
                //连接关闭时的操作
                _conn.reset();
            }
        }
    private:
        muduo::CountDownLatch _latch;//实现同步的
        muduo::net::EventLoopThread _loopthread;//异步循环处理线程
        muduo::net::TcpConnectionPtr _conn;//客户端对应的连接
        muduo::net::TcpClient _client;//客户端
        ProtobufDispatcher _dispatcher;//请求分发器
        ProtobufCodec _codec;//协议处理器
};

int main() 
{
    Client client("127.0.0.1", 8085);
    client.connect();

    client.Translate("hello");
    client.Add(11, 22);

    sleep(1);
    return 0;
}

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

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

相关文章

域内用户名枚举 实验

1. 实验网络拓扑 kali: 192.168.72.128win2008: 192.168.135.129 192.168.72.139win7: 192.168.72.149win2012:(DC) 192.168.72.131 2. 简单原理 详细的报文分析在之前写过了&#xff0c;这里简单提一提。 利用的是Kerberos的AS阶段&#xff0c;AS_REP的回显不同&#xff0c…

迷宫中的最短路径:如何用 BFS 找到最近出口【算法模板】

如何通过广度优先搜索&#xff08;BFS&#xff09;求解迷宫问题 在这篇文章中&#xff0c;我们将学习如何使用 广度优先搜索&#xff08;BFS&#xff09; 解决一个典型的迷宫问题&#xff0c;具体是从迷宫的一个入口出发&#xff0c;找到最近的出口。我们将一步步分析 BFS 是如…

初识CyberBattleSim

现在许多企业都在使用AD域服务进行管理&#xff0c;我们现在通俗理解里面蕴含着许多重要资产。 对于这个东西有下列的描述: 1、攻击他能够获得用户权限 2、里面存在许多的计算机资产&#xff0c;当攻击者攻击其中的一台机器&#xff0c;可以通过某种手段在域中的环境横向移动…

golang rpc

RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;简单的理解是一个节点请求另一个节点提供的服务&#xff0c;对应rpc的是本地过程调用&#xff0c;函数调用是最常用的本地过程调用&#xff0c;将本地过程调用变成远程调用会面临着各种问题。 以两数…

第 21 章 一条记录的多幅面孔——事务的隔离级别与 MVCC

21.1 事前准备 CREATE TABLE hero ( number INT, NAME VARCHAR ( 100 ), country VARCHAR ( 100 ), PRIMARY KEY ( number ) ) ENGINE INNODB CHARSET utf8;INSERT INTO hero VALUES ( 1, 刘备, 蜀 );21.2 事务隔离级别 在保证事务隔离性的前提下&#xff0c;使用不同的隔…

【Burp入门第三十三篇】IP Rotate 插件实现IP轮换爆破

Burp Suite是一款功能强大的渗透测试工具,被广泛应用于Web应用程序的安全测试和漏洞挖掘中。 本专栏将结合实操及具体案例,带领读者入门、掌握这款漏洞挖掘利器 读者可订阅专栏:【Burp由入门到精通 |CSDN秋说】 文章目录 正文安装步骤使用步骤应用场景实战文章正文 在 Burp…

基于SpringBoot+Vue+MySQL的智能垃圾分类系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着城市化进程的加速&#xff0c;垃圾问题日益凸显&#xff0c;不仅对环境造成污染&#xff0c;也给城市管理带来了巨大挑战。传统的垃圾分类方式不仅费时费力&#xff0c;而且手工操作容易出现错误&#xff0c;导致垃圾分类效…

探索未来工业自动化的钥匙:OPC UA与AI的融合

文章目录 探索未来工业自动化的钥匙&#xff1a;OPC UA与AI的融合背景&#xff1a;为什么选择OPC UA&#xff1f;OPC UA库简介安装OPC UA库简单的库函数使用方法连接到服务器获取节点读取节点值设置节点值订阅数据变更 库的使用场景工业自动化监控能源管理系统预测性维护 常见问…

L8910 【哈工大_操作系统】CPU管理的直观想法多进程图像用户级线程

L2.1 CPU管理的直观想法 管理CPU -> 引出多进程视图 设置 PC 指针初值为程序在内存中开始的地址&#xff0c;自动取指执行多个程序同时放在内存中&#xff0c;让CPU交替执行&#xff08;并发&#xff1a;程序在读I/O时太慢&#xff0c;CPU空闲&#xff0c;则会去执行其他程序…

Jupyterhub 多用户分析平台在线和离线部署(自定义用户认证)

Jupyterhub 文章目录 Jupyterhub1、简介2、安装配置&#xff08;在线&#xff09;2.1 安装准备2.2 安装jupyterhub2.2 自定义身份验证器2.3 自定义单用户jupyter服务生成器2.4 配置 jupyterhub_config.py2.4 启动服务2.5 登录测试2.5.1 用户登录 http://da.db.com2.5.2 管理界面…

synchronized底层是怎么通过monitor进行加锁的?

一、monitor是什么 monitor叫做对象监视器、也叫作监视器锁&#xff0c;JVM规定了每一个java对象都有一个monitor对象与之对应&#xff0c;这monitor是JVM帮我们创建的&#xff0c;在底层使用C实现的。 ObjectMonitor() {_header;_count ; // 非常重要&#xff0c;表示锁计数…

3 个简单的微分段项目

与许多大型网络安全项目一样&#xff0c;微分段似乎很复杂、耗时且成本高昂。 它涉及管理有关设备间服务连接的复杂细节。 一台 Web 服务器应连接到特定数据库&#xff0c;但不连接到其他数据库&#xff0c;或者负载平衡器应连接到某些 Web 服务器&#xff0c;同时限制与其他…

图解大模型计算加速系列:vLLM源码解析1,整体架构

整个vLLM代码读下来&#xff0c;给我最深的感觉就是&#xff1a;代码呈现上非常干净历练&#xff0c;但是逻辑比较复杂&#xff0c;环环嵌套&#xff0c;毕竟它是一个耦合了工程调度和模型架构改进的巨大工程。 所以在源码解读的第一篇&#xff0c;我想先写一下对整个代码架构…

Golang | Leetcode Golang题解之第449题序列化和反序列化二叉搜索树

题目&#xff1a; 题解&#xff1a; type Codec struct{}func Constructor() (_ Codec) { return }func (Codec) serialize(root *TreeNode) string {arr : []string{}var postOrder func(*TreeNode)postOrder func(node *TreeNode) {if node nil {return}postOrder(node.Le…

java基础 day1

学习视频链接 人机交互的小故事 微软和乔布斯借鉴了施乐实现了如今的图形化界面 图形化界面对于用户来说&#xff0c;操作更加容易上手&#xff0c;但是也存在一些问题。使用图形化界面需要加载许多图片&#xff0c;所以消耗内存&#xff1b;此外运行的速度没有命令行快 Wi…

针对考研的C语言学习(2019链表大题)

题目解析&#xff1a; 【考】双指针算法&#xff0c;逆置法&#xff0c;归并法。 解析&#xff1a;因为题目要求空间复杂度为O(1)&#xff0c;即不能再开辟一条链表&#xff0c;因此我们只能用变量来整体挪动原链表。 第一步先找出中间节点 typedef NODE* Node; Node find_m…

latex有哪些颜色中文叫什么,Python绘制出来

latex有哪些颜色中文叫什么&#xff0c;Python绘制出来 为了展示xcolor包预定义的颜色及其对应的中文名称&#xff0c;并使用Python打印出来&#xff0c;我们可以先列出常见的预定义颜色名称&#xff0c;然后将它们翻译成中文&#xff0c;并最后用Python打印出来。 步骤 列出…

家庭记账本的设计与实现+ssm(lw+演示+源码+运行)

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;家庭记账本小程序被用户普遍使用&#xff0c;为方便用户能…

MySQL高阶2066-账户余额

目录 题目 准备数据 分析数据 总结 题目 请写出能够返回用户每次交易完成后的账户余额. 我们约定所有用户在进行交易前的账户余额都为0&#xff0c; 并且保证所有交易行为后的余额不为负数。 返回的结果请依次按照 账户&#xff08;account_id), 日期( day ) 进行升序排序…

leetcode_238:除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂…