brunch

You can make anything
by writing

C.S.Lewis

by 앵버박사 Mar 18. 2016

처음 만드는 온라인 게임 04-01

Python web socket server 개발



왜 웹 소켓 인가




이번에 진행할 이야기는 웹 소켓 서버 개발입니다. 사실 저는 이 게임을 만들면서 우리가 앞에서 만들었던 HTTP 서버로 모든 처리를 할 예정이었고, 실제로 그렇게 개발을 진행했습니다. 그런데 예상치 못한 변수가 나왔는데, 바로 성능이었습니다. 싱글 플레이로 게임을 처음 개발했을 때에는 굉장히 부드러운 움직임을 보여줬던 캐릭터가 멀티 플레이로 변경하고부터 굉장히 버벅거리기 시작했습니다. 그리고 브라우저마다 성능 차이가 극명했습니다. 저는 여러 가지 방안을 생각해서 구현해보았지만 소용없었고, 결국 HTTP 통신방식이 너무 느리다는 결론에 도달하게 되었습니다. 왜 HTTP로 개발하면 느린지 생각해보기 위해 게임 로직을 한번 살펴보겠습니다.


우리가 만드는 온라인 게임 로직의 기본 프로세스는 이러합니다.



1. 클라이언트는 서버로부터 새로운 정보를 계속해서 수신하여 화면을 새로 그린다

2. 클라이언트는 사용자의 이벤트를 받아 서버로 변경된 정보를 송신한다

3. 서버는 클라이언트로부터 받은 데이터를 갱신하여 보관하고, 클라이언트로부터 요청이 오면 최신 데이터를 송신한다



여기서 클라이언트는 서버에게 데이터를 매 프레임마다 요청하게 됩니다. 화면을 부드럽게 갱신하기 위해서는 굉장히 빠른 속도로 서버를 호출해야 합니다. 제가 클라이언트를 개발하기 위해 자바스크립트에서 사용한 animationRequestFrame 함수는 최대 초당 60 프레임의 갱신을 하기 때문에 최대 초당 60번 서버에 데이터 요청을 하는 것입니다. 그렇기 때문에 매번 서버와 HTTP 메시지를 만들어 주고받는다면 느릴 수밖에 없습니다. 그렇게 고민하던 저는 웹 소켓이라는 HTML5의 새로운 스펙에 대해 알게 되었고, 확신을 얻기 위해 오픈소스로 공개된 웹 소켓 서버를 우선 적용하여 테스트한 결과, 놀랍게도 싱글 플레이로 구현했을 때와 비슷한 움직임을 보여주었습니다.




웹 소켓이란




웹 소켓은 말 그대로 웹에서 TCP 소켓 통신을 지원하여, 서버와 한번 연결하고 지속적으로 데이터를 주고받는 HTML5의 명세입니다. HTTP는 한 번의 요청과 응답으로 서버와의 연결이 끊어지고 매번 다시 요청을 만들어 서버에 접근해야 하기 때문에 느린 반면, 웹 소켓은 처음 한번 서버와 연결한 후 계속 데이터를 주고받을 수 있어 빠릅니다.


이제부터 웹 소켓 서버 개발을 위한 스펙에 대해 하나 씩 살펴보겠습니다. 웹 소켓으로 하는 서버와 클라이언트 간의 통신은 크게 두 가지 프로세스가 존재합니다.



1. 핸드쉐이크

2. 데이터 송수신



먼저 핸드쉐이크는 우리말로 악수라는 뜻으로, 서버와 클라이언트가 서로 본격적인 데이터 송수신을 하기에 앞서 준비를 하도록 하는 절차입니다. 핸드쉐이크가 완료되면 바로 데이터를 주고받을 수 있습니다. 처음 클라이언트가 서버로 핸드쉐이크를 요청할 때의 요청 헤더 정보는 웹 소켓의 스펙에 따라 다음과 같습니다( 서버 개발에 필요한 몇 가지만 추렸습니다 ).



GET /start HTTP/1.1
Host: simple-online-game.com
Upgrade: websocket
Connections: Upgrade
Sec-Websocket-Key: (임의의 키 값)



여기서 중요한 부분은 웹 소켓 연결인지 알기 위한 Upgrade와 서버와 핸드쉐이크를 하기 위한 키 값인 Sec-Websocket-Key 입니다. 클라이언트가 건넨 키를 서버가 읽어 매직넘버( 웹 소켓 스펙에서 그냥 정해져 있는 ) 인 258EAFA5-E914-47DA-95CA-C5AB0DC85B11를 붙여 sha1으로 해싱한 후 base64로 인코딩 하여 만들어낸 키 값을 다시 클라이언트로 보내고, 클라이언트가 이를 확인 함으로 핸드쉐이크가 완료됩니다. 여기서 서버가 클라이언트로 보내는 응답 헤더는 다음과 같습니다.



HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: (매직넘버를 붙여 sha1으로 해싱하고 base64로 인코딩한 키 값)



그 이후로는 소켓을 통하여 데이터를 송수신하면 되는데, 이때 주고받는 메시지를 웹 소켓 프레임이라고 합니다. 이 역시 정해진 프레임 구조를 따릅니다.



웹 소켓 프레임 구조



이게 뭔가 싶을 수도 있지만 데이터를 주고받을 때 이런 형식을 지켜서 보내야 한다는 뜻입니다. 왼쪽 위에서부터 오른쪽 아래 순으로 보면 됩니다. 필요한 것만 골라서 opcode 필드부터 쭉 살펴보겠습니다. opcode는 현재 프레임에 대한 특정 상태를 나타내는 코드로, 우리에게 필요한 상태는 opcode가 1 일 때( 메시지가 UTF-8로 인코딩 된 TEXT를 나타냄 ) 와 opcode가 8일 때( 클라이언트가 소켓 연결 종료를 요청함 ) 입니다.


그다음 MASK 필드인데요, 값이 1인 경우에는 Masking-key 필드의 값을 본문 데이터( Payload Data )와 XOR 연산해야 진짜 원하는 본문 데이터가 추출됩니다. 이는 Cache-poisoning attack을 막기 위함이라고 하는데 저도 잘은 모르겠고..;; 본문 데이터를 추출하는 방법은 추후 소스코드를 보면서 설명드리도록 하겠습니다.


다음 필드인 Payload len의 값이 126인 경우, 다음 2 Byte에 데이터의 실제 길이가 나오고 127인 경우 다음 4 Byte를 통해 데이터의 총 길이를 나타낼 수 있습니다. 125 이하인 경우에는 그 자체가 데이터의 실제 길이이고, Extended payload length를 넘기고 다음 필드부터 진행하면 됩니다.


마지막으로 Payload Data는 실제 데이터가 들어가는 부분이고 MASK 필드가 1인 경우 앞의 Masking-key의 값과 XOR 연산한 값이 입력됩니다.



지금 까지 왜 웹 소켓 서버를 선택했는지, 웹 소켓이 무엇이며 스펙은 어떻게 되는지 살펴보았습니다. 다음 글에서는 살펴본 스펙을 참조하여 실제 개발을 해보겠습니다.





Contents


01 주제 / 대상 독자 / 개발 원칙

02 요구사항 정의 / 설계

03 Python HTTP Server 개발

    03-01 멀티 쓰레드 지원 / POST 메서드 처리 / 로깅

    03-02 URL 라우팅 / 정적 리소스 처리


04 Python web socket server 개발
    04-01 웹 소켓 개요

    04-02 웹 소켓 서버 구현

05 클라이언트 개발

    05-01 캐릭터 스프라이트 개발

    05-02 클라이언트 로직

    05-03 서버 로직



매거진의 이전글 처음 만드는 온라인 게임 03-02
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari