最近又回归了金融行业,做 PC 客户端软件
服务端用 go 写的,用了 grpc,自然客户端也要共用 proto 文件
正巧,Qt 6.8 正式纳入了 qt grpc 模块,让我们搭建环境,Thinking in Qt!
Qt GRPC 模块一般和 Qt Protobuf 模块一起使用
它不是开箱可用的,必须先安装 protoc,可选安装 openssl
众所周知,Qt6 使用 CMake 构建工程,默认不支持 qmake 导入该模块
Qt 提供了 Protobuf 插件,通过引入 ProtobufTools 模块
xxxxxxxxxxfind_package(Qt6 COMPONENTS ProtobufTools REQUIRED)Qt 提供了 GRPC 插件,通过引入 GrpcTools 模块
xxxxxxxxxxfind_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_NAMESPACEenum 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::ValueAqDebug() << str; // 输出 "ValueA"Qt 推荐使用 vcpkg 安装 gRPC、Protobuf 和 OpenSSL 模块
这里说下 Qt Creator 配置 vcpkg
打开终端,依次输入命令,安装 protobuf、grpc 和 openssl
xxxxxxxxxxcd C:\git clone https://github.com/microsoft/vcpkg.gitbootstrap-vcpkg.batvcpkg.exe install protobufvcpkg.exe install grpcvcpkg.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.txtxxxxxxxxxxcmake_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
xxxxxxxxxxQFile 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);这里要特别提一下这句
xxxxxxxxxxsslConfig.setAllowedNextProtocols({{"h2"}});一开始一直连不上服务器
非常感谢下面这个链接,帮了我大忙
另外
xxxxxxxxxxsslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);也可以改成
xxxxxxxxxxsslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);这样就不用验证 key 了
qt grpc 主要有 unary calls 和 bidirectional streams (以及 server streams 和 client streams)
通过 QGrpcCallReply 和 QGrpcBidiStream 来使用,这里简单分享下用法
xxxxxxxxxxqint64 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(); });xxxxxxxxxxQObject::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
xxxxxxxxxxgit clone https://github.com/github/copilot.vim.git
现在我编写代码都常开代理,善用 tab 了
分享下我的快捷键设置

最后的最后,再次感谢 vcpkg 和 copilot!
