利用Qt在Android上使用grpc須要*.a的靜態庫,Windows上編譯的lib庫以及linux編譯出來的.a,通過嘗試,均沒法連接成功。本文嘗試使用NDK來編譯Android版本的grpc靜態庫。java
前言
Qt for Android上要使用grpc,須要用到對應Android架構的靜態庫文件,本文總結記錄下在Ubuntulinux
準備工做
安裝git:sudo apt install git
安裝cmake:sudo apt install cmake
安裝構建工具:sudo apt-get install build-essential autoconf libtool
下載Android NDK:android-ndk-r18b-linux-x86_64.zip
安裝grpc:
$ mkdir git & cd git
$ git clone https://github.com/grpc/grpc
$ cd grpc
$ git submodule update --initandroid
編譯
環境:Ubuntu18.04
使用Android NDK交叉工具鏈編譯:
$ cd ../ & mkdir grpc-build
$ vim compile.sh 輸入如下腳本內容
#!/bin/sh
cd /home/yakong/git/grpc-build
export NDK_ROOT=/home/yakong/Downloads/android-ndk-r16b
cmake /home/yakong/git/grpc/\
-DCMAKE_TOOLCHAIN_FILE=${NDK_ROOT}/build/cmake/android.toolchain.cmake\
-DANDROID_ABI=armeabi-v7a\
-DANDROID_PLATFORM=android-24\
-DANDROID_STL=c++_static\
-DRUN_HAVE_STD_REGEX=0\
-DRUN_HAVE_POSIX_REGEX=0\
-DRUN_HAVE_STEADY_CLOCK=0\
-DCMAKE_BUILD_TYPE=Release
cmake --build . --target grpc++ios
$ chmod +x ./compile.sh
$ ./compile.sh
成功構建完成後,在grpc-build生成各*.a靜態庫文件c++
Qt for Android使用
環境:Windows10+Qt5.12.3+Android NDK r18b+華爲榮耀9手機
新建QtgRPC-Server應用程序,連接好grpc以及第三方的protobuffer等靜態庫後,構建出現undefined reference to `rand/stderr/strtof`的連接錯誤,解決請看附錄章節。
程序構建打包生成apk後,在Android設備上安裝運行直接崩潰退出,提示Protocol Buffer版本不匹配錯誤,解決請看附錄章節。
程序能夠在Android真機上運行起來了,可是看日誌顯示好像?grpc服務沒有啓動起來,在應用程序輸出窗口有如下紅色錯誤(看不懂):git
一波剛平,一波又起,真是一波接一波,繼續打怪!可是,我直接運行Console服務端(QtgRPC-Server),以及Qt Widget客戶端(grpc-client),結果顯示能夠通訊,哈哈,上述紅色日誌部分不影響,最終運行的結果:github
至此,基於Qt C++跨平臺特性,Qt for Android調用grpc成功!golang
附錄
1. /bin/sh: 1: go: not found錯誤
[ 15%] Generating err_data.c
/bin/sh: 1: go: not found
third_party/boringssl/crypto/err/CMakeFiles/err.dir/build.make:83: recipe for target 'third_party/boringssl/crypto/err/err_data.c' failed
make[3]: *** [third_party/boringssl/crypto/err/err_data.c] Error 127
make[3]: *** Deleting file 'third_party/boringssl/crypto/err/err_data.c'
CMakeFiles/Makefile2:3345: recipe for target 'third_party/boringssl/crypto/err/CMakeFiles/err.dir/all' failed
make[2]: *** [third_party/boringssl/crypto/err/CMakeFiles/err.dir/all] Error 2
CMakeFiles/Makefile2:1016: recipe for target 'CMakeFiles/grpc++.dir/rule' failed
make[1]: *** [CMakeFiles/grpc++.dir/rule] Error 2
Makefile:463: recipe for target 'grpc++' failed
make: *** [grpc++] Error 2ubuntu
緣由爲缺乏go編譯環境
解決:sudo apt install golangvim
2. 查看*.a架構信息
yakong@ubuntu:~/git/grpc-build/third_party/protobuf$ ls
CMakeFiles lib Makefile protobuf.pc
cmake_install.cmake libprotobuf.a protobuf-lite.pc
yakong@ubuntu:~/git/grpc-build/third_party/protobuf$ file libprotobuf.a
libprotobuf.a: current ar archive
yakong@ubuntu:~/git/grpc-build/third_party/protobuf$ lipo -info libprotobuf.a
Command 'lipo' not found, did you mean:
command 'lilo' from deb lilo
Try: sudo apt install <deb name>
yakong@ubuntu:~/git/grpc-build/third_party/protobuf$ readelf -h libprotobuf.a | grep 'Class\|File\|Machine'
File: libprotobuf.a(any_lite.cc.o)
Class: ELF32
Machine: ARM
File: libprotobuf.a(arena.cc.o)
Class: ELF32
Machine: ARM
3. undefined reference to `rand/stderr/strtof`
Qt for Android中使用grpc出現連接錯誤:
該文章指出,在NDK15之後,標準IO設備:stderr 等都不被支持了~
仔細發現我使用的是r16b版本編譯grpc,而Qt Creator中卻使用的是r18b版本,版本確實不一致,有可能會形成影響。
然而,我將Qt Creator使用的ndk版本換成r16b,同樣的錯誤:
我再次使用r18b版本的ndk在Ubuntu上進行grpc編譯,仍是出現相似錯誤:
對照錯誤出現的位置,修改grpc源碼:
1) 將使用rand()的位置改成固定的之值,好比1(任意隨機的整數值)
2) 將使用stderr的地方註釋掉,不影響功能
3) 將strtof修改成strtod或使用低版本的ndk
以後從新編譯,從新在Qt中加載使用,比較幸運,構建成功了~
附:NDK各版本下載
4. 運行錯誤:Protocol Buffer版本不匹配
This program requires version 3.9.0 of the Protocol Buffer runtime library, but the installed version is 3.8.0. Please update your library. If you compiled the program yourself, make sure that your headers are from the same version of Protocol Buffers as your link-time library.
分析:我在Ubuntu中使用ndk編譯grpc使用的protobuf版本爲3.8.0,而我在qt for android中使用的protoc命令產生的*.pb.cc是3.9.0版本生成的。因此,但願*.pb.cc也由3.8.0版本的protobuf編譯器來生成。
編譯生成protobuf編譯器:
$ ./grpc/third_party/protobuf
$./autogen.sh
$./configure
$ make // 耗時較長
$ [sudo] make install
執行protoc –version時出錯:
protoc: error while loading shared libraries: libprotoc.so.19: cannot open shared object file: No such file or directory
該文章指出:protobuf的默認安裝路徑是/usr/local/lib,而/usr/local/lib 不在Ubuntu體系默認的 LD_LIBRARY_PATH 裏,因此就找不到該lib
解決方法:
1.在/etc/ld.so.conf.d目錄中建立文件libprotobuf.conf 寫入內容:/usr/local/lib
2.輸入命令重置內容生效:sudo ldconfig
這時,再運行protoc –version 就能夠正常看到版本號了
在grpc目錄下執行:
$ make
報錯了:zconf.h找不到
解決方法:sudo apt-get install zlib1g-dev
再次make, 編譯漫長,耐心等待…
$ sudo make install
接着編譯自帶cpp例子helloworld:
將生成的helloworld.pb.h/cpp以及helloworld.grpc.pb.h/cpp拷貝在qt工程中使用,並且包含頭文件也用同一套的,不然會出現「#error This file was generated by an older version of protoc which is ^」的錯誤:
這下構建成功後,運行,不會在運行時報protobuf版本不對致使崩潰了~
5. Qt工程pro文件設置
增長:DEFINES += _WIN32_WINNT=0x0600
增長包含路徑和連接靜態庫路徑:
INCLUDEPATH += $$PWD/../../include
DEPENDPATH += $$PWD/../../include
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/ -lgrpc++
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/libgrpc++.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/ -lgrpc
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/libgrpc.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/ -lgpr
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/libgpr.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/ -laddress_sorting
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/libaddress_sorting.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/third_party/protobuf/ -lprotobuf
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/third_party/protobuf/libprotobuf.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/third_party/zlib/ -lz
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/third_party/zlib/libz.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/third_party/boringssl/ssl/ -lssl
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/third_party/boringssl/ssl/libssl.a
unix:!macx: LIBS += -L$$PWD/../../grpc-build-r18b/third_party/cares/cares/lib/ -lcares
unix:!macx: PRE_TARGETDEPS += $$PWD/../../grpc-build-r18b/third_party/cares/cares/lib/libcares.a
注意:其中include路徑包含grpc的頭文件以及protobuf的./src/google中的文件
6. Qt測試工程部分代碼
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 |
syntax =
"proto3"
; option java_multiple_files = true ; option java_package = "io.grpc.examples.helloworld" ; option java_outer_classname = "HelloWorldProto" ; option objc_class_prefix = "HLW" ; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1 ; float score = 2 ; } // The response message containing the greetings message HelloReply { string message = 1 ; float score = 2 ; } |
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 |
#include
<QCoreApplication> #include <iostream> #include <memory> #include <string> #include <grpc++/grpc++.h> #include "helloworld.pb.h" #include "helloworld.grpc.pb.h" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using helloworld::HelloRequest; using helloworld::HelloReply; using helloworld::Greeter; // Logic and data behind the server's behavior. class GreeterServiceImpl final : public Greeter::Service { Status SayHello(ServerContext* context, const HelloRequest* request, HelloReply* reply) override { std::string prefix( "Hello " ); reply->set_message(prefix + request->name()); reply->set_score(request->score()); return Status::OK; } }; void RunServer() { std::cout << "RunServer() " << std::endl; std::string server_address( "0.0.0.0:50051" ); GreeterServiceImpl service; ServerBuilder builder; // Listen on the given address without any authentication mechanism. builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); // Register "service" as the instance through which we'll communicate with // clients. In this case it corresponds to an *synchronous* service. builder.RegisterService(&service); // Finally assemble the server. std::unique_ptr<Server> server(builder.BuildAndStart()); std::cout << "Server listening on " << server_address << std::endl; // Wait for the server to shutdown. Note that some other thread must be // responsible for shutting down the server for this call to ever return. server->Wait(); } int main( int argc, char *argv[]) { std::cout << "main(int argc, char *argv[]) " << std::endl; //QCoreApplication a(argc, argv); //return a.exec(); RunServer(); return 0 ; } |
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 |
#ifndef
WIDGET_H #define WIDGET_H #include <QWidget> #include <iostream> #include <memory> #include <string> #include <grpcpp/grpcpp.h> #include <limits> #include "helloworld.grpc.pb.h" using grpc::Channel; using grpc::ClientContext; using grpc::Status; using helloworld::HelloRequest; using helloworld::HelloReply; using helloworld::Greeter; class GreeterClient { public : GreeterClient(std::shared_ptr<Channel> channel) : stub_(Greeter::NewStub(channel)) {} // Assembles the client's payload, sends it and presents the response back // from the server. std::string SayHello( const std::string& user) { // Data we are sending to the server. HelloRequest request; request.set_name(user); // Container for the data we expect from the server. HelloReply reply; // Context for the client. It could be used to convey extra information to // the server and/or tweak certain RPC behaviors. ClientContext context; // The actual RPC. Status status = stub_->SayHello(&context, request, &reply); // Act upon its status. if (status.ok()) { return reply.message(); } else { std::cout << status.error_code() << ": " << status.error_message() << std::endl; return "RPC failed" ; } } float SayScore( const float score = std::numeric_limits< float >::max()) { // Data we are sending to the server. HelloRequest request; request.set_score(score); // Container for the data we expect from the server. HelloReply reply; // Context for the client. It could be used to convey extra information to // the server and/or tweak certain RPC behaviors. ClientContext context; // The actual RPC. Status status = stub_->SayHello(&context, request, &reply); // Act upon its status. if (status.ok()) { return reply.score(); } else { std::cout << status.error_code() << ": " << status.error_message() << std::endl; return 3 .14f; } } private : std::unique_ptr<Greeter::Stub> stub_; }; class QTextBrowser; class Widget : public QWidget { Q_OBJECT public : Widget(QWidget *parent = nullptr); ~Widget(); private slots: void req(); private : QTextBrowser *text; }; #endif // WIDGET_H |
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 |
#include
"Widget.h"
#include <QVBoxLayout> #include <QTextBrowser> #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) , text( new QTextBrowser()) { setWindowTitle(tr( "grpc-client" )); resize( 320 , 460 ); QVBoxLayout *layout = new QVBoxLayout( this ); QPushButton *button = new QPushButton( "req" ); connect(button, &QPushButton::clicked, this , &Widget::req); layout->addWidget(text); layout->addWidget(button); setLayout(layout); } Widget::~Widget() { } void Widget::req() { // Instantiate the client. It requires a channel, out of which the actual RPCs // are created. This channel models a connection to an endpoint (in this case, // localhost at port 50051). We indicate that the channel isn't authenticated // (use of InsecureChannelCredentials()). GreeterClient greeter(grpc::CreateChannel( "localhost:50051" , grpc::InsecureChannelCredentials())); std::string user( "world" ); std::string reply = greeter.SayHello(user); float replyf= greeter.SayScore(); std::cout << "Greeter received: " << reply << std::endl; std::cout << "Greeter received: " << replyf << std::endl; if (text != nullptr){ text->append(QString( "Greeter received: %1" ).arg(reply.c_str())); text->append(QString( "Greeter received: %1" ).arg(replyf)); } } |