2009. 9. 4. 15:08ㆍ언어/C++ Builder
볼랜드 포럼에 있는 내용을 퍼온 것 입니다.
시삽 : 원래 이학균님께서 Q/A에 올리신 글입니다만... Q/A에 두셔서 다른 질문/답변에 파묻히기에는
아깝고, 또 성격상 강좌에 가까워서 Tip 게시판으로 옮깁니다.
------------------------------------------------------------------------
제 목: C++ 빌더로 채팅서버를 만들어보자.
작성자: 이학균
U R L : http://explore.kwangwoon.ac.kr/~k98el560 (야후에서 '학균'검색)
작성일: 2001. 07. 19일
연락처: (HP)016-411-8187 (버디버디)lobin2 (e-mail)lobin2@hanmir.com
------------------------------------------------------------------------
앞에서 클라이언트 프로그램에 대해 알아봤습니다. 이젠 각 클라이언트별로
쓰레드를 생성하는 채팅 서버 프로그램에 대해 알아보겠습니다.
기본 루틴은, 접속이 될 때 마다 해당 Client Socket의 Handle을 저장하고,
쓰레드 별로 작업을 하되, 쓰레드에서 입력이 오면은 BroadCastOut이라는 함수를
호출하여 현재 가지고 있는 모든 Handle을 이용해 TCustomWinSock을 생성하여
이를 이용하여 전송하는 방법입니다.
여기서 눈여겨 봐야될 게 있는데, 먼저 이 방법을 하기 위해서 TServerSocket을
놓으시고 Server Type를 stThreadBlocking로 놓습니다. 소켓별로 Handle을 저장
해야 하는데, 이 정보를 배열에 저장을 하면 예를 들어 1,2,3,4 라는 값이 저장되어
있는데, 3이 나가면은 2와 4를 연결 시키기 애매해 집니다. 그래서 흔히들 Linked List를
써야 되는데, 프로그램이 복잡해 지는 관계로 간단히 TComboBox를 사용했습니다.
TServerSocket의 OnGetSocket 이벤트에서 접속된 소켓의 핸들값이 주어지므로
컴보박스에 저장하고, OnGetThread에서 TServerClientThread를 상속받은 SocketThread를
생성 시킵니다.
TServerSocket의 Server Type에 stNonBlocking를 선택하면 클라이언트가 정보를 보내
오면 OnClientRead 이벤트를 통해 정보를 얻지만, 쓰레드별로 돌리기 위해서는
쓰레드를 생성해서 항상 메시지 확인을 합니다. 그러니 OnGetThread에서 TServerClientThread
형의 쓰레드를 생성해 놓고 ClientExecute()에서 감지하는 루틴을 추가하면 됩니다.
그러면.. 우선 헤더 파일을 보겠습니다.
//--------------------------------------------------------------------------
#include <ExtCtrls.hpp>
#include <ScktComp.hpp>
//---------------------------------------------------------------------------
class TfmServer : public TForm
{
__published: // IDE-managed Components
TPanel *Panel1;
TLabel *Label1;
TLabel *Label2;
TEdit *edtTotalNum;
TPanel *Panel2;
TPanel *Panel3;
TPanel *Panel4;
TPanel *Panel5;
TMemo *mbChat;
TMemo *mbStat;
TServerSocket *ServerSocket1;
TEdit *edtActiveThread;
TComboBox *cbSocketHandle;
TLabel *Label4;
void __fastcall FormCreate(TObject *Sender);
void __fastcall ServerSocket1ClientDisconnect(TObject *Sender,
TCustomWinSocket *Socket);
void __fastcall ServerSocket1ClientError(TObject *Sender,
TCustomWinSocket *Socket, TErrorEvent ErrorEvent,
int &ErrorCode);
void __fastcall ServerSocket1Accept(TObject *Sender,
TCustomWinSocket *Socket);
void __fastcall ServerSocket1GetThread(TObject *Sender,
TServerClientWinSocket *ClientSocket,
TServerClientThread *&SocketThread);
void __fastcall ServerSocket1GetSocket(TObject *Sender, int Socket,
TServerClientWinSocket *&ClientSocket);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall ServerSocket1ThreadEnd(TObject *Sender,
TServerClientThread *Thread);
private: // User declarations
int cntNum;
public: // User declarations
__fastcall TfmServer(TComponent* Owner);
void __fastcall BroadCastOut(String SendString);
};
class TServerSocketThread : public TServerClientThread
{
private:
public:
__fastcall TServerSocketThread(bool CreateSuspended, TServerClientWinSocket * ASocket);
void __fastcall ClientExecute(void);
};
//---------------------------------------------------------------------------
extern PACKAGE TfmServer *fmServer;
//---------------------------------------------------------------------------
#endif
헤더 파일에서 눈여겨 봐야 될 것은, 폼 클래스에 있는 BroadCastOut이라는 모두에게 메시지를
전달하는 함수와 TServerClientThread 클래스의 선언문입니다. TServerSocketThread의
생성자는 위의 구문을 따르며 TServerClientWinSocket을 제공하므로 이 소켓으로 제어를 합니다.
또한 일반 Thread가 Execute()라는 함수로 실행되는데 반해 위 쓰레드는 ClientExecute라는
함수를 수행합니다.
그러면. cpp 파일을 보겠습니다.
//----------------------------------------------------------------------------
//Project Name: 쓰레드를 이용한 채팅 서버
//Project Period: 01.07.10 ~ 01.07.11
//Writer: 이학균
//Compliment: 각 클라이언트 접속마다 쓰레드를 생성하여
// 쓰레드 내에 각 ClientSocket을 가져서
// 소켓 통신을 한다.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "ThreadServer.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfmServer *fmServer;
//---------------------------------------------------------------------------
// 쓰레드 외부에서 ClientSocketHandle값으로 CustomWinSocket을 생성하여 접속한 각 클라이언트로
// 데이타를 날려준다. - BroadCast
void __fastcall TfmServer::BroadCastOut(String SendString)
{
for(int i=0; i<cbSocketHandle->Items->Count;i++) {
TCustomWinSocket *CustomWinSocket = new TCustomWinSocket(StrToInt(cbSocketHandle->Items->Strings[i]));
CustomWinSocket->SendText(SendString);
// delete CustomWinSocket;
}
}
__fastcall TServerSocketThread::TServerSocketThread(bool CreateSuspended, TServerClientWinSocket * ASocket)
: TServerClientThread(CreateSuspended, ASocket)
{
FreeOnTerminate=true;
}
void __fastcall TServerSocketThread::ClientExecute(void)
{
int HIndex;
TWinSocketStream *pStream; // 소켓을 스트림으로 받음
char Buffer[100];
while (!Terminated && ClientSocket->Connected)
{
pStream = new TWinSocketStream(ClientSocket, 60000);
try {
memset(Buffer, 0, 100);
if(pStream->WaitForData(300000)) { // 5분간 입력이 없으면 종료
if(pStream->Read(Buffer, 100) == 0) { // 입력된 값이 없으면 종료
HIndex=fmServer->cbSocketHandle->Items->IndexOf(IntToStr(ClientSocket->SocketHandle));
fmServer->cbSocketHandle->Items->Delete(HIndex);
if(fmServer->cbSocketHandle->Items->Count==0) fmServer->cbSocketHandle->Text="연결된 소켓 없음";
ClientSocket->Close();
}
fmServer->mbChat->Lines->Add(String(Buffer));
fmServer->BroadCastOut(String(Buffer));
fmServer->edtActiveThread->Text=fmServer->ServerSocket1->Socket->ActiveThreads;
}
else {
HIndex=fmServer->cbSocketHandle->Items->IndexOf(IntToStr(ClientSocket->SocketHandle));
fmServer->cbSocketHandle->Items->Delete(HIndex);
if(fmServer->cbSocketHandle->Items->Count==0) fmServer->cbSocketHandle->Text="연결된 소켓 없음";
ClientSocket->Close();
}
}
__finally {
delete pStream;
}
}
// 종료시 cbSocketHandle에서 ClientSocketHandle 삭제
HIndex=fmServer->cbSocketHandle->Items->IndexOf(IntToStr(ClientSocket->SocketHandle));
fmServer->cbSocketHandle->Items->Delete(HIndex);
if(fmServer->cbSocketHandle->Items->Count==0) fmServer->cbSocketHandle->Text="연결된 소켓 없음";
}
//---------------------------------------------------------------------------
__fastcall TfmServer::TfmServer(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::FormCreate(TObject *Sender)
{
ServerSocket1->Port=2000;
ServerSocket1->Open();
Sleep(100);
Application->ProcessMessages();
cntNum=0;
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::ServerSocket1ClientDisconnect(TObject *Sender,
TCustomWinSocket *Socket)
{
mbStat->Lines->Add("사용자가 접속을 종료했습니다.");
cntNum--;
edtTotalNum->Text=cntNum;
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::ServerSocket1ClientError(TObject *Sender,
TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode)
{
mbStat->Lines->Add("사용자의 서버에 문제가 있습니다.");
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::ServerSocket1Accept(TObject *Sender,
TCustomWinSocket *Socket)
{
mbStat->Lines->Add("사용자가 접속했습니다.");
cntNum++;
edtTotalNum->Text=cntNum;
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::ServerSocket1GetThread(TObject *Sender,
TServerClientWinSocket *ClientSocket,
TServerClientThread *&SocketThread)
{
SocketThread = new TServerSocketThread(false, ClientSocket); // TServerClientThread 를 생성
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::ServerSocket1GetSocket(TObject *Sender,
int Socket, TServerClientWinSocket *&ClientSocket)
{
cbSocketHandle->Items->Add(IntToStr(Socket)); // SocketHandle을 저장
if(cbSocketHandle->Items->Count==1) cbSocketHandle->Text=IntToStr(Socket);
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::FormClose(TObject *Sender, TCloseAction &Action)
{
ServerSocket1->Close();
}
//---------------------------------------------------------------------------
void __fastcall TfmServer::ServerSocket1ThreadEnd(TObject *Sender,
TServerClientThread *Thread)
{
edtActiveThread->Text=fmServer->ServerSocket1->Socket->ActiveThreads;
}
//---------------------------------------------------------------------------
여기서 눈여겨 봐야 될 것은, 입출력은 pStream으로 선언된 TWinSocketStream형으로 하고
종료시 TComboBox내에 현재 소켓의 핸들을 찾아 삭제를 합니다. 자신이 내용을 입력하면
fmServer->mbChat->Lines->Add(String(Buffer));
fmServer->BroadCastOut(String(Buffer));
fmServer->edtActiveThread->Text=fmServer->ServerSocket1->Socket->ActiveThreads;
여기에서 보았듯이 지신의 메모 박스에 내용을 뿌려주고 BroadCastOut이라는 함수를 호출해
자신이 입력한 값을 인자로 보냅니다. 그리고 현재 활성중인 Thread를 표현 하는 것으로
종료를 합니다.
마지막으로 눈여겨 봐야 될 것이 BroadCastOut이라는 함수인데, 이 함수는 앞서 설명 했듯이
TComboBox에 저장된 SocketHandle값을 가지고 TCustomWinSocket을 생성하여 그 소켓을 이용하여
전송을 하는 역할을 합니다.
void __fastcall TfmServer::BroadCastOut(String SendString)
{
for(int i=0; i<cbSocketHandle->Items->Count;i++) {
TCustomWinSocket *CustomWinSocket = new TCustomWinSocket(StrToInt(cbSocketHandle->Items->Strings[i]));
CustomWinSocket->SendText(SendString);
// delete CustomWinSocket;
}
}
나머지는 화면에 표현해 주는 것과 정리하는 루틴이므로 쉽게 이해 하실 수 있을 것입니다.
기본 루틴만 설명 드렸으므로 많은 걸 바라신 분들껜 죄송하구요, 첨 보시는 분들에게는
이해 하기 어려운 점도 있을 겁니다. 제 E-Mail은 항상 열려 있으니 E-Mail로 보내주시던가
Q/A란에 적어놓으시면 최대한 답변해 드리겠습니다. 위 프로그램은 테스트를 마쳤고,
C++ Builder 4.0으로 컴파일 했습니다.
그럼 이상으로 채팅 서버 강좌를 마치고, 어려울거 같던 채팅 서버가 이렇게 몇 줄 안되는
코딩만으로 구현 가능한 빌더의 능력에 놀라울 따름입니다.
다음으로 현재 회사에서 진행한 프로젝트를 하며 만든 RS-232C 시리얼 통신과 TCP통신을 하는
컴포넌트를 만들었는데, 시리얼 통신 부분과 컴포넌트 제작 부분 이렇게 두 가지로 나누어
강좌를 올릴 생각입니다.
앞으로 개발되는 프로그램들의 기본 루틴은 이 게시판을 통해 공개 될 것이며, 이 게시판은
여러분에게 공개하는 목적 외에 제가 구현한 프로그램들의 간략한 루틴들의 메모 장소로
의미를 가질 것입니다.
'언어 > C++ Builder' 카테고리의 다른 글
Enum, Set (0) | 2009.09.09 |
---|---|
쓰레드 기초. (0) | 2009.09.07 |
C++ Builder Component 설치 하기. (0) | 2009.09.01 |
Tcp/Ip Socket Programming (0) | 2009.08.31 |
컴포넌트 만들기..ㅣ 속성, 메소드, 이벤트 (0) | 2009.08.19 |