티스토리 뷰

Application/Delphi

[Delphi] 초간단 채팅서버 작성

알 수 없는 사용자 2009. 3. 30. 11:23

Source : http://cafe.naver.com/codeway

Chapter

1

초간단 채팅서버 작성

본 강좌는 “델파이로 만드는 네트워킹 게임”을 목적으로 작성하던 것을 다시 수정 보안하여 만들어 가고 있는 것입니다.

네트워킹의 기본적인 기술 및 지식은 설명하지 않을 생각입니다. 이러한 기본 정보가 필요하신 분들은 네트워킹에 관한 인터넷 자료나 서적을 미리 한 번 읽어보시길 권장합니다.

우선은 채팅 프로그램을 작성하면서 델파이로 작성되는 네트워킹 프로그램의 기본적인 개념을 단계별로 설명하고, 추후 게임까지 연장해 보도록 하겠습니다.

http://cafe.naver.com/codeway 로 가시면 부족하지만 그 동안의 작업들을 보실 수 있습니다.

2004년 2월 23일, 류종택 드림.

 

본 강좌에서 사용되는 콤포넌트는 Indy 콤포넌트 입니다. http://www.nevrona.com/indy 로 가시면 해당 콤포넌트에 대한 상세한 정보를 얻으실 수 있습니다. 이 강좌에서는 인디 콤포넌트 중에서도 TCP/IP 통신용 콤포넌트를 중심으로 설명하게 됩니다.

l 강좌 대상 : 델파이로 네트워킹(소켓) 프로그래밍을 처음 해보시는 분

l 강좌 목적

- C/S (클라이언트/서버) 방식의 1:n 네트워킹 프로그램의 개념적 이해

- Indy 콤포넌트의 TCP/IP 콤포넌트의 기본적인 사용 방법 연습

초간단 채팅 프로그램의 동작 설명

Array

[그림 1.1] 초간단 채팅 프로그램의 동작 설명

우리가 만들어야 할 초간단 채팅 프로그램은 [그림 1.1]에서 처럼 사용자 A가 서버에게 대화 메시지를 전송하면, 그 메시지를 접속된 모든 사용자에게 그대로 전달하는 것입니다. 어떤 한 사용자의 대화내용이 동시에 다른 접속자들에게 항상 전달되어 채팅방의 가장 기본적인 기능을 구현해 보는 것입니다.

동영상 강좌 및 소스

http://cafe.naver.com/codeway에 예제 프로그램의 작성과정을 동영상으로 제작하여 올리도록 하겠습니다. 우선 동영상으로 전반적인 흐름과 프로퍼티 설정 등에 관한 수순을 익혀두시고, 본 강좌를 따라서 직접 프로그램을 작성하시기를 권장합니다.

예제에 필요한 소스도 필요하신 분들은 카페에 방문하여 다운 받아 가시길 바랍니다.

프로퍼티 설정

Array

[그림 1.2] 메인폼(TForm1)

1: object Form1: TForm1

2: Width = 270

3: Height = 160

4:

5: object IdThreadMgrDefault1: TIdThreadMgrDefault

6: end

7:

8: object IdTCPServer1: TIdTCPServer

9: DefaultPort = 1234

10: Active = True

11: ThreadMgr = IdThreadMgrDefault1

12: OnExecute = IdTCPServer1Execute

13: end

14: end

동영상 강좌에서 작업한 속성 변경내용을 순서대로 정리해둔 것들입니다.

2-3: 라인은 메인폼의 크기를 작게 조정한 것 입니다.

9-11: 라인은 인디의 TCP 서버 소켓 콤포넌트인 IdTCPServer1의 속성을 설정한 것입니다. 포트 번호는 1234번으로 설정하였습니다.

11: 라인에서는 서버에서 생성되는 쓰레드를 관리할 메니저 콤포넌트를 지정하였습니다. 인디 콤포넌트의 쓰레드 메니저는 두 가지가 있으며, 클라이언트 접속에 따라 쓰레드가 너무 많이 증가되기를 원하지 않을 때에는 TIdThreadMgrDefault 대신 TIdThreadMgrPool 콤포넌트를 사용하시면 됩니다. 잘 모르시는 분들은 우선 예제처럼 TIdThreadMgrDefault를 사용하셔도 무난합니다.

12: 라인에서는 클라이언트에서 메시지가 도착하면 이어서 서버에서 해야 할 동작을 구현할 함수를 OnExecute 이벤트에 지정합니다. 해당 함수는 이벤트 핸들러라고 불리며, 이벤트가 발생하면 실행될 함수라고 생각하시면 됩니다.

소스 분석

1: unit uServer;

2:

3: interface

4:

5: uses

6: Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

7: Dialogs, IdThreadMgr, IdThreadMgrDefault, IdBaseComponent, IdComponent,

8: IdTCPServer;

9:

10: type

11: TForm1 = class(TForm)

12: IdTCPServer1: TIdTCPServer;

13: IdThreadMgrDefault1: TIdThreadMgrDefault;

14: procedure IdTCPServer1Execute(AThread: TIdPeerThread);

15: private

16: { Private declarations }

17: public

18: { Public declarations }

19: end;

20:

21: var

22: Form1: TForm1;

23:

24: implementation

25:

26: {$R *.dfm}

27:

28: procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);

29: Var

30: List : TList;

31: Loop : Integer;

32: stReceivedText : String;

33: IdPeerThread : TIdPeerThread;

34: begin

35: stReceivedText:= AThread.Connection.ReadLn(', 5);

36: If stReceivedText = ' then Exit;

37:

38: List:= IdTCPServer1.Threads.LockList;

39: Try

40: For Loop:= 0 to List.Count-1 do Begin

41: IdPeerThread:= TIdPeerThread(List.Items[Loop]);

42: Try

43: IdPeerThread.Connection.WriteLn(stReceivedText);

44: Except

45: IdPeerThread.Stop;

46: End;

47: End;

48: Finally

49: IdTCPServer1.Threads.UnlockList;

50: End;

51: end;

52:

53: end.

 

35: 라인에서는 클라이언트 에서 어떤 메시지가 도착했다는 이벤트가 발생하고, 이어 그 메시지를 읽어낸 후 문자열 변수 stReceivedText에 메시지를 저장합니다. 여기서 AThread는 각 클라이언트에서 날아드는 메시지를 처리할 쓰레드를 지칭합니다. ReadLn(‘’, 5)에서 5의 의미는 5ms(5/1000 초) 동안만 메시지를 기다린다는 뜻입니다.

36: 라인에서는 클라이언트에서 전송된 메시지가 없을 때는 현재의 함수를 빠져나가서 아무것도 처리하지 않겠다는 뜻입니다.

여기서 의문점이 생길 것입니다. OnExecute 이벤트가 클라이언트에서 메시지가 전달되어 올 때 발생한다고 하였는데도, 클라이언트에서 날아온 메시지가 있는 지 없는 지를 판단해야 한다는 점입니다.

38: 라인에서는 현재 접속된 클라이언트 컨넥션의 목록을 받아오면서 해당 목록을 잠가 둡니다. LockList는 목록을 잠그면서 목록을 TList 타입으로 반환하여 줍니다. 따라서 어떤 클라이언트들 연결되어 있는 지의 정보가 TList에 저장됩니다.

목록을 잠그는 이유는 만약 10 개의 접속이 있었고 10 번째 접속된 클라이언트에게 메시지를 보내려고 하는데, 클라이언트가 접속을 종료하여 연결 상태의 변동이 생기면 존재하지 않는 접속에 코드가 진행되면서 에러가 발생하기 때문입니다. 마치 식당에서 아직 다 먹지도 않았는데 접시를 치워서 식탁에 포크를 처박아 버리는 것과 같은 일이 발생합니다.

실제 상황에서 웃고 말일이지만, 네트워크 프로그래밍에서는 서버가 죽어버리는 현상이 발생합니다. 특히 1:n 접속이 이루어 지는 서버에서는 치명적인 에러의 대부분은 이와 같은 교통정리의 문제가 많습니다.

LockList로 잠긴 상태에서는 클라이언트가 접속을 끊어도 서버는 해당 연결을 삭제하는 것을 유보하게 됩니다. 물론 연결이 이미 끊어진 클라이언트로 메시지는 갈 수 없다는 상황은 변하지 않습니다.

40-47: 라인에서는 클라이언트 연결 수만큼 반복하면서 모든 연결된 클라이언트에 메시지를 전송하게 됩니다.

41: 라인에서는 지정된 쓰레드를 다룰 수 있는 클래스 TIdPeerThread를 목록에서 받아옵니다.

43: 라인에서는 TIdPeerThread.Connection의 메소드 WriteLn을 이용해서 실제 메시지를 전송하는 구현이 되어 있습니다.

45: 라인은 이 과정에서 에러가 발생하면 해당 연결을 종료하고자 쓰레드를 종료하도록 설정합니다. 이때 쓰레드는 당장 사라지지는 않습니다. 아까 우리가 잠가 놨으니까요.

49: 라인에서는 잠가 뒀던 목록을 풀어줍니다. “Try Finally End”를 사용한 이유는 목록이 잠긴 채 에러가 발생하여 영영 목록이 잠가 지는 것을 방지하기 위함입니다.

끝으로

간단하게 1:n 네트워크 서버 프로그래밍의 기본적인 설명을 마치도록 하겠습니다. 이어서 다음 강좌에서는 클라이언트를 작성하고 테스트하는 과정을 설명하도록 하겠습니다.