网络答疑汇总

简介

总结下目前视频教程中读者提出的问题,并逐一回答。视频地址在哔哩哔哩
https://space.bilibili.com/271469206

boost编译方法补充

很多同学提出windows环境下boost编译失败问题,这里在介绍一下,原文为
https://llfc.club/articlepage?id=2LUMUrk6n3nNEMHUOyWFwiPZZyo

当我们把boost库下载后解压,进入文件夹执行bootstrap.bat,就会生成b2.exe可执行文件。

https://cdn.llfc.club/1689424493724.jpg

之前我告诉大家执行的编译命令是

1
.\b2.exe toolset=gcc

这样生成的库是linux的.a库。
我们要用到生成windows使用的库

1
.\b2.exe install --toolset=msvc-14.2 --build-type=complete --prefix="D:\cppsoft\boost_1_81_0" link=static runtime-link=shared threading=multi debug release

先逐一解释各参数含义

  1. install可以更改为stage, stage表示只生成库(dll和lib), install还会生成包含头文件的include目录。一般来说用stage就可以了,我们将生成的lib和下载的源码包的include头文件夹放到项目要用的地方即可。

  2. toolset 指定编译器,gcc用来编译生成linux用的库,msvc-14.2(VS2019)用来编译windows使用的库,版本号看你的编译器比如msvc-10.0(VS2010),我的是VS2019所以是msvc-14.2

  3. 如果选择的是install 命令,指定生成的库文件夹要用--prefix,如果使用的是stage命令,需要用--stagedir指定。

  4. link 表示生成动态库还是静态库,static表示生成lib库,shared表示生成dll库。

  5. runtime-link 表示用于指定运行时链接方式为静态库还是动态库,指定为static就是MT模式,指定shared就是MD模式。MDMT 是微软 Visual C++ 编译器的选项,用于指定运行时库的链接方式。这两个选项有以下区别:

    • /MD:表示使用多线程 DLL(Dynamic Link Library)版本的运行时库。这意味着你的应用程序将使用动态链接的运行时库(MSVCRT.dll)。这样的设置可以减小最终可执行文件的大小,并且允许应用程序与其他使用相同运行时库版本的程序共享代码和数据。
    • /MT:表示使用多线程静态库(Static Library)版本的运行时库。这意味着所有的运行时函数将被静态链接到应用程序中,使得应用程序不再依赖于动态链接的运行时库。这样可以确保应用程序在没有额外依赖的情况下独立运行,但可能会导致最终可执行文件的体积增大。

执行上述命令后就会在指定目录生成lib库了,我们将lib库拷贝到要使用的地方即可。

运行时库类型的错误

windows平台有时我们使用多个第三方库时,每个库编译选择的运行库版本不同的,有的是static,有的是
shared,也就是MTMD。这两种类型不能混用,比如我们使用grpc通信时,也会使用jsoncpp库序列化,那如果这两个库版本不同就会报如下链接错误

https://cdn.llfc.club/1689427344568.jpg

解决这个问题也比较简单,将运行时库类型统一修改为MDMT

https://cdn.llfc.club/1689427184372.jpg

我们目前的项目采用的运行库类型都改为了MD,大家可以看源码
https://gitee.com/secondtonone1/boostasio-learn

注意条件变量激活信号发送的时机

我们使用条件变量激活其他线程时需要cond_variable.notifyone或者cond_variable.notifyall
但是发送前一定要解锁,如果不解锁其他线程处于wait状态将无法被激活,因为锁可能还被通知线程占用。目前项目中已解决该隐患。
那我们用代码模仿消费者和生产者逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
int main()
{
//控制队列安全mtx
std::mutex mtx;
//消息队列
std::queue<int> que;
//队列最大长度
int que_max = 1024;
//消费者条件变量
std::condition_variable consumer_cv;
//生产者条件变量
std::condition_variable producer_cv;
//退出标记
bool bexit = false;

auto consumer = std::thread([&]() {
for (;;) {
std::unique_lock<std::mutex> lock(mtx);
//队列为空则则挂起等待
while (que.empty() && !bexit) {
consumer_cv.wait(lock);
}

//收到退出信号
if (bexit) {
return;
}

//自动加锁消费队列
auto num = que.front();
std::cout << "consumer consume " << num << std::endl;
que.pop();
//判断是否需要通知
bool bnotify = (que.size() == que_max - 1) ? true:false;
lock.unlock();
if (bnotify) {
//通知生产者
producer_cv.notify_one();
}

// 创建随机数引擎
std::random_device rd;
std::mt19937 gen(rd());

// 创建分布器,这里以生成范围在[0, 100]的整数为例
std::uniform_int_distribution<> dis(1, 3);

// 生成随机数
int randomValue = dis(gen);

// 睡眠一毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(randomValue));
}

});

auto producer = std::thread([&]() {
for (int index = 0;;index++) {
std::unique_lock<std::mutex> lock(mtx);
//队列满并且没有设置退出标记则挂起
while (que.size() == que_max && !bexit) {
producer_cv.wait(lock);
}

//判断是否为退出标记
if (bexit) {
return;
}

que.push(index);
std::cout << "producer produce " << index << std::endl;
bool bnotify = (que.size() == 1) ? true : false;
lock.unlock();
if (bnotify) {
consumer_cv.notify_one();
}

// 创建随机数引擎
std::random_device rd;
std::mt19937 gen(rd());

// 创建分布器,这里以生成范围在[0, 100]的整数为例
std::uniform_int_distribution<> dis(1, 3);

// 生成随机数
int randomValue = dis(gen);

// 睡眠一毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(randomValue));
}
});

// 全局 io_context 对象
boost::asio::io_context ioContext;
// 创建一个信号集对象
boost::asio::signal_set signals(ioContext, SIGINT);

// 异步等待信号
signals.async_wait([&](const boost::system::error_code& error,
int signalNumber) {
std::cout << "Received signal: " << signalNumber << std::endl;
bexit = true;
consumer_cv.notify_one();
producer_cv.notify_one();
// 停止 io_context
ioContext.stop();
});

// 运行 io_context
ioContext.run();
//等待线程退出
consumer.join();
producer.join();

std::cout << "Exiting..." << std::endl;
}

io_context监听事件要启动

我们将iocontext的读写事件注册后,一定要执行iocontext.run,这样事件才会被asio底层派发。
项目中StrandServer和ThreadServer都遗漏了信号事件绑定的iocontext的run调用,目前已修复。

io_context::work析构后io_context不一会退出

io_context::work主要是用来监听io_context,防止io_context在没有事件绑定的时候退出。
io_context::work 析构后,如果io_context没有事件绑定则自动退出。
但是当io_context有事件监听,则io_context无法退出。
我们的代码中用智能指针管理io_context::work

1
2
   using Work = boost::asio::io_context::work;
using WorkPtr = std::unique_ptr<Work>;

当智能指针reset后调用work的析构函数,但是如果iocontext已经绑定了读或写事件,则无法停止。
所以当我们执行Pool的stop函数无法正常退出,这个问题已经修复,详见代码仓库。

grpc应用场景

grpc主要用于服务内部通信的情况,简言之后台网络中多个服务器通信的方式。如果是客户端和服务器通信最好使用并发性能好一点的网络库,同时也能支持根据自己的需求实现并发逻辑。如果客户端较少,则也可以考虑用grpc的方式通信。

接下来视频走向

  1. 搭建Linux环境下C++ 开发环境
  2. C++ 并发编程技术讲解
  3. 项目实战,QT聊天客户端和asio服务器全栈设计。

总结

本文回答了大家遇到的各种问题

视频连接https://space.bilibili.com/271469206/channel/collectiondetail?sid=313101

源码链接https://gitee.com/secondtonone1/boostasio-learn