Skip to content

Commit dc98517

Browse files
author
zhengshuxin
committed
Merge branch 'main' of github:acl-dev/acl-doc into main
2 parents de0177a + f462230 commit dc98517

File tree

3 files changed

+205
-2
lines changed

3 files changed

+205
-2
lines changed

fiber_qt.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
title: 在 QT 界面编程中使用协程
3+
date: 2024-09-24 21:01:24
4+
tags: 协程编程
5+
categories: 协程编程
6+
---
7+
8+
# 一、概述
9+
10+
人们在谈论协程编程时,往往与编写命令行网络程序有关,如编写网络客户端与网络服务器程序,很少涉及到客户端 UI 相关的界面编程。Acl 协程库是支持在 Windows 下的 UI 界面编程的,因为 Acl 协程的事件引擎支持了界面消息传递过程。最近学习了一下 QT UI 编程,轻松将 Acl 协程与 QT UI 集成在一起,从而实现了 QT 界面协程化,使开发人员在使用 QT 编写界面程序时,编写网络模块变得非常简单。
11+
12+
本文结合 Acl 中 lib_fiber/samples-gui/QtFiber 示例,演示了如何将 Acl 协程功能集成到 QT 界面中,实现了网络模块与界面模块的融合。
13+
14+
# 二、集成
15+
## 2.1、编译 Acl
16+
17+
目前 QT IDE 还无法直接使用 Acl 里的 CMakeLists.txt 文件编译 ACL,可以借助于 VC2019 打开 Acl 里的 acl_cpp_vc2019.sln 工程编译 Acl 五个库的动态库,分别为:lib_acl.dll, lib_protocol.dll, lib_acl_cpp.dll, libfiber.dll, libfiber_cpp.dll 及静态导出库:lib_acl.lib, lib_protocol.lib lib_acl_cpp.lib, libfiber.lib, libfiber_cpp.lib。
18+
19+
## 2.2、将 Acl 库集成到 QT 项目中
20+
21+
参考 lib_fiber/samples-gui/QtFiber/CMakeLists.txt 文件,将 Acl 库的头文件包含进去,如下:
22+
```txt
23+
set(acl_path ../../..)
24+
25+
include_directories(
26+
${acl_path}/lib_acl/include
27+
${acl_path}/lib_acl_cpp/include
28+
${acl_path}/lib_fiber/c/include
29+
${acl_path}/lib_fiber/cpp/include
30+
)
31+
```
32+
33+
然后设定编译条件:
34+
```txt
35+
add_definitions("-DACL_DLL"
36+
"-DACL_CPP_DLL"
37+
"-DHTTP_DLL"
38+
"-DICMP_DLL"
39+
"-DSMTP_DLL"
40+
"-DFIBER_CPP_DLL"
41+
"-D_CRT_SECURE_NO_WARNINGS"
42+
"-D_WINSOCK_DEPRECATED_NO_WARNINGS"
43+
)
44+
```
45+
46+
添加库到工程中,如下:
47+
```txt
48+
if (CMAKE_BUILD_TYPE STREQUAL "RELEASE")
49+
set(acl_libs_path ${CMAKE_CURRENT_SOURCE_DIR}/../../../x64/ReleaseDll)
50+
else()
51+
set(acl_libs_path ${CMAKE_CURRENT_SOURCE_DIR}/../../../x64/DebugDll)
52+
endif()
53+
54+
set(lib_all ${acl_libs_path}/libfiber_cpp.lib
55+
${acl_libs_path}/lib_acl_cpp.lib
56+
${acl_libs_path}/lib_protocol.lib
57+
${acl_libs_path}/lib_acl.lib
58+
${acl_libs_path}/libfiber.lib)
59+
60+
target_link_libraries(QtFiber PRIVATE Qt5::Widgets ${lib_all} Ws2_32)
61+
62+
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
63+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
64+
"${acl_libs_path}/libfiber_cpp.dll"
65+
"${acl_libs_path}/libfiber.dll"
66+
"${acl_libs_path}/lib_acl_cpp.dll"
67+
"${acl_libs_path}/lib_acl.dll"
68+
"${acl_libs_path}/lib_protocol.dll"
69+
$<TARGET_FILE_DIR:${PROJECT_NAME}>
70+
)
71+
```
72+
73+
## 2.3、开始编写代码
74+
75+
经过摸索研究,想要集成 Acl 协程到 QT UI 程序中,需要采用以下方法(主要是协程的初始化及退出):
76+
77+
### 2.3.1、QT 程序初始化时初始化 Acl 协程
78+
79+
在调用 QT APP exec() 前,需要先调用 Acl 协程初始化过程,如下:
80+
```c++
81+
static void startupCallback() {
82+
acl::fiber::schedule_gui(); // Won't return until schedule finished.
83+
}
84+
85+
void main() {
86+
QApplication app(argc, argv);
87+
88+
MainWindow w;
89+
w.show();
90+
91+
QTimer::singleShot(0, startupCallback);
92+
93+
app.exec();
94+
}
95+
```
96+
97+
可以看出,在调用 app.exec() 前注入了启动函数 startupCallback(),在里面启动了 acl 在界面模式下的协程调度过程 acl::fiber::schedule_gui(),该方法将进入界面消息循环过程,直到协程调度停止后才会返回。
98+
99+
### 2.3.2、在界面中创建协程
100+
101+
一旦协程调度器启动,就可以创建并运行协程了,可以在主界面上添加一个按钮,当点击该按钮后的处理函数中便可以创建并启动一个协程。比如在例子中,点击 "Start fiber server" 按钮,在处理函数 ` MainWindow::onStartServer()` 中,可以创建一个网络监听服务器,如下:
102+
```c++
103+
void MainWindow::onStartServer() {
104+
...
105+
server_ = new fiber_server("127.0.0.1", 9001, this);
106+
server_->start();
107+
...
108+
}
109+
```
110+
这样在界面里就创建了一个 TCP 监听协程,当有连接连接监听地址时,在监听协程里便可以创建一个客户端连接处理协程进行处理,如下:
111+
```c++
112+
while (true) {
113+
SOCKET conn = socket_accept(sock);
114+
if (conn == INVALID_SOCKET) {
115+
break;
116+
}
117+
118+
acl::fiber* fb = new fiber_echo(conn);
119+
fb->start();
120+
}
121+
```
122+
123+
上面例子的客户端协程启动后,便可以进行网络 IO 读写,如下:
124+
125+
```c++
126+
char buf[8192];
127+
while (true) {
128+
int ret = acl_fiber_recv(conn_, buf, sizeof(buf) - 1, 0);
129+
if (ret == -1) {
130+
break;
131+
}
132+
133+
buf[ret] = 0;
134+
if (acl_fiber_send(conn_, buf, ret, 0) != ret) {
135+
break;
136+
}
137+
}
138+
```
139+
140+
### 2.3.3、界面程序退出前需要停止协程调度
141+
142+
必须保证在界面程序退出前停止协程调度器,否则界面程序无法正常退出,该步骤也非常重要。可以在主界面处理类里重载基类的 ` void closeEvent(QCloseEvent *event);` 方法,在该方法里停止协程调度器,如下:
143+
```c++
144+
void MainWindow::closeEvent(QCloseEvent *event) {
145+
acl::fiber::schedule_stop(); // 停止协程调度器
146+
event->accept(); // 接受关闭事件
147+
}
148+
```
149+
150+
### 2.3.4、在界面线程中下载数据
151+
152+
点击主界面中点击HTTP下载按钮,在事件处理函数中创建协程从后端HTTP服务器下载数据,过程如下:
153+
```c++
154+
void MainWindow::onUrlGet() {
155+
...
156+
go[this] {
157+
const char *url = "http://www.baidu.com/";
158+
acl::http_request req(url);
159+
if (!req.request(nullptr, 0)) {
160+
printf("Send HTTP request failed\r\n");
161+
return;
162+
}
163+
acl::string body;
164+
if (!req.get_body(body)) {
165+
printf("Get HTTP body error\r\n");
166+
return;
167+
}
168+
qDebug() << "Got body:" << body.c_str();
169+
...
170+
};
171+
}
172+
```
173+
174+
### 2.3.5、在协程中延迟创建容器
175+
176+
如果想某个容器延迟创建,不必借助定时器,直接在协程中就可以轻松实现:
177+
```c++
178+
void MainWindow::delayCreate() {
179+
go[this] {
180+
acl::fiber::delay(5000); // 休眠 5 秒
181+
InputDialog dialog(this);
182+
dialog.exec();
183+
};
184+
qDebug() << "Fiber was created to create one window after a while";
185+
}
186+
```
187+
188+
## 2.4、效果展示
189+
190+
编译运行 acl/lib_fiber/samples-gui/QtFiber/ 工程,可以得到以下运行界面:
191+
![fiber_qt](/img/fiber_qt.jpg)
192+
- 在前面窗口中,右边请求HTTP服务器时的HTTP请求头,右连接为后端服务器返回的HTTP响应头,该下载过程中在协程中进行,运行结果显示在主界面上;
193+
- 窗口下方的进度条为客户端协程与服务端协程交互时的交互进度展示。
194+
195+
## 2.5、小结
196+
197+
以上便是如何编译集成 Acl 协程到 QT 界面程序的方法,主要的要点是:
198+
- 需要使用 vc2019 编译 Acl 的动态库,并集成至 QT 界面程序的工程文件中;
199+
- 编程时需要注意两点:
200+
- 在启动 QT (即调用 app.exec())前,需要先启动 Acl 协程调度器;
201+
- 在主界面类里需要重载基类关闭虚方法 `closeEvent()`,并在该方法里停止 Acl 协程调度器。

img/fiber_qt.jpg

254 KB
Loading

ssl_sni.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,12 @@ public:
9797

9898
SSL 客户端模块与服务端可以协商一个 sni 的生成规则,比如 sni 字符串类似于 `token|host`(比如:xxxxxx|myhost.test.com),期中 token 字段可以是双方私密生成的加密字符串,host 为主机域名。服务端仅针放行符合加密规则的客户端 SSL 连接,其它连接一概拦截。
9999

100-
#### 3.1.2、创建僵尸 SSL 对象中添加 SNI 验证对象
100+
#### 3.1.2、创建全局 SSL 配置对象并绑定 SNI 验证对象
101101

102102
下面代码是创建全局的 SSL 配置管理对象代码:
103103
```c++
104104
acl::sslbase_conf* ssl_conf = new acl::openssl_conf(true);
105+
// 添加服务端 SSL 证书
105106
```
106107

107108
全局 SSL 配置管理对象创建后,添加 SSL SNI 验证对象:
@@ -134,7 +135,7 @@ bool ssl_handshake(acl::sslbase_conf& ssl_conf, acl::socket_stream& conn) {
134135

135136
bool ssl_handshake(acl::sslbase_conf& conf, acl::socket_stream& conn) {
136137
acl::sslbase_io* ssl = conf.create(false);
137-
const char* sni = "crypted_token|mytest.com";
138+
const char* sni = "crypted_token|mytest.com"; // 期中的加密token将由服务端验证
138139
ssl->set_sni_host(sni); // 设置 SNI 字段
139140
if (conn.setup_hook(ssl) == ssl) {
140141
// SSL 握手失败,销毁 SSL IO 对象。
@@ -151,3 +152,4 @@ bool ssl_handshake(acl::sslbase_conf& conf, acl::socket_stream& conn) {
151152
- 客户端示例:acl/lib_acl_cpp/samples/ssl/client
152153
- 服务端示例:acl/lib_acl_cpp/samples/ssl/server
153154
- 使用SSL中对数据进行加密传输:https://acl-dev.cn/2020/01/15/ssl/
155+
- Acl库下载:https://github.com/acl-dev/acl/

0 commit comments

Comments
 (0)