Digital Angel Master

Spread the wing for new starting.

Archive for March, 2009

[Delphi] 초간단 채팅 클라이언트 작성

Posted by Digital Angel Master on March 30, 2009

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

Chapter 2

초간단 채팅 클라이언트 작성

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

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

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

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

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

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

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

l 강좌 목적

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

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

프로퍼티 설정

clip_image002

[그림 2.1] 메인폼(TForm1)

1: object Form1: TForm1

2: Width = 580

3: Height = 340

4:

5: object Panel1: TPanel

6: Align = alBottom

7: BevelOuter = bvNone

8:

9: object edUserName: TEdit

10: Left = 8

11: Top = 8

12: Width = 121

13: Height = 21

14: Text = ”

15: end

16: object edMsg: TEdit

17: Left = 136

18: Top = 8

19: Width = 345

20: Height = 21

21: Text = ”

22: OnKeyPress = edMsgKeyPress

23: end

24: object Button1: TButton

25: Left = 494

26: Top = 8

27: Width = 75

28: Height = 25

29: Anchors = [akTop, akRight]

30: Caption = ‘Á¢¼Ó’

31: OnClick = Button1Click

32: end

33: end

34:

35: object moMsg: TMemo

36: Align = alClient

37: ReadOnly = True

38: end

39:

40: object IdTCPClient1: TIdTCPClient

41: Host = ‘127.0.0.1′

42: Port = 1234

43: end

44:

45: object IdAntiFreeze1: TIdAntiFreeze

46: end

47:

48: object Timer1: TTimer

49: Interval = 300

50: OnTimer = Timer1Timer

51: end

52:

53: end

전반적인 프로퍼티 설정 방법 및 순서는 동영상 자료를 참고하시기 바랍니다. (http://cafe.naver.com/codeway)

22: 라인에서 사용자가 대화 내용을 입력하고 엔터키를 치면 대화내용을 서버로 전송하기 위해 키가 눌려질 때 발생하는 이벤트를 사용하였습니다. 이벤트 핸들러에서는 눌러진 키가 엔터키일 경우에는 서버로 메시지를 전달하고 현재 대화내용은 다음 대화내용을 작성하기 위해 공백으로 바꿔버리도록 코딩하기로 하겠습니다.

31: 라인에서는 채팅을 하기 위해 서버에 접속하기 위한 버턴이 클릭되었을 때 실행될 이벤트를 사용하였습니다. 이후 버턴이 눌러지면 서버로 접속하도록 코딩하겠습니다.

37: 라인은 대화내용을 순서대로 화면에 표시할 TMemo 콤포넌트를 읽기전용으로 변경하는 것입니다.

40: 라인에서 내려놓은 TidTCPClient 콤포넌트는 TCP/IP 클라이언트용 컴포넌트 입니다. 서버로 접속하여 메시지를 전달하거나 수신할 수 있는 기능을 갖추고 있습니다.

41-42: 라인에서는 서버로 접속하기 위하여 서버의 주소와 포트 번호를 지정하였습니다. ‘127.0.0.1’은 자신의 컴퓨터로 접속할 때 사용하는 IP 주소입니다.

45: 라인에서 TidAntiFreeze는 인디 콤포넌트가 송수신을 할 때 사용자 인터페이스를 멈추게(얼도록)할 수가 있습니다. 즉, 채팅 클라이언트 프로그램이 잠시 “응답없음” 상태와 같이 정지된 상태가 되는 것을 말합니다. 해당 콤포넌트는 이러한 현상을 방지하여 줍니다. 사용법은 내려놓기만 하면 스스로 작동합니다.

48: 라인에서 서버로부터 메시지가 전달 되었는지 수시로 점검하기 위해서 TTimer 콤포넌트를 사용하였습니다.

49: 라인에서는 서버로부터 메시지가 전달 되었는지 점검하는 시간을 300ms (300/1000초) 간격으로 지정하였습니다.

50: 라인에서는 타이머가 지정된 시간마다 발생하는 OnTimer 이벤트를 이용하여 서버로부터 메시지가 전달되었는 지 확인하게 됩니다.

소스 분석

1: unit uClient;

2:

3: interface

4:

5: uses

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

7: Dialogs, StdCtrls, ExtCtrls, IdAntiFreezeBase, IdAntiFreeze,

8: IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient;

9:

10: type

11: TForm1 = class(TForm)

12: moMsg: TMemo;

13: Panel1: TPanel;

14: edUserName: TEdit;

15: edMsg: TEdit;

16: IdTCPClient1: TIdTCPClient;

17: IdAntiFreeze1: TIdAntiFreeze;

18: Button1: TButton;

19: Timer1: TTimer;

20: procedure Button1Click(Sender: TObject);

21: procedure Timer1Timer(Sender: TObject);

22: procedure edMsgKeyPress(Sender: TObject; var Key: Char);

23: private

24: { Private declarations }

25: public

26: { Public declarations }

27: end;

28:

29: var

30: Form1: TForm1;

31:

32: implementation

33:

34: {$R *.dfm}

35:

36: procedure TForm1.Button1Click(Sender: TObject);

37: begin

38: IdTCPClient1.Connect;

39: end;

40:

41: Function ReceiveText(IdTCPClient:TIdTCPClient):String;

42: Var

43: Data : Pointer;

44: DataSize : Integer;

45: ssData : TStringStream;

46: Begin

47: DataSize:= IdTCPClient.ReadFromStack(True, 5, False);

48: If DataSize = 0 then Begin

49: Result:= ”;

50: Exit;

51: End;

52:

53: GetMem(Data, DataSize);

54: ssData:= TStringStream.Create(”);

55: Try

56: IdTCPClient.ReadBuffer(Data^, DataSize);

57: ssData.Write(Data^, DataSize);

58: ssData.Position:= 0;

59: Result:= ssData.DataString;

60: Finally

61: FreeMem(Data);

62: ssData.Free;

63: End;

64: End;

65:

66: procedure TForm1.Timer1Timer(Sender: TObject);

67: Var

68: stText : String;

69: begin

70: If IdTCPClient1.Connected = False then Exit;

71:

72: stText:= ReceiveText(IdTCPClient1);

73: If stText <> ” then moMsg.Lines.Add(stText);

74: end;

75:

76: procedure TForm1.edMsgKeyPress(Sender: TObject; var Key: Char);

77: begin

78: If Key = #13 then Begin

79: Key:= #0;

80: IdTCPClient1.WriteLn(Format(‘%s> %s’, [edUserName.Text, edMsg.Text]));

81: edMsg.Text:= ”;

82: End;

83: end;

84:

85: end.

38: 라인은 서버로 접속하는 부분입니다. 이미 주소와 포트를 프로퍼티 설정 부분에서 지정해 주었기 때문에 우리는 단순히 Connect 메소드만 실행하는 것만으로도 충분합니다.

41-64: 라인은 TidTCPClient 콤포넌트의 ReadLn 메소드의 버그를 피하기 위해서 필자가 작성한 함수 입니다. 지금은 버그가 패치된 버전이 있으니 http://www.nevrona.com/indy에서 새로운 버전을 다운 받아 설치하시면 이 함수는 굳이 작성하지 않으셔도 됩니다. 이것저것 귀찮거나 헛갈리시는 분들은 우선 ReceiveText 함수를 사용하시기 바랍니다.

66-74: 라인은 주기적으로 서버로부터 메시지가 전송 되었는지 확인하고, 메시지가 수신되었으면 화면(TMemo)에 표시하는 부분입니다.

72: 라인은 버그가 패치된 인디를 설치하였을 경우에는 stText:= IdTCPClient1.ReadLn(”, 5); 로 변경하셔도 됩니다.

76-83: 라인은 사용자가 대화내용을 입력하고 엔터키를 입력할 때 서버로 메시지를 전송하는 부분입니다.

78: 라인에서는 눌러진 키값이 엔터키와 같은 지 비교합니다. 엔터키가 눌러지면 Key 변수는 #13 즉, 13번 아스키 문자가 입력됩니다.

79: 라인에서는 엔터키 일 때에는 키값을 아무것도 안눌러졌음으로 변경했습니다. 이는 TEdit 콤포넌트에서 엔터키를 치면 “팅”하는 소리가 나는 것을 방지하기 위해서 입니다. 대화내용을 전송할 때 마다 “팅”, “팅”하는 소리가 난다면 그다지 대화하고픈 분위기가 되지는 않을 테니까요 ^^

80: 라인에서는 입력된 메시지와 입력된 사용자 이름을 “이름> 대화내용”과 같은 방식으로 서버에 전송합니다. 이후 모든 클라이언트에 해당 형식으로 전송되어 화면에 나타나게 됩니다.

81: 라인에서는 입력창을 비워서 다음 대화내용을 입력 받을 수 있도록 준비합니다.

실행 및 테스트

clip_image004

[그림 2.2] 채팅 프로그램을 실행한 화면

서버를 실행한 후 클라이언트 프로그램 두 개를 띄우고 각각 접속 버턴을 누르시기 바랍니다. 이후 각각의 클라이언트의 왼쪽에 있는 입력창에 자신의 대화명을 각각 입력하고 오른쪽 옆에 있는 메시지 입력창에 대화내용을 입력하고 엔터키를 치면 위의 화면처럼 서로 대화를 나눌 수 있게 됩니다.

원거리에 있는 사용자들 끼리 사용하고자 할 때에는 서버의 IP 주소를 다시 입력하시고 컴파일 한 후에 사용하시기 바랍니다.

끝으로

다음에는 1:1 메시지 전달, 귓속말 그리고 로그인 처리 등과 같은 기능을 하나씩 추가해 나가며 강좌를 진행해 나가도록 하겠습니다.

Posted in Network | Leave a Comment »

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

Posted by Digital Angel Master on March 30, 2009

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 콤포넌트의 기본적인 사용 방법 연습

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

clip_image002

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

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

동영상 강좌 및 소스

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

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

프로퍼티 설정

clip_image004

[그림 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 네트워크 서버 프로그래밍의 기본적인 설명을 마치도록 하겠습니다. 이어서 다음 강좌에서는 클라이언트를 작성하고 테스트하는 과정을 설명하도록 하겠습니다.

Posted in Network | Leave a Comment »