最近又回归了金融行业,做 PC 客户端软件
服务端用 go 写的,用了 grpc,自然客户端也要共用 proto 文件
正巧,Qt 6.8 正式纳入了 qt grpc 模块,让我们搭建环境,Thinking in Qt!
Qt GRPC 模块一般和 Qt Protobuf 模块一起使用
它不是开箱可用的,必须先安装 protoc,可选安装 openssl
众所周知,Qt6 使用 CMake 构建工程,默认不支持 qmake 导入该模块
Qt 提供了 Protobuf 插件,通过引入 ProtobufTools 模块
xxxxxxxxxx
find_package(Qt6 COMPONENTS ProtobufTools REQUIRED)
Qt 提供了 GRPC 插件,通过引入 GrpcTools 模块
xxxxxxxxxx
find_package(Qt6 COMPONENTS GrpcTools REQUIRED)
因为默认的 protoc 生成的都是 C++ 的类型,比如 std::string、std::vector
通过 Qt 的插件:qtprotobufgen 和 qtgrpcgen,生成的 .h 和 .cpp 就是 QString、QList 类型
生成的枚举类型自带 Q_ENUM_NS,可以直接 qDebug,也可以通过 QMetaEnum 转成 QString
xxxxxxxxxx
namespace MyNamespace {
Q_NAMESPACE
enum class MyEnum : int32_t {
ValueA = 0,
ValueB = 1,
ValueC = 2,
};
Q_ENUM_NS(MyEnum)
}
MyNamespace::MyEnum myValue = MyNamespace::MyEnum::ValueA;
QMetaEnum metaEnum = QMetaEnum::fromType<MyNamespace::MyEnum>();
QString str = metaEnum.valueToKey(int(myValue));
qDebug() << myValue; // 输出 MyNamespace::MyEnum::ValueA
qDebug() << str; // 输出 "ValueA"
Qt 推荐使用 vcpkg 安装 gRPC
、Protobuf
和 OpenSSL
模块
这里说下 Qt Creator 配置 vcpkg
打开终端,依次输入命令,安装 protobuf、grpc 和 openssl
xxxxxxxxxx
cd C:\
git clone https://github.com/microsoft/vcpkg.git
bootstrap-vcpkg.bat
vcpkg.exe install protobuf
vcpkg.exe install grpc
vcpkg.exe install openssl
打开 Qt Creator,点击【帮助】——【关于插件...】
新建项目后(Build system 选 cmake),点击【文件】——【New File...】
编辑 vcpkg.json
官网的太简单,这里给一份复杂点的参考,方便大家复制粘贴
xxxxxxxxxx
├─image
│ └─image.qrc
│
├─pem
│ └─pem.qrc
│
├─proto
│ ├─common
│ │ ├─const.proto
│ │ └─param.proto
│ │
│ ├─md
│ │ └─md.proto
│ │
│ ├─trader
│ │ └─trader.proto
│ │
│ └─tt
│ └─tt_server.proto
│
├─main.cpp
│
├─mainwindow.cpp
│
├─mainwindow.h
│
├─vcpkg.json
│
├─my_client.rc
│
└─CMakeLists.txt
xxxxxxxxxx
cmake_minimum_required(VERSION 3.16)
project(my_client VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenSSL REQUIRED)
find_package(Qt6 REQUIRED COMPONENTS
Core
Grpc
GrpcTools
Gui
Protobuf
ProtobufQtCoreTypes
ProtobufQtGuiTypes
ProtobufTools
ProtobufWellKnownTypes
Widgets
)
qt_standard_project_setup()
set(SOURCES
main.cpp
mainwindow.h
mainwindow.cpp
mainwindow.ui
my_client.rc
)
qt_add_resources(SOURCES pem/pem.qrc image/image.qrc)
qt_add_executable(my_client
${SOURCES}
)
qt_add_protobuf(my_client
PROTO_FILES proto/common/const.proto
OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/proto/common
)
qt_add_protobuf(my_client
PROTO_FILES proto/common/param.proto
OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/proto/common
)
qt_add_protobuf(my_client
PROTO_FILES proto/md/md.proto
PROTO_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/proto/md
)
qt_add_protobuf(my_client
PROTO_FILES proto/trader/trader.proto
PROTO_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/proto/trader
)
qt_add_protobuf(my_client
PROTO_FILES proto/tt/tt_server.proto
PROTO_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/proto/tt
)
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
qt_add_grpc(my_client
CLIENT
PROTO_FILES
${CMAKE_CURRENT_SOURCE_DIR}/proto/md/md.proto
${CMAKE_CURRENT_SOURCE_DIR}/proto/tt/tt_server.proto
PROTO_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/proto/md
${CMAKE_CURRENT_SOURCE_DIR}/proto/tt
)
target_include_directories(my_client
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_BINARY_DIR}
proto/trader
proto/md
proto/tt
)
target_link_libraries(my_client PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
Qt6::Core
Qt6::Grpc
Qt6::Gui
Qt6::Protobuf
Qt6::ProtobufQtCoreTypes
Qt6::ProtobufQtGuiTypes
Qt6::ProtobufWellKnownTypes
Qt6::Widgets
)
target_compile_options(my_client PRIVATE /utf-8)
set_target_properties(my_client PROPERTIES WIN32_EXECUTABLE TRUE)
这样,会在 proto 文件夹的不同子目录生成 *_protobuftyperegistrations.cpp、 *.qpb.cpp 和 *.qpb.h
xxxxxxxxxx
QFile certFile(":/pem/client-cert.pem");
certFile.open(QIODevice::ReadOnly);
QSslCertificate serverCert(&certFile);
certFile.close();
QFile keyFile(":/pem/client-key.pem");
keyFile.open(QIODevice::ReadOnly);
QSslKey serverKey(&keyFile, QSsl::Rsa, QSsl::Pem);
keyFile.close();
QFile caCertFile(":/pem/ca-cert.pem");
caCertFile.open(QIODevice::ReadOnly);
QSslCertificate caCert(&caCertFile);
caCertFile.close();
QSslConfiguration sslConfig;
sslConfig.setLocalCertificate(serverCert);
sslConfig.setPrivateKey(serverKey);
sslConfig.addCaCertificate(caCert);
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
sslConfig.setProtocol(QSsl::TlsV1_3);
sslConfig.setAllowedNextProtocols({{"h2"}});
QGrpcChannelOptions channelOptions;
channelOptions.setSslConfiguration(sslConfig);
auto tt_server_channel = std::make_shared<QGrpcHttp2Channel>(QUrl("https://127.0.0.1:7777"), channelOptions);
tt_server_client->attachChannel(tt_server_channel);
这里要特别提一下这句
xxxxxxxxxx
sslConfig.setAllowedNextProtocols({{"h2"}});
一开始一直连不上服务器
非常感谢下面这个链接,帮了我大忙
另外
xxxxxxxxxx
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
也可以改成
xxxxxxxxxx
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
这样就不用验证 key 了
qt grpc 主要有 unary calls 和 bidirectional streams (以及 server streams 和 client streams)
通过 QGrpcCallReply 和 QGrpcBidiStream 来使用,这里简单分享下用法
xxxxxxxxxx
qint64 requestTime = QDateTime::currentMSecsSinceEpoch();
ping::pong::Ping request;
request.setTime(requestTime);
auto reply = cl.PingPong(request,{});
QObject::connect(reply.get(), &QGrpcCallReply::finished, reply.get(),
[requestTime, replyPtr = reply.get()]() {
if (const auto response = replyPtr->read<ping::pong::Pong>())
qDebug() << "Ping-Pong time difference" << response->time() - requestTime;
qDebug() << "Failed deserialization";
});
QObject::connect(reply.get(), &QGrpcCallReply::errorOccurred, stream.get()
[](const QGrpcStatus &status) {
qDebug() << "Error occurred: " << status.code() << status.message();
});
xxxxxxxxxx
QObject::connect(stream.get(), &QGrpcBidiStream::messageReceived, stream.get(),
[streamPtr = stream.get(), &timer, &maxPingPongTime, &requestTime]{
if (const auto response = streamPtr->read<ping::pong::Pong>())
maxPingPongTime = std::max(maxPingPongTime, response->time() - requestTime);
});
QObject::connect(stream.get(), &QGrpcBidiStream::finished, stream.get(),
[streamPtr = stream.get(), &timer, &maxPingPongTime]{
qDebug() << "Maximum Ping-Pong time: " << maxPingPongTime;
timer.stop();
});
QObject::connect(stream.get(), &QGrpcBidiStream::errorOccurred, stream.get(),
[&timer](const QGrpcStatus &status){
qDebug() << "Error occurred: " << status.code() << status.message();
timer.stop();
});
可以登录 Qt 官方的 git,查看最新的 demo https://code.qt.io/cgit/qt/qtgrpc.git/tree/examples/grpc
之前我一直用网页打开 chatgpt.com,写 prompt 复制粘贴代码
Qt Creator 15.0.0 已经集成了 Copilot 插件,改变了我的编程习惯
首先是安装 node.js
然后安装 Neovim
最后 clone 以下仓库,参考下图配置 node.exe 及 language-server.js
xxxxxxxxxx
git clone https://github.com/github/copilot.vim.git
现在我编写代码都常开代理,善用 tab 了
分享下我的快捷键设置
最后的最后,再次感谢 vcpkg 和 copilot!