클라이언트 프로그램을 알아 보았으니까 이번에는 서버 쪽도 알아 봐야 겠지요. 서버 쪽도 QT가 준비를 잘해 놓아서 큰 문제가 없네요.

  1. 일단 클라이언트의 접속을 알아야 하니까 서버 소켓을 하나 만들어 놓고 기답니다.
  2. 클라이언트가 접속을 요청해 오면,
  3. 클라이언트와 통신을 하기 위한 소켓을 만들어 주면 끝!!

간단하죠. 그리고 이후에 클라이언트와 통신하는 방법은 이전 클라이언트 통신 방법과 동일합니다. 저는 이렇게 사용하는 방법이 동일한 라이브러리를 좋아합니다. 다 그렇지 않느냐구요? 강력하지만 아닌 것도 있습니다요. ^^;

자, 역시 디자인해 가면서 프로그램을 작성해 보겠습니다. 이번 서버는 에코서버로 클라이언트에서 문장을 전송하면 문자 길이를 앞에 붙여서 에코하는 매우 간단한 서버를 만들어 보겠습니다. 상대 클라이언트는 이전 강좌 시간에 올린 "QT - TCP/IP 소켓 프로그래밍(클라이언트)" 글에 올려진 클라이언트를 이요하겠습니다.

클라이언트도 이미 준비됬고, 빨리 서버를 만들어야 겠네요.

프로그램 작성

  1. 프로제트 파일 생성, "C++ Project"를 선택합니다.


  2. 적당한 프로젝트 이름을 입력합니다.


  3. 프로젝트 파일을 저장합니다.


  4. 폼 이름을 지정합니다. 이 이름이 클래스 이름이 되므로 적당한 이름을 지정해 줍니다. 여기서는 frmMain으로 입력하겠습니다.


  5. 필요한 위젯을 배치합니다. 달랑 하나입니다. 이 버튼은 그저 프로그램을 종료할 뿐 다른 기능은 없습니다.


서버 소켓

자 문제는 서버 소켓을 준비해야 하는데 어떻게 준비하느냐 하는 것이죠. 버튼처럼 폼 위에 올려 놓을 수 있다면 수월하겠습니다만, 그런것도 없고. 역시 QT의 샘플을 먼저 보았습니다. 역시 서버를 위한 QServerSocket이 있고, QServerSocket을 상속 받아 내가 만들고 싶은 서버 소켓을 만들어 사용합니다.

이 소스는 echo_server.h 로 저장할 것입니다. 그러므로 File >> New 메뉴를 클릭하시고,

생성된 헤더파일을 echo_server.h로 저장하고 아래의 코드를 작성합니다.

// 저는 클라이언트와의 통신을 처리하기 위한 소켓을 QSocket으로부터 상속을 받아
// ClientSocket 객체를 생성했습니다.
// 이 객체 안에서 클라이언트와 통신하면서 자료를 가공할 것입니다.

class ClientSocket : public QSocket
{
         ..... 생략 ....
};

class EchoServer : public QServerSocket
{
   Q_OBJECT
public:
   EchoServer( QObject* parent=0 ) : QServerSocket( 9527, 1, parent )
   {
      if ( !ok() )
      {
         qWarning("Failed to bind to port 9527");
         exit(1);
      }
   }
   void newConnection( int socket )
   {
      new ClientSocket( socket, this );    <--- A:위의 ClientSocket을 생성
   }
};

리슨 포트는 보시는 바와 같이 9527입니다. QServerSocket에서 클라이언트로부터 접속 요청이 들어 오면 newConnect( int) 함수가 호출되는데, 이 함수를 상속 받아서 그 함수에 (A 부분) 원하는 내용을 넣으면 되겠습니다. 서버 소켓이야 할일이 뭐겠습니까? 클라이언트의 접속 요청을 받으면 클라이언트와 통신하기 위한 소켓을 만들 것입니다.

예제에서는 간단하게 new ClientSocket()을 호출했습니다.

new ClientSocket( socket, this );

변수가 앞에 없어서 조금 이상하게 보이죠.

ClientSocket socket= new ClientSocket( socket, this );

이렇게 변수를 받을 것을, 변수가 필요 없어서 new 이하만 적은 것입니다.

정리해서 말씀드리면 클라이언트가 통신하기를 원했고, 그래서 접속이 허락되면 void newConnection( int socket ); 함수가 호출되는데, 이때 내가 준비한 클라이언트와 통신하는 소켓 객체를 생성하면 작어 끝이다라는 말씀이 되겠습니다.

이제 내가 만들 클라이언트 소스입니다.

class ClientSocket : public QSocket
{
   Q_OBJECT
public:
   ClientSocket( int sock, QObject *parent=0, const char *name=0 ) :
   QSocket( parent, name )
   {
      connect( this, SIGNAL(readyRead()),        SLOT( onReceive()) );        <-- A
      connect( this, SIGNAL(connectionClosed()), SLOT( deleteLater()) );      <-- B
      setSocket( sock );
   }

private slots:
   void onReceive()
   {
      QTextStream ts( this );
      while ( canReadLine() )
      {
         QString str = ts.readLine();
         ts << str.length() << ": " << str << endl;
      }
   }
};

클라이언트 소켓은 상대방과 실제 통신하는 부분인 만큼 자료를 송수신하는 것이 제일 중요하겠습니다. 그래서 (A)부분에 자료가 수신되면 처리할 슬롯을 지정해 주었습니다.

만일 상대와 연결이 끊기면 더 이상 소켓은 필요가 없겠지요. 그래서 나중에 클라이언트가 소멸이 되도록 스케쥴러에 등록되도록 (B)부분에 deleteLater() 슬롯을 준비했습니다. deleteLater()함수까지 준비해 주고....QT 개발자들이 예뻐 보이는 군요. 뭐, 더 학습하면 마음이 변할지 모르겠습니다만....ㅋㅋ

자, (A)부분에서 자료가 수신되면 void onReceive()가 실행되도록 했습니다. void onReceive() 내에서는 수신한 문자열의 길이를 구하고 길이와 함께 문장을 클라이언트로 전송이 되도록 코드가 되었습니다.

폼에서 서버 소켓 사용하는 방법

서버 소켓과 클라이언트와의 통신을 위한 클라이언트 소켓까지 준비했으므로 써 먹어야 겠습니다. 폼을 선택하고 Object Explorer에서 Functions에 init()를 추가합니다.

init() 함수 내용에 서버 소켓만 생성합니다. 클라이언트 소켓이야 서버 소켓에서 알아서 생성하기 때문에 폼에서는 클라이언트 소켓에 대해 알 필요가 없습니다.

그리고 버튼을 클릭하면 프로그램이 종료되도록 btnClose의 clicked() 시그널과 QApplicatoin의 exit() 슬롯을 연결합니다.

#include <qapplication.h>
#include "echo_server.h"

void frmMain::init()
{
   connect( btnClose, SIGNAL( clicked() ), qApp, SLOT( quit() ) );

   new EchoServer;
}

main.cpp 생성

프로젝트를 빌드하기 위해서 main.cpp를 생성합니다. File >> New메뉴를 선택합니다. main.cpp를 선택합니다.

frmMain을 선택하고 프로젝트를 빌드합니다.

]# qmake
]# make
]# server   -> 프로젝트 파일 이름

실행하면 서버가 출력됩니다.

이제 이전 강좌에 올렸던 클라이언트 프로그램을 실행합니다.

IP를 입력하고 connect로 연결합니다. 캡션에 Connected라고 문장이 바뀝니다. send 버튼을 클릭하면 문장이 전송되고, 주신 자료에 문자열 길이와 함께 전송한 문자열이 출력됩니다.

태그: *QT *소켓프로그래밍 *tcp/ip