티스토리 뷰

플래쉬 액션스크립트의 세계로 (4) - 채팅프로그램 만들기

벌써 플래쉬 연재의 마지막 회가 되었다. 이번 회에서는 플래쉬의 XML 소켓을 이용해서 간단한 채팅 시스템을 만들어보도록 하겠다. 또한 지난 회에 약속한 것처럼 초보 플래쉬 사용자를 위해서 간단한 애니메이션을 플래쉬로 만들어보는 것에 대해서도 알아보려 했지만 지면관계상 건너뛰겠다. 대신 참고문헌 1에 적어놓은 필자의 웹사이트를 방문하면 해당 글을 볼 수 있을 것이다. 관심있는 독자분이 계시다면 참고하기 바란다. 그럼 플래쉬의 XML 소켓 객체를 이용해서 채팅 프로그램을 만들어보도록 하자. 채팅 프로그램의 서버는 MFC로 만들어볼 것이고 클라이언트는 플래쉬로 만들어보겠다.

1. XML 소켓 객체란?

지난 회에서는 XML 객체를 사용해보았는데 이 번 회에는 XML 소켓 객체를 이용해서 간단한 채팅 프로그램을 만들어보도록 하겠다. 먼저 XML 소켓 객체가 무엇인지 부터 알아보도록 하자.


참고 1. TCP/IP

인터넷을 사용하는데 사용되는 가장 기본적인 프로토콜은 TCP/IP (Transmission Control Protocol/Internet Protocol)이다. 우리가 알고 있는 웬만한 네트워크 프로토콜들은 이 프로토콜을 바탕으로 그 위에서 동작한다. 그러한 프로토콜들로는 다음과 같은 것을 들 수 있다.

  • FTP(File Transfer Protocol)
  • HTTP(HyperText Transmission Protocol)
  • SMTP(Simple Mail Transfer Protocol)
  • POP3(Post Office Protocol)
  • DNS(Domain Name System)
  • NNTP(Network News Transfer Protocol)
  • NTP(Network Time Protocol)

    TCP/IP는 인터넷의 근간이 되는 아주 중요한 프로토콜이라고 할 수 있다. 이 프로토콜이 없었더라면 지금과 같은 인터넷의 탄생에는 더 많은 시간이 걸렸을 것이다.

    TCP/IP의 유래 TCP/IP는 원래 1980년대 초반에 미국 국방성(Department of Defense)의 주도하에 ARPAnet(Advanced Research Projects Agency network)에서 처음 만들어진 기술이다. 사실 이 기관에서 직접 만든 것은 아니고 여기서 이를 버클리 대학에 연구를 위탁해서 만들어진 것이기 때문에 사실상 TCP/IP는 버클리대학에 의해 만들어졌다고 볼 수 있다. 이 기술의 목표는 서로 다른 운영체제들이 동작하고 있는 컴퓨터들이 공통된 통신언어를 갖고 데이터를 주고받을 수 있도록 하는 것이었다. 사실 그 목표는 오늘날 기대이상으로 달성되었다고 해도 무방한 듯하다.

    TCP/IP의 구성 TCP/IP는 TCP (Transmission Control Protocol)와 IP (Internet Protocol)라는 두 개의 프로토콜로 구성되어 있다. TCP는 연결 중심(Connection-oriented) 프로토콜로 IP의 상부에 위치하며 실제 데이터 송수신에는 IP 프로토콜을 사용한다. IP는 비연결(Connectionless) 프로토콜로 데이터가 전달될지 여부를 보장하지 못한다. TCP는 IP 프로토콜을 이용해서 데이터를 송수신하면서 그 위에 에러처리 기능을 구현하였다. 즉 TCP는 IP로 송신한 데이터가 제대로 전달되지 않았으면 다시 송신하는 등의 플로우 컨트롤(Flow Control)을 별도로 수행한다.

    이 TCP 프로토콜도 사실은 크게 TCP(Transmission Control Protocol)와 UDP(User Datagram Protocol)라는 두 개의 메인 트랜스포트 프로토콜로 구성되어 있다. 쉽게 차이점을 설명하자면 TCP는 데이터가 손실없이 분명하게 송수신되는 것을 보장하는 프로토콜이고 UDP는 반대로 성능을 극대화하는 대신에 데이터의 송수신되는 것을 보장하지 않는 프로토콜이다. UDP는 비디오/오디오 데이터의 실시간 전송과 같이 약간의 데이터 손실이 그다지 문제되지 않은 경우에 적합한 프로토콜이라고 할 수 있다. 만일 UDP를 이용하면서 TCP와 같은 데이터 송수신의 완결성을 필요로 한다면 직접 그러한 기능을 구현해야 한다.

    IP 주소 TCP/IP 네트워크 상의 모든 노드(서버, 클라이언트, 라우터 등등)들은 모두 IP 주소라는 것을 가져야 한다. 이 주소를 바탕으로 데이터 송수신의 참가자가 구분되고 데이터의 이동 경로가 결정된다. 현재 IP 주소는 4개의 숫자로 구성되며 각 숫자는 0부터 255사이의 값을 갖는다. 그래서 최대 40억개의 주소가 가능하다. TCP/IP가 처음 생길 때만 해도 이 정도면 충분하다고 생각되었지만 인터넷의 엄청난 성장은 이것도 부족할 지경에 이르도록 만들었다. 그래서 현재의 IP v6라고 하여 기존의 IP 체계를 확장하려는 시도가 진행 중이다.

    포트 번호 TCP/IP를 이용해 두 프로그램이 연결될 때는 유일한 포트 번호라는 것이 할당되어야 한다. 이는 TCP/IP를 기반으로 한 서비스들에 따라 다른 값을 갖는다. 이 값은 1부터 65,535까지 가능하며 1024까지는 웰노운 (Well known) 포트라고 해서 IANA(Internet Assigned Numbers Authority)에 의해 관리된 예약된 번호이다. 여러분이 알고 있는 대부분의 프로토콜들은 이 범주에 들어간다. 예를 들어 HTTP의 포트 번호는 80이고 SMTP의 포트번호는 25이다. 이에 대한 자세한 정보를 보고 싶다면 다음 웹페이지를 참고하기 바란다.

    http://www.iana.org/assignments/port-numbers

    자신의 시스템에서 현재 동작중인 TCP/IP 포트 번호를 알고 싶다면 커맨드창에서 netstat라는 명령을 실행해보기 바란다.


    XML 소켓 (Socket) 객체가 무엇인지 이해하려면 먼저 소켓이 무엇인지 알아야 한다(물론 XML이 무엇인지도 알아야 겠지만 지난 회에 간략히 설명했으므로 여기서는 생략하기로 하겠다). 여기서의 소켓은 TCP/IP상의 소켓 프로그래밍에서 이야기하는 소켓과 동일하다. 즉, 플래쉬에서 HTTP 프로토콜의 한계를 벗어나 네트웍 프로그래밍을 하길 원한다면 XML 소켓 객체를 이용해야 한다. 단 이 객체는 기존의 소켓에서 이야기하는 패킷 중심 연결은 지원하지 않는다. 또한 플래쉬의 소켓 객체는 클라이언트 역할만 지원한다. 즉, 서버로 동작해서 클라이언트로부터의 연결을 받지는 못한다. 소켓 프로그래밍으로 이야기하자면 listen과 같은 기능을 지원하지는 않는다는 것이다.

    이 객체의 이름은 XMLSocket이며 이 것의 사용법 자체는 이전 연재에서 살펴보았던 LoadVars와 XML 객체와 아주 흡사하다. 뒤의 예제를 보면 왜 필자가 비슷하다고 하는지 쉽게 이해할 수 있을 것이다. XMLSocket 클래스에 대해 알아보기 전에 먼저 XMLSocket 클래스가 LoadVars, XML 클래스와 어떤 차이점이 있는지부터 알아보도록 하겠다.

    1> 프로토콜상의 차이

    LoadVars와 XML 클래스는 HTTP와 HTTPS 프로토콜로 데이터를 읽어오는 용도로 밖에 사용할 수 없다. 그렇기 때문에 통신의 상대방은 웹 서버밖에 되지 못하며 단방향 통신만 가능하다. 다시 말해서 LoadVars와 XML 클래스를 사용하는 프로그램은 웹브라우저처럼 웹서버하고만 연결가능하며 그것도 항상 통신 시작을 먼저 개시해야 한다. 웹 프로토콜의 구조상 웹 서버가 먼저 통신을 개시할 수는 없기 때문이다. 웹 사이트의 성격에 따라서는 서버에 어떤 정보가 이용가능해질 경우 연결된 클라이언트(웹 브라우저가 될 것이다)에게 이를 알려주고 싶은 경우가 있을 것이다.

    그런데 웹 프로토콜 자체는 이러한 기능을 제공하지 않는다. 이를 극복하기 위해서 어떤 웹 사이트들은 웹페이지를 주기적으로 갱신하도록 하기도 하고(구체적인 방법에 대해서는 참고 2.를 참조하기 바란다) 아예 별도로 자바 애플릿이나 액티브X 컨트롤을 만들어 웹 페이지내에서 별도 서버와 통신을 하도록 하기도 한다.

    플래쉬의 XMLSocket을 이용한다면 이런 경우 플래쉬로 자바애플릿과 액티브X 컨트롤의 기능을 대신하는 모듈을 만들 수 있을 것이다. 이전 연재에서 플래쉬가 널리 사용되는 예로 미국의 스포츠 채널인 ESPN에서 온라인 스포츠 중계에 자바 애플릿을 사용하다가 플래쉬를 사용하는 것을 들었썼다. 그 플래쉬 모듈에서 바로 이 XMLSocket 객체를 사용한다. 그림 1을 참고하기 바란다.


    <그림 1. XMLSocket을 이용하는 ESPN의 농구 중계 플래쉬 모듈>


    참고 2. 웹페이지를 주기적으로 갱신하기

    이는 meta 태그의 http-equiv 속성과 content 속성을 사용하면 된다. http-equiv 속성으로는 refresh를 지정하고 content 속성으로는 초단위로 갱신간격을 지정한다. 다음은 1분마다 (즉 60초마다) 페이지의 내용을 다시 읽어오도록 하는 예이다.

    <meta http-equiv=refresh content="60";>


    XMLSocket 클래스를 사용하면 일종의 새로운 통신 프로토콜을 만들 수 있으며 서버와의 양방향 통신이 가능하다. 즉, XMLSocket 클래스를 사용하는 프로그램이 통신을 먼저 개시할 수도 있고 이 프로그램과 통신하는 상대방 프로그램(기타 다른 언어로 개발된 소켓 프로그램일 것이다)이 통신을 먼저 개시할 수도 있다.

    2> 데이터 포맷상의 차이

    통신에 사용되는 데이터의 구조를 놓고 보면 LoadVars는 URL 인코딩 형식의 데이터를 지원하고 XML과 XMLSocket 클래스는 이름이 의미하듯이 XML 형식만을 지원한다. 이게 XMLSocket의 장점이자 단점인데 XML만을 데이터 포맷으로 지원하기 때문에 텍스트 데이터를 구조화된 형태로 주고 받는데는 유용하지만 대용량 텍스트 데이터나 바이내리 데이터를 주고 받는데는 적합하지 않다(바이내리 데이터가 지원되면 대용량 텍스트 데이터의 경우 압축이 가능하다). 하지만 XML은 텍스트 기반의 데이터이기 때문에 운영체제의 종류에 관계없이 어느 환경에서나 동작한다는 장점을 갖는다. 여담이지만 이게 사실 마이크로소프트 닷넷이 부르짖는 XML 기반 웹 서비스의 장점이다.


    참고 3. 소켓 프로그래밍

    TCP/IP를 이용해 데이터를 주고받는 프로그램을 만들려면 무엇을 이용해야할까? 그게 바로 소켓(Socket) 라이브러리이다. 소켓 프로그래밍을 하면 나름대로 클라이언트/서버 프로그램을 만들어서 그것들끼리 통신을 하도록 할 수 있다.

    소켓이란 양방향 통신 채널을 말한다. 이 채널은 송수신이 보장되는 채널(연결 중심 채널)일 수도 있고 보장되지 않는 채널(패킷 중심 비연결 채널)일 수도 있는데 이는 소켓의 생성시에 지정가능하다. 참고로 플래쉬의 소켓은 전자(연결 중심 채널)만을 지원한다. 소켓을 이용하면 서로 다른 시스템(사실은 같은 시스템안에서도 가능하다)에 존재하는 프로그램 간에 데이터를 주고 받을 수 있다. 이것이 가능하려면 일단 TCP/IP 네트워크가 두 시스템간에 설치되어 있어야 하고 각각의 시스템이 서로 다른 IP 주소를 갖고 있어야 한다. 또한 연결시에는 유일한 TCP/IP 포트 번호가 지정되어야 한다.

    원래 소켓 프로그래밍에는 몇 가지 버전이 존재하는데 그 중 널리 사용되는 것은 BSD (Berkeley Software Distribution)라고 하여 버클리 대학에서 만든 것인데 참고로 마이크로소프트 윈도우에서는 유닉스에서 만들어진 BSD 계열의 소켓 라이브러리에 마이크로소프트 운영체제에 맞게 약간의 API들을 추가한 윈도우 소켓 라이브러리를 제공한다. 이를 줄여서 흔히 윈속(WinSock)이라고 한다.


    2. XML 소켓 객체의 사용법

    이제 XML 소켓 객체의 실질적인 사용법에 대해 알아보도록 하자. 앞서 이야기했던 것처럼 사용법 자체는 LoadVars, XML 객체와 거의 동일하다. 주제별로 어떤 메소드와 프로퍼티와 이벤트를 사용할 수 있는지 간략히 알아보도록 하자. 단, 앞서 언급한 것처럼 XMLSocket 객체는 소켓 서버 프로그래밍에 필요한 기능은 제공하지 않는다. 단순히 소켓 클라이언트로서의 기능만을 제공한다.

    1> XML 소켓 객체 만들기

    XML 소켓 객체를 만드는 것은 여느 객체를 만드는 것과 다를 것이 없다. 다음과 같이 아주 간단하다.

    server = new XMLSocket(); 
    2> 연결 맺기

    이제 상대방과 연결을 맺고 끊는 방법에 대해 알아보자. 앞서 이야기한 것처럼 이것도 소켓 프로그래밍이기 때문에 상대방과 연결하려면 상대방의 IP 주소와 포트번호를 알고 있어야 한다. 이 정보를 갖고 있다면 XMLSocket의 connect라는 메소드를 이용하면 된다. 첫 번째 인자로는 상대방의 IP 주소를 지정하고 두 번째 인자로는 포트번호를 지정한다. 예를 들어 “192.168.1.100”이란 주소의 9999번 포트 번호를 사용하는 상대방과 연결하고 싶다면 다음과 같은 코드를 사용하면 된다.

    server.connect(“192.168.1.100”, 9999); 
    이 연결 시도의 성공 여부는 connect 함수의 리턴값으로 지정되지 않으며 대신에 onConnect라는 이벤트로 알 수 있다. 연결의 성공 여부에 관계없이 onConnect로 지정된 함수가 호출되며 인자로 성공여부를 알리는 Boolean 값이 넘어간다. 간단히 성공여부를 output 창에 출력하는 예제 코드는 다음과 같다.
    // 연결의 성공여부가 결정되면 호출될 함수를 만들고 function serverConnected(success) { if (success) trace(“Connection is successfully made”); else trace(“Connection failed”); } // 이렇게 만든 함수를 XMLSocket 객체의 onConnect 이벤트에 지정한다. server.onConnect = server.serverConnected; 
    3> 연결 끊기

    맺어진 연결을 끊는 것은 아주 간단하다. XMLSocket 클래스의 close 함수를 호출하면 된다.

    server.close(); 
    이 역시 앞서 connect 함수처럼 리턴값으로 연결이 끊어졌는지 알려주는 것이 아니라 onClose라는 이벤트를 통해 지정된 함수를 호출하여 연결이 끊어졌는지 알려준다. 간단히 예를 들어보면 다음과 같다.
    // 연결이 끊어지면 호출될 함수를 만들고 function serverClosed() { trace(“Connection has been closed”); } // 이렇게 만든 함수를 XMLSocket 객체의 onClose 이벤트에 지정한다. server.onClose = server.serverClosed; 
    4> 데이터 보내고 받기

    XMLSocket 객체로 데이터로 보내려면 send 라는 메소드를 이용하면 된다. send로 보내는 데이터의 내용은 반드시 XML이어야 한다. XML이어야 한다는 말의 의미는 XML 문법을 준수해야 한다는 의미이다. 한 가지만 예를 들자면 모든 태그는 시작 태그와 끝 태그의 쌍이 맞어야 한다.

    server.send(“What are you doing?”); 
    그러면 상대방이 보낸 데이터는 어떻게 받을 수 있을까? 데이터를 받는데는 onXML이란 이벤트를 사용한다. LoadVars와 XML 객체에서 load 이벤트로 데이터를 요구하고 onLoad 이벤트로 결과를 받는 것과 동일한 방식이다. 다음은 간단한 실행 예이다.
    // 인자인 xmlData로는 상대방으로부터 받은 XML 데이터가 들어온다. function XMLReceived(xmlData) { trace(xmlData); } server.onXML = XMLReceived; 
    위의 예제 주석에 쓴 것처럼 onXML 이벤트의 인자로는 상대방이 보낸 데이터가 넘어온다. 그런데 앞서 이야기한 것처럼 또한 XMLSocket이라는 이름이 의미하는 것처럼 이 데이터의 포맷은 XML만 가능하며 그렇기 때문에 인자로 넘어오는 데이터는 XML 타입의 객체이다. 즉, 데이터의 파싱에는 XML 객체가 제공해주는 기능들을 이용하면 된다. 이에 대해서는 지난 회에 이미 살펴본 적이 있다.

    3. XML 채팅 프로그램 만들기

    자 이제 모든 준비가 완료되었다. 이제 실제로 XML 채팅 프로그램을 만들어보자. 플래쉬로는 소켓 연결을 대기하는 서버 프로그램을 만들 수 없기 때문에 채팅 서버 프로그램은 MFC 콘솔 프로그램으로 간단히 만들어보겠다. 즉, MFC로 만든 서버 프로그램은 플래쉬 클라이언트로부터의 연결을 대기하고 플래쉬 클라이언트로부터 들어오는 채팅 메시지를 현재 연결되어 있는 모든 플래쉬 클라이언트에 그 채팅 메시지를 그대로 뿌려주는 역할을 한다. 사용자가 입력한 채팅 메시지는 XML로 변경되어 서버와 클라이언트 사이에 교환된다. 다음 그림은 본 예제 프로그램(서버와 클라이언트 포함)의 실행 화면이다.


    <그림 2. 예제 프로그램의 실행화면>

    참고로 채팅 서버의 디폴트 포트 번호는 5001로 지정하였다.

    1> XML 채팅 데이터의 구조

    먼저 서버 프로그램과 클라이언트 프로그램이 주고받은 XML 데이터의 포맷부터 정의해보자. 여러가지를 추가해볼 수 있겠지만 일단 다음과 같이 아주 간단하게 정의해보았다.

    <MESSAGE FROM=’Keeyong’>What are you doing?</MESSAGE>

    몇가지 더 추가한다면 MESSAGE 태그의 속성으로 color와 font, fontsize 등을 추가할 수 있을 것이다. 일단 이 예제 프로그램에서는 아주 간단하게 가겠다.

    2> 서버 프로그램의 제작

    서버 프로그램은 앞서 이야기한 것처럼 MFC를 사용해 콘솔 프로그램으로 만들어 보겠다. 하지만 소켓 서버 프로그래밍이 가능한 언어라면 무엇을 사용해도 무방하다. 단 플래쉬로 만들 클라이언트와 주고받는 데이터의 형식은 XML이란 점을 다시 한번 기억해두기 바란다. 서버 프로그램의 코드는 클라이언트와 관계된 부분만 간략하게 설명하도록 하겠다. 이 코드를 제대로 이해하려면 소켓 프로그래밍과 멀티스레드 프로그래밍에 대한 이해를 필요로 한다.

    사실 여기서 사용한 서버 프로그램은 작년말에 필자가 보안 관련 구루 컬럼을 연재할 때 사용했던 서버 프로그램을 약간 수정한 것이다. 달라진 부분은 전에는 클라이언트가 서버에게 채팅 데이터를 보낼 때 먼저 데이터의 크기를 보내고 그 다음에 데이터를 보내는 방식을 취했는데 이 번에는 데이터의 크기 없이 데이터의 끝을 null 문자로 해서 보낸다는 것이다. 즉 서버는 특정 클라이언트가 데이터를 다 전송했는지 여부를 null 문자가 있는지 여부를 보고 판단한다는 것이다. 이렇게 수정한 이유는 플래쉬의 XML 소켓 클래스가 그런 식으로 데이터를 전송하기 때문이다.

    그 부분을 제외하고는 전체적으로 달라진 부분은 없다. 이 프로그램의 특징은 특정 클라이언트로부터 날라온 데이터를 그대로 현재 연결된 모든 클라이언트들에게 그대로 다시 뿌린다는 점이다. 이 프로그램의 플로우를 설명하면 다음과 같다.

    1. 이 서버 프로그램은 특정 포트를 열고 (현재 5001로 설정되어 있다) 클라이언트로부터 연결이 들어오기를 대기한다. 소켓 서버 프로그램에서 전형적으로 볼 수 있는 bind, listen, accept 등의 함수를 이용해서 클라이언트로부터의 연결 요청을 대기한다.
    2. 연결이 들어올때 마다 스레드를 하나씩 생성한다. 즉, 클라이언트마다 전담 스레드를 하나씩 생성하는 방식이다. 스레드 함수의 이름은 ThreadFunc이다. 이 방식은 문제점은 동시 연결 사용자가 많을 경우 스레드의 과다한 생성으로 인한 리소스 오버헤드가 있고 스레드들간의 컨텍스트 스위칭으로 인한 오버헤드가 있다는 것이다. 이를 극복하고 싶다면 IOCP(Input Output Completion Port)를 사용해야 한다.단 IOCP를 사용하는 방식의 문제는 코딩이 상당히 난해해진다는 것이다. 작년에 필자가 연재했던 소켓 프로그래밍 관련기사의 마지막 회에 필자가 관련 내용을 연재한 적이 있으므로 관심있으면 보기 바란다 (참고문헌 5).
    3. 스레드 함수의 시작부분에서는 해당 클라이언트의 소켓 핸들을 전역 리스트(CPtrList g_listConnections)에 등록한다. 그리고 해당 클라이언트로부터 데이터가 오기를 기다렸다가 데이터가 오면 이를 현재 연결되어 있는 모든 클라이언트들에게 그대로 전송해버린다. 이 때 모든 클라이언트에게 데이터를 전송하는 방식은 각 클라이언트들과 연결된 소켓의 정보가 기록되어 있는 리스트를 훑으면서 받은 데이터를 그대로 다시 보내는 방식이다.

    위의 플로우에서 좀더 상세히 설명해야 하는 부분은 크리티컬 섹션을 사용해서 연결되어 있는 모든 클라이언트를 관리하는 리스트에 대한 접근을 관리하는 부분이다. 연결 클라이언트 리스트를 접근하는 부분이 여러 군데(클라이언트가 생길 때, 기존 클라이언트와의 연결이 끊어질 때, 클라이언트에게 데이터를 보낼 때)이고 이런 접근이 여러 스레드에서 동시에 발생할 수 있기 때문에 리스트를 접근하는 부분은 한번에 한 스레드로 제한되어야 한다. 그래서 크리티컬 섹션을 사용하였으며 그 용도로 사용된 크리티컬 섹션의 이름은g_csNumberOfConnections이다.

    위의 플로우에서 1>에 해당하는 부분은 건너뛰고 (소켓 서버 프로그래밍을 한 번 이라도 해본 이라면 굳이 볼 필요없다) 스레드 함수의 코드만 보기로 하겠다. 일단 아래 코드에 주석을 상세히 달아놓았으므로 읽어보기 바란다.

    unsigned __stdcall ThreadFunc(void* pArguments) { // 스레드 함수의 인자로는 소켓 핸들이 넘어온다. SOCKET msgSock = (SOCKET)pArguments; int retval; // 이 소켓 핸들을 일단 g_listConnections 리스트에 추가한다. // 이 리스트에 대한 접근에 한번에 한 스레드에게만 허용한다. EnterCriticalSection(&g_csNumberOfConnections); g_listConnections.AddHead(new SOCKET(msgSock)); g_nNumberOfCurrentConnections++; printf("# of current connection is %d\n", g_nNumberOfCurrentConnections); LeaveCriticalSection(&g_csNumberOfConnections); // 이 클라이언트와의 연결이 끊어지거나 에러가 날때까지 루프를 돌면서 // 이 클라이언트로부터 받은 메시지를 그대로 모든 연결된 클라이언트들에게 // 재전송한다. while(1) { CString strData; // 사용자에게서 받은 채팅 메시지 char ch; // 다음 루프는 사용자가 보낸 메시지 하나를 받는 용도이다. 사용자가 // 보낸 메시지의 끝은 받은 문자의 값이 null 문자인지 보면 된다. do { // 일단 한문자씩 읽어본다. retval = recv(msgSock, &ch, 1, 0); // 에러 혹은 접속이 끊겼는지 본다. if (retval == SOCKET_ERROR||retval == 0) { fprintf(stderr,"recv() failed: error %d\n", WSAGetLastError()); closesocket(msgSock); break; } // 지금 읽은 문자가 null 문자가 아니면 strData 뒤에 추가한다. if (ch != '\0') strData += ch; else // null 문자이면 사용자가 보낸 메시지 하나를 다 읽은 것이다. break; // do … while 루프를 빠져나간다. } while (1); if (retval == SOCKET_ERROR||retval == 0) break; printf("Received %d bytes from client\n",retval); // strData의 내용을 현재 연결된 모든 클라이언트에게 전송해버린다. printf("Sending data back to clients\n"); SendBackResultToAllClient(strData); } closesocket(msgSock); /////////////////////////////////////////////////////////////// // 여기까지 실행되었다는 것은 클라이언트와의 연결이 끊어졌다는 것이다. // 이 클라이언트의 소켓을 리스트에서 빼버린다. EnterCriticalSection(&g_csNumberOfConnections); RemoveConnection(msgSock); g_nNumberOfCurrentConnections--; printf("# of current connection is %d\n", g_nNumberOfCurrentConnections); LeaveCriticalSection(&g_csNumberOfConnections); return 0; } 
    위의 코드에서 현재 연결된 모든 클라이언트에게 채팅 데이터를 재전송하는데 사용한 함수가 SendBackResultToAllClient 함수이다. 이 함수의 정의는 다음과 같다. 인자로 전송할 데이터가 넘어온다. 윈속의 send 함수를 이용해서 데이터를 전송한다.
    void SendBackResultToAllClient(LPCSTR lpData) { POSITION pos; // 전송데이터의 크기를 계산한다. 반드시 null까지 포함해야 한다. DWORD dwPacketSize = strlen(lpData) + 1; // 루프를 돌며 연결된 모든 클라이언트에게 데이터를 차례로 보낸다. for( pos = g_listConnections.GetHeadPosition(); pos != NULL; ) { int retval = send(*(SOCKET*)g_listConnections.GetNext(pos), lpData, dwPacketSize,0); if (retval == SOCKET_ERROR) { fprintf(stderr,"send() failed: error %d\n", WSAGetLastError()); break; } } } 
    클라이언트와의 연결이 끊어질 경우 해당 소켓 값을 연결 리스트에서 빼버리기 위해서 사용하는 함수가 RemoveConnection이란 함수이었다. 이 함수의 정의는 다음과 같다.
    void RemoveConnection(SOCKET msgSock) { POSITION pos = g_listConnections.Find((void *)msgSock); if (pos) { SOCKET *pa = (SOCKET*) g_listConnections.GetAt(pos); g_listConnections.RemoveAt(pos); delete pa; } } 
    MFC의 CPtrList와 같은 컬렉션 클래스를 좀 써 본이라면 어렵지 않게 코드를 이해할 수 있을 것이다. 전체 프로그램 소스의 내용은 지면관계상 생략했다.

    3> 클라이언트 프로그램의 제작

    플래쉬로 만들어볼 이 클라이언트 프로그램은 XMLSocket 객체를 이용해 서버와 연결하며 모두 2개의 레이어로 구성된다. 첫 번째 레이어(Layer1)는 다음 그림 3처럼 연결할 서버의 정보(IP 주소와 포트번호)와 사용자의 이름을 입력하는 용도로 사용된다. 아래 그림에서 오른쪽의 세 개의 입력창은 모두 Input text 타입의 텍스트필드들이다. 그리고 처음부터 차례로 텍스트필드들의 ID는 IPADDR, PORT, NAME이다. 하단의 Connect 버튼의 ID는 connect이다.


    <그림 3. 시작 화면의 구성>

    사용자가 정보를 입력하고 연결 버튼을 누르면 서버와의 연결이 성공했을 경우 레이어 1의 내용을 모두 안 보이게 만들고 (_visible 프로퍼티를 이용) 레이어 2에 존재하는 객체들을 보이게 만든다. 이 화면이 바로 메인 화면(그림 4 참조)으로 사용자가 메시지를 입력하면 서버로 보내고 서버가 보내는 메시지를 메시지 박스에 뿌리고 하는 등의 일이 여기서 이뤄진다.


    <그림 4. 메인 화면의 구성>

    위의 메인 화면에서 채팅 내용이 기록되는 큰 텍스트필드는 Dynamic text 타입의 텍스트 필드이고 속성으로는 Multiline을 지정했고 ID는 ChatLog이다. 이 필드의 옆에 놓인 스크롤바는 이름이 ChatScroll이고 이것은 속성박스를 이용해서 옆의 ChatLog 텍스트 박스와 연동되어 있다.스크롤바의 속성 박스를 보면 Target Textfield라는 항목이 있는데 그것이 ChatLog를 가리키도록 해야 한다. 그렇기 때문에 굳이 별도로 작업을 해주지 않아도 스크롤바의 조절이 현재 텍스트 박스의 상태에 맞게 이뤄진다. 사용자가 메시지를 입력하는 텍스트필드는 Input text 타입이고 ID는 input이고 그 옆의 버튼의 ID는 send이다.

    사실 위의 화면들을 이쁘게 구성하는데 별다른 노력을 기울이지 않았기 때문에 좀 초라하게 보일지도 모르겠지만 여기에 디자이너의 노력을 조금만 더 추가하면 아주 화려하고 멋진 채팅 프로그램으로 변신시킬 수 있다. 여기서는 구현상의 문제에만 집중하기 위해 디자인을 좀더 이쁘게 장식하는 부분은 생략하였다.

    다음은 플래쉬 클라이언트의 액션스크립트 소스이다. 전에 보았던 것처럼 소스는 한군데에 다 몰아서 보관하였다.

    // 전역 변수를 정의한다. g_strIPAddress = ""; // 서버의 IP 주소를 저장한다. g_nPort = 5001; // 채팅 서비스의 포트 번호를 가리킨다. g_strYourName = ""; // 사용자의 이름을 저장한다. // 초기 접속 화면과 관련된 객체만 보이게 한다. ShowScreen(true); // XML 소켓 객체를 하나 생성한다. g_server = new XMLSocket; // 서버 접속 시도시 접속 성공 여부를 알려줄 이벤트 함수를 만들어 지정한다 g_server.onConnect = function (success) { if (success) // 접속에 성공했으면 메인 화면 관련 객체만 보이게 한다. { ShowScreen(false); // show main screen } } // 서버가 보내는 데이터를 받는데 사용될 함수를 만들어 onXML 이벤트에 지정한다. g_server.onXML = function(data) // 인자 data는 서버가 보낸 XML 데이터에 해당한다 { // strContent는 메시지 내용을 담고 strSender는 보낸 사람의 이름을 담게 된다. var newData, strSender, strContent; // 인자로 넘어온 data를 파싱해서 strContent와 strSender를 채운다. strContent = data.firstChild.childNodes[0].nodeValue; strSender = data.firstChild.attributes.from; // 채팅 기록창에 추가될 한 줄을 만든다. “이름: 메시지” 의 형식이다. newData = strSender +": " + strContent+ "\n"; // 채팅 기록창의 끝에 앞서 만든 한 줄을 추가한다. ChatLog.text += newData; } // 초기 접속 화면에서 connect 버튼을 누르면 호출될 함수를 정의한다. _root.connect.onPress = function () { g_strIPAddress = IPADDR.text; g_nPort = Number(PORT.text); g_strYourName = NAME.text; // 제대로 하려면 위의 값이 제대로 입력되었는지 검사하는 코드가 들어가야 한다. // 여기서는 코드를 간단히 하기 위해 생략하였다. // 서버에 연결을 시도한다. g_server.connect(g_strIPAddress, g_nPort); } // 채팅 메인 화면에서 사용자가 메시지를 보내기 위해 Send 버튼을 누르면 호출되는 함수이다. _root.send.onPress = function () { var strMsg; // 입력 메시지 var strString; // 입력 메시지를 바탕으로 만들어진 XML 데이터 strMsg = _root.input.text; // 입력 메시지를 strMsg 변수에 담아둔다. if (strMsg == "") // 입력된 데이터가 없으면 return; // 그냥 리턴한다. // 앞서 논의한 형식으로 XML 데이터를 만든다. strString = ""+strMsg+""; // 서버로 전송한다. g_server.send(strString); } // 인자인 bInit이 true 이면 초기 접속 화면과 관련된 객체만 보이고 // false이면 채팅 메인 화면과 관련된 객체만 표시한다. function ShowScreen(bInit) { _root.IPADDR._visible = bInit; _root.PORT._visible = bInit; _root.NAME._visible = bInit; _root.ChatLog._visible = !bInit; _root.send._visible = !bInit; _root.input._visible = !bInit; _root.ChatScroll._visible = !bInit; } 
    프로그램의 개선점

    사실 XMLSocket 객체의 사용법을 중심으로 설명하다보니 프로그램이 전체적으로 가다듬어지지 않았다. 개선점을 나열해보면 다음과 같다.

  • 입력창에서 포커스가 처음 입력 부분으로 가 있으면 좋은데 현재 그게 되어 있지 않다.
  • 채팅 입력창에서 Enter 키를 누르면 메시지를 보내도록 해야한다. 현재는 Send 버튼을 눌러야먄 메시지가 서버로 전송된다.
  • 에러가 발생하거나 초기 화면에서 사용자의 입력이 불충분할 경우 그걸 사용자에게 표시해주어야 하는데 그것의 처리가 현재 되어 있지 않다.
  • 또한 원거리에 있는 서버에 접속할 경우 시간이 꽤 걸릴 수 있는데 그런 경우 프로그레스바를 하나 만들어 현재 연결 중임을 사용자에게 보일 수 있다면 더욱더 사용자에게 친숙한 프로그램이 될 것이다.
  • 전체적으로 디자인을 손봐야함도 물론이다.
  • 또한 현재 사용자가 입력하는 내용에 ‘<’나 ‘>’가 내용의 일부로 들어갈 경우 서버로 전송되는 XML문이 깨질 수 있는데 그걸 XML에 맞게 인코딩하는 부분이 빠져있다.
  • 또 채팅 메인 화면에서 채팅창이 지금은 단일한 폰트에 단일한 크기로 표시되고 있는데 채팅 텍스트필드를 html 텍스트 필드로 사용하고 채팅 XML 데이터의 속성부분을 추가하면 (앞서 1>에서 이야기한 color, font, fontsize 등의 속성) 좀더 이쁘게 바꿀 수 있을 것이다.

    이런 부족한 부분들은 여러분들이 직접 XML과 플래쉬, 액션스크립트를 공부해나가면서 보강해보기 바란다. 상당부분은 지난 연재기사들을 참고하면 수정가능하다. 테크니컬하게 아주 어려운 부분은 아니다. 정말 모르겠는 부분이 있으면 필자에게 메일을 주기 바란다.


    참고 4. SVG (Scalable Vector Graphics)

    인터넷이 거의 모든 서비스와 응용프로그램의 통신 내지는 데이터 교환의 수단으로 자리를 잡아가면서 각종 데이터를 모두 XML로 표현하려는 시도가 이뤄지고 있다. 그 이유는 XML을 이용하면 데이터를 구조적으로 표현가능하고 XML은 텍스트 기반이기 때문에 운영체제의 종류에 관계없이 파서만 있으면 쉽게 그 내용을 해독할 수 있기 때문이다.

    그러한 시도 중 두개를 예로 들자면 하나는 XBRL(eXtensible Business Reporting Language)라는 것과 SVG라는 것이 있다. 전자는 회사는 금융기관이나 국가 감독 기관에 자신이 재무상태를 보고할 때 사용하도록 하기 위해 만들어진 언어로 현재 세계 유수의 금융기관과 IT 기업들이 함께 제안서를 만들고 있는 중이다. 물론 이게 전세계적인 표준이 되는데는 시간이 좀 걸릴 것이다. 후자는 XML을 이용해 벡터 그래픽(과 래스터 그래픽)을 표현하려는 시도로 이를 이용하면 그래프나 차트는 물론 심지어 게임까지도 웹 서버상에서 간단하게 XML(과 자바 스크립트)로 만들기만 하면 브라우저나 웹 클라이언트 프로그램쪽에서 표시가 가능하다. 관심있다면 참고문헌 3>과 4>를 보기 바란다.

    XBRL와 SVG에서 볼 수 있는 것처럼 XML은 어디서나 사용할 수 있다는 장점과 데이터를 구조적으로 표현할 수 있다는 장점 때문에 점점 그 활용범위를 확대해 나가고 있다.


    5. 맺음말

    총 4회에 걸쳐 플래쉬 액션스크립트 프로그래밍에 대해 알아보았다. 사실 플래쉬 액션스크립트의 강력한 파워를 설명하기에는 턱없이 부족한 분량이지만 플래쉬를 단순한 애니메이션 제작도구로만 알고 있는 사람들의 생각을 바꿀 수 있는 기회 내지는 플래쉬의 가능성을 보이기에는 충분하지 않을까 싶다.

    첫 회에 강조한 것처럼 플래쉬는 멀티미디어 제작환경 + 프로그래밍 툴의 절묘한 조화를 자랑하는 차세대 인터넷 컨텐츠 제작환경이다. 웬만한 시스템에는 이미 플래쉬 플레이어가 설치되어 있기 때문에 플레이어를 다시 다운로드받아야하는 번거로움도 없다(물론 설치안된 시스템도 있고 일부 스팸 광고 퇴치 프로그램들은 플래쉬를 뜨는 것을 막아버리기도 한다).

    플래쉬의 단점은 플래쉬가 제공하는 멀티미디어 컨텐츠 제작기법과 액션스크립트라는 프로그래밍 관련된 두 부분을 모두 이해하기가 힘들다는 것인데 오히려 역설적으로 그런 부분때문에 플래쉬를 열심히 공부해두면 취업 시장에서 자신의 값어치를 올릴 수 있는 좋은 기회가 되지 않을까 싶다. 필자의 판단에는 플래쉬에는 앞으로 더욱더 널리 사용될 것으로 생각하며 지금 공부해둔다면 분명히 좋은 방향으로 플러스가 될 것이라고 생각한다. 또한 플래쉬를 제대로 사용하려면 수학 내지는 물리학에 대한 지식이 어느 정도는 필수적이다. 참고문헌 2에 관련 사이트를 하나 적어놓았으므로 필요하다면 참고하기 바란다.

    참고문헌

    1. 필자의 웹 사이트 ? http://www.winapiprogramming.com
    2. 플래쉬 수학 101 - http://members.shaw.ca/flashmath101/FM101_mathlinks.html#
    3. SVG 튜토리얼 사이트 - http://www.kevlindev.com/tutorials/basics/index.htm
    4. SVG 규격 사이트 - http://www.w3.org/TR/SVG/
    5. IOCP 소켓 프로그래밍 ? 프로그램 세계 2002년 6월호, 고성능 윈속 프로그래밍 
  • '프로그래밍 > 플래쉬' 카테고리의 다른 글

    오픈 차트 목록  (0) 2009.11.15
    플래쉬 액션 스크립트 Flash Action Script  (0) 2008.06.22
    [Flash Lite] 예제를 이용한 개론  (0) 2008.06.22
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크