3. 멀티쓰레드 프로그래밍
이제
드디어
실제
에코서버에대해
설명해보기로
한다. 이
초간단
서버프로그램은
단순히
입력을
받은
문자열
그대로를
출력해
주는
것으로서
가장
기초적인
서버
프로그램이라고
할
수있다. 이
프로그램을
분석하기
위해
먼저
소스를
참조하기
바란다.
소스를
들여다
보면
이전의
서비스
프로그램
예제에 3개의
클래스가
추가된
것을
알
수
있을
것이다. 각
클래스는
다음과
같다.
LXSCK – 윈속
통신을
위한
모듈. 서버/클라이언트에서
사용가능한
공통모듈로서
컨넥션
기능은
없고
통신
기능만
있다. 통신을
위해 2개의
thread(read/write)를
생성하여
사용한다.
LXSERVER –
서버용으로
윈속을
초기화하고
클라이언트로부터
접속을
기다린다. 접속이
있을
경우
그
접속을
받아들인
후
소켓
번호를
리턴한다.
ECHO_SERVER –
실제적인
에코서버
클래스이다. LXSCK 클래스를
받아들여서
이를
사용하여
클라이언트와
통신한다. 멀티
쓰레드
서버
통신을
위해
클래스가
생성되면 1개의 thread를
생성한다.
LXSCK와 LSXSERVER
클래스는
통신을
위한
기본
클래스이므로
일단
관심있는
사람만
소스를
들여다
보고
설명은
다음에
하기로한다. 오늘은
어떻게 ECHO_SERVER
클래스가
동작하는
지에
대해서만
살펴본다.
먼저 ServiceMain()
함수를
보면 while 루프가
다소
달라졌다는
것을
알
수
있다.
LXSERVER *lxsrv = new LXSERVER; //서버클래스를
생상한다.
lxsrv->StartServer(LISTEN_PORT); //서버용으로
윈속을
초기화한다.
LISTEN_PORT는
777로
지정되었다.
ECHO_SERVER *lxaServer[MAX_CLIENT]; //멀티쓰레드를
위한 ECHO_SERVER
클래스
어레이를
지정한다.
memset(lxaServer, 0,
MAX_CLIENT*sizeof(ECHO_SERVER *));//어레이를
NULL로
초기화
while
(g_isRunning)
{
DbgOut("EchoServer : Service running...
Let's wait for a next connection.\n");\
// Waits a
connection
SOCKET
idSocketAccept;
if (
lxsrv->AcceptConnection(&idSocketAccept) != SOCKET_ERROR
)
{ //클라이언트로부터의
접속을
기다리고
접속이
있을
경우
소켓을
돌려준다.
// There is a connection. Create an
independant server handler.
// create socket communication
class
LXSCK *lxsck = new LXSCK(idSocketAccept);
//접속이
이루어진
소켓으로
통신을
위한
클래스를
초기화한다.
ECHO_SERVER *echo = new
ECHO_SERVER(lxsck); //통신클래스가
준비됬으면
이제
에코서버를
준비한다.
//에코서버
자체가
쓰레드를
생성해서
이미
실행중이다.
이에
에코서버
클래스를
배열의
빈
슬롯에
넣는다.
for(int i = 0; i < MAX_CLIENT; i++)
{
if ( lxaServer[i] == NULL )
{
DbgOut("New server thread is added on
slot [%d].\n", i);
lxaServer[i] =
echo;
i =
MAX_CLIENT;
}
}
} else {
DbgOut("Fatal
error\n");
}
// check each server is
still on. if not, let's remove it.
//
실행이
종료된
에코서버가
있다면
클래스를
제거한다.
for(int i = 0; i < MAX_CLIENT; i++)
{
if ( lxaServer[i] &&
!lxaServer[i]->IsThreadOn() ) {
DbgOut("The Server in slot [%d] is not
active, let's close it\n", i);
delete
lxaServer[i];
lxaServer[i] =
NULL;
}
}
Sleep(1000);
}
자세한
설명은
위의
소스에
붉은
글씨로
남겼으니
참조하기
바란다.
에코
서버는
클래스가
생성될
때
아래와
같이
쓰레드를
생성하여
그
쓰레드
안에서
서버의
역활을
담당한다. 이렇게
쓰레드를
생성함으로서
위의
메인 while루프는
다시
다음
접속을
기다릴
수
있는
것이다.
void ECHO_SERVER::Start(
void )
{
DWORD
dwThreadID;
m_hThread = CreateThread( NULL, 0, (unsigned long (__stdcall
*)(void *))ServerProc, (LPVOID)this,
(DWORD)0, &dwThreadID );
}
쓰레드가
생성되면
파라메터로서 1개의 LPARAM 변수를
쓰레드에
전달
할
수
있다. 무엇을
전달하는
가는
전적으로
사용자에게
달려있지만
클래스의
주소를
전달한다면
쓰레드
안에서
클래스의
퍼블릭
멤버함수들을
호출
할
수도
있으므로
클래스의
주소를
전달하는
것이
좋다.
쓰레드를
생성할
때
클래스의
주소(this)를
쓰레드로
넘겨주어
쓰레드가
소속된
멤버함수를
아래와
같이
호출한다.
DWORD ECHO_SERVER::ServerProc( LPVOID lpParam )
{
ECHO_SERVER *lxs
= (ECHO_SERVER *)lpParam;
DbgOut( "ServerProc Thread is ready...\n"
);
while( lxs->ServerHandler() )
{
Sleep(10);
}
DbgOut( "ServerProc Thread is ended...\n"
);
ExitThread( 0 );
return (DWORD)0;
}
그럼
이제
실제
서버
동작을
하는 ServerHandler()
멤버
함수를
보자.
bool ECHO_SERVER::ServerHandler( void
)
{
bool bRet =
true;
TCHAR
tszBuffer[MAX_PATH];
int nRead =
m_lxSck->ReadLine(tszBuffer,
MAX_PATH);
if ( nRead > 0 )
{
TCHAR
tszTemp[MAX_PATH];
wsprintf(tszTemp, "ECHO [%s]\r\n",
tszBuffer);
m_lxSck->SendLine(tszTemp);
if ( !stricmp(tszBuffer, "quit") )
{
Sleep(1000);
m_lxSck->Stop();
Sleep(1000);
delete
m_lxSck;
bRet =
false;
}
}
return bRet;
}
이
함수에선
소켓
통신
클래스인 LXSCK의
멤버함수중
하나인 ReadLine() 함수를
사용하여
만약
클라이언트로
부터
입력이
있다면 1 라인의
문자열을
읽어들인다. ReadLine()
함수는
입력
버퍼에
문자열이
있을
경우
그
문자열과
함께
문자열의
길이를
리턴한다.
만약
입력된
문자열이
있을
경우 SendLine() 함수를
통해
그
문자열을
포함하는 ECHO 문자열을
클라이언트에
전송한다.
만약
입력된
문자열이 “quit”이면
소켓
통신을
종료하고 LXSCK 클래스를
제거한다. ECHO_SERVER
클래스는
이
경우 false 값을
쓰레드에
리턴하고
쓰레드의 while()
루프는 false가
되어
쓰레드는
종료하게
된다. 쓰레드가
종료될
경우
쓰레드의
종료코드를 ExitThread()
함수를
써서
지정해준다.
멀티쓰레드는
클래스
자체내에서
각자의
쓰레드를
생성하여
사용하므로
해결이
가능하다. 하지만
독립적으로
동작하는
클래스들을
생성, 제거하기
위해서는
생성된
클래스
주소를
어딘가에
저장해
놓아야
한다. 이를
위해
링크드
리스트를
사용할
것을
권장하지만
이
강좌에선
이해를
돕기위해
ECHO_SERVER클래스
주소의
배열을
사용하였다. 배열은
최대 50개까지이며
이는
클라이언트를
동시에 50개까지
받을
수
있다는
뜻이다. 실제로 50개
넘는
경우의
에러체크는
예제에서
보이지
않았다. 각자
해보기
바란다.
클라이언트가
접속될
때마다
생성된 ECHO_SERVER
클래스의
주소를
저장해둘
배열.
ECHO_SERVER
*lxaServer[MAX_CLIENT];
배열중
빈
슬롯을
찾아
그곳에
새로
생성된
ECHO_SERVER클래스의
주소를
저장한다.
for(int i = 0;
i < MAX_CLIENT; i++) {
if ( lxaServer[i] == NULL )
{
DbgOut("New server thread is added on slot
[%d].\n", i);
lxaServer[i] =
echo;
i =
MAX_CLIENT;
}
}
실행이
종료(쓰레드가
종료)된 ECHO_SERVER
클래스가
존재한다면
클래스를
제거하고
슬롯에 NULL 값을
넣어
다음
쓰레드가
사용할
수있게
한다.
for(int i = 0;
i < MAX_CLIENT; i++) {
if ( lxaServer[i] &&
!lxaServer[i]->IsThreadOn() ) {
DbgOut("The Server in slot [%d] is not
active, let's close it\n", i);
delete
lxaServer[i];
lxaServer[i] =
NULL;
}
}
이로서
멀티쓰레드를
구현한
서버프로그래밍에
대해
살펴보았다.
LXSCK과
LXSERVER의
내용은
네버엔딩님의
윈속강좌를
충실히
보았다면
알
수
있는
내용들
이기
때문에
굳이
설명을
해야할
필요는
못느끼나. 몇가지
팁이
있기에
다음편에
보충설명을
하고자
한다.
'[ Windows Program ] > Windows API' 카테고리의 다른 글
[WinAPI] The Message Loop (1) | 2011.08.25 |
---|---|
멀티쓰레드 윈도우즈 소켓 프로그래밍 #5– 멀티쓰레드 프로그래밍 2 (0) | 2011.03.08 |
멀티쓰레드 윈도우즈 소켓 프로그래밍 #3 – 윈도우즈 서비스 프로그래밍 (0) | 2011.03.08 |
멀티쓰레드 윈도우즈 소켓 프로그래밍 #2 – 윈도우즈 서비스 프로그래밍 (2) | 2011.03.08 |
멀티쓰레드 윈도우즈 소켓 프로그래밍 #1 (0) | 2011.03.08 |