【RabbitMQ 项目】Muduo 库快速上手

news2024/11/24 19:30:39

Muduo 库快速上手

  • 一.Muduo 库简介
  • 二.五个常用类介绍
  • 三.结合 Protobuf 定制的应用层协议

一.Muduo 库简介

一句话概括,Muduo 库是基于非阻塞 IO 和事件驱动的 C++ 高并发 TCP 网络编程库。使用主从 Reactor 模型,使用的线程模型是是 one thread one loop

  1. Reactor:基于事件触发的模型(基于 epoll 进行 IO 事件监控)
  2. 主从 Reactor:将 IO 事件监控进一步划分,主 Reactor 线程只监控新建连接的事件,一旦连接事件触发,就会读取连接,然后执行回调函数;从 Reactor 线程针对新建连接进行 IO 事件监控,一旦 IO 事件触发,就会执行 IO 操作以及业务处理(我们设置的回调函数)。
  3. one thread one loop:一个线程只能有一个事件循环(EventLoop 对象),用于响应计时器和和 IO 事件
  4. 一个文件描述符只能由一个线程进行读写,换句话说一个 TCP 连接必须由某个 EventLoop 对象管理

二.五个常用类介绍

muduo::net::TcpServer:通常由服务端主线程调用其 start 接口,监听端口。通常需要配合 EventLoop 对象使用,循环地监控新建连接事件

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:用于循环地监控事件,需要调用 loop 接口开始监控,注意这是一个阻塞式接口

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_);
};

muduo::net::TcpConnection:代表一个连接,当连接事件触发时,会创建 TcpConnection 对象。我们在从 Reactor 线程的回调函数中通常会用到它的 send 方法

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++11
    void send(const void *message, int len);
    void send(const StringPiece &message);
    // void send(Buffer&& message); // C++11
    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::Buffer:用户层缓冲区,当普通 IO 事件触发时,会将接受缓冲区数据读取到 Buffer 对象中,我们在从 Reactor 线程的回调函数中通常会使用 retrieve 接口提取数据

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
        const char *findEOL() const
        const char *findEOL(const char *start) const
        void 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() const
        void 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() const
        int32_t peekInt32() const
        int16_t peekInt16() const
        int8_t peekInt8() const
        void 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[];
};

muduo::net::Client:调用其内部的 connect 与服务端建立连接,但 connect 是非阻塞接口,我们能 send 数据的前提是获取到一个有效的 TcpConnection,所以通常需要配合 CountDownLatch 来同步

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_);
};

三.结合 Protobuf 定制的应用层协议

在这里插入图片描述
在这里插入图片描述

Client.hpp:向外提供翻译和加法服务,向服务端发送这两种请求

#pragma once
#include "muduo/protobuf/codec.h"
#include "muduo/protobuf/dispatcher.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 <functional>
#include <iostream>

using std::cin;
using std::cout;
using std::endl;
class Client
{
public:
    typedef std::shared_ptr<ns_proto::TranslateResponse> translateResponsePtr;
    typedef std::shared_ptr<ns_proto::AddResponse> AddResponsePtr;
    typedef std::shared_ptr<google::protobuf::Message> MessagePtr;

private:
    muduo::net::EventLoopThread _loopThread;
    muduo::CountDownLatch _latch;
    muduo::net::TcpClient _client;
    muduo::net::TcpConnectionPtr _connPtr;
    ProtobufDispatcher _distpatcher;
    ProtobufCodec _codec;

public:
    Client(const std::string &serverIp, int serverPort)
        : _loopThread(),
          _latch(1),
          _client(_loopThread.startLoop(), muduo::net::InetAddress(serverIp, serverPort), "client"),
          _connPtr(),
          _distpatcher(std::bind(&Client::onUnknownMessage, this, std::placeholders::_1,
                                 std::placeholders::_2, std::placeholders::_3)),
          _codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_distpatcher, std::placeholders::_1,
                           std::placeholders::_2, std::placeholders::_3))
    {
        _client.setConnectionCallback(std::bind(&Client::onConnection, this, std::placeholders::_1));
        _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1,
                                             std::placeholders::_2, std::placeholders::_3));

        // 给响应分发器注册业务处理函数
        _distpatcher.registerMessageCallback<ns_proto::TranslateResponse>(std::bind(&Client::onTranslate,
                                                                                    this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        _distpatcher.registerMessageCallback<ns_proto::AddResponse>(std::bind(&Client::onAdd,
                                                                              this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    }

    void connect()
    {
       _client.connect(); //非阻塞
       _latch.wait();
    }

    void translate(const std::string& word)
    {
        ns_proto::TranslateRequest req;
        req.set_msg(word);
        send(_connPtr, req); //多态
    }

    void add(int num1, int num2)
    {
        ns_proto::AddRequest req;
        req.set_num1(num1);
        req.set_num2(num2);
        send(_connPtr, req); //多态
    }

private:
    // 给_client设置的回调
    void onConnection(muduo::net::TcpConnectionPtr connPtr)
    {
        if (connPtr->connected())
        {
            cout << "连接成功" << endl;
            _connPtr = connPtr;
            _latch.countDown();
        }
        else
        {
            cout << "连接关闭" << endl;
            _connPtr.reset();
        }
    }
    // 业务处理函数
    //分发器在回调函数表中找不到该请求时的处理方法
    void onUnknownMessage(muduo::net::TcpConnectionPtr connPtr, MessagePtr msgPtr, muduo::Timestamp time)
    {
        cout << "未知消息:" << msgPtr->GetTypeName() << endl;
        connPtr->shutdown();
    }

    void onTranslate(muduo::net::TcpConnectionPtr connPtr, translateResponsePtr respPtr, muduo::Timestamp time)
    {
        cout << respPtr->msg() << endl;
    }

    void onAdd(muduo::net::TcpConnectionPtr connPtr, AddResponsePtr respPtr, muduo::Timestamp time)
    {
        cout << respPtr->result() << endl;
    }

    //成员方法的子方法
    void send(muduo::net::TcpConnectionPtr connPtr, const google::protobuf::Message& message)
    {
        _codec.send(connPtr, message);
    }
};

Server.hpp:处理和响应客户端的翻译和加法请求

#pragma once
#include "muduo/protobuf/codec.h"
#include "muduo/protobuf/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 <functional>
#include <stdio.h>
#include <unistd.h>

using std::cin;
using std::cout;
using std::endl;

class Server
{
public:
    typedef std::shared_ptr<ns_proto::TranslateRequest> TranslateRequestPtr;
    typedef std::shared_ptr<ns_proto::TranslateResponse> TranslateResponsePtr;
    typedef std::shared_ptr<ns_proto::AddRequest> AddRequestPtr;
    typedef std::shared_ptr<ns_proto::AddResponse> AddResponsePtr;
    typedef std::shared_ptr<google::protobuf::Message> MessagePtr;

private:
    muduo::net::EventLoop _baseLoop;
    muduo::net::TcpServer _server;
    ProtobufDispatcher _dispatcher;
    ProtobufCodec _codec;

public:
    Server(int serverPort)
        : _baseLoop(),
          _server(&_baseLoop, muduo::net::InetAddress("0.0.0.0", serverPort), "TcpServer", 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))
    {
        //给_server注册两个回调函数
        _server.setConnectionCallback(std::bind(&Server::onConnection, this, std::placeholders::_1));
        _server.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1,
                                   std::placeholders::_2, std::placeholders::_3));

        //给分发器注册业务处理函数
        _dispatcher.registerMessageCallback<ns_proto::TranslateRequest>(std::bind(&Server::onTranslate, this, std::placeholders::_1, 
        std::placeholders::_2, std::placeholders::_3));
        _dispatcher.registerMessageCallback<ns_proto::AddRequest>(std::bind(&Server::onAdd, this, std::placeholders::_1, 
        std::placeholders::_2, std::placeholders::_3));
    }

    void start()
    {
        //开启监听状态
        _server.start();
        //开始循环监控事件
        _baseLoop.loop();
    }

private:
    // 给TcpServer设置的回调函数
    void onConnection(muduo::net::TcpConnectionPtr connPtr)
    {
        if (connPtr->connected())
        {
            cout << "连接成功" << endl;
        }
        else
        {
            cout << "连接关闭" << endl;
            connPtr.reset();
        }
    }

    // 业务处理函数
    void onUnknownMessage(muduo::net::TcpConnectionPtr connPtr, MessagePtr msgPtr, muduo::Timestamp time)
    {
        cout << "未知消息" << endl;
        connPtr->shutdown();
    }

    void onTranslate(muduo::net::TcpConnectionPtr connPtr, TranslateRequestPtr reqPtr, muduo::Timestamp time)
    {
        std::string word = reqPtr->msg();
        std::string desc;
        translate(word, &desc);
        //构建响应
        ns_proto::TranslateResponse resp;
        resp.set_msg(desc);
        //让协议处理器帮我们打包发送
        _codec.send(connPtr, resp);
    }

    void onAdd(muduo::net::TcpConnectionPtr connPtr, AddRequestPtr reqPtr, muduo::Timestamp time)
    {
        int num1 = reqPtr->num1();
        int num2 = reqPtr->num2();
        ns_proto::AddResponse resp;
        resp.set_result(num1 + num2);

        //让协议处理器帮我们发送
        _codec.send(connPtr, resp);
    }

    bool translate(const std::string &word, std::string *descPtr)
    {
        static std::unordered_map<std::string, std::string> dict = {{"hello", "你好"}, {"你好", "hello"}};
        if (dict.count(word) == 0)
        {
            *descPtr = "该单词不存在";
            return false;
        }
        else
        {
            *descPtr = dict[word];
            return true;
        }
    }
};

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

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

相关文章

JWT令牌——详解

目录 一、JWT是什么&#xff1f; 二、JWT的组成 三、JWT应用场景 四、生成和校验JWT令牌 五、具体应用 一、JWT是什么&#xff1f; 在当今的Web开发中&#xff0c;安全认证和授权变得尤为重要。JWT&#xff08;JSON Web Tokens&#xff09;是一种开放标准&#xff08;RFC …

大厂中秋福利哪家强?字节发被子,京东联名三星堆!网友:最强的还是我们......

又是一年中秋佳节&#xff0c;大家公司发的中秋福利都拿到手了嘛&#xff1f; 从上周开始&#xff0c;各社交网络平台上就有人陆续晒出了公司发放的中秋礼盒。 其中&#xff0c;财大气粗的互联网大厂的礼盒尤为精致亮眼。 今天&#xff0c;小码就带大家一起来看看大厂那些让人…

在线机房迁移之“玩转TiDB迁移”

作者&#xff1a; 代晓磊_Mars 原文来源&#xff1a; https://tidb.net/blog/02df4029 要想搞定在线机房迁移之TiDB数据库迁移&#xff0c;看完本文基本上所有的迁移方案你都可以搞定了&#xff08;数据库迁移方案和流程大同小异&#xff09;。本文给了3种TiDB在线迁移的场景…

四款音频剪辑软件免费使用,你更pick哪一个?

视频剪辑知随着软件的不断更新&#xff0c;入门门槛和操作难度也随之变得越来越低&#xff0c;但是依然有不少人不知道剪辑视频要用什么工具&#xff0c;作为一个视频剪辑爱好者&#xff0c;我尝试过不少编辑软件&#xff0c;今天就来跟大家分享一下四款视频剪辑软件在实际使用…

我与Linux的爱恋:yum和vim以及gcc、gdb、git的使用

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;Linux的学习 文章目录 ​1.Linux软件包管理器yum2.Linux开发工具3.Linux编译器 vimvim的基本概念vim的基本操作vim正常模式命令集vim末行模式命令集vim操作总结批量化注释批量化去注释简…

Oracle反向键索引Reverse Key Index

Oracle反向键索引&#xff08;Reverse Key Index&#xff09;是一种特殊的B-Tree索引&#xff0c;它在创建索引时对索引列的键值进行字节反转。这种索引的主要设计目的是为了解决在多实例环境&#xff08;如Oracle RAC&#xff09;中由于索引键值顺序插入导致的索引块争用问题。…

Vue 3 watchEffect:如何用 onInvalidate 优化副作用处理

在 Vue3 中&#xff0c;watchEffect 是一个用于在响应式数据变化时自动重新执行的函数。它在创建响应式副作用时特别有用&#xff0c;比如在某些数据变化时更新 DOM、发起网络请求或处理复杂的逻辑。 watchEffect 的 onInvalidate 是一个非常重要的功能&#xff0c;用于处理副…

新闻资讯类APP流量变现技巧——提升广告变现收益

新闻资讯类APP拥有庞大的用户基础&#xff0c;始终拥有较强的广告变现能力&#xff0c;如何在激烈的行业竞争中凸显媒体的优势&#xff0c;进而吸引更多的广告主&#xff1f;优化核心场景广告样式的同时&#xff0c;挖掘更多的广告场景样式&#xff1f;如何把握好广告变现和用户…

“Interface 和 Type 区别”深度解析

“Interface 和 Type 区别”深度解析 文章目录 一、Interface 和 Type 是什么二、如何使用 Interface 和 Type1. 定义 Interface2. 定义 Type3. 使用 Interface 和 Type4. 区别与联系 三、Interface 和 Type 二者有哪些区别&#xff0c;分别在哪些场景使用1. 区别2. 场景 四、扩…

将Ruoyi框架系统的Swagger接口文档页面优化为knife4j风格

将Swagger文档原来的页面效果改成比较好看的knife4j风格文档页面 优化前&#xff1a; 请求地址&#xff1a;http://localhost:端口号/swagger-ui/index.html 优化后&#xff1a; 请求地址&#xff1a;http://localhost:端口号/doc.html#/home 修改步骤&#xff1a; 1.引入依…

CPU调度算法之优先级调度

点击下载《CPU调度算法之优先级调度》 摘要 CPU的优先级调度算法是一种通过为不同任务分配优先级来决定执行顺序的调度策略。这种算法使得系统能够优先处理那些被认为更重要或紧急的任务&#xff0c;从而提高整体效率和响应速度。然而&#xff0c;优先级调度也可能带来一些问…

快速求和

请编写程序&#xff0c;输入整数 n&#xff0c;快速计算&#xff1a; 输入格式 n 输出格式 s 要求&#xff1a;输出 6 位小数&#xff0c;末位四舍五入。 代码如下&#xff1a; #include<stdio.h> int main(){int n;double s;scanf("%d",&n);s1.0-1.0/(n1…

DeepACO:用于组合优化的神经增强蚂蚁系统

文章目录 Abstract1 Introduction2 Related work2.1 神经组合优化2.2 蚁群优化3 蚁群优化初探4 Methodology4.1 参数化启发式空间4.2 局部搜索与局部神经引导扰动交织4.3 训练启发式学习器4.4 更好的探索4.4.1 多头解码器4.4.2 Top-k熵损失4.4.3 模仿损失5 实验5.1 实验设置5.2…

DWS=管理员用户创建

管理员用户简介 管理员也称作系统管理员&#xff0c;是指具有SYSADMIN属性的帐户。 非三权分立模式下&#xff0c;拥有系统的最高权限&#xff0c;能够执行所有的操作。系统管理员具有与对象所有者相同的权限。管理员用户创建 su - omm source /opt/huawei/Bigdata/mppdb/.mpp…

【数据结构-二维前缀最小值】力扣3148. 矩阵中的最大得分

给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格&#xff08;不必相邻&#xff09;。从值为 c1 的单元格移动到值为 c2 的单元格的得分为 c2 - c1 。 你可以从 任一 单元格开始&#xff0c;并且必须…

2024年汉字小达人校内选拔的常见问题和解答

广受上海市小学生们关注的2024年第十一届汉字小达人的时间已经确定了&#xff0c;参赛的孩子们已经开始紧锣密鼓的准备中。 如昨天分析的2024年汉字小达人的赛程&#xff0c;即日起到10月20日是学校自行选拔的时间节点。9月25-30日是区级自由报名集中参赛的日子。两个日子有一…

中秋佳节,如何挑选实用有意义的礼物?精选中秋节最佳送礼清单!

当秋风送爽&#xff0c;丹桂飘香&#xff0c;我们即将迎来一年一度的中秋佳节。在这个充满温情与团圆的节日里&#xff0c;赠送礼物成为了表达我们对家人、朋友以及同事的关怀与祝福的重要方式。然而&#xff0c;面对琳琅满目的商品&#xff0c;如何挑选出既实用又有意义的礼物…

《现代食品》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《现代食品》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《现代食品》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a; 中粮工程科技有限公司 主办单位…

韩国汽车工业的绿色革命:古瑞瓦特光伏逆变器助力能源转型与可持续发展

韩国汽车工业的绿色革命&#xff1a;古瑞瓦特光伏逆变器助力能源转型与可持续发展 汉江奇迹 带来韩国的工业化和现代化 能源的可持续供给 逐步成为韩国工业发展的关键议题为此&#xff0c;韩国颁布了「国家能源基本计划」 推动再生能源发展 预计到2030年新能源电力占总电力的20…

CCS10导入CCS3.3工程

选择工程 下一步 下一步 下一步 下一步 去掉XDAIS 下一步 下一步编译