콘솔 GameClient를 대체하는 브라우저 픽셀 클라이언트. 실제 IOCP_Server(TCP·IOCP)에 붙어 다른 플레이어의 이동·시야(AOI)·채팅을 실시간 렌더한다. 서버는 무수정 — 브라우저는 WebSocket만 쓰고, 그 사이 얇은 WS↔TCP 릴레이가 서버의 바이너리 프로토콜을 그대로 흘려보낸다.

브라우저(live.html) ──WebSocket──▶ relay.js(Node) ──TCP──▶ IOCP_Server(무수정)
                                                              ▲
                                                MMOStressClient(더미 N)

소스: MMO/WebClient/ (live.html · proto.js · relay.js · start-demo)

적용 기법 로그

A. 네트워크·프로토콜 — "브라우저라서" 필요한 것

1. WebSocket ↔ TCP 릴레이

브라우저는 raw TCP 소켓을 못 연다(WebSocket만 가능). 그래서 WS↔TCP 바이트 브릿지(relay.js, Node net+ws)를 두어 서버 TCP를 그대로 중계 — 서버·프로토콜 무변경. (websocat 대신 자작 → 외부 설치 0.)

2. 스트림 재조립 (length-prefix reframing)

WebSocket은 메시지(프레임) 단위지만 서버는 TCP 바이트 스트림 + 2바이트 길이 프리픽스. WS 프레임 경계 ≠ 패킷 경계가 핵심 함정. 수신 바이트를 버퍼에 누적하고 header.size로 다시 잘라 패킷 복원(proto.jsReassembler). TCP를 읽듯 처리.

3. 바이너리 파싱 (ArrayBuffer / DataView, little-endian, pack(1))

ws.binaryType='arraybuffer'DataView.getUint16(off, true) / getFloat32(off, true)로 C++ #pragma pack(1) 구조체를 오프셋 그대로 읽음. proto.js = Shared/Protocol/Protocol.h 1:1 (헤더 4B = size u16 + type u16, MsgType 1000~1021).

4. UTF-16LE 채팅 디코드

서버 채팅 문자는 char16_t(Protocol.hChatChar, 2바이트 고정) → JS TextDecoder('utf-16le') + 널 종단 잘라냄. 원래 wchar_t였으나 리눅스 이식 때 폭을 고정하려고 교체 — 와이어 폭은 2바이트로 같아 동작은 그대로. JS 문자열이 내부적으로 UTF-16이어도 와이어 바이트는 명시적으로 디코드해야 한글이 안 깨진다.

5. 하트비트

서버 세션 타임아웃 60초 → 클라가 15초마다 C2S_HEARTBEAT(헤더 4B) 송신으로 연결 유지.

B. 렌더링 — Canvas 2D

6. 픽셀 퍼펙트 업스케일

ctx.imageSmoothingEnabled=false + CSS image-rendering:pixelated → LPC 스프라이트를 nearest-neighbor로 선명하게 확대(뭉개짐 방지).

7. DPR 대응 캔버스