본문 바로가기
C++ 200제/코딩 IT 정보

[Facade 패턴] Qt 프로그래밍, TCP Client 소스 예제

by vicddory 2018. 1. 11.

[Facade 패턴] Qt 프로그래밍, TCP Client 소스 예제


디자인 패턴 Facade 예제TcpClient.tar.gz [링크]


TCP 소스에서 1:1 기반과 1:N 기반의 환경이라면 구성이 달라질 수밖에 없습니다. 어떻게 해야 소스 코드 변경을 최소로 줄이며 많은 클라이언트(또는 서버)와 통신이 가능한가, 라는 생각에서 출발한 디자인 패턴(Facade 패턴) 예제입니다. 1:1 환경보다는 N:N 환경의 어떤 부분에서도 가능토록 꾸며봤습니다. tcp client 소스 소개합니다.


디자인패턴 Qt 프로그래밍[Facade 패턴] Qt 프로그래밍, TCP Client 소스 예제


서버 - 예제 - N개의 클라이언트


1. tcpdemo (main)


1
2
3
4
5
worker_ = new Worker();
 
QTcpSocket *g_se = new QTcpSocket();
worker_->AddSocket(g_se, kGSE, "192.168.1.17"3999);
socket_.append(g_se);
cs

2. Worker class

Worker 클래스에는 단순히 소켓 통신에 필요한 기본적인 것들(Conn, Read, Write, Disconn)만 구현되어 있습니다. g_se 객체는 N개의 클라이언트 중 하나로 다수의 클라이언트와 통신할 때 하나씩 늘어날 객체입니다. (디자인 패턴 중 Facade 패턴이라 헤더 파일은 회사 숫자만큼 늘려야함) 여기선 tcp client 하나만 선언했으니 컨테이너를 별도로 구성하진 않았습니다.


kGSE는 g_se와 매칭되는 상수로 컨테이너 구현시 필요한 인덱스를 가리키게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
signals:
    void ReadyMake();
 
public:
    void AddSocket(QTcpSocket *socket, 
                   const int type,
                   const QString ip, 
                   const int port);
 
private:
    QTcpSocket *socket_;
    Company company_;
cs


위의 헤더에서 선언된 company_ 객체는 필요한 동작이 구현될 때 가리킬 객체를 의미합니다.


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
#include "worker.h" // facade 패턴
 
Worker::Worker() { }
 
Worker::~Worker(void)
{
    HandleDisconnected();
}
 
void Worker::AddSocket(QTcpSocket *socket, const int type, const QString ip, const int port)
{
    company_.SetType(type);
    socket_ = socket;
 
    connect(socket_, SIGNAL(connected()), this, SLOT(HandleConnected()));
    connect(socket_, SIGNAL(readyRead()), this, SLOT(HandleReadyRead()));
    connect(socket_, SIGNAL(disconnected()), this, SLOT(HandleDisconnected()));
    connect(socket_, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(HandleError()));
 
    connect(this, SIGNAL(ReadyMake()), &company_, SLOT(AlreadyMake()));
    connect(&company_, SIGNAL(MakeComplete()), this, SLOT(SendPacket()));
 
    socket_->connectToHost(ip, port);
    socket_->write("H000020150506120518 ");
}
 
void Worker::HandleConnected()
{
    qWarning() << "connect Complete";
}
 
void Worker::HandleReadyRead()
{
    QByteArray tmp = socket_->readAll();
    company_.Job(tmp);
    qWarning() << "Read : " << tmp;
 
    emit ReadyMake();
}
 
void Worker::HandleDisconnected()
{
    socket_->close();
}
 
void Worker::HandleError()
{
    qWarning() << socket_->errorString();
}
 
void Worker::SendPacket()
{
    socket_->write(company_.PacketValue().toUtf8().constData());
    qWarning() << "Write : " << company_.PacketValue().toUtf8().constData();
}
cs


ARM 기반의 UTF-8 ..... 뻔한 환경에서 구동되는 코드지만, 이기종의 OS나 언어간의 호환성을 위해서라도 굳이 Utf8로 변환하여 사용할 필요가 있습니다. 실제로 Intel 기반의 Ubuntu와 통신할 때 종종 패킷이 깨지는 경우가 있었습니다. (디자인 패턴 facade 패턴과는 상관 없음)


OS나 언어별로 데이터 타입을 일일이 대응해 맞춰주는건 원칙적으로 너무 힘들고 번거로운데다가 실제로 구현한다 해도 소스만 어지럽고 난리가 날게 뻔하니 받는 쪽에서 변환하는 게 속 편합니다. (물론, 100% 장담은 못함. 보내는 쪽도 신경쓰는게 당연하거늘... tcp client)

3. Company class


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
#ifndef COMPANY1_H
#define COMPANY1_H
 
#include <QThread>
#include <QObject>
#include <QStringList>
#include <QByteArray>
 
#include "company_type.h"
 
#include "packet/itype1.h"
 
class Company : public QObject
{
    Q_OBJECT
 
public:
    Company();
    ~Company();
 
private slots:
    void AlreadyMake();
 
signals:
    void MakeComplete();
 
private:
    int type_;
    QStringList make_;
    
public:
    void SetType(const int company_type);
    void Job(const QByteArray &rcv);
 
    QString PacketValue();
    CompanyType company_type_;
};
 
#endif
cs


Qt를 다룰 때 마다 항상 아쉬운건 Q_OBJECT의 존재입니다.


slots과 signals 등 이벤트를 위해선 꼭 상속을 받아야 하는데, 굳이 상속을 받아서 사용해야할 이유가 있을진 의문이네요. (디자인 패턴과는 상관 없습니다. tcp client Qt 프로그래밍 이슈임)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "company.h"
 
Company::Company()
  : type_(0)
{
    make_.clear();
}
 
Company::~Company(void)
{
    make_.clear();
}
 
void Company::SetType(const int company_type)
{
    type_ = company_type;
}
 
void Company::Job(const QByteArray &rcv)
{
    company_type_.Job(type_, rcv);
}
 
QString Company::PacketValue()
{
    return company_type_.Packet();
}
 
void Company::AlreadyMake()
{
    emit MakeComplete();
}
cs


Company 클래스는 빈 회사입니다.


클래스 이름은 Company지만 선언만 된 상태에선 어떤 회사인지를 모릅니다. 그래서 미리 Type을 정해주고 Job을 내려줍니다. 그래야 이 회사는 일을 합니다.

4. CompanyType class


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
#ifndef COMPANYTYPE_H
#define COMPANYTYPE_H
 
#include <QByteArray>
 
#include "packet/gse_type.h"
/** add here other type */
 
class CompanyType // 디자인 패턴 (facade 패턴)
{
public:
    CompanyType();
 
public:
    void Job(const int type, const QByteArray &rcv);
    QString Packet();
 
private:
    int type_;
    QByteArray rcv_;
    GSEType gse_type_;
 
    void TypeGSE();
};
 
#endif
cs


회사 정보를 담아두는 곳입니다.


이 클래스가 없다면 통신 상대가 늘어날 수록 Compnay 클래스의 수도 그에 비례해 늘어날 겁니다. 그렇지만 CompanyType 클래스를 최대한 유연하게 구현하면 packet 폴더 하단에 헤더 파일만 추가해도 됩니다. 프로세스 로직과 이 소스는 연관이 없습니다. 순전히 tcp client 정해진 포맷에 맞춰 보내고 받기만 해줘도 충분합니다.


비지니스 로직은 비니지스 로직을 담당하는 클래스에 위임해도 됩니다.


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
#include "company_type.h"
 
CompanyType::CompanyType(void)
  : type_(0)
{
    rcv_.clear();
}
 
void CompanyType::Job(const int type, const QByteArray &rcv)
{
    rcv_.clear();
    rcv_ = rcv;
    type_ = type;
 
    if (type == 0)
        TypeGSE();
}
 
void CompanyType::TypeGSE()
{
    gse_type_.MakePacket(rcv_);
}
 
QString CompanyType::Packet()
{
    if (type_ == 0)
        return gse_type_.Packet();
}
cs


5. 실행


Qt Facade 패턴 - TcpDemo[디자인 패턴] Facade 기반 Qt 프로그래밍 예제 결과


저는 위와 같이 테스트를 끝냈습니다. 잘안되거나 추가 문의는 댓글창을 이용해 주세요.


[Facade 패턴] Qt 프로그래밍, TCP Client 소스 예제

댓글