LINUX.ORG.RU

Qt & gRPC

 ,


0

2

Как Qt и gRPC заставить работать вместе? В чем идея постоения архитектуры? Опыты провожу на helloworld отсюда https://github.com/plasticbox/grpc-windows/tree/master/grpc_helloworld/src

Попытка первая, просто попробовать добавить синнал

class GreeterServiceImpl final : public Greeter::Service, public QObject {
Q_OBJECT
 public:
  Status SayHello(ServerContext* context, const HelloRequest* request,  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    emit message();
    return Status::OK;
  }
signals:
   void message();
};
не позволяет собрать приложение
In function `GreeterServiceImpl::GreeterServiceImpl()':
error: undefined reference to `vtable for GreeterServiceImpl'
In function `GreeterServiceImpl::~GreeterServiceImpl()':
error: undefined reference to `vtable for GreeterServiceImpl'
error: collect2: error: ld returned 1 exit status

Добавляем конструктор

explicit GreeterServiceImp(QObject *parent=nullptr) : QObject(parent) {}
ошибки те же.

Потытка вторая, связана с программированием на Qt внутри gRPC методов, и тоже не приводит к успеху

  Status SayHello(ServerContext* context, const HelloRequest* request,  HelloReply* reply) override {
    QSslSocket mSocket;
    QSslConfiguration config = mSocket.sslConfiguration();
    config.setPeerVerifyMode(QSslSocket::VerifyNone);
    config.setProtocol(QSsl::SecureProtocols);
    mSocket.setSslConfiguration(config);
    mSocket.connectToHostEncrypted("google.com", 8080);
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    emit message();
    return Status::OK;
  }
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSslSocket(0x5598fd195520), parent's thread is QThread(0x5598fd18e800), current thread is QThread(0x7f3a30004510)

In function GreeterServiceImpl::GreeterServiceImpl()': error: undefined reference tovtable for GreeterServiceImpl’

Линкуешь правильно? qmake запускал перед сборкой?

QObject: Cannot create children for a parent that is in a different thread.

Всё правильно ругается. У grpc свой поток.

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff

Линкуешь правильно? qmake запускал перед сборкой?

Проект создал в qtcreator.

 
QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
QT += network
LIBS += `pkg-config --libs protobuf grpc++` -lgrpc++_reflection
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += main.cpp \
    helloworld.pb.cc \
    helloworld.grpc.pb.cc
HEADERS += \
    helloworld.pb.h \
    helloworld.grpc.pb.h
fffafasnow ()
Ответ на: комментарий от fffafasnow

Можно. Но зависит от класса. Некоторым нужен eventloop. Конкретно у тебя сейчас ошибка из-за того, что родительский и дочерний объекты создаются в разных потоках. Ты точно привёл код, который запускаешь? Где ssl сокет получает родителя?

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff

Ты точно привёл код, который запускаешь?

Да, я в точности запускаю тот код, что указал. Дополню, в helloworld я добавил QApplication, правда QEventLoop возможно не заюзывается конечно же.

void RunServer() {
  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;
  grpc::EnableDefaultHealthCheckService(true);
  grpc::reflection::InitProtoReflectionServerBuilderPlugin();
  ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
}

int main(int argc, char** argv) {
  QCoreApplication a(argc, argv);
  RunServer();
  return a.exec();
}

И даже идейно я пока не понимаю: как тут строить архитектуру взаимодействия qt и grcp?

Ну и похоже, что сокет создается в main-thread, затем что-то внутри grpc уводит его в какой-то дочерний thread и потому он не работает.

fffafasnow ()
Ответ на: комментарий от ox55ff

Возможно вот эту проблему нужно решать. Потытка все-таки загнать сигнал в сервер

class GreeterServiceImpl final : public QObject, public Greeter::Service {
Q_OBJECT
public:
  explicit GreeterServiceImpl(QObject *parent=nullptr);
  Status SayHello(ServerContext* context, const HelloRequest* request,  HelloReply* reply);
signals:
   void message();
};

class MySslSock
{
public:
    MySslSock();
public slots:
    void setMesaage();
};
потом при попытке, в main.cpp, соединить сигнал и слот
MySslSock sock;
QObject::connect(&service, &GreeterServiceImpl::message, &sock, &MySslSock::setMesaage);
возникает такая проблема
In file included from /usr/include/x86_64-linux-gnu/qt5/QtCore/QObject:1:0,
                 from greeterserviceimpl.h:4,
                 from main.cpp:19:
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject.h: In instantiation of ‘static QMetaObject::Connection QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*, Func2, Qt::ConnectionType) [with Func1 = void (GreeterServiceImpl::*)(); Func2 = void (MySslSock::*)(); typename QtPrivate::FunctionPointer<Func>::Object = GreeterServiceImpl; typename QtPrivate::FunctionPointer<Func2>::Object = MySslSock]’:
main.cpp:40:89:   required from here
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject.h:256:27: error: no matching function for call to ‘QObject::connectImpl(const Object*&, void**, const Object*&, void**, QtPrivate::QSlotObject<void (MySslSock::*)(), QtPrivate::List<>, void>*, Qt::ConnectionType&, const int*&, const QMetaObject*)’
         return connectImpl(sender, reinterpret_cast<void **>(&signal),
                ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                            receiver, reinterpret_cast<void **>(&slot),
                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                            new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                            typename SignalType::ReturnType>(slot),
                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                             type, types, &SignalType::Object::staticMetaObject);
                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject.h:459:36: note: candidate: static QMetaObject::Connection QObject::connectImpl(const QObject*, void**, const QObject*, void**, QtPrivate::QSlotObjectBase*, Qt::ConnectionType, const int*, const QMetaObject*)
     static QMetaObject::Connection connectImpl(const QObject *sender, void **signal,
                                    ^~~~~~~~~~~
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject.h:459:36: note:   no known conversion for argument 3 from ‘const Object* {aka const MySslSock*}’ to ‘const QObject*’
In file included from /usr/include/x86_64-linux-gnu/qt5/QtCore/qobject.h:56:0,
                 from /usr/include/x86_64-linux-gnu/qt5/QtCore/QObject:1,
                 from greeterserviceimpl.h:4,
                 from main.cpp:19:
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject_impl.h: In instantiation of ‘static void QtPrivate::QSlotObject<Func, Args, R>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) [with Func = void (MySslSock::*)(); Args = QtPrivate::List<>; R = void]’:
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject_impl.h:129:56:   required from ‘QtPrivate::QSlotObject<Func, Args, R>::QSlotObject(Func) [with Func = void (MySslSock::*)(); Args = QtPrivate::List<>; R = void]’
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject.h:258:28:   required from ‘static QMetaObject::Connection QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*, Func2, Qt::ConnectionType) [with Func1 = void (GreeterServiceImpl::*)(); Func2 = void (MySslSock::*)(); typename QtPrivate::FunctionPointer<Func>::Object = GreeterServiceImpl; typename QtPrivate::FunctionPointer<Func2>::Object = MySslSock]’
main.cpp:40:89:   required from here
/usr/include/x86_64-linux-gnu/qt5/QtCore/qobject_impl.h:120:94: error: invalid static_cast from type ‘QObject*’ to type ‘QtPrivate::FunctionPointer<void (MySslSock::*)()>::Object* {aka MySslSock*}’
                 FuncType::template call<Args, R>(static_cast<QSlotObject*>(this_)->function, static_cast<typename FuncType::Object *>(r), a);
                                                                                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Makefile:376: recipe for target 'main.o' failed
make: *** [main.o] Error 1

fffafasnow ()

Что то ты не договариваешь. Собрал я у себя проект из твоих кусков кода. Нет ошибок.

class GreeterServiceImpl final : public QObject

Вижу, что исправился. Сразу не заметил. Если наследуешься от QObject, то он должен стоять первым в списке наследования.

std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
}

int main(int argc, char** argv) {
QCoreApplication a(argc, argv);
RunServer();
return a.exec();

server->Wait() блокирует поток, поэтому a.exec() не выполнится, пока работает grpc сервер. А именно эта строчка запускает цикл событий. Пока сюда не отдашь управление, сигналы, слоты и таймеры главного потока работать не будут. Этот вызов также блокирует поток, так что менять местами строчки смысла нет, тогда у тебя grpc сервер не заработает.

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff

собирается, но не работают сигналы и слоты

$ cat mysslsock.h 
#ifndef MYSSLSOCK_H
#define MYSSLSOCK_H

#include <QObject>
#include <QSslSocket>
#include <QSslConfiguration>

class MySslSock: public QObject
{
Q_OBJECT
public:
    explicit MySslSock(QObject *parent=nullptr);
public slots:
    void setMesaage();
};

#endif // MYSSLSOCK_H

r$ cat mysslsock.cpp 
#include "mysslsock.h"

#include <iostream>

MySslSock::MySslSock(QObject* parent)
    : QObject(parent)
{
}

void MySslSock::setMesaage()
{
    std::cout << "send" << std::endl;
    QSslSocket mSocket;
    QSslConfiguration config = mSocket.sslConfiguration();
    config.setPeerVerifyMode(QSslSocket::VerifyNone);
    config.setProtocol(QSsl::SecureProtocols);
    mSocket.setSslConfiguration(config);
    mSocket.connectToHostEncrypted("google.com", 8080);
}

$ cat greeterserviceimpl.h 
#ifndef GREETERSERVICEIMPL_H
#define GREETERSERVICEIMPL_H

#include <QObject>

#include <QCoreApplication>
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>

#include "helloworld.grpc.pb.h"
#include "mysslsock.h"

#include <QObject>
#include <QSslSocket>
#include <QSslConfiguration>

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

class GreeterServiceImpl final : public QObject, public Greeter::Service {
Q_OBJECT

public:
  explicit GreeterServiceImpl(QObject *parent=nullptr);
  Status SayHello(ServerContext* context, const HelloRequest* request,  HelloReply* reply);
  //Greeter::Service service;

  MySslSock sock;

signals:
   void message();

};

#endif // GREETERSERVICEIMPL_H



$ cat greeterserviceimpl.cpp
#include "greeterserviceimpl.h"


#include <QDebug>

GreeterServiceImpl::GreeterServiceImpl(QObject *parent) : QObject(parent)
{
    //MySslSock sock;
    connect(this, &GreeterServiceImpl::message, &sock, &MySslSock::setMesaage);

}

Status GreeterServiceImpl::SayHello(ServerContext* context, const HelloRequest* request,  HelloReply* reply)
{
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());

    std::cout << "emit" << std::endl;
    emit message();
    return Status::OK;
}

$ cat main.cpp 
#include "greeterserviceimpl.h"
#include "mysslsock.h"
#include <thread>

void RunServer() {
  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;
  grpc::EnableDefaultHealthCheckService(true);
  grpc::reflection::InitProtoReflectionServerBuilderPlugin();
  ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();

}

int main(int argc, char** argv) {
  QCoreApplication a(argc, argv);
  std::thread t  (
       []
       {
            RunServer();
       }
  );
  t.detach();
  return a.exec();
}
$ cat server.pro
QT -= gui

CONFIG += c++11 console
CONFIG -= app_bundle

QT += network

LIBS += `pkg-config --libs protobuf grpc++` -lgrpc++_reflection

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += main.cpp \
    helloworld.pb.cc \
    helloworld.grpc.pb.cc \
    greeterserviceimpl.cpp \
    mysslsock.cpp

HEADERS += \
    helloworld.pb.h \
    helloworld.grpc.pb.h \
    greeterserviceimpl.h \
    mysslsock.h

fffafasnow ()
Ответ на: комментарий от fffafasnow

Как ты понял, что не работают? Запись в std::cout без std::flush может буферизироваться и не сразу показаться на консоли.

connect(this, &GreeterServiceImpl::message, &sock, &MySslSock::setMesaage);

У тебя здесь будет DirectConnection. Т.е. простой вызов метода в том же потоке. Что ты хотел сделать выносом кода работы с сокетом в отдельный класс?

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff

Как ты понял, что не работают? Запись в std::cout без std::flush может буферизироваться и не сразу показаться на консоли.

Понял, что не работает с помощью tshark. Кстати, std::endl делает flush.

У тебя здесь будет DirectConnection. Т.е. простой вызов метода в том же потоке. Что ты хотел сделать выносом кода работы с сокетом в отдельный класс?

Я хотел проверить то, что сигнально слотный механизм работает как минимум. И я смогу из рпс метода передать сигнал некоторому Qt классу.

fffafasnow ()
Ответ на: комментарий от ox55ff

В исходном топике я не претендую на то, что я выбрал правильную идею интеграции Qt и gRPC.

Я прежде всего спрашиваю про то, как построить архитектуру? Идейно как провести интеграцию?

В инетах есть обертки какие-то, но нифига там не понятно и Qt явно 4-й. Хотелось бы понять суть того, как сделать интеграцию на простейшем хеллоуворд примере.

fffafasnow ()
Ответ на: комментарий от fffafasnow

Честно говоря, я не совсем понимаю, что ты хочешь сделать. Наверное получить некий запрос, потом с помощью кода на Qt что-то сделать и вернуть ответ.

Я сам лично не сопрягал grpc и Qt, поэтому историей успеха поделиться не могу. Мысли вслух. Если нужно выполнять код в отдельном от grpc потоке, то:

Можно сделать класс, который будет жить в своём Qt потоке (или в главном) и общаться с ним при помощи сигналов. Там есть специальный тип соединения Qt::BlockingQueuedConnection, который ждёт пока отработает слот, вызванный твоим сигналом. Но через сигналы не удобно возвращать результат. А тебе он нужен, как я понял.

Можно нагородить инфраструктуру тасков с QFuture или std::future, как хочешь. Вызываешь метод, который поставит таск в очередь к другому потоку (Qt), а взамен получаешь future. Можно на ней заблокироваться и ждать результата. Но у тебя grpc сервер синхронный, поэтому пока ты ждёшь результат, другие запросы не будут приходить. Это снизить количество обрабатываемых запросов в секунду. Если это критично, конечно. Но я видел, что есть и асинхронный grpc. Если не лень, то можно попробовать сделать на нём.

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff

Да, так и есть. Мне нужно внутри gRPC-нрго метода вызвать Qt класс, который сделает некоторую работу. Причем Qt класс во всю использует сигналы и слоты, возможно и цикл обработки событий. Но, похоже gRPC уводит сервер в свой поток, который не QThread и Qt перестает работать.

fffafasnow ()

Сталкивался с этой же проблемой

Первое:

GreeterServiceImpl.h

#ifndef GREETERSERVICEIMPL_H
#define GREETERSERVICEIMPL_H

class GreeterServiceImpl final : public Greeter::Service, public QObject {
Q_OBJECT
 public:
  explicit GreeterServiceImpl( QObject *parent = nullptr );
  ~GreeterServiceImpl();
signals:
   void message();
};

#endif //GREETERSERVICEIMPL_H


GreeterServiceImpl.cpp
GreeterServiceImpl::GreeterServiceImpl(QObject *parent) : QObject(parent) {}
GreeterServiceImpl::~GreeterServiceImpl() {}


Qt вот эти вот Q_OBJECT Q_INVOKABLE Q_PROPERTY ищет и заменяет только в заголовочных файлах. А сам заголовочный файл должен быть указан в файле .pro а не просто заинклюжен в коде
HEADERS += \
        GreeterServiceImpl.h
SOURCES += \
        GreeterServiceImpl.cpp


И да, на будущее - сигнал emit message(); со стороны принимающего кода выглядит как onMessage() {} так что имей в виду.

uin ★★★ ()
Ответ на: комментарий от uin

1. из опыта

не работает

class GreeterServiceImpl final : public Greeter::Service, public QObject

работает

class GreeterServiceImpl final : public QObject, public Greeter::Service

2. я в предпоследнем топике сделал как Qt хочет, класс в отдельный файл и файл прописал в pro. Все собирается.

3. Проблема здесь следующая, что из grpc методов не работает Qt, а точнее даже сигнально-слотный механизм. Не смотря на то, что есть QApplication и gRPC сервер я задеттачил в отдельном потоке (дал главному циклу обработки событий запуститься)

int main(int argc, char** argv) {
  QCoreApplication a(argc, argv);
  std::thread t  (
       []
       {
            RunServer();
       }
  );
  t.detach();
  return a.exec();
}
GreeterServiceImpl::GreeterServiceImpl(QObject *parent) : QObject(parent)
{
    connect(this, &GreeterServiceImpl::message, &sock, &MySslSock::onMessage); // не ругается компилятор, все собирается, но соединение не работает, причем молча

}

Status GreeterServiceImpl::SayHello(ServerContext* context, const HelloRequest* request,  HelloReply* reply)
{
    // ...
    emit message();
    return Status::OK;
}

4. если есть конкретные идеи как из gRPC методов вызывать Qt методы, которые используют сигналы и слоты --- прошу высказаться.

fffafasnow ()