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

Qt TCP 통신 연결 끊김 상태 확인: QTcpSocket 소켓과 랜선 뽑힘

by vicddory 2017. 4. 3.

Unplugging ethernet (이더넷 연결 끊김)

Qt에서 tcp ip 소켓을 사용하다보면 끊어진 경우를 확인해야할 상황이 발생합니다. 굳이 Qt가 아니더라도 언제 어디서나 서비스 제공자는 무조건 통신 상태를 확인해야 합니다만, Qt 프로그래밍에선, 플러그가 뽑힌 경우를 체크하기 까다롭습니다.


왜냐면 QAbstractSocket 클래스가 제공하는 ConnectedState는 tcp ip 연결된 이후에 물리적인 플러그 뽑힘 현상을 알려주지 않습니다. 슬프게도, Qt가 제공하는 그 어떤 소켓 클래스들도 물리적인 플러그 Unconnected를 확인하질 못합니다. 그래서 돌아가는 방법을 찾아야 합니다.

Simple Main Code (이더넷 연결 끊김 기본 소스)

우선, 간단한 tcp ip Socket Program을 만들어 봅니다.


main.h


#include <QProcess>
#include <QTcpSocket>
QProcess ping_process_;
QStringList ping_params_;
private slots:
void HandleConnected();
void HandleDisconnected();
void HandleReadyRead();
void HandleStateChange(QAbstractSocket::SocketState state)


main.cpp


cp_socket_ = new QTcpSocket(this);
//tcp_socket_->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
connect(tcp_socket_, SIGNAL(connected()), this, SLOT(HandleConnected()));
connect(tcp_socket_, SIGNAL(readyRead()), this, SLOT(HandleReadyRead()));
connect(tcp_socket_, SIGNAL(disconnected()), this, SLOT(HandleDisconnected()));
connect(tcp_socket_, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this,
SLOT(HandleStateChange(QAbstractSocket::SocketState)));
ping_params_ << "-c" << "1" << server_ip_;\


간단한 소켓 프로그램으로 4개의 slot은 연결, 끊김, 읽기, 상태 변화를 의미합니다.


메인 코드의 마지막엔 연결이 잘 이루어졌는지 ping을 날려보는 간단한 통신 코드도 들어있습니다.

Ping and QProcess

Ping 테스트를 할 땐, http://를 넣으면 오류가 발생하고, Window와 Linux에선 서로 다른 인자를 사용하니 그것도 확인해 줘야 합니다. 이를 확인하지 않으면 아래와 유사한 에러 메세지가 뜰겁니다.


error, message

Try removing "http://" in line number

Ping expects an IP or an hostname, "http://" isn't part of the hostname.

cout<<"\n\nexitCode,exitStatus=="<<exitCode<<","<<exitStatus


ping 코드를 작성할 아래처럼 http://를 제거하고 난 뒤에 시도하셔야 합니다. (더 아래에선 이더넷 연결 끊김을 Ping으로 확인합니다)


아래와 같은 tcp ip 코드를 작성합니다.


#if defined(WIN32)
QString parameter = "-n 1";
#else
QString parameter = "-c 1";
#endif
int exitCode = QProcess::execute("ping", QStringList() << parameter << "1.1.1.11");
if (exitCode==0)
{
// it's alive
} else
{
// it's dead
}
.....
params << "-c" << "1" << "www.google.com";


ConnectToServer function


void TcpClient::ConnectToServer()
{
if (tcp_socket_->state() != QAbstractSocket::ConnectedState &&
tcp_socket_->state() != QAbstractSocket::ConnectingState) {
tcp_socket_->connectToHost(server_ip_, server_port_);
}
}


위에서 선언만 했던 ConnecToServer 소켓 함수를 정의합니다.


socket 객체가 연결되지 않은 상태라면 통신 연결을 시도합니다. 이것도 아주 간단하죠?

HandleConnected function

void TcpClient::HandleConnected()
{
qWarning() << "connect Complete";
bla~ bla~ bla~
emit ConnectionComplete();
}


이어서 HandleConnected 함수도 정의합니다. 이름 그대로 통신 연결되면 구동되는 함수입니다. 내부 코드는 입맛에 맞춰 구성하면 됩니다.


StateChange property

tcp ip StateChange 속성은 아래와 같습니다. (아쉽지만, StateChange에는 이더넷 연결 끊김, 플러그 뽑힘 상태가 없습니다)


0. QAbstractSocket::UnconnectedState : 0

The socket is not connected.


1. QAbstractSocket::HostLookupState : 1

The socket is performing a host name lookup.


2. QAbstractSocket::ConnectingState : 2

The socket has started establishing a connection.


3. QAbstractSocket::ConnectedState : 3

A connection is established.


4. QAbstractSocket::BoundState : 4

The socket is bound to an address and port (for servers).


6. QAbstractSocket::ClosingState : 6

The socket is about to close (data may still be waiting to be written).


5. QAbstractSocket::ListeningState : 5

For internal use only.


일반적으로 사용하는 Constant는 굵게 표시된 위의 두 개입니다. (1, 2번)


저 표를 통해서 유추할 수 있는 사실은,


// Normal
QAbstractSocket::ConnectedState
// Abnormal
QAbstractSocket::UnconnectedState


이겁니다.


그래서 아래와 같은 소켓 코드로 분기할 수 있습니다.


tcp_socket_->state() == QAbstractSocket::UnconnectedState


아래엔 실제 구현할 수 있는 tcp ip 소켓 프로그램의 전체 코드입니다. 이어지는 내용


void TcpClient::HandleStateChange(QAbstractSocket::SocketState state)
{
if (tcp_socket_->state() == QAbstractSocket::UnconnectedState)
HandleDisconnected();
qWarning() << "State Changed : " << state;
}

void TcpClient::HandleDisconnected()
{
if (timer_connecte_->isActive() == false) {
tcp_socket_->close();
}
emit ConnectionError();
qWarning() << "HandleDisconnected";
}

void TcpClient::HandleReadyRead()
{
recv_data_ = tcp_socket_->readAll();
CheckPacketID();
}


자, 이제 몇 가지 케이스를 들어 Qt 소켓이 여러 끊김 현상을 감지할 수 있는지 확인합니다. 물론 위에 나온 코드를 이용해 테스트를 진행할 겁니다. (위의 코드만 복사하면 프로그램 하나가 만들어지니깐요)

Case1 : Unplugged ethernet

QProcess에서 과연 ethernet의 끊김을 확인할 수 있을까? output 메시지와 tcp ip error 메시지를 확인하면 알 수 있습니다.


output message

정상적인 상황 : 아래 메시지


"PING 192.168.1.17 (192.168.1.17) 56(84) bytes of data. 64 bytes from 192.168.1.17: icmp seq=1 ttl=128 time=0.830ms

--- 192.168.1.17 ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 0.830/0.830/0.830/0.000 ms"


비정상적인 상황 : 없음


error message

정상적인 상황 : 없음


비정상적인 상황 : 아래 메시지

connect: Network is unreachable


분명히 소켓 error 메시지를 통해서 이더넷 통신 연결 끊김은 확인 할 수 있습니다.


Case2 : Power off the target (thing..)

어떤게 연결된진 모르겠지만, 하여간에 통신 연결된 장치쪽의 전원이 꺼진 상태입니다. PLC가 될 수도 있고, 작은 임베디드 보드일 수도 있고, 데스크탑일 수도 있고, 경우는 많습니다.


output message

정상적인 상황 : 아래 메시지

"PING 192.168.1.17 (192.168.1.17) 56(84) bytes of data.

64 bytes from 192.168.1.17: icmp seq=1 ttl=128 time=0.830ms


--- 192.168.1.17 ping statistics ---


1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.830/0.830/0.830/0.000 ms"


비정상적인 상황 : 없음


error message

정상적인 상황 : 아래 메시지

QProcess: Destroyed while process ("ping") is still running.


비정상적인 상황 : 없음


ping을 통해서 tcp ip 이더넷 연결 끊김이 확인됩니다.


Case1에선 ethernet이 끊어짐을 확인했으나 이번엔 그러질 못했습니다. 오직 ping 메시지를 통해서만 알 수 있습니다.

Solution

StateChange 속성만 가지고선 (안타깝게도) 언플러그를 확인할 수 없습니다. 그래서 Case2와 같이 ping을 전달하는 방법으로 언플러그를 확인합니다. Ping 소스는 간단합니다(정말 간단해서 계속 간단하다고...).


QProcess p;
p.start( /* whatever your command is, see the doc for param types */ );
p.waitForFinished(-1);
QString p_stdout = p.readAllStandardOutput();
QString p_stderr = p.readAllStandardError();


원형은 위와 같은데 실제 응용하기 위해선 변화를 줘야 합니다.


저는 아래처럼 소켓 소스를 구성해 메인 클래스에 추가했습니다.


/** ping 테스트 */
ping_process_.start("ping", ping_params_, QIODevice::ReadOnly);
ping_process_.waitForFinished(kInterval1500);
QString out = ping_process_.readAllStandardOutput();
QString err = ping_process_.readAllStandardError();
if (out.compare("") == 0 && err.compare("") == 0)
HandleDisconnected();


넣고 싶은 곳에 적당히 넣어주면 되는 tcp ip 관련 소스입니다. 그리고 아래와 같은 소스도 구동해 볼 수 있습니다.



출처 : 간단한 ping 명령어 사용하기(QProcess::execute) [클릭]


그렇지만 이 통신 코드는 프로세스 자체가 wait 상태로 진입하는 문제가 있습니다.


#include <qapplication>
#include <qprocess>
#include <qtgui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMessageBox *box = new QMessageBox;
int res = -1;
QStringList list;
list << "66.249.89.99";
res = QProcess::execute("ping", list);
if(res == 0)
{
box->setText("ping success");
}
else
{
box->setText("ping failure");
}
box->show();
return app.exec();
}
</qtgui></qprocess></qapplication>


위에서 살펴봤지만 플러그 뽑힘 증상을 QTcpSocket으로는 확인하질 못합니다.


부득이하게 ping을 이용해 확인할 순 있는데 이것도 임시 방편에 지나지 않기에, Qt에선 이 부분을 반영하는 형태로 Disconnected 속성을 변경해주는게 좋다고 봅니다.



댓글