안녕하세요, 공백기간이 길었습니다.

지난번에 작성한 C#을 이용하여 간단한 1:1 비동기 채팅 프로그램을 만들어보자 글에 어떤 분께서 1:N 채팅 예제도 올려달라고 부탁하셔서 이제서야... 작성을 하게 됬습니다.


이번 프로그램의 실행 환경은 CLI(Command-Line Interface)가 아닌 GUI 환경입니다!

이는 프로그램 실행이나 사용성 면에서 이점을 챙기기 위함도 있지만 더 나아가서는 예제로만 사용되는 것이 아니라 기능을 보완하고 추가하여 실 운용이 가능한 프로그램으로 사용하기 위함도 있습니다. 그래서 소스 코드가 길어지고 어려운 문법이 나올 수도 있습니다. 하지만, 이러한 부분들은 제가 하나씩 설명하면서 넘어가도록 할것이니 겁먹지 마시고 천천히 따라와주시기를 바라겠습니다.


이번 프로그램에서 사용하는 모델은 서버-클라이언트 모델입니다!

서버-클라이언트 모델이란?

네트워크를 통해 하나의 서버와 여러 대의 클라이언트가 통신하는 구조를 이루는 모델입니다. 이 내용을 실생활에 비유하자면 선생님과 학생으로 비유할 수가 있습니다. 공기(네트워크)를 통해 학생(클라이언트)들과 선생님(서버)이 서로 의사소통(통신)을 하는 것으로 말이죠.


왜 서버-클라이언트 모델을 사용하는지?

1(서버):N(클라이언트) 통신에선 서버가 클라이언트의 연결이나 수신한 데이터에 대한 관리 및 처리를 담당해야 하기 때문입니다.


클라이언트끼리 연결하게 만들면 되는데 왜 굳이 서버와 클라이언트가 연결하도록 만드나요?

연결이 복잡하지 않고 단순화됩니다. 클라이언트는 서버로의 연결만 유지하면 되고, 서버는 클라이언트들의 연결을 유지하게 됩니다.

이는 서버쪽에선 다소 복잡하게 작성되겠지만 클라이언트는 단순하게 작성되기 때문입니다. 즉, 연결이 단순하다는 것은 그만큼 소스 코드를 작성하는 것도 쉬워진다는 의미로 이해하시면 됩니다. (서버-클라이언트 연결에 대한 내용은 그림 1 참고)


하지만, 클라이언트끼리 연결하게 만들면 어떨까요?

N개의 클라이언트가 통신을 하는 환경이라고 하면 각 클라이언트는 자신을 제외한 N-1개의 연결을 유지해야 합니다. 즉, 각 클라이언트가 바로 위에서 설명드렸던 서버역할을 하게 되는거죠. 그리고 당연하게도 클라이언트는 매우 복잡하게 작성되게 됩니다. 또한, 서버-클라이언트 모델의 경우 서버의 성능만 적당하다면 클라이언트에겐 문제가 되지 않지만, 클라이언트끼리 연결하게 된다면 각 클라이언트의 성능이 통신에 영향을 미치게 되기 때문에 클라이언트 간 연결이 아닌 서버와 클라이언트를 연결하도록 만들려고 합니다.


(그림 1. 서버-클라이언트 모델, 출처, 원작자: David Vignoni, LGPL)


이번 프로그램은 총 세 개의 프로젝트로 구성되어 있습니다!

원래는 클라이언트 프로젝트, 서버 프로젝트 총 두 개로 구성하려 했습니다만 공통 라이브러리를 하나 놓고 클라이언트, 서버가 해당 라이브러리를 참조하는 식으로 작성한다면 개발자 입장에선 효율성이 올라가고, 프로그래밍을 배우시는 분들께는 모듈화(Modulation) 개념을 접하고 이해하실 수 있는 기회가 될 것이라 생각하여 총 세 개의 프로젝트(공통 라이브러리 프로젝트, 클라이언트 프로젝트, 서버 프로젝트)로 구성하게 되었습니다.




서론은 여기까지만 하고, 본론으로 넘어가도록 하겠습니다!


이번 프로그램의 실행 순서도도 이전의 1:1 채팅 프로그램과 큰 차이는 없습니다만, 약간의 차이점이 있기 때문에 표로 서버와 클라이언트의 동작에 대해서 소개하고 넘어가도록 하겠습니다.

Server

Client

 [1] Bind > Listen > BeginAccept 순서대로 호출

 [2] Connect/BeginConnect를 이용하여 연결

 [3] 연결 요청이 있다면 EndAccept로 연결 수락 후 BeginReceive로 클라이언트 데이터 수신 대기

 [4] 연결에 성공했다면 BeginReceive로 서버 데이터 수신 대기 및 BeginSend로 서버에 데이터 송신

 [5] 수신한 데이터가 있다면 EndReceive를 이용하여 데이터를 수신하고 BeginReceive를 사용하여 다시 데이터 수신 대기

 [6] 데이터를 보낸 클라이언트를 제외하고 연결된 모든 클라이언트에게 수신한 데이터를 전달


 [7] 수신한 데이터를 텍스트박스에 표시


서버와 클라이언트의 차이는 데이터를 수신받고 표시만 해주는지 아니면 추가적인 처리를 해주는지와 연결 관리의 차이입니다.

이번 글에서는 함수에 대한 자세한 설명을 하지 않겠습니다. 만약, 모르시는 분들이 이 글을 읽고 계시다면 이전에 작성한 글을 읽어주시기 바랍니다!


이번 프로젝트에서는 윈도우 폼을 사용하기 때문에 아래의 디자인을 사용할 것이며, 이 디자인은 서버 및 클라이언트 프로그램에서 동일하게 사용합니다.
이 글의 초점 또한 이 디자인에 맞춰져 있기 때문에 따라하실 때엔 이 디자인을 사용해주시면 따라하기가 수월해집니다.

(그림 2. 프로그램 폼 디자인)


솔루션 파일에 문제가 생겨서 업로드한 파일을 지웠습니다. 이 주제의 마지막 장인 '클라이언트' 파트에 모든 구현이 완료된 솔루션 파일이 있으니, 해당 파일을 봐주시기 바랍니다.


포트 번호는 0부터 65535까지의 범위를 갖고 있는 16비트 부호 없는 정수(ushort)를 사용합니다.

따라서, 10진수 숫자가 아닌 문자들은 입력되지 않도록 만들어야겠죠!

방법은 간단합니다. 텍스트박스의 KeyPress 이벤트를 연결하고, 눌려진 문자(KeyPressEventArgs.KeyChar)를 검사하여 입력된 문자에 대한 입력을 허용할 것인지 또는 차단할 것인지를 결정(KeyPressEventArgs.Handled)해주면 됩니다.


그럼 이벤트는 어떻게 연결하나요?

Visual Studio의 경우 텍스트박스를 더블 클릭하면 KeyPress 이벤트가 아닌 TextChanged 이벤트로 연결됩니다. 따라서, 속성 창의 이벤트를 통해 연결을 할 수 있습니다.

그림 3에 표시된 붉은 네모 안의 연녹색 네모를 클릭하시면 그림 4처럼 해당 컨트롤이 사용할 수 있는 이벤트가 나오게 됩니다.


(그림 3. 속성 창의 이벤트)


(그림 4. 이벤트 연결)



그림 4에 표시된 부분을 더블 클릭하게 되면 해당 이벤트를 처리하기 위한 함수가 ControlName_EventName 형식으로 자동 생성됩니다.

단, 이 때 자동으로 생성되는 이벤트와 컨트롤을 더블 클릭했을 때 자동으로 생성되는 이벤트와는 약간의 차이가 있습니다.

속성 창의 이벤트를 통해서 처리 함수를 만드는 경우엔 특정 이벤트에 대한 처리기를 개발자가 직접 정해서 만들 수가 있습니다.

하지만, 컨트롤을 더블 클릭하여 처리 함수를 만드는 경우엔 해당 컨트롤의 기본 이벤트(Load, ClickTextChanged 등) 만 연결됩니다.


* 그림 4에 표시된 부분을 더블 클릭하는 것이 아니라 텍스트를 입력하고 엔터를 누르는 경우, 입력된 이름을 가진 이벤트 처리 함수가 생성됩니다.




자! 다시 본론으로 돌아와서 KeyPress에 대한 이벤트를 연결하도록 하겠습니다.

저는 DigitFilter란 이름을 가진 이벤트 처리 함수를 만들었습니다.


텍스트박스 10진수 입력 필터링 (C#)
void DigitFilter(object sender, KeyPressEventArgs e) {
    // 입력된 문자가 제어 문자 혹은 10진수 문자일 경우엔 입력을 허용하고
    // 그 외의 문자가 입력된 경우 입력을 차단한다.
    // 괄호 앞의 느낌표(!)는 bool 값을 반대로 하라는 것이다.
    // 따라서 아래 코드는 다음 코드와 동일하다.
    // 1. e.Handled = (char.IsControl(e.KeyChar) || char.IsDigit(e.KeyChar)) == false;
    // 2. e.Handled = char.IsControl(e.KeyChar) == false && char.IsDigit(e.KeyChar) == false;
    e.Handled = !(char.IsControl(e.KeyChar) || char.IsDigit(e.KeyChar));
}


코드에 대한 설명은 주석으로 다 해놓았기 때문에 건너뜁니다!

포트 번호에 대한 입력만 필터링하면 디자인에선 더 이상 할 것이 없기 이번 글은 이쯤 마치도록 하고, 다음에 쓸 글인 서버편에선 소켓을 적용하여 서버 프로그램을 완성하도록 하겠습니다.


긴 글 읽어주셔서 감사합니다 :)

+ Recent posts