이미 서버 얘기를 했는데 또 꺼내는 이유는, 이전 강좌 내용에다가 살을 조금 붙이려는 것입니다. 에코 서버가 클라이언트의 접속 요청에 따라 클라이언트 소켓을 만들고, 그 소켓으로 통신을 했습니다만, 만일 송수신되는 내용이나 가공된 자료를 frmMain 폼에 출력해야 된다면 어떻게 처리해야 될까요?

물론 frmMain 객체를 생성한 변수를 전역으로 선언해서 클라이언트 소켓에서 직접 사용해도 되겠습니다. 물론 틀린 방법은 아니지만 클라이언트에서 frmMain 쪽으로 알려주고, frmMain이 자기가 원하는 대로 처리하게하는 것이 더 좋은 방법이라고 생각합니다.

오늘은 시그널 발생

알려 주는 방법이야 시그널이겠지요. 그래서 오늘은 시그널을 직접 발생시켜 보겠습니다. 물론 frmMain은 시그널에 대한 슬롯을 준비해야 하는데, 뭐 이것은 자주 해본 것이라 어렵지 않습니다.

새로 만들려는 시그널의 이름을 void onClientData( const QString& ) 라고 하겠습니다. 누구든 이 시그널을 이용하면 클라이언트가 수신되었을 때 마다 인수로 얻어지는 QString로 알 수 있게 됩니다.

시그널 이름까지 정했으므로 클라이언트 소켓에서 자료를 수신할 때 시그널을 발생 시키겠습니다.

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()) );
      connect( this, SIGNAL(connectionClosed()), SLOT(deleteLater()) );
      setSocket( sock );
   }

signals:
   void onClientData( const QString& );

private slots:
   void onReceive()
   {
      QTextStream ts( this );
      while ( canReadLine() )
      {
         QString str = ts.readLine();
         emit onClientData( tr("Read: '%1'n").arg(str) );

         ts << str.length() << ": " << str << endl;
      }
   }
};

이전 소스에서 새로 추가된 부분만 색을 바꾸었습니다. 보시면 emit가 시그널을 발생한다는 것을 알 수 있습니다.

이제 이 시그널이 frmMain에서 처리할 수 있도록 해야 하는데, 처리 대상이 클라이언트 소켓입니다. 클라이언트 소켓은 클라이언트와 접속이 이루어졌을 때, 생성되므로, frmMain은 클라이언트와의 접속부터 알아야 합니다. 그러므로 EchoServer는 접속을 알려주는 시그널을 발생 시킵니다.

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 )
   {
      ClientSocket *s = new ClientSocket( socket, this );
      emit onNewConnect( s );
   }

signals:
   void onNewConnect( ClientSocket* );
};

접속이 이루어지면 onNewConnect() 시그널이 발생할 것입니다. 이제 frmMain의 init()함수에서 클라이언트와의 접속 때 처리할 슬롯을 연결합니다.

void frmMain::onConnected( ClientSocket *sock )
{
   connect( sock, SIGNAL( onClientData( const QString &)), this, SLOT( onClientData( const QString &)) );
}

void frmMain::init()
{
   server = new EchoServer;

   connect( server, SIGNAL( onNewConnect(ClientSocket*)), this,
      SLOT( onConnected(ClientSocket*)) );
   connect( btnClose, SIGNAL( clicked() ), qApp, SLOT( quit() ) );
}

void frmMain::onConnected( ClientSocket *sock ) 함수 내용을 보시면 생성된 클라이언트 소켓 객체를 알 수 있으므로, 그 안에서 클라이언트 소켓이 자료를 수시할 때 발생하는 onClientData() 시그널과 fmrMain의 슬롯, onClientData()를 연결합니다. 이름이 시그널과 슬롯의 이름이 같더라도 양해를 바랍니다. ^^

void frmMain::onClientData( const QString &str )
{
   labReceive->setText( str);
}

frmMain에서의 onClientData() 슬롯은 인수로 받은 문자열을 labReceive에 출력합니다. 이로써 클라이언트 소켓은 frmMain을 알지 못해도, 또한 frmMain이 아닌 다른 곳이라도 정보를 전송할 수 있습니다.

프로그램 실행

프로그램을 실행하고 클라이언트 프로그램을 실행하십시오. 클라이언트에서 접속을 하고 문자열을 전송합니다.

그러면 서버 프로그램에서는 아래와 같이 클라이언트 소켓에서 수신한 문자열이 라벨에 출력됩니다.

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