[C++,QT/Qml]36.Qt 프로세스간의 통신(IPC) 구현하기5(나의 qt프로젝트가 클라이언트인 ui앱 만들기)
안녕하세요 고급 프로그래머가 꿈인 코린이 입니다.
오늘은 지난시간에 포팅한 commonapi라이브러리를 이용해서 서버와 통신하는 내용을 다루어 보겠습니다.
서버쪽에서 브로드 캐스트 함수를 사용해서 client쪽 즉 ui앱의 팝업을 출력시켰다가 종료시켰다가 하는 내용을
다루어 보겠습니다. 서버쪽에서는 브로드 캐스트 함수를 3초에 한번씩 호출해서 ui쪽에서는 3초에 한번씩
이 이벤트를 받아서 팝업이 열려있으면 팝업을 닫아주고 팝업이 닫혀있으면 팝업을 출력하는 내용을
구현하겠습니다.
commonapi 브로드캐스트 받는 함수를 새로 생성한 쓰레드에서 등록을 해준 후 이벤트를 받으면 메인 쓰레드에
전달해서 ui를 출력하는 식으로 구현을 했는데요 그렇다 보니 qthread를 상속받는 클래스를 하나 생성해 주었습니다.
아래 소스코드를 보면서 설명 드리겠습니다.
일단 서버쪽 코드입니다. 이글을 읽고 계시는 분들은 commonapi broadcast 사용하는 방법을 이미 알고 있다고
생각하고 만든 내용 입니다.
broadcast 사용하는 내용은 아래 url에서 보고 공부하시면 됩니다.
https://youonlyliveonce1.tistory.com/60?category=712090
일단 서버쪽 코드를 먼저 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#include <iostream>
#include <thread>
#include <CommonAPI/CommonAPI.hpp>
#include "HelloWorldStubImpl.hpp"
#include <unistd.h>
using namespace std;
int main() {
int i = 0;
std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::get();
std::shared_ptr<HelloWorldStubImpl> myService = std::make_shared<HelloWorldStubImpl>();
if(runtime->registerService("local", "shin", myService)){
std::cout << "Service registered." << std::endl;
}else{
std::cout << "Service not registered." << std::endl;
}
std::cout << "Successfully Registered Service!" << std::endl;
std::thread broadcastMethod([&]() { //스레드를 만들어서 3초에 한번씩 브로드 캐스트 함수를 호출하도록 구현함
bool loop = true;
while(loop){
if (true == loop) {
std::cout <<"send broad cast method" << std::endl;
myService->fireEnumBroadCastMethodEvent(v1::commonapi::HelloWorld::testEnum::TEST_THIRD);
sleep(3); // 3s
}
}
});
broadcastMethod.detach();
while (true) {
std::cout << "Waiting for calls... (Abort with CTRL+C)" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(10));
}
return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
위의 서버 쪽 코드를 보시면 쓰레드를 하나 만들어서 그 쓰레드가 3초에 한번씩 실행이 되면서 브로드 캐스트 함수를
호출하는 것을 보실수가 있습니다.
그리고 이번에는 클라이언트쪽 소스를 보겠습니다.
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <RegisterCommonapi.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
ConnectEvent::getInstance(); //뷰가 그려지기 전에 객체를 생성해서 필요한 것들을 미리 만들어 준다.
RegisterCommonapi *registerCommonapi = new RegisterCommonapi(); //스레드를 상속받은 클래스 객체를 생성해 준다.
registerCommonapi->start();//스레드를 시작 시켜준다.
//RegisterCommonapi::getInstance().start();
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject *root = engine.rootObjects()[0];//qrc:/main.qml를 등록한 엔진의 object값을 가져옴
ConnectEvent::getInstance().setWindow(qobject_cast<QQuickWindow *>(root));//qrc:/main.qml를 등록한 엔진의 object값을 window타입으로 변경해준다.
ConnectEvent::getInstance().init();
//event->cppCalledQmlFunction();
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
위의 메인 함수에서 스레드를 실행 시키고 ui도 실행 시켜 줍니다.
그리고 스레드를 상속받은 클래스에서 commonapi 브로드 캐스드 받을수 있는 이벤트를 등록 시켜줍니다.
RegisterCommonapi.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#ifndef REGISTERCOMMONAPI_H
#define REGISTERCOMMONAPI_H
#include <QThread>
#include <thread>
#include <iostream>
#include <CommonAPI/CommonAPI.hpp>
#include <v1/commonapi/HelloWorldProxy.hpp>
#include <unistd.h>
#include <ConnectEvent.h>
class RegisterCommonapi : public QThread
{
private:
std::shared_ptr<v1::commonapi::HelloWorldProxy<>> myProxy;
public:
RegisterCommonapi();
protected:
void run() override;
public:
void init();
};
#endif // REGISTERCOMMONAPI_H
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
RegisterCommonapi.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#include "RegisterCommonapi.h"
using namespace v1::commonapi;
RegisterCommonapi::RegisterCommonapi()
{
}
void RegisterCommonapi::run()
{
std::cout <<"RegisterCommonapi thread start" <<std::endl;
init();
}
void RegisterCommonapi::init()
{
std::cout << __FUNCTION__<<std::endl;
std::shared_ptr < CommonAPI::Runtime > runtime = CommonAPI::Runtime::get();
myProxy = runtime->buildProxy<HelloWorldProxy>("local", "shin");
std::cout << "Checking availability!" << std::endl;
while (!myProxy->isAvailable())
usleep(10);
std::cout << "Available..." << std::endl;
CommonAPI::CallStatus callStatus;
std::string returnMessage;
myProxy->getEnumBroadCastMethodEvent().subscribe([&](HelloWorld::testEnum value){
std::cout << "Client Log!! getEnumBroadCastMethodEvent value: " << value << std::endl;
//QTTESTENUM testEnum = static_cast<QTTESTENUM>(value);
int tmp = static_cast<int>(value);//enum값을 qt에서 사용할 enum값으로 변형하기위해서 int형으로 우선 변환해 줍니다.
ConnectEvent::QTTESTENUM testEnum1 = static_cast<ConnectEvent::QTTESTENUM>(tmp);//변환된 int형 변수를 qt에서 사용할 enum값으로 변환해 줍니다.
ConnectEvent::getInstance().openPopup(testEnum1);//qml의 팝업 출력하는 signal을 호출하는 connectEvent클래스의 함수를 호출해 줍니다.
//이 함수로 발생시킨 signal로 qml의함수가 호출되고 qml의 함수는 팝업이 열려있으면 팝업을 닫아주고 팝업이 닫혀있으면 팝업을 출력해 줍니다.
});
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
이 클래스에서는 앞선 포스팅의 client쪽 소스코드에 있는 브로드 캐스트 함수의 이벤트를 등록시켜 주는 코드를 넣어
줍니다. 그리고 그 이벤트를 받았을때 connectEvent클래스의 openPopup함수가 불려지도록 구현을 하였습니다.
connectEvent클래스의 openPopup함수는 qml에서 팝업이 출력이 되도록하는 시그널이 호출되는 함수 입니다.
그리고 commonapi에서 사용하는 enum값을 qml에서 사용할수 있도록 enum값도 똑같이 만들어 주었습니다.
아래는 enum값을 추가한 내용 입니다.
allEnum.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#ifndef ALLENUM_HPP
#define ALLENUM_HPP
enum WEEK{
SUNDAY = 0,//0은 써도되고 안써도 된다.
MONDAY,//밑에있는 수들은 첫번째 설정한 값에서 1씩 추가되어 설정된다.즉 MONDAY는 1이된다.
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
};
enum QTTESTENUM{ //commonapi 만들어준 enum값과 똑같은 내용을 선언해줍니다.
TEST_FIRST = 0,
TEST_SECOND = 1,
TEST_THIRD = 2
};
#endif // ALLENUM_HPP
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
connectEvent.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#ifndef CONNECTEVENT_H
#define CONNECTEVENT_
#include <QQuickView>
#include <QObject>
#include <iostream>
using namespace std;
class ConnectEvent : public QObject//connection을 사용하기 위해 상속 받아야 하는 클래스
{
public:
Q_OBJECT
Q_ENUMS(WEEK)
Q_ENUMS(QTTESTENUM)
public:// enum 클래스를 사용하기 위해 등록
#include <AllEnum.hpp>//enum값 들을 등록하기 위한 헤더파일
public:
ConnectEvent();
~ConnectEvent();
void init();
static ConnectEvent &getInstance(); //singleton instance를 만들기 위한 함수
void setWindow(QQuickWindow* Window);
QQuickWindow *getWindow();
void openPopup(QTTESTENUM value);
private:
int value = 0;
QQuickWindow* mMainView;
QObject *mRootObject = nullptr;
signals:
void cppSignalCommonapiBroadCastEvent(QVariant);
};
Q_DECLARE_METATYPE(ConnectEvent::WEEK)
Q_DECLARE_METATYPE(ConnectEvent::QTTESTENUM)
#endif // CONNECTEVENT_H
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
cpmmectEvent.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#include "ConnectEvent.h"
ConnectEvent::ConnectEvent()
{
cout << "ConnectEvent" << endl;
qmlRegisterType<ConnectEvent>("ConnectEvent", 1, 0, "ConnectEvent");//class를 qml에서 사용하기 위해서 등록해주는 부분
}
ConnectEvent::~ConnectEvent()
{
}
void ConnectEvent::init()
{
std::cout <<__FUNCTION__<<std::endl;
QObject::connect(this,SIGNAL(cppSignalCommonapiBroadCastEvent(QVariant)),mMainView,SLOT(qmlSlotCommonapiBroadCastEvent(QVariant)));//브로드 캐스트로 받은 이벤트를 qml로 보내기 위해서 시그널 슬롯을 등록해 준다.
}
ConnectEvent &ConnectEvent::getInstance()
{
static ConnectEvent *mpInstance;
if(mpInstance == nullptr){
mpInstance = new ConnectEvent();
}
return *mpInstance;
}
void ConnectEvent::setWindow(QQuickWindow* Window)
{
std::cout << "setWindow called "<<std::endl;
mMainView = Window;//connection을 해주기 위해 윈도우를 등록
}
QQuickWindow *ConnectEvent::getWindow()
{
std::cout << "getWindow called "<<std::endl;
return mMainView;
}
void ConnectEvent::openPopup(ConnectEvent::QTTESTENUM value)
{
std::cout << __FUNCTION__<<std::endl;
emit cppSignalCommonapiBroadCastEvent(value);//브로드 캐스트로 이벤트를 받으면 qml에 signal을 쏴준다.
}
|
그리고 connectEvnet라는 클래스에서 시그널 slot을 등록해 주어서 특정한 시그널이 발생되면 qml의 슬롯함수가 호출이
되도록 등록해줍니다.
그리고 브로드 캐스트를 받으면 qml로 시그널을 쏠수 있도록 함수에 시그널을 발생 시키는 함수를 추가해 줍니다.
그리고 main.qml에서 이 시그널을 받으면 어떻게 동작을 할지 구현을 해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
import QtQuick 2.9
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
import "./"
import "./style" 1.0
Window {
property bool mbImageClicked : true
property int mCount : 0
visible: true
width: 640
height: 480
title: qsTr("Hello World")
function qmlSlotCommonapiBroadCastEvent(value){//서버에서 브로드캐스트 함수가 호출되면 cpp시그널이 발생되면서 이 함수가 호출이 된다.
console.log("value: " + value);
if(customPopup.visible){
customPopup.closeCustomTwoButtonPopup()
}
else
customPopup.openCustomTwoButtonPopup("screen2 popup","go screen2","go previous")
}
CustomTwoButtonPopup
{
id:customPopup
onButton1Onclicked: //custom type에서 설정한 signal이 발생되었을때 동작하는 함수(button1Onclicked)
{
stackView.push(Qt.resolvedUrl("qrc:/screen2.qml"))
}
onButton2Onclicked: //custom type에서 설정한 signal이 발생되었을때 동작하는 함수(button2Onclicked)
{
stackView.pop();
}
}
StackView
{
id:stackView
anchors.fill: parent
initialItem: Item //제일 첫화면을 설정하는 것으로 설정을 안하면 되돌아오기가 안된다.
{
Rectangle//배경 색을 지정하는 부분
{
id:background
anchors.fill: parent
color:"red"
}
Text
{
id:testData
anchors.horizontalCenter: parent.horizontalCenter
text:"main screen testData"
font.pixelSize: 30
font.bold: true
}
Text
{
id:testText
anchors.centerIn: parent
text:"main screen"
font.pixelSize: 50
font.bold: true
}
Button
{
anchors.top:testText.bottom
anchors.horizontalCenter: parent.horizontalCenter
text:"open popup"
onClicked:
{
stackView.push(Qt.resolvedUrl("qrc:/anchors.qml"))
}
}
}
}
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs |
위의 main.qml 소스코드의 qmlSlotCommonapiBroadCastEvent 함수를 통해서 custom팝업을 출력하거나
팝업을 닫도록 구현을 해주었습니다.
이렇게 해서 아래 실행 영상처럼 서버에서 3초에 한번씩 브로드 캐스트 함수를 호출하면 client 쪽 즉 qt ui프로세스에서
팝업이 출력되었다가 닫히도록 구현이 됩니다.
여기까지 commonapi를 사용하는 방법과 자신의 qt프로젝트에 포팅하는 방법까지 모두 다루어 보았는데요
여기까지 잘 따라오셨다면 기본적이 서비스 로직과 ui로직을 따로 두는 설계내용을 구현한 것과 다름이 없습니다.
정말 긴 포스팅이었고 조금 지루하셨을지도 모르겠지만 여기까지 잘 읽어 주셔서 감사합니다.
다음 포스팅에서는 새로운 내용을 준비해서 포스팅 하도록 하겠습니다.
감사합니다.!!