[微服务即时通讯系统]3.服务端-环境搭建

本专栏内容为:项目专栏
💓博主csdn个人主页:小小unicorn

⏩专栏分类:微服务即时通讯系统

🚚代码仓库:小小unicorn的代码仓库🚚

🌹🌹🌹关注我带你学习编程知识

环境安装

ODB

现在根目录下,创建一个Build2目录,然后进入官网:

shell 复制代码
cd ~/build2-build

curl -sSfO https://download.build2.org/0.17.0/build2-install-0.17.0.sh

shasum -a 256 -b build2-install-0.17.0.sh
b84e4114c61aa94c3f6278f010a0dc0536dda65ac39d3863152ec9b64510b86e

sh build2-install-0.17.0.sh --timeout 1800

检查一致后,进行安装即可:

安装完成后如上图所示。

安装 odb-compiler

shell 复制代码
#注意这里的 gcc-11 需要根据你自己版本而定
sudo apt-get install gcc-11-plugin-dev
mkdir odb-build && cd odb-build
bpkg create -d odb-gcc-N cc\
config.cxx=g++ \
config.cc.coptions=-O3 \
config.bin.rpath=/usr/lib \
config.install.root=/usr/ \
config.install.sudo=sudo
cd odb-gcc-N
bpkg build odb@https://pkg.cppget.org/1/beta
bpkg test odb
test odb-2.5.0-b.25+1/tests/testscript{testscript}
tested odb/2.5.0-b.25+1
bpkg install odb
odb --version
bash: /usr/bin/odb: No such file or directory
#如果报错了,找不到 odb,那就在执行下边的命令
sudo echo 'export
PATH=${PATH}:/usr/local/bin' >> ~/.bashrc
export PATH=${PATH}:/usr/local/bin
odb --version
ODB object-relational mapping (ORM) compiler for C++ 2.5.0-b.25
Copyright (c) 2009-2023 Code Synthesis Tools CC.
This is free software; see the source for copying conditions.
There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.

先看版本:

安装的时候,输入版本11:

shell 复制代码
sudo apt-get install gcc-11-plugin-dev

创建目录:

shell 复制代码
mkdir odb-build && cd odb-build

进入到目录后安装:

shell 复制代码
bpkg create -d odb-gcc-N cc\
config.cxx=g++ \
config.cc.coptions=-O3 \
config.bin.rpath=/usr/lib \
config.install.root=/usr/ \
config.install.sudo=sudo

进入到这个目录里面,然后在进行安装:

shell 复制代码
bpkg build odb@https://pkg.cppget.org/1/beta


接下安装odbg

shell 复制代码
bpkg install odb

安装完后看一下版本:

shell 复制代码
odb --version

安装 ODB 运行时库:

shell 复制代码
cd ..
bpkg create -d libodb-gcc-N cc\
config.cxx=g++ \
config.cc.coptions=-O3 \
config.install.root=/usr/ \
config.install.sudo=sudo
cd libodb-gcc-N
bpkg add https://pkg.cppget.org/1/beta
bpkg fetch
bpkg build libodb
bpkg build libodb-mysql


安装 mysql 和客户端开发包

shell 复制代码
sudo apt install mysql-server
sudo apt install -y libmysqlclient-dev

配置mysql

cpp 复制代码
sudo vim /etc/my.cnf 或者 /etc/mysql/my.cnf 有哪个修改哪个就行
#添加以下内容
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8
bind-address = 0.0.0.0

修改 root 用户密码

shell 复制代码
dev@bite:~$ sudo cat /etc/mysql/debian.cnf
# Automatically generated for Debian scripts. DO NOT TOUCH!
[client]
host = localhost
user = debian-sys-maint
password = UWcn9vY0NkrbJMRC
socket = /var/run/mysqld/mysqld.sock
[mysql_upgrade]
host = localhost
user = debian-sys-maint
password = UWcn9vY0NkrbJMRC
socket = /var/run/mysqld/mysqld.sock
dev@bite:~$ sudo mysql -u debian-sys-maint -p
Enter password: #这里输入上边第 6 行看到的密码
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'xxxxxx';
Query OK, 0 rows affected (0.01 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)
mysql> quit


重启 mysql,并设置开机启动

shell 复制代码
sudo systemctl restart mysql

sudo systemctl enable mysql

安装 boost profile 库

shell 复制代码
bpkg build libodb-boost

总体打包安装:

shell 复制代码
bpkg install --all --recursive

查看一下odb里面的头文件:

到这里我们的安装已经彻底结束了!

测试用例:

person.hxx

cpp 复制代码
#pragma once
#include <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
/*
在 C++ 中,要使用 ODB 将类声明为持久化类,需要包含 ODB 的核心头文
件,并使用 #pragma db object 指令
#pragma db object 指示 ODB 编译器将 person 类视为一个持久化类。
*/
#include <odb/core.hxx>
typedef boost::posix_time::ptime ptime;
#pragma db object
class Person
{
public:
    Person(const std::string &name, int age, const ptime &update) : _name(name), _age(age), _update(update) {}
    void age(int val) { _age = val; }
    int age() { return _age; }
    void name(const std::string &val) { _name = val; }
    std::string name() { return _name; }
    void update(const ptime &update) { _update = update; }
    std::string update() { return boost::posix_time::to_simple_string(_update); }

private:
    // 将 odb:: access 类作为 person 类的朋友。
    // 这是使数据库支持代码可访问默认构造函数和数据成员所必需的。
    // 如果类具有公共默认构造函数和公共数据成员或数据成员的公共访问器和修饰符,则不需要友元声明
    friend class odb::access;
    Person() {}
//_id 成员前面的 pragma 告诉 ODB 编译器,以下成员是对象的标识符。 auto 说明符指示它是数据库分配的 ID。
#pragma db id auto // 表示 ID 字段将自动生成(通常是数据库中的主键)。
    unsigned long _id;
    unsigned short _age;
    std::string _name;
#pragma db type("TIMESTAMP") not_null
    boost::posix_time::ptime _update;
};
// 将 ODB 编译指示组合在一起,并放在类定义之后。它们也可以移动到一个单独的标头中,使原始类完全保持不变
//  #pragma db object(person)
//  #pragma db member(person::_name) id
// 完成后,需要使用 odb 编译器将当前所写的代码生成数据库支持代码
// odb -d mysql --generate-query --generate-schema person.hxx
// 如果用到了 boost 库中的接口,则需要使用选项 : --profile boost/datetime
// odb -d mysql --generate-query --generate-schema --profileboost / date - time person.hxx

生成数据库支持的代码文件

shell 复制代码
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time person.hxx

修改person.sql文件:

sql 复制代码
/* This file was generated by ODB, object-relational mapping (ORM)
 * compiler for C++.
 */

DROP DATABASE IF EXISTS  `TestDB`;
CREATE DATABASE `TestDB`;
USE `TestDB`;

DROP TABLE IF EXISTS `Person`;

CREATE TABLE `Person` (
  `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `age` SMALLINT UNSIGNED NOT NULL,
  `name` TEXT NOT NULL,
  `update` TIMESTAMP NOT NULL)
 ENGINE=InnoDB;


库和表都创建好了之后,我们编写主函数代码:
main.cc

cpp 复制代码
#include <string>
#include <memory>  // std::auto_ptr
#include <cstdlib> // std::exit
#include <iostream>
#include <odb/database.hxx>
#include <odb/mysql/database.hxx>
#include "person.hxx"
#include "person-odb.hxx"

int main()
{
    std::shared_ptr<odb::core::database> db(
        new odb::mysql::database("root", "123456",
                                 "mytest", "127.0.0.1", 0, 0, "utf8"));
    if (!db)
    {
        return -1;
    }
    ptime p = boost::posix_time::second_clock::local_time();
    Person zhang("小张", 18, p);
    Person wang("小王", 19, p);
    typedef odb::query<Person> query;
    typedef odb::result<Person> result;
    {
        odb::core::transaction t(db->begin());
        size_t zid = db->persist(zhang);
        size_t wid = db->persist(wang);
        t.commit();
    }
    {
        odb::core::transaction t(db->begin());
        result r(db->query<Person>());
        for (result::iterator i(r.begin()); i != r.end(); ++i)
        {
            std::cout << "Hello, " << i->name() << " ";
            std::cout << i->age() << " " << i->update() << std::endl;
        }
        t.commit();
    }
    return 0;
}
// 如果用到了 boost 库中的接口,需要链接库: -lodb-boost
// c++ -o mysql_test mysql_test.cpp person-odb.cxx -lodb-mysql -lodb - lodb - boost

代码编译:

shell 复制代码
main : main.cc person-odb.cxx
	c++  $^ -o $@ -lodb-mysql -lodb -lodb-boost

这里说明我们的odb就已经全部安装完毕了!!!

ODB常见操作介绍

类型映射

ODB编程

ODB(Open Database)在数据元结构定义时,使用预处理器指令(#pragma)来提

供元数据,这些元数据指示如何将 C++类型映射到数据库模式。这些#pragma 指令是

C++代码中使用的,它们不是 C++语言的一部分,而是特定于ODB编译器的扩展。

以下是ODB中常用的一些#pragma 指令:

  1. #pragma db object:
  • 用于声明一个类是数据库对象,即这个类将映射到数据库中的一个表
  1. #pragma db table("table_name"):
  • 指定类映射到数据库中的表名。如果不指定,则默认使用类名。
  1. #pragma db id:
  • 标记类中的一个成员变量作为数据库表的主键。
  1. #pragma db column("column_name"):
  • 指定类成员映射到数据库表中的列名。如果不指定,则默认使用成员变量的
    名字。
  1. #pragma db view:
  • 用于声明一个类是一个数据库视图,而不是一个表。
  1. #pragma db session:
  • 用于声明一个全局或成员变量是数据库会话。
  1. #pragma db query("query"):
  • 用于定义自定义的查询函数。
  1. #pragma db index("index_name"):
  • 指定成员变量应该被索引。
  1. #pragma db default("default_value"):
  • 指定成员变量的默认值。
  1. #pragma db unique:
  • 指定成员变量或一组变量应该具有唯一性约束。
  1. #pragma db not_null:
  • 指定成员变量不允许为空。
  1. #pragma db auto:
  • 指定成员变量的值在插入时自动生成(例如,自动递增的主键)。
  1. #pragma db transient:
  • 指定成员变量不应该被持久化到数据库中。
  1. #pragma db type("type_name"):
  • 指定成员变量的数据库类型。
  1. #pragma db convert("converter"):
  • 指定用于成员变量的自定义类型转换器。
  1. #pragma db pool("pool_name"):
  • 指定用于数据库连接的连接池。
  1. #pragma db trigger("trigger_name"):
  • 指定在插入、更新或删除操作时触发的触发器。

操作样例编写

student.cc

cpp 复制代码
#pragma once
#include <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>

#pragma db object
class Student
{
public:
    Student() {}
    Student(unsigned long sn, const std::string &name, unsigned short age, unsigned long cid) : _sn(sn), _name(name), _age(age), _classes_id(cid) {}
    void sn(unsigned long num) { _sn = num; }
    unsigned long sn() { return _sn; }

    void name(const std::string &name) { _name = name; }
    std::string name() { return _name; }

    void age(unsigned short num) { _age = num; }
    odb::nullable<unsigned short> age() { return _age; }

    void classes_id(unsigned long cid) { _classes_id = cid; }
    unsigned long classes_id() { return _classes_id; }

private:
    friend class odb::access;
#pragma db id auto
    unsigned long _id;
#pragma db unique
    unsigned long _sn;
    std::string _name;
    odb::nullable<unsigned short> _age;
#pragma db index
    unsigned long _classes_id;
};

#pragma db object
class Classes
{
public:
    Classes() {}
    Classes(const std::string &name) : _name(name) {}
    void name(const std::string &name) { _name = name; }
    std::string name() { return _name; }

private:
    friend class odb::access;
#pragma db id auto
    unsigned long _id;
    std::string _name;
};

// 查询所有的学生信息,并显示班级名称
#pragma db view object(Student)                                      \
    object(Classes = classes : Student::_classes_id == classes::_id) \
        query((?))
struct classes_student
{
#pragma db column(Student::_id)
    unsigned long id;
#pragma db column(Student::_sn)
    unsigned long sn;
#pragma db column(Student::_name)
    std::string name;
#pragma db column(Student::_age)
    odb::nullable<unsigned short> age;
#pragma db column(classes::_name)
    std::string classes_name;
};

// 只查询学生姓名  ,   (?)  外部调用时传入的过滤条件
#pragma db view query("select name from Student" + (?))
struct all_name
{
    std::string name;
};

// odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time student.hxx

执行命令

shell 复制代码
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time student.hxx

main.cc

cpp 复制代码
#include <odb/database.hxx>
#include <odb/mysql/database.hxx>
#include "student.hxx"
#include "student-odb.hxx"
#include <gflags/gflags.h>

DEFINE_string(host, "127.0.0.1", "这是Mysql服务器地址");
DEFINE_int32(port, 0, "这是Mysql服务器端口");
DEFINE_string(db, "TestDB", "数据库默认库名称");
DEFINE_string(user, "root", "这是Mysql用户名");
DEFINE_string(pswd, "123456", "这是Mysql密码");
DEFINE_string(cset, "utf8", "这是Mysql客户端字符集");
DEFINE_int32(max_pool, 3, "这是Mysql连接池最大连接数量");

void insert_classes(odb::mysql::database &db)
{
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        Classes c1("一年级一班");
        Classes c2("一年级二班");
        db.persist(c1);
        db.persist(c2);
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "插入数据出错:" << e.what() << std::endl;
    }
}

void insert_student(odb::mysql::database &db)
{
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        Student s1(1, "张三", 18, 1);
        Student s2(2, "李四", 19, 1);
        Student s3(3, "王五", 18, 1);
        Student s4(4, "赵六", 15, 2);
        Student s5(5, "刘七", 18, 2);
        Student s6(6, "孙八", 23, 2);
        db.persist(s1);
        db.persist(s2);
        db.persist(s3);
        db.persist(s4);
        db.persist(s5);
        db.persist(s6);
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "插入学生数据出错:" << e.what() << std::endl;
    }
}

void update_student(odb::mysql::database &db, Student &stu)
{
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        db.update(stu);
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "更新学生数据出错:" << e.what() << std::endl;
    }
}
Student select_student(odb::mysql::database &db)
{
    Student res;
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        typedef odb::query<Student> query;
        typedef odb::result<Student> result;
        result r(db.query<Student>(query::name == "张三"));
        if (r.size() != 1)
        {
            std::cout << "数据量不对!\n";
            return Student();
        }
        res = *r.begin();
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "更新学生数据出错:" << e.what() << std::endl;
    }
    return res;
}

void remove_student(odb::mysql::database &db)
{
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        typedef odb::query<Student> query;
        db.erase_query<Student>(query::classes_id == 2);
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "更新学生数据出错:" << e.what() << std::endl;
    }
}

void classes_student(odb::mysql::database &db)
{
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        typedef odb::query<struct classes_student> query;
        typedef odb::result<struct classes_student> result;
        result r(db.query<struct classes_student>(query::classes::id == 1));
        for (auto it = r.begin(); it != r.end(); ++it)
        {
            std::cout << it->id << std::endl;
            std::cout << it->sn << std::endl;
            std::cout << it->name << std::endl;
            std::cout << *it->age << std::endl;
            std::cout << it->classes_name << std::endl;
        }
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "更新学生数据出错:" << e.what() << std::endl;
    }
}

void all_student(odb::mysql::database &db)
{
    try
    {
        // 获取事务对象开启事务
        odb::transaction trans(db.begin());
        typedef odb::query<Student> query;
        typedef odb::result<struct all_name> result;
        result r(db.query<struct all_name>(query::id == 1));
        for (auto it = r.begin(); it != r.end(); ++it)
        {
            std::cout << it->name << std::endl;
        }
        // 5. 提交事务
        trans.commit();
    }
    catch (std::exception &e)
    {
        std::cout << "查询所有学生姓名数据出错:" << e.what() << std::endl;
    }
}

int main(int argc, char *argv[])
{
    google::ParseCommandLineFlags(&argc, &argv, true);
    // 1. 构造连接池工厂配置对象
    std::unique_ptr<odb::mysql::connection_pool_factory> cpf(
        new odb::mysql::connection_pool_factory(FLAGS_max_pool, 0));
    // 2. 构造数据库操作对象
    odb::mysql::database db(
        FLAGS_user, FLAGS_pswd, FLAGS_db,
        FLAGS_host, FLAGS_port, "", FLAGS_cset,
        0, std::move(cpf));
    // 4. 数据操作
    // insert_classes(db);
    // insert_student(db);
    //  auto stu = select_student(db);
    //  std::cout << stu.sn() << std::endl;
    //  std::cout << stu.name() << std::endl;
    //  if (stu.age()) std::cout << *stu.age() << std::endl;
    //  std::cout << stu.classes_id() << std::endl;

    // stu.age(15);
    // update_student(db, stu);
    // remove_student(db);
    // classes_student(db);
    all_student(db);
    return 0;
}

RabbitMQ

安装

shell 复制代码
sudo apt install rabbitmq-server
systemctl status rabbitmq-server

安装结果:

shell 复制代码
# 安装完成的时候默认有个用户 guest ,但是权限不够,要创建一个
administrator 用户,才可以做为远程登录和发表订阅消息:
#添加用户
sudo rabbitmqctl add_user root 123456
#设置用户 tag
sudo rabbitmqctl set_user_tags root administrator
#设置用户权限
sudo rabbitmqctl set_permissions -p / root "." "." ".*"
# RabbitMQ 自带了 web 管理界面,执行下面命令开启
sudo rabbitmq-plugins enable rabbitmq_management

访问 webUI 界面, 默认端口为 15672.


安装 RabbitMQ 的 C++客户端库

shell 复制代码
sudo apt install libev-dev #libev 网络库组件
git clone https://github.com/CopernicaMarketingSoftware/AMQPCPP.git
cd AMQP-CPP/
make
make install



如果安装报错:

这种错误是ssl版本的问题。

解决方案:卸载当前的 ssl 库,重新进行修复安装

shell 复制代码
dpkg -l |grep ssl
shell 复制代码
sudo dpkg -P --force-all libevent-openssl-2.1-7
sudo dpkg -P --force-all openssl
sudo dpkg -P --force-all libssl-dev
sudo apt --fix-broken install

AMQP-CPP 库的简单使用

介绍

  • AMQP-CPP 是用于与 RabbitMq 消息中间件通信的 c++库。它能解析从 RabbitMq服务发送来的数据,也可以生成发向 RabbitMq 的数据包。AMQP-CPP库不会向RabbitMq 建立网络连接,所有的网络 io 由用户完成。
  • 当然, AMQP-CPP 提供了可选的网络层接口,它预定义了TCP模块,用户就不用自己实现网络 io,我们也可以选择 libevent、 libev、 libuv、 asio 等异步通信组件,需要手动安装对应的组件。
  • AMQP-CPP 完全异步,没有阻塞式的系统调用,不使用线程就能够应用在高性能应用中。
  • 注意:它需要 c++17 的支持。

编写测试用例

consume.cc

cpp 复制代码
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>

//消息回调处理函数的实现
void MessageCb(AMQP::TcpChannel *channel, const AMQP::Message &message, uint64_t deliveryTag, bool redelivered)
{
    std::string msg;
    msg.assign(message.body(), message.bodySize());
    std::cout << msg << std::endl;
    channel->ack(deliveryTag); // 对消息进行确认
}

int main()
{
    //1. 实例化底层网络通信框架的I/O事件监控句柄
    auto *loop = EV_DEFAULT;
    //2. 实例化libEvHandler句柄 --- 将AMQP框架与事件监控关联起来
    AMQP::LibEvHandler handler(loop);
    //2.5. 实例化连接对象
    AMQP::Address address("amqp://root:123456@127.0.0.1:5672/");
    AMQP::TcpConnection connection(&handler, address);
    //3. 实例化信道对象
    AMQP::TcpChannel channel(&connection);
    //4. 声明交换机
    channel.declareExchange("test-exchange", AMQP::ExchangeType::direct)
        .onError([](const char *message) {
            std::cout << "声明交换机失败:" << message << std::endl;
            exit(0);
        })
        .onSuccess([](){
            std::cout << "test-exchange 交换机创建成功!" << std::endl;
        });
    //5. 声明队列
    channel.declareQueue("test-queue")
        .onError([](const char *message) {
            std::cout << "声明队列失败:" << message << std::endl;
            exit(0);
        })
        .onSuccess([](){
            std::cout << "test-queue 队列创建成功!" << std::endl;
        });
    //6. 针对交换机和队列进行绑定
    channel.bindQueue("test-exchange", "test-queue", "test-queue-key")
        .onError([](const char *message) {
            std::cout << "test-exchange - test-queue 绑定失败:" << message << std::endl;
            exit(0);
        })
        .onSuccess([](){
            std::cout << "test-exchange - test-queue 绑定成功!" << std::endl;
        });
    //7. 订阅队列消息 -- 设置消息处理回调函数
    auto callback = std::bind(MessageCb, &channel, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    channel.consume("test-queue", "consume-tag")  //返回值 DeferredConsumer
        .onReceived(callback)
        .onError([](const char *message){
            std::cout << "订阅 test-queue 队列消息失败:" << message << std::endl;
            exit(0);
        }); // 返回值是 AMQP::Deferred
    
    
    //8. 启动底层网络通信框架--开启I/O
    ev_run(loop, 0);
    return 0;
}

publish.cc

cpp 复制代码
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>

int main()
{
    // 1. 实例化底层网络通信框架的I/O事件监控句柄
    auto *loop = EV_DEFAULT;
    // 2. 实例化libEvHandler句柄 --- 将AMQP框架与事件监控关联起来
    AMQP::LibEvHandler handler(loop);
    // 2.5. 实例化连接对象
    AMQP::Address address("amqp://root:123456@127.0.0.1:5672/");
    AMQP::TcpConnection connection(&handler, address);
    // 3. 实例化信道对象
    AMQP::TcpChannel channel(&connection);
    // 4. 声明交换机
    channel.declareExchange("test-exchange", AMQP::ExchangeType::direct)
        .onError([](const char *message)
                 {
            std::cout << "声明交换机失败:" << message << std::endl;
            exit(0); })
        .onSuccess([]()
                   { std::cout << "test-exchange 交换机创建成功!" << std::endl; });
    // 5. 声明队列
    channel.declareQueue("test-queue")
        .onError([](const char *message)
                 {
            std::cout << "声明队列失败:" << message << std::endl;
            exit(0); })
        .onSuccess([]()
                   { std::cout << "test-queue 队列创建成功!" << std::endl; });
    // 6. 针对交换机和队列进行绑定
    channel.bindQueue("test-exchange", "test-queue", "test-queue-key")
        .onError([](const char *message)
                 {
            std::cout << "test-exchange - test-queue 绑定失败:" << message << std::endl;
            exit(0); })
        .onSuccess([]()
                   { std::cout << "test-exchange - test-queue 绑定成功!" << std::endl; });
    // 7. 向交换机发布消息
    for (int i = 0; i < 10; i++)
    {
        std::string msg = "Hello Bite-" + std::to_string(i);
        bool ret = channel.publish("test-exchange", "test-queue-key", msg);
        if (ret == false)
        {
            std::cout << "publish 失败!\n";
        }
    }
    // 启动底层网络通信框架--开启I/O
    ev_run(loop, 0);
    return 0;
}

makefile

cpp 复制代码
all : publish consume
publish : publish.cc
	g++ -std=c++17 $^ -o $@ -lamqpcpp -lev
consume : consume.cc
	g++ -std=c++17 $^ -o $@ -lamqpcpp -lev

运行测试用例

二次封装

rabbitmq.hpp

cpp 复制代码
#pragma once
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>
#include <iostream>
#include <functional>
#include "logger.hpp"

namespace bite_im
{
    class MQClient
    {
    public:
        using MessageCallback = std::function<void(const char *, size_t)>;
        using ptr = std::shared_ptr<MQClient>;
        MQClient(const std::string &user,
                 const std::string passwd,
                 const std::string host)
        {
            _loop = EV_DEFAULT;
            _handler = std::make_unique<AMQP::LibEvHandler>(_loop);
            // amqp://root:123456@127.0.0.1:5672/
            std::string url = "amqp://" + user + ":" + passwd + "@" + host + "/";
            AMQP::Address address(url);
            _connection = std::make_unique<AMQP::TcpConnection>(_handler.get(), address);
            _channel = std::make_unique<AMQP::TcpChannel>(_connection.get());

            _loop_thread = std::thread([this]()
                                       { ev_run(_loop, 0); });
        }
        ~MQClient()
        {
            ev_async_init(&_async_watcher, watcher_callback);
            ev_async_start(_loop, &_async_watcher);
            ev_async_send(_loop, &_async_watcher);
            _loop_thread.join();
            _loop = nullptr;
        }
        void declareComponents(const std::string &exchange,
                               const std::string &queue,
                               const std::string &routing_key = "routing_key",
                               AMQP::ExchangeType echange_type = AMQP::ExchangeType::direct)
        {
            _channel->declareExchange(exchange, echange_type)
                .onError([](const char *message)
                         {
                    LOG_ERROR("声明交换机失败:{}", message);
                    exit(0); })
                .onSuccess([exchange]()
                           { LOG_ERROR("{} 交换机创建成功!", exchange); });
            _channel->declareQueue(queue)
                .onError([](const char *message)
                         {
                    LOG_ERROR("声明队列失败:{}", message);
                    exit(0); })
                .onSuccess([queue]()
                           { LOG_ERROR("{} 队列创建成功!", queue); });
            // 6. 针对交换机和队列进行绑定
            _channel->bindQueue(exchange, queue, routing_key)
                .onError([exchange, queue](const char *message)
                         {
                    LOG_ERROR("{} - {} 绑定失败:", exchange, queue);
                    exit(0); })
                .onSuccess([exchange, queue, routing_key]()
                           { LOG_ERROR("{} - {} - {} 绑定成功!", exchange, queue, routing_key); });
        }
        bool publish(const std::string &exchange,
                     const std::string &msg,
                     const std::string &routing_key = "routing_key")
        {
            LOG_DEBUG("向交换机 {}-{} 发布消息!", exchange, routing_key);
            bool ret = _channel->publish(exchange, routing_key, msg);
            if (ret == false)
            {
                LOG_ERROR("{} 发布消息失败:", exchange);
                return false;
            }
            return true;
        }
        void consume(const std::string &queue, const MessageCallback &cb)
        {
            LOG_DEBUG("开始订阅 {} 队列消息!", queue);
            _channel->consume(queue, "consume-tag") // 返回值 DeferredConsumer
                .onReceived([this, cb](const AMQP::Message &message,
                                       uint64_t deliveryTag,
                                       bool redelivered)
                            {
                    cb(message.body(), message.bodySize());
                    _channel->ack(deliveryTag); })
                .onError([queue](const char *message)
                         {
                    LOG_ERROR("订阅 {} 队列消息失败: {}", queue, message);
                    exit(0); });
        }

    private:
        static void watcher_callback(struct ev_loop *loop, ev_async *watcher, int32_t revents)
        {
            ev_break(loop, EVBREAK_ALL);
        }

    private:
        struct ev_async _async_watcher;
        struct ev_loop *_loop;
        std::unique_ptr<AMQP::LibEvHandler> _handler;
        std::unique_ptr<AMQP::TcpConnection> _connection;
        std::unique_ptr<AMQP::TcpChannel> _channel;
        std::thread _loop_thread;
    };
}

运行结果:

语音技术SDK

安装使用

第一步:登录百度云
网址

第二步: 搜索语音技术

第三步:领取免费试用资源

第四步:创建应用

第五步:使用 sdk 调用服务

下载语音识别 sdk
下载地址

选择c下载,然后在上传到服务器:技术文档

注意:

  1. 关于参数:如果相关音频参数不符合要求,可以使用 ffmpeg 工具进行转码
  • 采样率: 百度语音识别一般仅支持 16000 的采样率。即 1 秒采样 16000
    次。
  • 位深: 无损音频格式 pcm 和 wav 可以设置,百度语音识别使用 16bits 小
    端序 ,即 2 个字节记录 1/16000 s 的音频数据。
  • 声道: 百度语音识别仅支持单声道。
  1. 语音识别返回结果与音频内容不匹配,例如: "嗨嗨嗨"、 "嗯嗯嗯嗯嗯"、 "什么"等错误返回
  • 解决方法:排查音频采样率、声道、格式等参数是否符合接口规范。如与
    要求不符,需要用工具对音频进行转码。
  1. 在使用之前一定先过一遍官方文档: https://ai.baidu.com/aidoc/SPEECH/dlbxfrs5o

在浏览器直接搜索ffmpeg

进入官网后直接点击下载,然后选第二个自动弹到github链接:

下载下来,之后解压。

解压之后,将这个bin目录添加到环境变量中,然后在命令行窗口检测一下信息。

说明没有问题,就可以使用了

接下来我们编写测试用例:

测试用例

test.cc

cpp 复制代码
#include "../aip-cpp-sdk/speech.h"

void asr(aip::Speech &client)
{
    std::string file_content;
    aip::get_file_content("16k.pcm", &file_content);

    Json::Value result = client.recognize(file_content, "pcm", 16000, aip::null);
    if (result["err_no"].asInt() != 0)
    {
        std::cout << result["err_msg"].asString() << std::endl;
        return;
    }

    std::cout << result["result"][0].asString() << std::endl;
}

int main()
{
    // 设置APPID/AK/SK
    std::string app_id = "122158574";
    std::string api_key = "uWRSFaE9ympqz4VHEkKjmjRS";
    std::string secret_key = "FDxVRstJxNcErErDdiPBHtqJtzHopp5g";

    aip::Speech client(app_id, api_key, secret_key);

    asr(client);
    return 0;
}

makefile

shell 复制代码
test : test.cc
	g++ -std=c++17 $^ -o $@ -lcurl -lcrypto /usr/lib/x86_64-linux-gnu/libjsoncpp.so.1.8.4

这里我们链接的时候要注意:

这里的测试用例通过了,说明我们的语音服务是没问题的。

二次封装

asr.hpp

cpp 复制代码
#pragma once
#include "aip-cpp-sdk/speech.h"
#include "logger.hpp"

class ASRClient
{
public:
    using ptr = std::shared_ptr<ASRClient>;
    ASRClient(const std::string &app_id,
              const std::string &api_key,
              const std::string &secret_key) : _client(app_id, api_key, secret_key) {}
    std::string recognize(const std::string &speech_data)
    {
        // 直接发请求即可
        Json::Value result = _client.recognize(speech_data, "pcm", 16000, aip::null);
        if (result["err_no"].asInt() != 0)
        {
            LOG_ERROR("语音识别失败:{}", result["err_msg"].asString());
            // err = result["err_msg"].asString();
            return std::string();
        }
        return result["result"][0].asString();
    }

private:
    aip::Speech _client;
};

test.cc

cpp 复制代码
#include "../../common/asr.hpp"
#include "gflags/gflags.h"

DEFINE_string(app_id, "122158574", "语音平台应用ID");
DEFINE_string(api_key, "uWRSFaE9ympqz4VHEkKjmjRS", "语音平台API密钥");
DEFINE_string(secret_key, "FDxVRstJxNcErErDdiPBHtqJtzHopp5g", "语音平台加密密钥");

DEFINE_bool(run_mode, false, "程序的运行模式,false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");

int main(int argc, char *argv[])
{
    google::ParseCommandLineFlags(&argc, &argv, true);
    bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);

    ASRClient client(FLAGS_app_id, FLAGS_api_key, FLAGS_secret_key);

    std::string file_content;
    aip::get_file_content("16k.pcm", &file_content);

    std::string res = client.recognize(file_content);
    if (res.empty())
    {
        return -1;
    }
    std::cout << res << std::endl;
    return 0;
}

makefile

cpp 复制代码
test : test.cc
	g++ -std=c++17 $^ -o $@ -lfmt -lspdlog -lgflags -lcurl -lcrypto /usr/lib/x86_64-linux-gnu/libjsoncpp.so.1.8.4

运行结果:

这里显示是没有问题的!

项目构建

相关推荐
OxyTheCrack2 小时前
【C++】简述Observer观察者设计模式附样例(C++实现)
开发语言·c++·笔记·设计模式
一知半解仙2 小时前
从“玩具项目“到“生产级架构“:Spring Boot + Spring Cloud + AI 微服务实战避坑指南
spring boot·spring cloud·架构
admin and root2 小时前
记一次攻防演练redis未授权访问案例
网络·数据库·redis·安全·web安全·渗透测试·src漏洞挖掘
Mr.朱鹏2 小时前
分布式-redis主从复制架构
java·spring boot·redis·分布式·缓存·架构·java-ee
格林威2 小时前
工业相机图像高速存储(C++版):先存内存,后批量转存方法,附堡盟相机实战代码!
开发语言·c++·人工智能·数码相机·计算机视觉·视觉检测·堡盟相机
倔强的石头1062 小时前
KWDB 3.1.0 智慧能源实战:构建城市级智能电表监测平台
数据库·能源·kwdb
九河云2 小时前
容器化与微服务:企业上云过程中的技术债务治理
大数据·微服务·云原生·重构·架构·数字化转型
Mr.朱鹏2 小时前
分布式-redis哨兵模式架构
数据库·redis·分布式·spring·缓存·架构·java-ee
愿天堂没有C++2 小时前
Pimpl 设计模式(指针指向实现)
开发语言·c++·设计模式