├── README.md ├── qgrpc ├── examples │ ├── server │ │ ├── main.cpp │ │ ├── qgrpc-test-server.pro │ │ ├── qgrpc-test-server.ui │ │ └── qgrpc-test-server.h │ ├── client │ │ ├── main.cpp │ │ ├── qgrpc-test-client.pro │ │ ├── qgrpc-test-client.ui │ │ └── qgrpc-test-client.h │ └── proto │ │ └── pingpong.proto ├── QAbstractGrpc.h ├── QGrpcServerMonitor.h ├── QGrpcClientMonitor.h ├── proto3parser.py ├── genQGrpc.py ├── QGrpcClient.h └── QGrpcServer.h ├── grpc.pri └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # qgrpc 2 | Qt wrapper around Google RPC 3 | 4 | For more detail read the article (in Russian): https://habr.com/ru/articles/420237/ 5 | -------------------------------------------------------------------------------- /qgrpc/examples/server/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qgrpc-test-server.h" 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | QApplication app(argc, argv); 7 | mainWindow mw; 8 | return app.exec(); 9 | } 10 | -------------------------------------------------------------------------------- /qgrpc/examples/client/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "qgrpc-test-client.h" 5 | 6 | int main(int argc, char* argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | mainWindow mw; 10 | return app.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /qgrpc/examples/client/qgrpc-test-client.pro: -------------------------------------------------------------------------------- 1 | 2 | unix: QMAKE_CXXFLAGS += -std=c++11 3 | unix: QMAKE_LFLAGS_DEBUG += -std=c++11 4 | win32-g++: QMAKE_CXXFLAGS += -std=c++0x 5 | 6 | DEFINES += __STDC_LIMIT_MACROS 7 | 8 | QGRPC_CONFIG = client 9 | 10 | TEMPLATE = app 11 | 12 | TARGET = qgrpc-test-client 13 | 14 | CONFIG += qt 15 | CONFIG += debug_and_release 16 | QT *= widgets 17 | 18 | GRPC += ../proto/pingpong.proto 19 | 20 | HEADERS += qgrpc-test-client.h 21 | 22 | SOURCES += main.cpp 23 | 24 | FORMS += qgrpc-test-client.ui 25 | 26 | PWD = ../../.. 27 | 28 | CONFIG(debug, debug|release) { 29 | MOC_DIR = debug 30 | } else { 31 | MOC_DIR = release 32 | } 33 | 34 | INC_GRPC = $${PWD}/grpc.pri 35 | !include($${INC_GRPC}) { 36 | error("$$TARGET: File not found: $${INC_GRPC}") 37 | } 38 | 39 | AUTHORS = dmakarov 40 | 41 | -------------------------------------------------------------------------------- /qgrpc/examples/server/qgrpc-test-server.pro: -------------------------------------------------------------------------------- 1 | 2 | unix: QMAKE_CXXFLAGS += -std=c++11 3 | unix: QMAKE_LFLAGS_DEBUG += -std=c++11 4 | win32-g++: QMAKE_CXXFLAGS += -std=c++0x 5 | 6 | DEFINES += __STDC_LIMIT_MACROS 7 | 8 | QGRPC_CONFIG = server 9 | 10 | TEMPLATE = app 11 | 12 | TARGET = qgrpc-test-server 13 | 14 | CONFIG += qt 15 | CONFIG += debug_and_release 16 | QT *= widgets 17 | 18 | GRPC += ../proto/pingpong.proto 19 | 20 | HEADERS += qgrpc-test-server.h 21 | 22 | SOURCES += main.cpp 23 | 24 | FORMS += qgrpc-test-server.ui 25 | 26 | PWD = ../../.. 27 | 28 | CONFIG(debug, debug|release) { 29 | MOC_DIR = debug 30 | } else { 31 | MOC_DIR = release 32 | } 33 | 34 | INC_GRPC = $${PWD}/grpc.pri 35 | !include($${INC_GRPC}) { 36 | error("$$TARGET: File not found: $${INC_GRPC}") 37 | } 38 | 39 | AUTHORS = dmakarov 40 | 41 | -------------------------------------------------------------------------------- /qgrpc/examples/proto/pingpong.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | //option java_multiple_files = true; 4 | //option java_package = "io.grpc.examples.helloworld"; 5 | //option java_outer_classname = "HelloWorldProto"; 6 | //option objc_class_prefix = "HLW"; 7 | 8 | package pingpong; 9 | 10 | // The greeting service definition. 11 | service ping 12 | { 13 | // Sends a greeting 14 | rpc SayHello (PingRequest) returns (PingReply) {} 15 | 16 | rpc GladToSeeMe(PingRequest) returns (stream PingReply){} 17 | 18 | rpc GladToSeeYou(stream PingRequest) returns (PingReply){} 19 | 20 | rpc BothGladToSee(stream PingRequest) returns (stream PingReply){} 21 | } 22 | 23 | // The request message containing the user's name. 24 | message PingRequest 25 | { 26 | string name = 1; 27 | string message = 2; 28 | } 29 | 30 | // The response message containing the greetings 31 | message PingReply 32 | { 33 | string message = 1; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /qgrpc/QAbstractGrpc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "assert.h" 6 | #include 7 | 8 | 9 | #ifndef WIN32 10 | #if (__cplusplus >= 201103L && __cplusplus < 201402L) 11 | namespace std { 12 | // Note: despite not being even C++11 compliant, Visual Studio 2013 has their own implementation of std::make_unique. 13 | // Define std::make_unique for pre-C++14 14 | template inline unique_ptr make_unique(Args&&... args) { 15 | return unique_ptr(new T(forward(args)...)); 16 | } 17 | } // namespace std 18 | #endif // C++11 <= version < C++14 19 | #endif // WIN32 20 | 21 | 22 | namespace QGrpcSrvAbstract 23 | { 24 | struct AbstractService 25 | { 26 | virtual void CheckCQ() = 0; 27 | virtual void PrepareForShutdown() = 0; 28 | }; 29 | } 30 | 31 | 32 | 33 | namespace QGrpcCliAbstract 34 | { 35 | class AbstractService 36 | { 37 | public: 38 | virtual bool CheckCQ() = 0; 39 | }; 40 | } 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /qgrpc/QGrpcServerMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "QAbstractGrpc.h" 7 | 8 | class QSrvMonitorPrivate : public QObject 9 | { 10 | Q_OBJECT 11 | QTimer* cq_timer_ = nullptr; 12 | QGrpcSrvAbstract::AbstractService& service_; 13 | std::atomic stopped_; 14 | public: 15 | QSrvMonitorPrivate(QGrpcSrvAbstract::AbstractService& service) 16 | :service_(service), 17 | stopped_(false) 18 | {} 19 | 20 | virtual ~QSrvMonitorPrivate(){} 21 | public slots : 22 | void start() 23 | { 24 | if (!cq_timer_) 25 | { 26 | cq_timer_ = new QTimer(this); 27 | cq_timer_->setSingleShot(true); 28 | bool ok = connect(cq_timer_, SIGNAL(timeout()), this, SLOT(AsyncMonitorRpc())); assert(ok); 29 | } 30 | cq_timer_->start(); 31 | stopped_.store(false); 32 | } 33 | void stop() 34 | { 35 | stopped_.store(true); 36 | service_.PrepareForShutdown(); 37 | } 38 | 39 | 40 | private slots: 41 | void AsyncMonitorRpc() 42 | { 43 | service_.CheckCQ(); 44 | if (stopped_.load()) 45 | return; 46 | cq_timer_->start(); 47 | } 48 | }; 49 | 50 | class QGrpcSrvMonitor : public QObject 51 | { 52 | Q_OBJECT 53 | QThread serverThread_; 54 | QSrvMonitorPrivate server_; 55 | public: 56 | QGrpcSrvMonitor(QGrpcSrvAbstract::AbstractService& service) 57 | :server_(service) 58 | { 59 | server_.moveToThread(&serverThread_); 60 | bool c = connect(&serverThread_, SIGNAL(started()), &server_, SLOT(start())); assert(c); 61 | } 62 | 63 | inline void start() 64 | { 65 | if (!serverThread_.isRunning()) 66 | serverThread_.start(); 67 | } 68 | 69 | ~QGrpcSrvMonitor() 70 | { 71 | server_.stop(); 72 | serverThread_.quit(); 73 | serverThread_.wait(); 74 | } 75 | inline QThread* grpcThread() const { return const_cast(&(this->serverThread_)); } 76 | }; -------------------------------------------------------------------------------- /qgrpc/examples/server/qgrpc-test-server.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | service 1 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | false 30 | 31 | 32 | 0.0.0.0 33 | 34 | 35 | 36 | 37 | 38 | 39 | ip 40 | 41 | 42 | 43 | 44 | 45 | 46 | 1 47 | 48 | 49 | 65535 50 | 51 | 52 | 50051 53 | 54 | 55 | 56 | 57 | 58 | 59 | port 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | start 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 0 88 | 0 89 | 800 90 | 21 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /qgrpc/QGrpcClientMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "QAbstractGrpc.h" 7 | 8 | 9 | struct SleepIfActive 10 | { 11 | explicit SleepIfActive(size_t msec) : msec(msec) {} 12 | size_t msec; 13 | }; 14 | 15 | struct SleepIfInactive 16 | { 17 | explicit SleepIfInactive(size_t msec) : msec(msec) {} 18 | size_t msec; 19 | }; 20 | 21 | class QCliServerPrivate : public QObject 22 | { 23 | Q_OBJECT 24 | struct ServiceConfig 25 | { 26 | //QGrpcCliBase::AbstractService* service; 27 | size_t active_msec; 28 | size_t inactive_msec; 29 | QTimer* timer; 30 | }; 31 | std::map services; 32 | public: 33 | inline void addService(QGrpcCliAbstract::AbstractService* const service, SleepIfActive amsec, SleepIfInactive iamsec) 34 | { 35 | services[service] = { amsec.msec, iamsec.msec, nullptr }; 36 | } 37 | inline void deleteService(QGrpcCliAbstract::AbstractService* const service) 38 | { 39 | services.erase(service); 40 | } 41 | 42 | virtual ~QCliServerPrivate() 43 | { 44 | } 45 | public slots : 46 | void start() 47 | { 48 | for (auto& p : services) 49 | { 50 | if (!p.second.timer) 51 | { 52 | QTimer* timer = new QTimer(this); 53 | bool ok = connect(timer, SIGNAL(timeout()), this, SLOT(MonitorRpc())); assert(ok); 54 | p.second.timer = timer; 55 | } 56 | p.second.timer->start(p.second.active_msec); 57 | } 58 | } 59 | void stop() 60 | { 61 | for (const auto& p : services) 62 | stop(p.first); 63 | } 64 | 65 | void stop(QGrpcCliAbstract::AbstractService* const service) 66 | { 67 | services.at(service).timer->stop(); 68 | } 69 | private slots: 70 | void MonitorRpc() 71 | { 72 | auto timer = qobject_cast(QObject::sender()); 73 | if (!timer->isActive()) 74 | return; 75 | auto service_ = serviceByTimer(timer); 76 | auto config = services.at(service_); 77 | bool active = service_->CheckCQ(); 78 | if (active) 79 | config.timer->start(config.active_msec); 80 | else 81 | config.timer->start(config.inactive_msec); 82 | } 83 | private: 84 | QGrpcCliAbstract::AbstractService* serviceByTimer(QTimer* timer) 85 | { 86 | using spair = const std::pair; 87 | auto it = std::find_if(services.cbegin(), services.cend(), [&timer](spair it) {return it.second.timer == timer; }); 88 | assert(it != services.cend()); 89 | return it->first; 90 | } 91 | }; 92 | 93 | class QGrpcCliServer : public QObject 94 | { 95 | Q_OBJECT 96 | QThread serverThread_; 97 | QCliServerPrivate server_; 98 | public: 99 | QGrpcCliServer() 100 | { 101 | server_.moveToThread(&serverThread_); 102 | bool c = connect(&serverThread_, SIGNAL(started()), &server_, SLOT(start())); assert(c); 103 | c = connect(&serverThread_, SIGNAL(finished()), &server_, SLOT(stop())); assert(c); 104 | c = connect(this, SIGNAL(toStart()), &server_, SLOT(start())); assert(c); 105 | c = connect(this, SIGNAL(toStop()), &server_, SLOT(stop())); assert(c); 106 | c = connect(this, SIGNAL(toStop(QGrpcCliAbstract::AbstractService* const)), &server_, SLOT(stop(QGrpcCliAbstract::AbstractService* const))); assert(c); 107 | } 108 | 109 | inline void addService(QGrpcCliAbstract::AbstractService* const service, 110 | SleepIfActive amsec = SleepIfActive(0u), 111 | SleepIfInactive iamsec = SleepIfInactive(3000u)) { server_.addService(service, amsec, iamsec); } 112 | inline void deleteService(QGrpcCliAbstract::AbstractService* const service) { server_.deleteService(service); } 113 | 114 | inline void start() 115 | { 116 | if (!serverThread_.isRunning()) 117 | serverThread_.start(); 118 | else 119 | emit toStart(); 120 | } 121 | 122 | inline void stop() 123 | { 124 | emit toStop(); 125 | } 126 | 127 | inline void stop(QGrpcCliAbstract::AbstractService* const service) { emit toStop(service); } 128 | 129 | ~QGrpcCliServer() 130 | { 131 | emit toStop(); 132 | serverThread_.quit(); 133 | serverThread_.wait(); 134 | } 135 | inline QThread* grpcThread() const { return const_cast(&(this->serverThread_)); } 136 | signals: 137 | void toStart(); 138 | void toStop(); 139 | void toStop(QGrpcCliAbstract::AbstractService* const); 140 | }; -------------------------------------------------------------------------------- /qgrpc/examples/server/qgrpc-test-server.h: -------------------------------------------------------------------------------- 1 | #ifndef QGRPC_TEST_SERVER_H 2 | #define QGRPC_TEST_SERVER_H 3 | 4 | #include "pingpong.qgrpc.server.h" 5 | 6 | #include 7 | 8 | #include "ui_qgrpc-test-server.h" 9 | #include 10 | 11 | class mainWindow: public QMainWindow, public Ui_MainWindow 12 | { 13 | Q_OBJECT; 14 | QpingServerService pingservice; 15 | QGrpcSrvMonitor pingmonitor; 16 | 17 | public: 18 | mainWindow():Ui_MainWindow(), pingmonitor(pingservice) 19 | { 20 | setupUi(this); 21 | 22 | bool is_ok; 23 | is_ok = connect(pb_start, SIGNAL(clicked()), this, SLOT(onStart1())); assert(is_ok); 24 | is_ok = connect(&pingservice, SIGNAL(SayHelloRequest(QpingServerService::SayHelloCallData*)), this, SLOT(onSayHello(QpingServerService::SayHelloCallData*))); assert(is_ok); 25 | is_ok = connect(&pingservice, SIGNAL(GladToSeeMeRequest(QpingServerService::GladToSeeMeCallData*)), this, SLOT(onGladToSeeMe(QpingServerService::GladToSeeMeCallData*))); assert(is_ok); 26 | is_ok = connect(&pingservice, SIGNAL(GladToSeeYouRequest(QpingServerService::GladToSeeYouCallData*)), this, SLOT(onGladToSeeYou(QpingServerService::GladToSeeYouCallData*))); assert(is_ok); 27 | is_ok = connect(&pingservice, SIGNAL(BothGladToSeeRequest(QpingServerService::BothGladToSeeCallData*)), this, SLOT(onBothGladToSee(QpingServerService::BothGladToSeeCallData*))); assert(is_ok); 28 | this->show(); 29 | } 30 | ~mainWindow() 31 | {} 32 | 33 | void setText(QPlainTextEdit* te, const QString& text) 34 | { 35 | te->insertPlainText(text + "\n"); 36 | QScrollBar *sb = te->verticalScrollBar(); 37 | sb->setValue(sb->maximum()); 38 | } 39 | 40 | private slots: 41 | 42 | void onStart1() 43 | { 44 | auto ipaddr = le_ipaddr->text(); 45 | auto port = sb_port->value(); 46 | auto addr_port = QString("%1:%2").arg(ipaddr).arg(port); 47 | setText(plainTextEdit, QString("Server started on %1").arg(addr_port)); 48 | pingservice.Start(QString("%1:%2").arg(ipaddr).arg(port).toStdString()); 49 | pingmonitor.start(); 50 | pb_start->setDisabled(true); 51 | //pb_stop->setDisabled(false); 52 | } 53 | 54 | void onStop1() 55 | { 56 | } 57 | 58 | void onSayHello(QpingServerService::SayHelloCallData* cd) 59 | { 60 | std::string prefix("Hello "); 61 | setText(plainTextEdit, QString("[%1][11]: request: %2").arg(QString::fromStdString(cd->peer())).arg(QString::fromStdString(cd->request.name()))); 62 | cd->reply.set_message(prefix + cd->request.name()); 63 | cd->Finish(); 64 | } 65 | 66 | void onGladToSeeMe(QpingServerService::GladToSeeMeCallData* cd) 67 | { 68 | static std::unordered_map mc_many; 69 | 70 | void* tag = static_cast(cd); 71 | 72 | std::string prefix("Hello "); 73 | std::vector greeting = 74 | { 75 | std::string(prefix + cd->request.name() + "!"), 76 | "I'm very glad to see you!", 77 | "Haven't seen you for thousand years.", 78 | "I'm server now. Call me later." 79 | }; 80 | 81 | auto it = mc_many.find(tag); 82 | if (it == mc_many.cend()) 83 | { 84 | mc_many.insert(std::make_pair(tag, 0)); 85 | it = mc_many.find(tag); 86 | assert(it != mc_many.cend()); 87 | } 88 | 89 | if (it->second < greeting.size()) 90 | { 91 | if(it->second == 0) 92 | setText(plainTextEdit, QString("[%1][1M]: request: %2").arg(QString::fromStdString(cd->peer())).arg(QString::fromStdString(cd->request.name()))); 93 | cd->reply.set_message(greeting.at(it->second++)); 94 | cd->Write(); 95 | } 96 | else 97 | { 98 | cd->Finish(); 99 | mc_many.erase(cd); 100 | } 101 | } 102 | 103 | void onGladToSeeYou(QpingServerService::GladToSeeYouCallData* cd) 104 | { 105 | if (cd->StreamFinished()) 106 | { 107 | setText(plainTextEdit, QString("[%1][M1]: sending reply and finishing...").arg(QString::fromStdString(cd->peer()))); 108 | cd->reply.set_message("Hello, Client!"); 109 | cd->Finish(); 110 | return; 111 | } 112 | cd->Read(); 113 | if(!cd->request.name().empty()) 114 | setText(plainTextEdit, QString("[%1][M1]: request: %2").arg(QString::fromStdString(cd->peer())).arg(QString::fromStdString(cd->request.name()))); 115 | } 116 | 117 | void onBothGladToSee(QpingServerService::BothGladToSeeCallData* cd) 118 | { 119 | void* tag = static_cast(cd); 120 | if (!cd->WriteMode())//reading mode 121 | { 122 | cd->Read(); 123 | if (!cd->request.name().empty()) 124 | setText(plainTextEdit, QString("[%1][MM]: request: %2").arg(QString::fromStdString(cd->peer())).arg(QString::fromStdString(cd->request.name()))); 125 | } 126 | else 127 | { 128 | static std::unordered_map mc_many; 129 | std::string prefix("Hello "); 130 | std::vector greeting = 131 | { 132 | std::string(prefix + cd->request.name() + "!"), 133 | "I'm very glad to see you!", 134 | "Haven't seen you for thousand years.", 135 | "I'm server now. Call me later." 136 | }; 137 | 138 | auto it = mc_many.find(tag); 139 | if (it == mc_many.cend()) 140 | { 141 | mc_many.insert(std::make_pair(tag, 0)); 142 | it = mc_many.find(tag); 143 | assert(it != mc_many.cend()); 144 | } 145 | 146 | if (it->second >= greeting.size() || cd->TimeToFinish()) 147 | { 148 | cd->Finish(); 149 | mc_many.erase(cd); 150 | } 151 | else 152 | { 153 | cd->reply.set_message(greeting.at(it->second++)); 154 | cd->Write(); 155 | } 156 | } 157 | } 158 | 159 | } ; 160 | 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /qgrpc/proto3parser.py: -------------------------------------------------------------------------------- 1 | # protobuf_parser.py 2 | # 3 | # simple parser for parsing protobuf .proto files 4 | # 5 | # Copyright 2010, Paul McGuire 6 | # 7 | 8 | """ 9 | Edited by: dmakarov 10 | Time: 26 april 2018 12:02 11 | Comments: parse services and RPCs 12 | """ 13 | 14 | from pyparsing import (Word, alphas, alphanums, Regex, Suppress, Forward, 15 | Group, oneOf, ZeroOrMore, Optional, delimitedList, Keyword, 16 | restOfLine, quotedString, Dict) 17 | 18 | ident = Word(alphas+"_'\"",alphanums+"_'\"").setName("identifier") 19 | integer = Regex(r"[+-]?\d+") 20 | 21 | LBRACE,RBRACE,LBRACK,RBRACK,LPAR,RPAR,EQ,SEMI = map(Suppress,"{}[]()=;") 22 | 23 | kwds = """message required optional repeated enum extensions extends extend 24 | to package service rpc returns true false option import syntax stream""" 25 | 26 | for kw in kwds.split(): 27 | exec("%s_ = Keyword('%s')" % (kw.upper(), kw)) 28 | 29 | messageBody = Forward() 30 | 31 | messageDefn = MESSAGE_ - ident("messageName") + LBRACE + messageBody("body") + RBRACE 32 | 33 | typespec = oneOf("""double float int32 int64 uint32 uint64 sint32 sint64 34 | fixed32 fixed64 sfixed32 sfixed64 bool string bytes""") | ident 35 | """""" 36 | rvalue = integer | TRUE_ | FALSE_ | ident 37 | fieldDirective = LBRACK + Group(ident + EQ + rvalue) + RBRACK 38 | fieldDefn = (Optional(( REQUIRED_ | OPTIONAL_ | REPEATED_ )("fieldQualifier")) + 39 | typespec("typespec") + ident("ident") + EQ + integer("fieldint") + ZeroOrMore(fieldDirective) + SEMI) 40 | 41 | # enumDefn ::= 'enum' ident '{' { ident '=' integer ';' }* '}' 42 | enumDefn = ENUM_("typespec") - ident('name') + LBRACE + Dict( ZeroOrMore( Group(ident + EQ + integer + SEMI) ))('values') + RBRACE 43 | 44 | # extensionsDefn ::= 'extensions' integer 'to' integer ';' 45 | extensionsDefn = EXTENSIONS_ - integer + TO_ + integer + SEMI 46 | 47 | # messageExtension ::= 'extend' ident '{' messageBody '}' 48 | messageExtension = EXTEND_ - ident + LBRACE + messageBody + RBRACE 49 | 50 | # messageBody ::= { fieldDefn | enumDefn | messageDefn | extensionsDefn | messageExtension }* 51 | messageBody << Group(ZeroOrMore( Group(fieldDefn | enumDefn | messageDefn | extensionsDefn | messageExtension) )) 52 | 53 | # methodDefn ::= 'rpc' ident '(' [ ident ] ')' 'returns' '(' [ ident ] ')' ';' 54 | methodDefn = (RPC_ - ident("methodName") + 55 | LPAR + Optional(STREAM_("paramStreamQualifier")) + Optional(ident("methodParam")) + RPAR + 56 | RETURNS_ + LPAR + Optional(STREAM_("returnStreamQualifier")) + Optional(ident("methodReturn")) + RPAR + ((LBRACE + RBRACE) | SEMI)) 57 | 58 | # serviceDefn ::= 'service' ident '{' methodDefn* '}' 59 | serviceDefn = SERVICE_ - ident("serviceName") + LBRACE + ZeroOrMore( Group(methodDefn) )('RPCs') + RBRACE 60 | 61 | # packageDirective ::= 'package' ident [ '.' ident]* ';' 62 | packageDirective = Group(PACKAGE_ - delimitedList(ident('packageName'), '.', combine=True) + SEMI) 63 | 64 | syntaxDirective = Group(SYNTAX_ - EQ + ident + SEMI) 65 | 66 | comment = '//' + restOfLine 67 | 68 | importDirective = IMPORT_ - quotedString("importFileSpec") + SEMI 69 | 70 | optionDirective = OPTION_ - ident("optionName") + EQ + quotedString("optionValue") + SEMI 71 | 72 | topLevelStatement = Group(messageExtension | enumDefn | importDirective | optionDirective| serviceDefn | messageDefn )# | Group(serviceDefn)('services') | Group(messageDefn)('messages') 73 | 74 | parser = Optional(syntaxDirective)("syntax") + (Optional(packageDirective) + ZeroOrMore(topLevelStatement))('package') 75 | 76 | parser.ignore(comment) 77 | 78 | 79 | test1 = """message Person { 80 | required int32 id = 1; 81 | required string name = 2; 82 | optional string email = 3; 83 | }""" 84 | 85 | test2 = """package tutorial; 86 | 87 | message Person { 88 | required string name = 1; 89 | required int32 id = 2; 90 | optional string email = 3; 91 | 92 | enum PhoneType { 93 | MOBILE = 0; 94 | HOME = 1; 95 | WORK = 2; 96 | } 97 | 98 | message PhoneNumber { 99 | required string number = 1; 100 | optional PhoneType type = 2 [default = HOME]; 101 | } 102 | 103 | repeated PhoneNumber phone = 4; 104 | } 105 | 106 | message AddressBook { 107 | repeated Person person = 1; 108 | }""" 109 | 110 | test3 = """\ 111 | syntax = "proto3"; 112 | 113 | //option java_multiple_files = true; 114 | //option java_package = "io.grpc.examples.helloworld"; 115 | //option java_outer_classname = "HelloWorldProto"; 116 | //option objc_class_prefix = "HLW"; 117 | 118 | package pingpong; 119 | 120 | // The greeting service definition. 121 | service ping 122 | { 123 | // Sends a greeting 124 | rpc SayHello (PingRequest) returns (PingReply) {} 125 | 126 | rpc GladToSeeMe(PingRequest) returns (stream PingReply){} 127 | 128 | rpc GladToSeeYou(stream PingRequest) returns (PingReply){} 129 | 130 | rpc BothGladToSee(stream PingRequest) returns (stream PingReply){} 131 | } 132 | 133 | // The request message containing the user's name. 134 | message PingRequest 135 | { 136 | string name = 1; 137 | string message = 2; 138 | } 139 | 140 | // The response message containing the greetings 141 | message PingReply 142 | { 143 | string message = 1; 144 | } 145 | """ 146 | 147 | def runAllTests(): 148 | parser.runTests([test1, test2, test3]) 149 | r = parser.parseString(test3) 150 | print(len(r.asDict()['package'])) 151 | print(r.asDict()['package'][0]) 152 | print(r.asDict()['package'][1]) 153 | print(r.asDict()['package'][2]) 154 | print(r.asDict()['package']) 155 | for k in r.items(): 156 | print(k) 157 | 158 | 159 | class grpc_rpc: 160 | def __init__(self): 161 | self.rpcname = '' 162 | self.paramtype = '' 163 | self.paramisstream = False 164 | self.returntype = '' 165 | self.returnisstream = False 166 | def __str__(self): 167 | return 'RPC{\nname: %s\nparam: %s\nreturn: %s\npstream: %s\nrstream: %s\n}' % (self.rpcname, self.paramtype, self.returntype, self.paramisstream, self.returnisstream) 168 | 169 | class grpc_service: 170 | def __init__(self, name): 171 | self.rpc_list = [] 172 | self.name = name 173 | 174 | 175 | def parseProtoFile(path): 176 | try: 177 | with open(path) as f: 178 | s = ''.join(f.readlines()) 179 | #parser.runTests([s]) 180 | res = parser.parseString(s) 181 | except Exception as e: 182 | print(e) 183 | return None 184 | 185 | d = res.asDict() 186 | #print(d) 187 | if not d: return None 188 | try: 189 | package = d['package'] 190 | except KeyError: return [] 191 | services = [] 192 | messages = [] 193 | packageName = '' 194 | for smth in package: 195 | if 'messageName' in smth.keys(): 196 | messages.append(smth['messageName']) 197 | continue 198 | if 'packageName' in smth.keys(): 199 | assert(not packageName) 200 | packageName = smth['packageName'] 201 | continue 202 | if not 'serviceName' in smth.keys(): continue 203 | service = grpc_service(smth['serviceName']) 204 | for r in smth['RPCs']: 205 | rpc = grpc_rpc() 206 | rpc.rpcname = r['methodName'] 207 | rpc.paramtype = r.get('methodParam', '') 208 | rpc.returntype = r.get('methodReturn', '') 209 | rpc.paramisstream = 'paramStreamQualifier' in r.keys() 210 | rpc.returnisstream = 'returnStreamQualifier' in r.keys() 211 | service.rpc_list.append(rpc) 212 | #yield service 213 | services.append(service) 214 | assert(packageName) 215 | return packageName, services, messages 216 | 217 | 218 | def printServices(services): 219 | for s in services: 220 | print(s.name+' : ') 221 | for rpc in s.rpc_list: 222 | print(rpc) 223 | print() 224 | 225 | #pckg, services, messages = parseProtoFile('/home/user/projects/meteo-branch/src/meteo/proto/pingpong.proto') 226 | #print(pckg) 227 | #printServices(services) 228 | #print(messages) 229 | -------------------------------------------------------------------------------- /qgrpc/examples/client/qgrpc-test-client.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | mainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 908 10 | 735 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Say hello 24 | 25 | 26 | 27 | 28 | 29 | 30 | start "Both glad to see" 31 | 32 | 33 | 34 | 35 | 36 | 37 | stop "Both glad to see" 38 | 39 | 40 | 41 | 42 | 43 | 44 | stop "Glad to see you" 45 | 46 | 47 | 48 | 49 | 50 | 51 | start "Glad to see you" 52 | 53 | 54 | 55 | 56 | 57 | 58 | start "Glad to see me" 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Qt::Horizontal 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 172.23.148.117 79 | 80 | 81 | 82 | 83 | 84 | 85 | ip 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 1 97 | 98 | 99 | 65535 100 | 101 | 102 | 50051 103 | 104 | 105 | 106 | 107 | 108 | 109 | port 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | connect 119 | 120 | 121 | 122 | 123 | 124 | 125 | disconnect 126 | 127 | 128 | 129 | 130 | 131 | 132 | reconnect 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | Qt::Vertical 144 | 145 | 146 | QSizePolicy::Minimum 147 | 148 | 149 | 150 | 20 151 | 20 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | connection indicator 162 | 163 | 164 | 165 | 166 | 167 | 168 | true 169 | 170 | 171 | 172 | 20 173 | 20 174 | 175 | 176 | 177 | 178 | 179 | 180 | true 181 | 182 | 183 | 184 | 185 | 186 | 187 | Qt::Horizontal 188 | 189 | 190 | 191 | 40 192 | 20 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | true 205 | 206 | 207 | 208 | 209 | layoutWidget_2 210 | line 211 | layoutWidget_3 212 | textEdit 213 | pb_indicator 214 | pb_indicator 215 | 216 | le_ipaddr 217 | sb_port 218 | label_2 219 | label_3 220 | verticalSpacer 221 | 222 | 223 | 224 | 225 | 0 226 | 0 227 | 908 228 | 21 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /grpc.pri: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Включаемый файл QMake для использования GRPC 4 | # 5 | ############################################################################### 6 | 7 | !defined(GRPC_VERSION, var){ 8 | GRPC_VERSION=1.9 9 | } 10 | 11 | GRPC_SUPPORT_VERSIONS = "1.0" "1.9" 12 | 13 | for(v, GRPC_SUPPORT_VERSIONS){ 14 | GRPC_VERSION_OK= 15 | isEqual(GRPC_VERSION, $${v}){ 16 | GRPC_VERSION_OK=1 17 | break() 18 | } 19 | } 20 | 21 | isEmpty(GRPC_VERSION_OK) { 22 | error("$$TARGET: Unknown GRPC version. Possible versions: $$GRPC_SUPPORT_VERSIONS") 23 | } 24 | 25 | 26 | PROTOC3_NAME=protoc3 27 | GRPC_PREFIX_PATH= 28 | 29 | unix { 30 | GRPC_CANDIDATES = "/opt/fort" "/usr/local" 31 | GRPC_PREFIX_PATH= 32 | for(c, GRPC_CANDIDATES){ 33 | exists($${c}/bin/$$PROTOC3_NAME){ 34 | GRPC_PREFIX_PATH=$${c} 35 | break() 36 | } 37 | } 38 | 39 | isEmpty( GRPC_PREFIX_PATH ) { 40 | error("$$TARGET: Cannot find GRPC path") 41 | } 42 | } 43 | #specify win32 prefix path here 44 | win32: GRPC_PREFIX_PATH="C:\\Program Files (x86)\\SOME_SDK" 45 | 46 | !build_pass:message("$$TARGET: GRPC prefix path = $$GRPC_PREFIX_PATH") 47 | 48 | INCLUDEPATH = "$$GRPC_PREFIX_PATH/include" $$INCLUDEPATH 49 | win32: LIBS += -L"$$GRPC_PREFIX_PATH\win32-msvc2015\grpc" #path to libs 50 | ###error("$$INCLUDEPATH") 51 | 52 | ### Описываем "компилятор" .proto --> C++ Source, .cc файл: 53 | # Наш вход - это файлы, перечисленные в PROTOC. 54 | 55 | 56 | protoc3.input = GRPC 57 | protoc3.output = ${QMAKE_FILE_BASE}.pb.cc 58 | protoc3.CONFIG += no_link target_predeps 59 | protoc3.variable_out = SOURCES 60 | unix: protoc3.commands = $$GRPC_PREFIX_PATH/bin/$$PROTOC3_NAME --proto_path=${QMAKE_FILE_PATH} --cpp_out="$$_PRO_FILE_PWD_" ${QMAKE_FILE_IN} 61 | win32: protoc3.commands = $$PROTOC3_NAME --proto_path=${QMAKE_FILE_PATH} --cpp_out="$$_PRO_FILE_PWD_" ${QMAKE_FILE_IN} 62 | protoc3.dependency_type = TYPE_C 63 | 64 | ## Описываем "компилятор" .proto --> C++ Source, .h файл: 65 | protoc3_h.input = GRPC 66 | protoc3_h.output =${QMAKE_FILE_BASE}.pb.h 67 | protoc3_h.CONFIG += no_link target_predeps 68 | protoc3_h.variable_out = HEADERS 69 | protoc3_h.commands = $${protoc3.commands} 70 | protoc3_h.dependency_type = TYPE_C 71 | 72 | ### Подключаем "компилятор" PROTOC --> C к системе сборки QMake: 73 | QMAKE_EXTRA_COMPILERS += protoc3 protoc3_h 74 | 75 | ### Описываем "компилятор" .proto --> C++ Source, .cc файл: 76 | # Наш вход - это файлы, перечисленные в PROTOC. 77 | grpc.input = GRPC 78 | grpc.output = ${QMAKE_FILE_BASE}.grpc.pb.cc 79 | grpc.CONFIG += no_link target_predeps 80 | grpc.variable_out = SOURCES 81 | win32: grpc_cpp_plugin = \"$$GRPC_PREFIX_PATH/bin/grpc_cpp_plugin.exe\" 82 | unix: grpc_cpp_plugin = $$GRPC_PREFIX_PATH/bin/grpc_cpp_plugin 83 | unix: grpc.commands = $$GRPC_PREFIX_PATH/bin/$$PROTOC3_NAME --proto_path=${QMAKE_FILE_PATH} --grpc_out="$$_PRO_FILE_PWD_" --plugin=protoc-gen-grpc=$${grpc_cpp_plugin} ${QMAKE_FILE_IN} 84 | win32: grpc.commands = $$PROTOC3_NAME --proto_path=${QMAKE_FILE_PATH} --grpc_out="$$_PRO_FILE_PWD_" --plugin=protoc-gen-grpc=$${grpc_cpp_plugin} ${QMAKE_FILE_IN} 85 | grpc.dependency_type = TYPE_C 86 | 87 | ## Описываем "компилятор" .proto --> C++ Source, .h файл: 88 | grpc_h.input = GRPC 89 | grpc_h.output =${QMAKE_FILE_BASE}.grpc.pb.h 90 | grpc_h.CONFIG += no_link target_predeps 91 | grpc_h.variable_out = HEADERS 92 | grpc_h.commands = $${grpc.commands} 93 | grpc_h.dependency_type = TYPE_C 94 | 95 | ### Подключаем "компилятор" PROTOC --> C к системе сборки QMake: 96 | QMAKE_EXTRA_COMPILERS += grpc grpc_h 97 | 98 | win32 { 99 | DEFINES += _WIN32_WINNT=0x600 # TODO Guess correct version 100 | DEFINES += _SCL_SECURE_NO_WARNINGS 101 | DEFINES += _CRT_SECURE_NO_WARNINGS 102 | QMAKE_CFLAGS_WARN_ON += -wd4100 103 | QMAKE_CXXFLAGS_WARN_ON += -wd4100 104 | QMAKE_LIBDIR = "$$GRPC_PREFIX_PATH\lib\win32-msvc2015\grpc" $${QMAKE_LIBDIR} 105 | LIBS *= -lgdi32 106 | CONFIG(debug, debug|release) { 107 | LIBS *= -lgrpc++d -lgprd -lgrpcd 108 | } else { 109 | LIBS *= -lgrpc++ -lgpr -lgrpc 110 | } 111 | 112 | CONFIG(debug, debug|release) { 113 | !win32-g++: LIBS *= -llibprotobufd 114 | } else { 115 | !win32-g++: LIBS *= -llibprotobuf 116 | } 117 | 118 | isEqual(GRPC_VERSION, "1.0") { 119 | LIBS *= -llibeay32 -lssleay32 -lzlib1 120 | } 121 | isEqual(GRPC_VERSION, "1.9") { 122 | LIBS *= -lssl -lcrypto -lzlibstatic 123 | } 124 | 125 | } 126 | unix { 127 | LIBS *= -L$$GRPC_PREFIX_PATH/lib/ -lgrpc++ -lgrpc \ 128 | -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed \ 129 | -L$$GRPC_PREFIX_PATH/lib/protobuf3 -lprotobuf -lpthread -ldl 130 | } 131 | 132 | build_pass{ 133 | defined(QGRPC_CONFIG, var) { 134 | QGRPC_PY_PATH=$$PWD/qgrpc/genQGrpc.py 135 | win32:QGRPC_PY_PATH=$$replace(QGRPC_PY_PATH, /, \\) 136 | INCLUDEPATH += $$PWD/qgrpc 137 | MOC_PATH=$$[QT_INSTALL_BINS]/moc 138 | win32:MOC_PATH=$$replace(MOC_PATH, /, \\) 139 | 140 | contains(QGRPC_CONFIG, "server") { 141 | QGRPC_MONITOR = $$PWD/qgrpc/QGrpcServerMonitor.h 142 | qgrpc_monitor_moc.input = QGRPC_MONITOR 143 | qgrpc_monitor_moc.output = $${MOC_DIR}/moc_${QMAKE_FILE_BASE}.cpp 144 | qgrpc_monitor_moc.CONFIG = target_predeps 145 | qgrpc_monitor_moc.variable_out = GENERATED_SOURCES 146 | qgrpc_monitor_moc.commands = $${MOC_PATH} $(DEFINES) ${QMAKE_FILE_IN} -o $${MOC_DIR}/moc_${QMAKE_FILE_BASE}.cpp 147 | qgrpc_monitor_moc.dependency_type = TYPE_C 148 | QMAKE_EXTRA_COMPILERS *= qgrpc_monitor_moc 149 | } 150 | 151 | contains(QGRPC_CONFIG, "client") { 152 | QGRPC_MONITOR = $$PWD/qgrpc/QGrpcClientMonitor.h 153 | qgrpc_monitor_moc.input = QGRPC_MONITOR 154 | qgrpc_monitor_moc.output = $${MOC_DIR}/moc_${QMAKE_FILE_BASE}.cpp 155 | qgrpc_monitor_moc.CONFIG = target_predeps 156 | qgrpc_monitor_moc.variable_out = GENERATED_SOURCES 157 | qgrpc_monitor_moc.commands = $${MOC_PATH} $(DEFINES) ${QMAKE_FILE_IN} -o $${MOC_DIR}/moc_${QMAKE_FILE_BASE}.cpp 158 | qgrpc_monitor_moc.dependency_type = TYPE_C 159 | QMAKE_EXTRA_COMPILERS *= qgrpc_monitor_moc 160 | } 161 | 162 | CUR_CONFIG= 163 | contains(QGRPC_CONFIG, "client") { 164 | CUR_CONFIG = "client" 165 | } 166 | 167 | contains(QGRPC_CONFIG, "server") { 168 | CUR_CONFIG = "server" 169 | } 170 | 171 | isEmpty(CUR_CONFIG) { 172 | error("QGRPC_CONFIG variable is not (correctly) specified or is empty") 173 | } 174 | 175 | #YES! 'client' and 'server' are not supported together 176 | #To support it, create special compiler for both configurations 177 | #(These compilers would have no difference except '.command' section) 178 | 179 | 180 | OUT_H=${QMAKE_FILE_BASE}.qgrpc.$${CUR_CONFIG}.h 181 | qgrpc_$${CUR_CONFIG}_h.input = GRPC 182 | qgrpc_$${CUR_CONFIG}_h.output = $${OUT_H} 183 | qgrpc_$${CUR_CONFIG}_h.CONFIG += target_predeps 184 | qgrpc_$${CUR_CONFIG}_h.clean += $${OUT_H} 185 | qgrpc_$${CUR_CONFIG}_h.variable_out = HEADERS 186 | qgrpc_$${CUR_CONFIG}_h.commands += python $${QGRPC_PY_PATH} ${QMAKE_FILE_IN} $${CUR_CONFIG} $$_PRO_FILE_PWD_ 187 | qgrpc_$${CUR_CONFIG}_h.dependency_type = TYPE_C 188 | QMAKE_EXTRA_COMPILERS *= qgrpc_$${CUR_CONFIG}_h 189 | 190 | 191 | 192 | for(file, GRPC) { 193 | QGRPC_OUT_FILENAME=$$system('python $${QGRPC_PY_PATH} $${file} $${CUR_CONFIG} --get-outfile') 194 | QGRPC_OUT_H=$${QGRPC_OUT_FILENAME}.h 195 | QGRPC_OUT_MOC_CPP=moc_$${QGRPC_OUT_FILENAME}.cpp 196 | MOC_OUT_FILENAME=$${MOC_DIR}/$${QGRPC_OUT_MOC_CPP} 197 | win32:MOC_OUT_FILENAME=$$replace(MOC_OUT_FILENAME, /, \\) 198 | 199 | #generate and compile moc file 200 | COMPILER_NAME=QGRPC_$${CUR_CONFIG}_MOC_GEN_$${QGRPC_OUT_FILENAME} 201 | $${COMPILER_NAME}.input = QGRPC_OUT_H 202 | $${COMPILER_NAME}.output = $${MOC_OUT_FILENAME} 203 | $${COMPILER_NAME}.CONFIG += target_predeps 204 | $${COMPILER_NAME}.variable_out = SOURCES 205 | $${COMPILER_NAME}.commands = $${MOC_PATH} $(DEFINES) $${QGRPC_OUT_H} -o $${MOC_OUT_FILENAME} 206 | $${COMPILER_NAME}.dependency_type = TYPE_C 207 | QMAKE_EXTRA_COMPILERS *= $${COMPILER_NAME} 208 | 209 | } 210 | 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Byte-compiled / optimized / DLL files 35 | __pycache__/ 36 | *.py[cod] 37 | *$py.class 38 | 39 | # C extensions 40 | *.so 41 | 42 | # Distribution / packaging 43 | .Python 44 | build/ 45 | develop-eggs/ 46 | dist/ 47 | downloads/ 48 | eggs/ 49 | .eggs/ 50 | lib/ 51 | lib64/ 52 | parts/ 53 | sdist/ 54 | var/ 55 | wheels/ 56 | *.egg-info/ 57 | .installed.cfg 58 | *.egg 59 | MANIFEST 60 | 61 | # PyInstaller 62 | # Usually these files are written by a python script from a template 63 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 64 | *.manifest 65 | *.spec 66 | 67 | # Installer logs 68 | pip-log.txt 69 | pip-delete-this-directory.txt 70 | 71 | # Unit test / coverage reports 72 | htmlcov/ 73 | .tox/ 74 | .coverage 75 | .coverage.* 76 | .cache 77 | nosetests.xml 78 | coverage.xml 79 | *.cover 80 | .hypothesis/ 81 | .pytest_cache/ 82 | 83 | # Translations 84 | *.mo 85 | *.pot 86 | 87 | # Django stuff: 88 | *.log 89 | local_settings.py 90 | db.sqlite3 91 | 92 | # Flask stuff: 93 | instance/ 94 | .webassets-cache 95 | 96 | # Scrapy stuff: 97 | .scrapy 98 | 99 | # Sphinx documentation 100 | docs/_build/ 101 | 102 | # PyBuilder 103 | target/ 104 | 105 | # Jupyter Notebook 106 | .ipynb_checkpoints 107 | 108 | # pyenv 109 | .python-version 110 | 111 | # celery beat schedule file 112 | celerybeat-schedule 113 | 114 | # SageMath parsed files 115 | *.sage.py 116 | 117 | # Environments 118 | .env 119 | .venv 120 | env/ 121 | venv/ 122 | ENV/ 123 | env.bak/ 124 | venv.bak/ 125 | 126 | # Spyder project settings 127 | .spyderproject 128 | .spyproject 129 | 130 | # Rope project settings 131 | .ropeproject 132 | 133 | # mkdocs documentation 134 | /site 135 | 136 | # mypy 137 | .mypy_cache/ 138 | 139 | 140 | #SVC 141 | *.svn/* 142 | *.git/* 143 | 144 | # User-specific files 145 | *.suo 146 | *.user 147 | *.userosscache 148 | *.sln.docstates 149 | 150 | # User-specific files (MonoDevelop/Xamarin Studio) 151 | *.userprefs 152 | 153 | # Build results 154 | [Dd]ebug/ 155 | [Dd]ebugPublic/ 156 | [Rr]elease/ 157 | [Rr]eleases/ 158 | x64/ 159 | x86/ 160 | bld/ 161 | [Bb]in/ 162 | [Oo]bj/ 163 | [Ll]og/ 164 | 165 | # Visual Studio 2015/2017 cache/options directory 166 | .vs/ 167 | # Uncomment if you have tasks that create the project's static files in wwwroot 168 | #wwwroot/ 169 | 170 | # Visual Studio 2017 auto generated files 171 | Generated\ Files/ 172 | 173 | # MSTest test Results 174 | [Tt]est[Rr]esult*/ 175 | [Bb]uild[Ll]og.* 176 | 177 | # NUNIT 178 | *.VisualState.xml 179 | TestResult.xml 180 | 181 | # Build Results of an ATL Project 182 | [Dd]ebugPS/ 183 | [Rr]eleasePS/ 184 | dlldata.c 185 | 186 | # Benchmark Results 187 | BenchmarkDotNet.Artifacts/ 188 | 189 | # .NET Core 190 | project.lock.json 191 | project.fragment.lock.json 192 | artifacts/ 193 | 194 | # StyleCop 195 | StyleCopReport.xml 196 | 197 | # Files built by Visual Studio 198 | *_i.c 199 | *_p.c 200 | *_i.h 201 | *.ilk 202 | *.meta 203 | *.obj 204 | *.iobj 205 | *.pch 206 | *.pdb 207 | *.ipdb 208 | *.pgc 209 | *.pgd 210 | *.rsp 211 | *.sbr 212 | *.tlb 213 | *.tli 214 | *.tlh 215 | *.tmp 216 | *.tmp_proj 217 | *.log 218 | *.vspscc 219 | *.vssscc 220 | .builds 221 | *.pidb 222 | *.svclog 223 | *.scc 224 | 225 | # Chutzpah Test files 226 | _Chutzpah* 227 | 228 | # Visual C++ cache files 229 | ipch/ 230 | *.aps 231 | *.ncb 232 | *.opendb 233 | *.opensdf 234 | *.sdf 235 | *.cachefile 236 | *.VC.db 237 | *.VC.VC.opendb 238 | 239 | # Visual Studio profiler 240 | *.psess 241 | *.vsp 242 | *.vspx 243 | *.sap 244 | 245 | # Visual Studio Trace Files 246 | *.e2e 247 | 248 | # TFS 2012 Local Workspace 249 | $tf/ 250 | 251 | # Guidance Automation Toolkit 252 | *.gpState 253 | 254 | # ReSharper is a .NET coding add-in 255 | _ReSharper*/ 256 | *.[Rr]e[Ss]harper 257 | *.DotSettings.user 258 | 259 | # JustCode is a .NET coding add-in 260 | .JustCode 261 | 262 | # TeamCity is a build add-in 263 | _TeamCity* 264 | 265 | # DotCover is a Code Coverage Tool 266 | *.dotCover 267 | 268 | # AxoCover is a Code Coverage Tool 269 | .axoCover/* 270 | !.axoCover/settings.json 271 | 272 | # Visual Studio code coverage results 273 | *.coverage 274 | *.coveragexml 275 | 276 | # NCrunch 277 | _NCrunch_* 278 | .*crunch*.local.xml 279 | nCrunchTemp_* 280 | 281 | # MightyMoose 282 | *.mm.* 283 | AutoTest.Net/ 284 | 285 | # Web workbench (sass) 286 | .sass-cache/ 287 | 288 | # Installshield output folder 289 | [Ee]xpress/ 290 | 291 | # DocProject is a documentation generator add-in 292 | DocProject/buildhelp/ 293 | DocProject/Help/*.HxT 294 | DocProject/Help/*.HxC 295 | DocProject/Help/*.hhc 296 | DocProject/Help/*.hhk 297 | DocProject/Help/*.hhp 298 | DocProject/Help/Html2 299 | DocProject/Help/html 300 | 301 | # Click-Once directory 302 | publish/ 303 | 304 | # Publish Web Output 305 | *.[Pp]ublish.xml 306 | *.azurePubxml 307 | # Note: Comment the next line if you want to checkin your web deploy settings, 308 | # but database connection strings (with potential passwords) will be unencrypted 309 | *.pubxml 310 | *.publishproj 311 | 312 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 313 | # checkin your Azure Web App publish settings, but sensitive information contained 314 | # in these scripts will be unencrypted 315 | PublishScripts/ 316 | 317 | # NuGet Packages 318 | *.nupkg 319 | # The packages folder can be ignored because of Package Restore 320 | **/[Pp]ackages/* 321 | # except build/, which is used as an MSBuild target. 322 | !**/[Pp]ackages/build/ 323 | # Uncomment if necessary however generally it will be regenerated when needed 324 | #!**/[Pp]ackages/repositories.config 325 | # NuGet v3's project.json files produces more ignorable files 326 | *.nuget.props 327 | *.nuget.targets 328 | 329 | # Microsoft Azure Build Output 330 | csx/ 331 | *.build.csdef 332 | 333 | # Microsoft Azure Emulator 334 | ecf/ 335 | rcf/ 336 | 337 | # Windows Store app package directories and files 338 | AppPackages/ 339 | BundleArtifacts/ 340 | Package.StoreAssociation.xml 341 | _pkginfo.txt 342 | *.appx 343 | 344 | # Visual Studio cache files 345 | # files ending in .cache can be ignored 346 | *.[Cc]ache 347 | # but keep track of directories ending in .cache 348 | !*.[Cc]ache/ 349 | 350 | # Others 351 | ClientBin/ 352 | ~$* 353 | *~ 354 | *.dbmdl 355 | *.dbproj.schemaview 356 | *.jfm 357 | *.pfx 358 | *.publishsettings 359 | orleans.codegen.cs 360 | 361 | # Including strong name files can present a security risk 362 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 363 | #*.snk 364 | 365 | # Since there are multiple workflows, uncomment next line to ignore bower_components 366 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 367 | #bower_components/ 368 | 369 | # RIA/Silverlight projects 370 | Generated_Code/ 371 | 372 | # Backup & report files from converting an old project file 373 | # to a newer Visual Studio version. Backup files are not needed, 374 | # because we have git ;-) 375 | _UpgradeReport_Files/ 376 | Backup*/ 377 | UpgradeLog*.XML 378 | UpgradeLog*.htm 379 | ServiceFabricBackup/ 380 | *.rptproj.bak 381 | 382 | # SQL Server files 383 | *.mdf 384 | *.ldf 385 | *.ndf 386 | 387 | # Business Intelligence projects 388 | *.rdl.data 389 | *.bim.layout 390 | *.bim_*.settings 391 | *.rptproj.rsuser 392 | 393 | # Microsoft Fakes 394 | FakesAssemblies/ 395 | 396 | # GhostDoc plugin setting file 397 | *.GhostDoc.xml 398 | 399 | # Node.js Tools for Visual Studio 400 | .ntvs_analysis.dat 401 | node_modules/ 402 | 403 | # Visual Studio 6 build log 404 | *.plg 405 | 406 | # Visual Studio 6 workspace options file 407 | *.opt 408 | 409 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 410 | *.vbw 411 | 412 | # Visual Studio LightSwitch build output 413 | **/*.HTMLClient/GeneratedArtifacts 414 | **/*.DesktopClient/GeneratedArtifacts 415 | **/*.DesktopClient/ModelManifest.xml 416 | **/*.Server/GeneratedArtifacts 417 | **/*.Server/ModelManifest.xml 418 | _Pvt_Extensions 419 | 420 | # Paket dependency manager 421 | .paket/paket.exe 422 | paket-files/ 423 | 424 | # FAKE - F# Make 425 | .fake/ 426 | 427 | # JetBrains Rider 428 | .idea/ 429 | *.sln.iml 430 | 431 | # CodeRush 432 | .cr/ 433 | 434 | # Python Tools for Visual Studio (PTVS) 435 | __pycache__/ 436 | *.pyc 437 | 438 | # Cake - Uncomment if you are using it 439 | # tools/** 440 | # !tools/packages.config 441 | 442 | # Tabs Studio 443 | *.tss 444 | 445 | # Telerik's JustMock configuration file 446 | *.jmconfig 447 | 448 | # BizTalk build output 449 | *.btp.cs 450 | *.btm.cs 451 | *.odx.cs 452 | *.xsd.cs 453 | 454 | # OpenCover UI analysis results 455 | OpenCover/ 456 | 457 | # Azure Stream Analytics local run output 458 | ASALocalRun/ 459 | 460 | # MSBuild Binary and Structured Log 461 | *.binlog 462 | 463 | # NVidia Nsight GPU debugger configuration file 464 | *.nvuser 465 | 466 | # MFractors (Xamarin productivity tool) working folder 467 | .mfractor/ 468 | 469 | *vcxproj* 470 | *.sln 471 | *pb.h 472 | *pb.cc 473 | *ui_* 474 | *.qgrpc.*.h 475 | *Makefile* 476 | 477 | html/ 478 | latex/ 479 | 480 | -------------------------------------------------------------------------------- /qgrpc/genQGrpc.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import sys, os 4 | import proto3parser as proto 5 | 6 | genServicePrefix = 'Q' 7 | genServicePostfix = '%sService' 8 | genRPCtypesPrefix = '' 9 | genRPCtypesPostfix = '_RPCtypes' 10 | genCallDataPrefix = '' 11 | genCallDataPostfix = 'CallData' 12 | genSignalPrefix = '' 13 | genSignalPostfix = 'Response' 14 | genSrvSignalPostfix = 'Request' 15 | genFilePrefix = '' 16 | genFilePostfix = '.qgrpc.%s' 17 | 18 | def _genServiceName(service, config): return genServicePrefix + service + genServicePostfix % config.title() 19 | def _genRPCtype(rpc): return genRPCtypesPrefix + rpc + genRPCtypesPostfix 20 | def _genCallData(rpc): return genCallDataPrefix + rpc + genCallDataPostfix 21 | def _genSignal(rpc, config='client'): return genSignalPrefix + rpc + genSignalPostfix if config == 'client' else genSignalPrefix + rpc + genSrvSignalPostfix 22 | def _genFile(package, config, ext='.h'): return genFilePrefix + package + (genFilePostfix % config) + ext 23 | 24 | def RPCkind(rpc): 25 | if not rpc.paramisstream and (not rpc.returnisstream): return 'RPC_KIND_UNARY_T' 26 | elif rpc.paramisstream and (not rpc.returnisstream): return 'RPC_KIND_CLIENTSTREAMING_T' 27 | elif not rpc.paramisstream and rpc.returnisstream: return 'RPC_KIND_SERVERSTREAMING_T' 28 | else: return 'RPC_KIND_BIDISTREAMING_T' 29 | 30 | 31 | def generateAutoGenText(grpc_pb_h_file): 32 | s = """\ 33 | // Generated by the %s compiler. DO NOT EDIT! 34 | // source: %s.proto 35 | """ % (os.path.basename(__file__), grpc_pb_h_file) 36 | return s 37 | 38 | 39 | 40 | def generateDeclarations(package, services, messages, grpc_pb_h_file, config): 41 | s = """\ 42 | #pragma once 43 | 44 | #include 45 | #include "%s.grpc.pb.h" 46 | #include "QGrpc%s.h" 47 | #include "QGrpc%sMonitor.h" 48 | 49 | """ % (grpc_pb_h_file, config.title(), config.title(),) 50 | for sm in services + messages: s += """using %s::%s;\n""" % (package, sm) 51 | return s 52 | 53 | 54 | 55 | 56 | def generateCliClassDef(package, service): 57 | return """ 58 | class %s: public QObject, public QGrpcCliBase::ConnectivityFeatures<%s>, public QGrpcCliBase::MonitorFeatures<%s> 59 | { 60 | Q_OBJECT 61 | """ % (_genServiceName(service, 'client'), service, _genServiceName(service, 'client')) 62 | 63 | 64 | def generateSrvClassDef(package, service): 65 | return """ 66 | class %s: public QObject, public QGrpcSrvBase::QGrpcServerService 67 | { 68 | Q_OBJECT 69 | """ % (_genServiceName(service, 'server')) 70 | 71 | 72 | def generateCliRestOfClass(): 73 | return """ 74 | }; 75 | """ 76 | 77 | def generateSrvRestOfClass(): 78 | return """ 79 | }; 80 | """ 81 | 82 | 83 | 84 | def generateCliRPCtypes(service, rpc_list): 85 | s = '' 86 | def _gentypes(_rpc): 87 | return """\ 88 | using %s = QGrpcCliBase::RPCtypes< QGrpcCliBase::%s, %s, %s, %s >; 89 | """ % (_genRPCtype(_rpc.rpcname), RPCkind(_rpc), _rpc.paramtype, _rpc.returntype, _genServiceName(service, 'client')) 90 | 91 | for rpc in rpc_list: s += _gentypes(rpc) 92 | return s 93 | 94 | def generateSrvRPCtypes(service, rpc_list): 95 | s = """\ 96 | %s::AsyncService service_; 97 | """ % service 98 | def _gentypes(_rpc): 99 | return """\ 100 | using %s = QGrpcSrvBase::RPCtypes< QGrpcSrvBase::%s, %s, %s, %s, %s::AsyncService >; 101 | """ % (_genRPCtype(_rpc.rpcname), RPCkind(_rpc), _rpc.paramtype, _rpc.returntype, _genServiceName(service, 'server'), service) 102 | 103 | for rpc in rpc_list: s += _gentypes(rpc) 104 | 105 | 106 | s += '\n' 107 | needAnotherCallData = """\ 108 | needAnotherCallData< %s, %s >(); 109 | """ 110 | s += """\ 111 | virtual void makeRequests() override 112 | { 113 | %s 114 | } 115 | """ % (''.join( [needAnotherCallData % (_genRPCtype(_rpc.rpcname), _genCallData(_rpc.rpcname),) for _rpc in rpc_list] ).rstrip() ) 116 | return s 117 | 118 | 119 | def generateCliClassCtors(service): 120 | genservicename = _genServiceName(service, 'client') 121 | return """ 122 | public: 123 | explicit %s(): 124 | QGrpcCliBase::MonitorFeatures<%s>(this, &%s::channelStateChanged) 125 | {} 126 | 127 | virtual ~%s(){} 128 | """ % (genservicename, genservicename, genservicename, genservicename) 129 | 130 | 131 | def generateSrvClassCtors(service): 132 | genservicename = _genServiceName(service, 'server') 133 | return """ 134 | public: 135 | explicit %s(): QGrpcSrvBase::QGrpcServerService(&service_){} 136 | virtual ~%s(){} 137 | """ % (genservicename, genservicename,) 138 | 139 | 140 | def generateCliCallData(service, rpc_list): 141 | s = """ 142 | public: 143 | """ 144 | def _gencd(rpc): 145 | genCD = _genCallData(rpc.rpcname) 146 | genrpctype = _genRPCtype(rpc.rpcname) 147 | return """ 148 | struct %s : public QGrpcCliBase::ClientCallData< %s, %s > 149 | { 150 | virtual ~%s() {} 151 | %s() : QGrpcCliBase::ClientCallData< %s, %s >( &%s::%s ) {} 152 | }; 153 | """ % (genCD, genrpctype, genCD, genCD, genCD, genrpctype, genCD, _genServiceName(service, 'client'), _genSignal(rpc.rpcname, 'client')) 154 | 155 | for rpc in rpc_list: s += _gencd(rpc) 156 | return s 157 | 158 | 159 | def generateSrvCallData(service, rpc_list): 160 | s = """ 161 | public: 162 | """ 163 | def _gencd(rpc): 164 | genCD = _genCallData(rpc.rpcname) 165 | genrpctype = _genRPCtype(rpc.rpcname) 166 | return """ 167 | struct %s : public QGrpcSrvBase::ServerCallData< %s, %s > 168 | { 169 | virtual ~%s() {} 170 | %s() : QGrpcSrvBase::ServerCallData< %s, %s >( &%s::%s, &%s::AsyncService::Request%s ) {} 171 | }; 172 | """ % (genCD, genrpctype, genCD, genCD, genCD, genrpctype, genCD, _genServiceName(service, 'server'), _genSignal(rpc.rpcname, 'server'), service, rpc.rpcname) 173 | 174 | for rpc in rpc_list: s += _gencd(rpc) 175 | return s 176 | 177 | 178 | def generateCliFuncs(rpc_list): 179 | s = """ 180 | public: 181 | """ 182 | def _genFunc(rpc): 183 | rpckind = RPCkind(rpc) 184 | gentypes = _genRPCtype(rpc.rpcname) 185 | gencd = _genCallData(rpc.rpcname) 186 | name = rpc.rpcname 187 | commonpart = """\ 188 | if(!connected()) return; 189 | %s* call = new %s;""" % (gencd, gencd) 190 | if rpckind == 'RPC_KIND_UNARY_T': 191 | return """ 192 | void %s(const %s::RequestType& request) 193 | { 194 | %s 195 | call->request = request; 196 | call->responder = stub_->Async%s(&call->context, request, &cq_); 197 | call->responder->Finish(&call->reply, &call->status, (void*)call); 198 | } 199 | """ % (name, gentypes, commonpart, name) 200 | elif rpckind == 'RPC_KIND_SERVERSTREAMING_T': 201 | return """ 202 | void %s(const %s::RequestType& request) 203 | { 204 | %s 205 | call->request = request; 206 | call->responder = stub_->Async%s(&call->context, request, &cq_, (void*)call); 207 | } 208 | """ % (name, gentypes, commonpart, name) 209 | elif rpckind == 'RPC_KIND_CLIENTSTREAMING_T': 210 | return """ 211 | void %s() 212 | { 213 | %s 214 | call->responder = stub_->Async%s(&call->context, &call->reply, &cq_, (void*)call); 215 | } 216 | """ % (name, commonpart, name) 217 | elif rpckind == 'RPC_KIND_BIDISTREAMING_T': 218 | return """ 219 | void %s() 220 | { 221 | %s 222 | call->responder = stub_->Async%s(&call->context, &cq_, (void*)call); 223 | } 224 | """ % (name, commonpart, name) 225 | assert(False) 226 | 227 | for rpc in rpc_list: s += _genFunc(rpc) 228 | return s 229 | 230 | 231 | def generateCliSignals(service, rpc_list): 232 | s = """ 233 | 234 | signals: 235 | void channelStateChanged(int /*prev*/, int /*cur*/); 236 | """ 237 | def _genSig(rpc): 238 | return """ 239 | void %s(%s::%s* /*response*/); 240 | """ % (_genSignal(rpc.rpcname, 'client'), _genServiceName(service, 'client'), _genCallData(rpc.rpcname)) 241 | 242 | for rpc in rpc_list: s += _genSig(rpc) 243 | return s 244 | 245 | 246 | def generateSrvSignals(service, rpc_list): 247 | s = """ 248 | 249 | signals: 250 | """ 251 | def _genSig(rpc): 252 | return """ 253 | void %s(%s::%s* /*response*/); 254 | """ % (_genSignal(rpc.rpcname, 'server'), _genServiceName(service, 'server'), _genCallData(rpc.rpcname)) 255 | 256 | for rpc in rpc_list: s += _genSig(rpc) 257 | return s 258 | 259 | 260 | 261 | def generateQGrpc(protofile, mode, outpath): 262 | #if mode == 'server': sys.exit('server mode not implemented yet') 263 | tpl = proto.parseProtoFile(protofile) 264 | if not tpl: sys.exit('Cannot parse proto file: %s' % protofile) 265 | outfname = os.path.splitext(os.path.basename(os.path.basename(protofile)))[0] 266 | package, services, messages = tpl 267 | res = generateAutoGenText(outfname) 268 | res += generateDeclarations(package, [s.name for s in services], messages, outfname, mode) 269 | for s in services: 270 | name = s.name 271 | if mode == 'client': 272 | res += ( generateCliClassDef(package, name) + 273 | generateCliRPCtypes(name, s.rpc_list) + 274 | generateCliClassCtors(name) + 275 | generateCliCallData(name, s.rpc_list) + 276 | generateCliFuncs(s.rpc_list) + 277 | generateCliSignals(name, s.rpc_list) + 278 | generateCliRestOfClass() 279 | ) 280 | elif mode == 'server': 281 | res += ( generateSrvClassDef(package, name) + 282 | generateSrvRPCtypes(name, s.rpc_list) + 283 | generateSrvClassCtors(name) + 284 | generateSrvCallData(name, s.rpc_list) + 285 | generateSrvSignals(name, s.rpc_list) + 286 | generateSrvRestOfClass() 287 | ) 288 | res += '\n\n' 289 | outfile = os.path.join(outpath, _genFile(outfname, mode)) 290 | try: 291 | with open(outfile, 'w') as f: f.write(res) 292 | except Exception as e: sys.exit(e) 293 | return 294 | 295 | 296 | def main(): 297 | help = 'usage: genQGrpc.py PROTOFILE {client,server} {OUTPATH,--get-outfile}' 298 | if len(sys.argv) != 4: sys.exit(help) 299 | protofile = os.path.abspath(sys.argv[1]) 300 | mode = sys.argv[2] 301 | outpath = sys.argv[3] 302 | if outpath == '--get-outfile': 303 | outfname = os.path.splitext(os.path.basename(os.path.basename(protofile)))[0] 304 | print(_genFile(outfname, mode, ext='')) 305 | return 306 | if not os.path.isfile(protofile): sys.exit('No such file: %s' % protofile) 307 | if not mode in ('client', 'server'): sys.exit('Bad mode: %s' % mode) 308 | if not os.path.isdir(outpath): sys.exit('No such path: %s' % outpath) 309 | generateQGrpc(protofile, mode, outpath) 310 | 311 | 312 | 313 | 314 | if __name__ == '__main__': 315 | main() 316 | -------------------------------------------------------------------------------- /qgrpc/examples/client/qgrpc-test-client.h: -------------------------------------------------------------------------------- 1 | #ifndef QGRPC_TEST_CLIENT 2 | #define QGRPC_TEST_CLIENT 3 | 4 | #include "pingpong.qgrpc.client.h" 5 | 6 | 7 | //*********************************************************** 8 | //*********************************************************** 9 | //*********************************************************** 10 | //Client side 11 | //*********************************************************** 12 | //*********************************************************** 13 | //*********************************************************** 14 | /**/ 15 | 16 | #include "ui_qgrpc-test-client.h" 17 | #include 18 | 19 | class mainWindow : public QMainWindow, public Ui_mainWindow 20 | { 21 | Q_OBJECT 22 | QpingClientService pingPongSrv; 23 | //QPingPongService pingPongSrv; 24 | QGrpcCliServer server; 25 | bool stop_1M_stream; 26 | bool stop_MM_stream; 27 | public: 28 | mainWindow() : 29 | stop_1M_stream(false), 30 | stop_MM_stream(false) 31 | { 32 | setupUi(this); 33 | bool c = false; 34 | c = connect(pb_sayhello, SIGNAL(clicked()), this, SLOT(onPbSayHello())); assert(c); 35 | c = connect(pb_start_read_stream, SIGNAL(clicked()), this, SLOT(onPbStartReadStream())); assert(c); 36 | c = connect(pb_start_write_stream, SIGNAL(clicked()), this, SLOT(onPbStartWriteStream())); assert(c); 37 | c = connect(pb_stop_write_stream, SIGNAL(clicked()), this, SLOT(onPbStopWriteStream())); assert(c); 38 | c = connect(pb_start_read_write_stream, SIGNAL(clicked()), this, SLOT(onPbStartReadWriteStream())); assert(c); 39 | c = connect(pb_stop_read_write_stream, SIGNAL(clicked()), this, SLOT(onPbStopReadWriteStream())); assert(c); 40 | c = connect(pb_connect, SIGNAL(clicked()), this, SLOT(onPbConnect())); assert(c); 41 | c = connect(pb_disconnect, SIGNAL(clicked()), this, SLOT(onPbDisconnect())); assert(c); 42 | c = connect(pb_reconnect, SIGNAL(clicked()), this, SLOT(onPbReconnect())); assert(c); 43 | // 44 | c = connect(&pingPongSrv, SIGNAL(SayHelloResponse(QpingClientService::SayHelloCallData*)), this, SLOT(onSayHelloResponse(QpingClientService::SayHelloCallData*))); assert(c); 45 | c = connect(&pingPongSrv, SIGNAL(GladToSeeMeResponse(QpingClientService::GladToSeeMeCallData*)), this, SLOT(onGladToSeeMeResponse(QpingClientService::GladToSeeMeCallData*))); assert(c); 46 | c = connect(&pingPongSrv, SIGNAL(GladToSeeYouResponse(QpingClientService::GladToSeeYouCallData*)), this, SLOT(onGladToSeeYouResponse(QpingClientService::GladToSeeYouCallData*))); assert(c); 47 | c = connect(&pingPongSrv, SIGNAL(BothGladToSeeResponse(QpingClientService::BothGladToSeeCallData*)), this, SLOT(onBothGladToSeeResponse(QpingClientService::BothGladToSeeCallData*))); assert(c); 48 | c = connect(&pingPongSrv, SIGNAL(channelStateChanged(int, int)), this, SLOT(onPingPongStateChanged(int, int))); assert(c); 49 | // 50 | server.addService(&pingPongSrv); 51 | pb_stop_write_stream->setDisabled(true); 52 | pb_stop_read_write_stream->setDisabled(true); 53 | pb_disconnect->setDisabled(true); 54 | pb_reconnect->setDisabled(true); 55 | this->show(); 56 | } 57 | 58 | void setText(const QString& text) 59 | { 60 | textEdit->insertPlainText(text); 61 | QScrollBar *sb = textEdit->verticalScrollBar(); 62 | sb->setValue(sb->maximum()); 63 | } 64 | 65 | std::string randomStr(size_t len = 20) 66 | { 67 | static const char alphanum[] = 68 | "0123456789" 69 | "!@#$%^&*" 70 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 71 | "abcdefghijklmnopqrstuvwxyz"; 72 | assert(sizeof(alphanum)); 73 | size_t alphabetLen = sizeof(alphanum) - 1; 74 | std::string str; 75 | for (size_t i = 0; i < len; ++i) 76 | str += alphanum[rand() % alphabetLen]; 77 | return str; 78 | } 79 | 80 | public slots: 81 | void onPbSayHello() 82 | { 83 | PingRequest request; 84 | request.set_name("user"); 85 | request.set_message("user"); 86 | pingPongSrv.SayHello(request); 87 | } 88 | void onPbStartReadStream() 89 | { 90 | PingRequest request; 91 | request.set_name("user"); 92 | pingPongSrv.GladToSeeMe(request); 93 | } 94 | void onPbStartWriteStream() 95 | { 96 | stop_1M_stream = false; 97 | pingPongSrv.GladToSeeYou(); 98 | pb_stop_write_stream->setDisabled(false); 99 | pb_start_write_stream->setDisabled(true); 100 | } 101 | void onPbStopWriteStream() 102 | { 103 | stop_1M_stream = true; 104 | pb_stop_write_stream->setDisabled(true); 105 | pb_start_write_stream->setDisabled(false); 106 | } 107 | void onPbStartReadWriteStream() 108 | { 109 | stop_MM_stream = false; 110 | pingPongSrv.BothGladToSee(); 111 | pb_stop_read_write_stream->setDisabled(false); 112 | pb_start_read_write_stream->setDisabled(true); 113 | } 114 | void onPbStopReadWriteStream() 115 | { 116 | stop_MM_stream = true; 117 | pb_stop_read_write_stream->setDisabled(true); 118 | pb_start_read_write_stream->setDisabled(false); 119 | } 120 | void onPbConnect() 121 | { 122 | QString ip = le_ipaddr->text(); 123 | if (ip.isEmpty()) return; 124 | int port = sb_port->value(); 125 | pingPongSrv.grpc_connect(QString("%1:%2").arg(ip).arg(port).toStdString()); 126 | server.start(); 127 | pb_connect->setDisabled(true); 128 | pb_disconnect->setDisabled(false); 129 | pb_reconnect->setDisabled(false); 130 | le_ipaddr->setDisabled(true); 131 | sb_port->setDisabled(true); 132 | } 133 | void onPbDisconnect() 134 | { 135 | pingPongSrv.grpc_disconnect(); 136 | pb_connect->setDisabled(false); 137 | pb_disconnect->setDisabled(true); 138 | pb_reconnect->setDisabled(true); 139 | le_ipaddr->setDisabled(false); 140 | sb_port->setDisabled(false); 141 | } 142 | void onPbReconnect() { pingPongSrv.grpc_reconnect(); } 143 | 144 | void onSayHelloResponse(QpingClientService::SayHelloCallData* response) 145 | { 146 | setText("[11]: reply: " + QString::fromStdString(response->reply.message()) + "\n"); 147 | if (response->CouldBeDeleted()) 148 | delete response; 149 | int a = 0; 150 | ++a; 151 | } 152 | 153 | void onGladToSeeMeResponse(QpingClientService::GladToSeeMeCallData* response) 154 | { 155 | if (response->StreamFinished()) 156 | { 157 | response->Finish(); 158 | return; 159 | } 160 | if (response->CouldBeDeleted()) 161 | { 162 | delete response; 163 | return; 164 | } 165 | response->Read(); 166 | if (!response->reply.message().empty()) 167 | setText("[1M]: reply: " + QString::fromStdString(response->reply.message()) + "\n"); 168 | int a = 0; 169 | ++a; 170 | } 171 | void onGladToSeeYouResponse(QpingClientService::GladToSeeYouCallData* response) 172 | { 173 | if (response->CouldBeDeleted()) 174 | { 175 | if(!response->reply.message().empty()) 176 | setText("[M1]: reply: " + QString::fromStdString(response->reply.message()) + "\n"); 177 | delete response; 178 | //onPbStopWriteStream(); 179 | return; 180 | } 181 | if (response->WriteMode()) 182 | { 183 | if (stop_1M_stream) 184 | { 185 | setText("[M1]: change to READ mode\n"); 186 | response->ChangeMode(); 187 | response->WritesDone(); 188 | return; 189 | } 190 | auto str = randomStr(); 191 | response->request.set_name(str); 192 | response->Write(); 193 | setText("[M1]: request: " + QString::fromStdString(str) + "\n"); 194 | return; 195 | } 196 | else 197 | { 198 | response->Finish(); 199 | } 200 | } 201 | 202 | 203 | void onBothGladToSeeResponse(QpingClientService::BothGladToSeeCallData* response) 204 | { 205 | if (response->CouldBeDeleted()) 206 | { 207 | delete response; 208 | //onPbStopReadWriteStream(); 209 | return; 210 | } 211 | if (response->WriteMode()) 212 | { 213 | if (stop_MM_stream) 214 | { 215 | setText("[MM]: change to READ mode\n"); 216 | response->ChangeMode(); 217 | response->WritesDone(); 218 | return; 219 | } 220 | auto str = randomStr(); 221 | response->request.set_name(str); 222 | response->Write(); 223 | setText("[MM]: request: " + QString::fromStdString(str) + "\n"); 224 | return; 225 | } 226 | else //Read Mode 227 | { 228 | if (response->StreamFinished()) 229 | { 230 | response->Finish(); 231 | return; 232 | } 233 | response->Read(); 234 | if (!response->reply.message().empty()) 235 | setText("[MM]: reply: " + QString::fromStdString(response->reply.message()) + "\n"); 236 | } 237 | } 238 | 239 | 240 | void onPingPongStateChanged(int prevcode, int curcode) 241 | { 242 | QPixmap px(32, 32); 243 | grpc_connectivity_state prev = static_cast(prevcode); 244 | grpc_connectivity_state cur = static_cast(curcode); 245 | setText(QString("connectivity state changed from %1 to %2\n").arg(prev).arg(cur)); 246 | QString tooltip; 247 | switch (cur) 248 | { 249 | case grpc_connectivity_state::GRPC_CHANNEL_IDLE: 250 | px.fill(Qt::darkYellow); 251 | tooltip = "idle"; 252 | break; 253 | case grpc_connectivity_state::GRPC_CHANNEL_CONNECTING: 254 | tooltip = "connecting"; 255 | px.fill(Qt::yellow); 256 | break; 257 | case grpc_connectivity_state::GRPC_CHANNEL_READY: 258 | tooltip = "ready"; 259 | px.fill(Qt::darkGreen); 260 | break; 261 | case grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN: 262 | tooltip = "shutdown"; 263 | px.fill(Qt::darkRed); 264 | break; 265 | case grpc_connectivity_state::GRPC_CHANNEL_TRANSIENT_FAILURE: 266 | tooltip = "transient failure"; 267 | px.fill(Qt::darkRed); 268 | break; 269 | default: 270 | px.fill(Qt::darkRed); 271 | break; 272 | } 273 | pb_indicator->setIcon(px); 274 | pb_indicator->setToolTip(tooltip); 275 | } 276 | 277 | }; 278 | 279 | 280 | #endif 281 | -------------------------------------------------------------------------------- /qgrpc/QGrpcClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "QAbstractGrpc.h" 4 | 5 | using grpc::Channel; 6 | using grpc::ClientAsyncResponseReader; 7 | using grpc::ClientContext; 8 | using grpc::ClientAsyncReader; 9 | using grpc::ClientAsyncWriter; 10 | using grpc::ClientAsyncReaderWriter; 11 | using grpc::CompletionQueue; 12 | using grpc::Status; 13 | 14 | 15 | namespace QGrpcCliBase 16 | { 17 | 18 | struct RPC_KIND_UNARY_T; 19 | struct RPC_KIND_SERVERSTREAMING_T; 20 | struct RPC_KIND_CLIENTSTREAMING_T; 21 | struct RPC_KIND_BIDISTREAMING_T; 22 | 23 | template 24 | struct ClientCallDataResponse 25 | { 26 | RequestType request; 27 | ReplyType reply; 28 | }; 29 | 30 | 31 | template< template class R, typename...Args> struct ClientResponderBase 32 | { 33 | using responder_type = std::unique_ptr< R< Args...> >; 34 | ClientContext context; 35 | Status status; 36 | ClientResponderBase() :couldBeDeleted_(false), tag_(nullptr) {} 37 | bool CouldBeDeleted() const { return couldBeDeleted_; }; 38 | virtual ~ClientResponderBase() {} 39 | protected: 40 | responder_type responder; //must be deleted before context 41 | bool couldBeDeleted_; 42 | void* tag_; 43 | virtual bool processEvent(void*, bool) = 0; 44 | }; 45 | 46 | template struct ClientResponder; 47 | template 48 | struct ClientResponder : public ClientResponderBase< ClientAsyncResponseReader, ReplyType >, public ClientCallDataResponse 49 | { 50 | virtual ~ClientResponder() {} 51 | protected: 52 | virtual bool processEvent(void* tag, bool) override { this->tag_ = tag; return (this->couldBeDeleted_ = true); } 53 | }; 54 | 55 | template 56 | struct ClientResponder : public ClientResponderBase< ClientAsyncReader, ReplyType >, public ClientCallDataResponse 57 | { 58 | ClientResponder() : isFinished_(false) {} 59 | virtual ~ClientResponder() {} 60 | inline bool StreamFinished() const { return isFinished_; } 61 | inline void Finish() { this->responder->Finish(&this->status, this->tag_); this->couldBeDeleted_ = true; } 62 | inline void Read() { return this->responder->Read(&this->reply, this->tag_); } 63 | protected: 64 | virtual bool processEvent(void* tag , bool ok) override 65 | { 66 | this->tag_ = tag; 67 | isFinished_ = !ok; 68 | return true; 69 | } 70 | bool isFinished_; 71 | }; 72 | 73 | template 74 | struct ClientResponder : public ClientResponderBase< ClientAsyncWriter, RequestType >, public ClientCallDataResponse 75 | { 76 | ClientResponder() : write_mode_(true) {} 77 | inline bool WriteMode() const { return write_mode_; } 78 | inline void ChangeMode() { write_mode_ = !write_mode_; } 79 | inline void Write() { return this->responder->Write(this->request, this->tag_); } 80 | inline void WritesDone() { return this->responder->WritesDone(this->tag_); } 81 | inline void Finish() { this->couldBeDeleted_ = true; return this->responder->Finish(&this->status, this->tag_); } 82 | protected: 83 | virtual bool processEvent(void* tag, bool ok) override 84 | { 85 | this->tag_ = tag; 86 | if (!this->couldBeDeleted_) this->couldBeDeleted_ = !ok; 87 | return true; 88 | } 89 | bool write_mode_; 90 | }; 91 | 92 | template 93 | struct ClientResponder : public ClientResponderBase< ClientAsyncReaderWriter, RequestType, ReplyType >, public ClientCallDataResponse 94 | { 95 | ClientResponder() : write_mode_(true), isFinished_(false) {} 96 | inline bool WriteMode() const { return write_mode_; } 97 | inline void ChangeMode() { write_mode_ = !write_mode_; } 98 | inline void Write() { return this->responder->Write(this->request, this->tag_); } 99 | inline void WritesDone() { return this->responder->WritesDone(this->tag_); } 100 | inline void Finish() { this->couldBeDeleted_ = true; return this->responder->Finish(&this->status, this->tag_); } 101 | inline bool StreamFinished() const { return isFinished_; } 102 | inline void Read() { return this->responder->Read(&this->reply, this->tag_); } 103 | protected: 104 | virtual bool processEvent(void* tag, bool ok) override 105 | { 106 | this->tag_ = tag; 107 | if (!this->couldBeDeleted_) this->couldBeDeleted_ = !ok; 108 | return true; 109 | } 110 | bool write_mode_; 111 | bool isFinished_; 112 | }; 113 | 114 | 115 | inline std::chrono::system_clock::time_point deadlineFromSec(long long seconds) { return std::chrono::system_clock::now() + std::chrono::seconds(seconds); } 116 | inline std::chrono::system_clock::time_point deadlineFromMSec(long long mseconds) { return std::chrono::system_clock::now() + std::chrono::milliseconds(mseconds); } 117 | 118 | 119 | class simple_lock_guard 120 | { 121 | std::atomic& is_locked_; 122 | public: 123 | simple_lock_guard(std::atomic& is_locked_var) : is_locked_(is_locked_var) 124 | { 125 | while (is_locked_.load()); 126 | is_locked_.store(true); 127 | } 128 | ~simple_lock_guard() { is_locked_.store(false); } 129 | }; 130 | 131 | 132 | class ChannelFeatures 133 | { 134 | static const int nettag = 0xff; 135 | CompletionQueue channel_cq_; 136 | long long mseconds_; 137 | grpc_connectivity_state state_; 138 | std::shared_ptr channel_; 139 | mutable std::atomic me_locked_; 140 | public: 141 | explicit ChannelFeatures(std::shared_ptr& channel) : mseconds_(1000u), state_(grpc_connectivity_state::GRPC_CHANNEL_CONNECTING), channel_(channel), me_locked_(false){} 142 | ~ChannelFeatures() 143 | { 144 | auto tmp = simple_lock_guard(me_locked_); 145 | waitForTimeout_(); 146 | channel_cq_.Shutdown(); 147 | } 148 | 149 | inline grpc_connectivity_state checkChannelState() 150 | { 151 | auto tmp = simple_lock_guard(me_locked_); 152 | void* tag; 153 | bool ok; 154 | /*auto st = */channel_cq_.AsyncNext(&tag, &ok, std::chrono::system_clock::time_point()); 155 | subscribeOnChannelState_(); 156 | state_ = channelState_(); 157 | return state_; 158 | } 159 | 160 | inline grpc_connectivity_state channelState() const 161 | { 162 | auto tmp = simple_lock_guard(me_locked_); 163 | return state_; 164 | } 165 | 166 | private: 167 | inline bool channelTag_(void* tag) { return static_cast(reinterpret_cast< intptr_t >(tag)) == nettag; } 168 | inline grpc_connectivity_state channelState_(bool try_to_connect = true) const { return channel_->GetState(try_to_connect); } 169 | inline void subscribeOnChannelState_() 170 | { 171 | if (!channel_) return; 172 | channel_->NotifyOnStateChange(state_, std::chrono::system_clock::time_point(), &channel_cq_, (void*)nettag); 173 | } 174 | void waitForTimeout_() 175 | { 176 | if (!channel_) return; 177 | void* tag; 178 | bool ok = false; 179 | while ((channel_cq_.AsyncNext(&tag, &ok, std::chrono::system_clock::time_point()) != grpc::CompletionQueue::TIMEOUT)); 180 | } 181 | }; 182 | 183 | 184 | struct AbstractConnectivityFeatures 185 | { 186 | virtual grpc_connectivity_state channelState() const = 0; 187 | virtual grpc_connectivity_state checkChannelState() const = 0; 188 | virtual bool connected() const = 0; 189 | //CompletionQueue cq_; 190 | }; 191 | 192 | 193 | template 194 | class ConnectivityFeatures : public AbstractConnectivityFeatures 195 | { 196 | std::string target_; 197 | std::shared_ptr< grpc::ChannelCredentials > creds_; 198 | std::shared_ptr channel_; 199 | std::unique_ptr channelFeatures_; 200 | //std::atomic connected_; 201 | protected: 202 | std::unique_ptr stub_; 203 | public: 204 | explicit ConnectivityFeatures() 205 | {} 206 | void grpc_connect(const std::string& target, const std::shared_ptr< grpc::ChannelCredentials >& creds = grpc::InsecureChannelCredentials()) 207 | { 208 | if (channelFeatures_) return; 209 | target_ = target; 210 | creds_ = creds; 211 | grpc_connect_(); 212 | } 213 | void grpc_disconnect() 214 | { 215 | if (!channelFeatures_) return; 216 | channelFeatures_ = nullptr; 217 | channel_ = nullptr; 218 | stub_ = nullptr; 219 | //creds_.reset(); 220 | } 221 | 222 | void grpc_reconnect() 223 | { 224 | grpc_disconnect(); 225 | grpc_connect_(); 226 | } 227 | protected: 228 | virtual grpc_connectivity_state channelState() const override 229 | { 230 | if (!channelFeatures_) return grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN; 231 | return channelFeatures_->channelState(); 232 | } 233 | virtual grpc_connectivity_state checkChannelState() const override 234 | { 235 | if (!channelFeatures_) return grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN; 236 | return channelFeatures_->checkChannelState(); 237 | } 238 | virtual bool connected() const override 239 | { 240 | return (channelFeatures_) && (channelFeatures_->channelState() == grpc_connectivity_state::GRPC_CHANNEL_READY); 241 | } 242 | private: 243 | void grpc_connect_() 244 | { 245 | assert(!target_.empty()); assert(creds_); 246 | auto new_channel_ = grpc::CreateChannel(target_, creds_); 247 | channel_.swap(new_channel_); 248 | auto new_stub_ = GRPCService::NewStub(channel_); 249 | stub_.swap(new_stub_); 250 | channelFeatures_ = std::make_unique(channel_); 251 | channelFeatures_->checkChannelState(); 252 | } 253 | }; 254 | 255 | template 256 | class AbstractCallData 257 | { 258 | public: 259 | virtual ~AbstractCallData() {} 260 | virtual void cqActions(const SERVICE*, bool) = 0; 261 | }; 262 | 263 | template 264 | class MonitorFeatures : public QGrpcCliAbstract::AbstractService 265 | { 266 | AbstractConnectivityFeatures* conn_; 267 | using FuncType = void (SERVICE::*)(int, int); 268 | FuncType channelStateChangedSignal_; 269 | bool not_connected_notified_; 270 | CompletionQueue cq_; 271 | friend typename SERVICE; 272 | public: 273 | explicit MonitorFeatures(AbstractConnectivityFeatures* conn, FuncType signal) : conn_(conn), channelStateChangedSignal_(signal), not_connected_notified_(false) {} 274 | virtual bool CheckCQ() override 275 | { 276 | auto service_ = dynamic_cast< SERVICE* >(this); 277 | if (!conn_->connected() && !not_connected_notified_) 278 | { 279 | (const_cast< SERVICE* >(service_)->*channelStateChangedSignal_)(static_cast(grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN), static_cast(grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN)); 280 | not_connected_notified_ = true; 281 | return false; 282 | } 283 | 284 | auto old_state = conn_->channelState(); 285 | auto new_state = conn_->checkChannelState(); 286 | if (old_state != new_state) 287 | { 288 | (const_cast< SERVICE* >(service_)->*channelStateChangedSignal_)(static_cast(old_state), static_cast(new_state)); 289 | not_connected_notified_ = false; 290 | } 291 | 292 | //if (!conn_->connected()) 293 | // return false; 294 | 295 | // 296 | void* tag; 297 | bool ok = false; 298 | 299 | grpc::CompletionQueue::NextStatus st; 300 | st = cq_.AsyncNext(&tag, &ok, deadlineFromMSec(100)); 301 | if ((st == grpc::CompletionQueue::SHUTDOWN) || (st == grpc::CompletionQueue::TIMEOUT))/* || (st != grpc::CompletionQueue::GOT_EVENT) || !ok)*/ 302 | return false; 303 | static_cast< AbstractCallData< SERVICE >* >(tag)->cqActions(service_, ok); 304 | return true; 305 | } 306 | }; 307 | 308 | template 309 | class ClientCallData : public AbstractCallData, public ClientResponder< typename RPC::kind, typename RPC::ReplyType, typename RPC::RequestType > 310 | { 311 | using FuncType = void (RPC::Service::*)(RPCCallData*); 312 | FuncType func_; 313 | friend typename RPC::Service; 314 | public: 315 | explicit ClientCallData(FuncType func) :func_(func) {} 316 | virtual ~ClientCallData() {} 317 | private: 318 | inline virtual void cqActions(const typename RPC::Service* service, bool ok) override 319 | { 320 | auto response = dynamic_cast(this); 321 | void* tag = static_cast(response); 322 | if (!this->processEvent(tag, ok)) return; 323 | //if (response->CouldBeDeleted())//comment out to manually delete tags 324 | //{ 325 | // delete response; 326 | // return; 327 | //} 328 | (const_cast< typename RPC::Service* >(service)->*func_)( response ); 329 | } 330 | }; 331 | 332 | template struct RPCtypes 333 | { 334 | using kind = KIND; 335 | using ReplyType = REPLY; 336 | using RequestType = REQUEST; 337 | using Service = SERVICE; 338 | }; 339 | 340 | } 341 | -------------------------------------------------------------------------------- /qgrpc/QGrpcServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "QAbstractGrpc.h" 4 | 5 | using grpc::Channel; 6 | using grpc::Server; 7 | using grpc::ServerBuilder; 8 | using grpc::ServerAsyncResponseWriter; 9 | using grpc::ServerContext; 10 | using grpc::ServerAsyncReader; 11 | using grpc::ServerAsyncWriter; 12 | using grpc::ServerAsyncReaderWriter; 13 | using grpc::CompletionQueue; 14 | using grpc::ServerCompletionQueue; 15 | using grpc::Status; 16 | 17 | namespace QGrpcSrvBase 18 | { 19 | 20 | /*struct callCounter 21 | { 22 | size_t unary_finish_call; 23 | size_t server_stream_finish_call; 24 | size_t client_stream_finish_call; 25 | size_t bidi_stream_finish_call; 26 | static callCounter& get() 27 | { 28 | static callCounter cc; 29 | return cc; 30 | } 31 | private: 32 | explicit callCounter(): 33 | unary_finish_call(0), 34 | server_stream_finish_call(0), 35 | client_stream_finish_call(0), 36 | bidi_stream_finish_call(0) 37 | {} 38 | };*/ 39 | 40 | 41 | struct RPC_KIND_UNARY_T; 42 | struct RPC_KIND_SERVERSTREAMING_T; 43 | struct RPC_KIND_CLIENTSTREAMING_T; 44 | struct RPC_KIND_BIDISTREAMING_T; 45 | 46 | 47 | template 48 | struct ServerCallDataResponse 49 | { 50 | RequestType request; 51 | ReplyType reply; 52 | }; 53 | 54 | 55 | template< template class R, typename...Args> struct ServerResponderBase 56 | { 57 | using responder_type = R< Args...>; 58 | ServerContext context; 59 | Status status; 60 | ServerResponderBase(): responder(&context), couldBeDeleted_(false), tag_(nullptr) {} 61 | virtual ~ServerResponderBase() {} 62 | inline std::string peer() const { return context.peer(); } 63 | protected: 64 | responder_type responder; //must be deleted before context 65 | bool couldBeDeleted_; 66 | void* tag_; 67 | virtual bool processEvent(void*, bool) = 0; 68 | bool CouldBeDeleted() const { return couldBeDeleted_; }; 69 | }; 70 | 71 | template struct ServerResponder; 72 | template 73 | struct ServerResponder : public ServerResponderBase< ServerAsyncResponseWriter, ReplyType >, public ServerCallDataResponse 74 | { 75 | virtual ~ServerResponder() {} 76 | inline void Finish() 77 | { 78 | this->couldBeDeleted_ = true; 79 | this->responder.Finish(this->reply, this->status, this->tag_); 80 | } 81 | protected: 82 | virtual bool processEvent(void* tag, bool) override { this->tag_ = tag; return true; } 83 | }; 84 | 85 | template 86 | struct ServerResponder : public ServerResponderBase< ServerAsyncWriter, ReplyType >, public ServerCallDataResponse 87 | { 88 | ServerResponder() : isFinished_(false) {} 89 | virtual ~ServerResponder() {} 90 | inline void Write() { return this->responder.Write(this->reply, this->tag_); } 91 | inline void Finish() { this->responder.Finish(this->status, this->tag_); this->couldBeDeleted_ = true; } 92 | protected: 93 | virtual bool processEvent(void* tag, bool/* ok*/) override { this->tag_ = tag; return true; } 94 | bool isFinished_; 95 | }; 96 | 97 | template 98 | struct ServerResponder : public ServerResponderBase< ServerAsyncReader, ReplyType, RequestType >, public ServerCallDataResponse 99 | { 100 | ServerResponder() : read_mode_(true) {} 101 | inline void Read() { return this->responder.Read(&this->request, this->tag_); } 102 | inline bool StreamFinished() const { return !this->read_mode_; } 103 | inline void Finish() { this->couldBeDeleted_ = true; return this->responder.Finish(this->reply, this->status, this->tag_); } 104 | virtual ~ServerResponder() {} 105 | protected: 106 | virtual bool processEvent(void* tag, bool ok) override 107 | { 108 | this->tag_ = tag; 109 | read_mode_ = ok; 110 | return true; 111 | } 112 | bool read_mode_; 113 | }; 114 | 115 | template 116 | struct ServerResponder : public ServerResponderBase< ServerAsyncReaderWriter, ReplyType, RequestType >, public ServerCallDataResponse 117 | { 118 | ServerResponder() : write_mode_(false), time_to_finish_(false) {} 119 | inline bool WriteMode() const { return write_mode_; } 120 | inline void Write() { return this->responder.Write(this->reply, this->tag_); } 121 | inline void Finish() { this->couldBeDeleted_ = true; return this->responder.Finish(this->status, this->tag_); } 122 | inline bool TimeToFinish() const { return this->time_to_finish_; } 123 | inline void Read() { return this->responder.Read(&this->request, this->tag_); } 124 | virtual ~ServerResponder() {} 125 | protected: 126 | virtual bool processEvent(void* tag, bool ok) override 127 | { 128 | this->tag_ = tag; 129 | if (!ok) 130 | { 131 | if (!write_mode_) write_mode_ = true; 132 | else time_to_finish_ = true; 133 | } 134 | return true; 135 | } 136 | bool write_mode_; 137 | bool time_to_finish_; 138 | }; 139 | 140 | 141 | inline std::chrono::system_clock::time_point deadlineFromSec(long long seconds) { return std::chrono::system_clock::now() + std::chrono::seconds(seconds); } 142 | inline std::chrono::system_clock::time_point deadlineFromMSec(long long mseconds) { return std::chrono::system_clock::now() + std::chrono::milliseconds(mseconds); } 143 | 144 | //class QGrpcServerService; 145 | 146 | 147 | template 148 | typename std::enable_if::value>::type RequestRPC(ASYNCGRPCSERVICE* service, RPCREQUESTFUNCTYPE requestFunc, grpc::ServerContext* ctx, REQUEST* request, RESPONDERTYPE* responder, grpc::CompletionQueue* call_cq, grpc::ServerCompletionQueue* notification_cq, void* tag) 149 | { 150 | return (service->*requestFunc)(ctx, request, responder, call_cq, notification_cq, tag); 151 | } 152 | 153 | template 154 | typename std::enable_if::value>::type RequestRPC(ASYNCGRPCSERVICE* service, RPCREQUESTFUNCTYPE requestFunc, grpc::ServerContext* ctx, REQUEST* request, RESPONDERTYPE* responder, grpc::CompletionQueue* call_cq, grpc::ServerCompletionQueue* notification_cq, void* tag) 155 | { 156 | return (service->*requestFunc)(ctx, request, responder, call_cq, notification_cq, tag); 157 | } 158 | 159 | template 160 | typename std::enable_if::value>::type RequestRPC(ASYNCGRPCSERVICE* service, RPCREQUESTFUNCTYPE requestFunc, grpc::ServerContext* ctx, REQUEST* /*request*/, RESPONDERTYPE* responder, grpc::CompletionQueue* call_cq, grpc::ServerCompletionQueue* notification_cq, void* tag) 161 | { 162 | return (service->*requestFunc)(ctx, responder, call_cq, notification_cq, tag); 163 | } 164 | 165 | template 166 | typename std::enable_if::value>::type RequestRPC(ASYNCGRPCSERVICE* service, RPCREQUESTFUNCTYPE requestFunc, grpc::ServerContext* ctx, REQUEST* /*request*/, RESPONDERTYPE* responder, grpc::CompletionQueue* call_cq, grpc::ServerCompletionQueue* notification_cq, void* tag) 167 | { 168 | return (service->*requestFunc)(ctx, responder, call_cq, notification_cq, tag); 169 | } 170 | 171 | 172 | class QGrpcServerService; 173 | template class ServerCallData; 174 | 175 | struct AbstractCallData 176 | { 177 | virtual void cqReaction(const QGrpcServerService*, bool) = 0; 178 | virtual void Destroy() = 0; 179 | virtual ~AbstractCallData() {} 180 | }; 181 | 182 | class QGrpcServerService : public QGrpcSrvAbstract::AbstractService 183 | { 184 | grpc::Service* service_; 185 | std::string addr_uri_; 186 | std::mutex mutex_; 187 | std::unique_ptr server_; 188 | std::unique_ptr server_cq_; 189 | std::atomic started_; 190 | public: 191 | explicit QGrpcServerService(grpc::Service* service) : service_(service), started_(false) {} 192 | virtual ~QGrpcServerService() 193 | { 194 | PrepareForShutdown(); 195 | } 196 | 197 | virtual void PrepareForShutdown() override 198 | { 199 | started_.store(false); 200 | if (server_) 201 | { 202 | server_->Shutdown(std::chrono::system_clock::time_point()); 203 | 204 | grpc::CompletionQueue::NextStatus st; 205 | while (true) 206 | { 207 | void* tag; 208 | bool ok; 209 | st = server_cq_->AsyncNext(&tag, &ok, std::chrono::system_clock::time_point()); 210 | if ((st == grpc::CompletionQueue::SHUTDOWN) || (st == grpc::CompletionQueue::TIMEOUT))/* || (st != grpc::CompletionQueue::GOT_EVENT) || !ok)*/ 211 | break; 212 | tagActions_(tag, ok); 213 | } 214 | size_t cdsize = cdatas_.size(); 215 | for (size_t i = 0; i < cdsize; ++i) 216 | destroyCallData(*cdatas_.begin()); 217 | 218 | server_->Wait(); 219 | } 220 | if (server_cq_) 221 | { 222 | server_cq_->Shutdown(); // Always after the associated server's Shutdown()! 223 | // Drain the cq_ that was created 224 | void* ignored_tag; bool ignored_ok; 225 | while (server_cq_->Next(&ignored_tag, &ignored_ok)) {} 226 | } 227 | server_ = nullptr; 228 | server_cq_ = nullptr; 229 | } 230 | 231 | virtual void CheckCQ() override 232 | { 233 | std::lock_guard _(mutex_); 234 | if (!started_.load()) return; 235 | void* tag; 236 | bool ok = false; 237 | /*bool re = */server_cq_->Next(&tag, &ok); 238 | return tagActions_(tag, ok); 239 | } 240 | 241 | void Start(const std::string& addr_uri) 242 | { 243 | if (started_.load()) return; 244 | addr_uri_ = addr_uri; 245 | assert(!addr_uri_.empty()); assert(service_); 246 | ServerBuilder builder; 247 | builder.AddListeningPort(addr_uri_, grpc::InsecureServerCredentials()); 248 | builder.RegisterService(service_); 249 | server_cq_ = builder.AddCompletionQueue(); 250 | server_ = builder.BuildAndStart(); 251 | makeRequests(); 252 | started_.store(true); 253 | } 254 | 255 | inline std::string ListeningPort() { return addr_uri_; } 256 | 257 | void AsyncCheckCQ() 258 | { 259 | std::lock_guard _(mutex_); 260 | if (!started_.load()) return; 261 | void* tag; 262 | bool ok = false; 263 | grpc::CompletionQueue::NextStatus st; 264 | st = server_cq_->AsyncNext(&tag, &ok, std::chrono::system_clock::time_point()); 265 | if ((st == grpc::CompletionQueue::SHUTDOWN) || (st == grpc::CompletionQueue::TIMEOUT))/* || (st != grpc::CompletionQueue::GOT_EVENT) || !ok)*/ 266 | return; 267 | return tagActions_(tag, ok); 268 | } 269 | 270 | protected: 271 | virtual void makeRequests() = 0; 272 | 273 | template 274 | void needAnotherCallData() 275 | { 276 | RPCCallData* cd = new RPCCallData(); 277 | cdatas_.insert(cd); 278 | QGrpcSrvBase::RequestRPC 279 | (dynamic_cast(service_), cd->request_func_, &cd->context, &cd->request, &cd->responder, server_cq_.get(), dynamic_cast(server_cq_.get()), (void*)cd); 280 | } 281 | 282 | void destroyCallData(const AbstractCallData* cd) 283 | { 284 | assert(cdatas_.count(const_cast(cd))); 285 | cdatas_.erase(const_cast(cd)); 286 | const_cast(cd)->Destroy(); 287 | } 288 | 289 | void tagActions_(void* tag, bool ok) 290 | { 291 | if (!tag) 292 | return; 293 | if (!started_.load()) 294 | { 295 | destroyCallData(static_cast(tag)); 296 | return; 297 | } 298 | static_cast(tag)->cqReaction(this, ok); 299 | } 300 | private: 301 | std::set cdatas_; 302 | template friend class ServerCallData; 303 | 304 | }; 305 | 306 | 307 | 308 | /* Polymorphism hierarchy 309 | ---------------------------- ------------------------ 310 | | AbstractCallData | | ServerResponder | 311 | ---------------------------- ------------------------ 312 | ^ ^ 313 | | | 314 | | | 315 | ----------------------- 316 | | ServerCallData | 317 | ----------------------- 318 | ^ 319 | | 320 | | 321 | ------------------------------------------------ 322 | | GeneratedCallData (e.g. SayHelloCallData) | 323 | ------------------------------------------------ 324 | 325 | */ 326 | template 327 | class ServerCallData : public AbstractCallData, public ServerResponder< typename RPC::kind, typename RPC::ReplyType, typename RPC::RequestType > 328 | { 329 | using SignalType = void (RPC::ServiceType::*)(RPCCallData*); 330 | using RPCRequestType = typename RPC::RPCRequestFuncType; 331 | SignalType signal_func_; 332 | RPCRequestType request_func_; 333 | bool first_time_reaction_; 334 | friend class QGrpcServerService; 335 | public: 336 | explicit ServerCallData(SignalType signal_func, RPCRequestType request_func) :signal_func_(signal_func), request_func_(request_func), first_time_reaction_(false) {} 337 | virtual ~ServerCallData() {} 338 | private: 339 | virtual void Destroy() override 340 | { 341 | //upcast this to generated call data and delete it 342 | auto response = dynamic_cast(this); 343 | delete response; 344 | } 345 | inline virtual void cqReaction(const QGrpcServerService* service_, bool ok) override 346 | { 347 | if (!first_time_reaction_) 348 | { 349 | first_time_reaction_ = true; 350 | (const_cast(service_))->needAnotherCallData(); 351 | } 352 | auto genRpcCallData = dynamic_cast(this); //Generated RPCCallData inherited from this (ServerCallData) 353 | void* tag = static_cast(genRpcCallData); //generated RPCCallData uses as tag for request calls 354 | 355 | if (this->CouldBeDeleted())//CouldBeDeleted is a part of ServerResponder (from which this is inherited) 356 | { 357 | (const_cast(service_))->destroyCallData(this); //this inherited from AbstractCallData 358 | return; 359 | } 360 | if (!this->processEvent(tag, ok)) return; //processEvent is a part of ServerResponder (from which this is inherited) 361 | //call generated service signal with generated call data argument 362 | ((const_cast(dynamic_cast(service_)))->*signal_func_)(genRpcCallData); 363 | } 364 | }; 365 | 366 | 367 | 368 | 369 | template 370 | struct RPCRequestFunc_; 371 | 372 | template 373 | struct RPCRequestFunc_ 374 | { 375 | using RequestFuncType = void(ASYNCGRPCSERVICE::*)( 376 | grpc::ServerContext*, 377 | REQUEST*, 378 | typename ServerResponder::responder_type*, 379 | grpc::CompletionQueue*, 380 | grpc::ServerCompletionQueue*, 381 | void *); 382 | }; 383 | 384 | template 385 | struct RPCRequestFunc_ 386 | { 387 | using RequestFuncType = void(ASYNCGRPCSERVICE::*)( 388 | grpc::ServerContext*, 389 | REQUEST*, 390 | typename ServerResponder::responder_type*, 391 | grpc::CompletionQueue*, 392 | grpc::ServerCompletionQueue*, 393 | void *); 394 | }; 395 | 396 | template 397 | struct RPCRequestFunc_ 398 | { 399 | using RequestFuncType = void(ASYNCGRPCSERVICE::*)( 400 | grpc::ServerContext*, 401 | typename ServerResponder::responder_type*, 402 | grpc::CompletionQueue*, 403 | grpc::ServerCompletionQueue*, 404 | void *); 405 | }; 406 | 407 | template 408 | struct RPCRequestFunc_ 409 | { 410 | using RequestFuncType = void(ASYNCGRPCSERVICE::*)( 411 | grpc::ServerContext*, 412 | typename ServerResponder::responder_type*, 413 | grpc::CompletionQueue*, 414 | grpc::ServerCompletionQueue*, 415 | void *); 416 | }; 417 | 418 | 419 | template 420 | struct RPCtypes 421 | { 422 | using kind = KIND; 423 | using ReplyType = REPLY; 424 | using RequestType = REQUEST; 425 | using ServiceType = SERVICE; 426 | using AsyncGrpcServiceType = ASYNCGRPCSERVICE; 427 | using RPCRequestFuncType = typename RPCRequestFunc_::RequestFuncType; 428 | }; 429 | 430 | 431 | } --------------------------------------------------------------------------------