12. 게이트웨이의 존 서버 메시지 포워딩 (프레이밍/버전/멀티플렉싱) · C#

난이도 최상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 게이트웨이의 존 서버 메시지 포워딩 (프레이밍/버전/멀티플렉싱, 종합/최상)
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버):
//   - 게이트웨이가 수많은 클라 연결을 받아, 메시지를 뒤단의 존(zone) 서버로 포워딩한다.
//   - 여러 클라 세션의 메시지가 게이트웨이↔존 사이의 "하나의 공유 TCP 연결"로 멀티플렉싱
//     되어 흐른다(존 서버는 라우팅 헤더의 sessionId 로 어떤 클라인지 구분).
//   - 와이어 포맷:
//       클라→게이트웨이 한 프레임: [u32 len][u16 msgType][payload]
//                                   (len = msgType(2) + payload 바이트 수)
//       게이트웨이→존 한 프레임:   [u32 zlen][u64 sessionId][u16 zoneId][u16 msgType][payload]
//                                   (zlen = 그 뒤 전체 바이트 수)
//   - 모든 정수는 네트워크 바이트 순서(빅엔디안)로 보낸다는 것이 프로토콜 약속이다.
//   - 게이트웨이는 여러 IO 워커 스레드를 쓴다. 서로 다른 세션의 ForwardToZone 이
//     서로 다른 스레드에서 동시에 호출될 수 있다.
//   - 게이트웨이와 존 서버는 서로 다른 시점에 배포되어 프로토콜 버전이 다를 수 있다
//     (롤링 배포 중 구·신 버전 공존). msgType 번호 체계가 버전 간 다를 수 있다.
//
// 요구사항:
//   - 존 서버가 프레임 경계를 정확히 복원할 수 있어야 한다(길이 프리픽스 정확).
//   - 라우팅 헤더/페이로드의 바이트 순서가 프로토콜 약속과 일치해야 한다.
//   - 버전이 다른 존으로 보낼 때 msgType/스키마가 호환되게 변환되어야 한다.
//   - 공유 연결로의 멀티플렉싱이 프레임을 섞지 않아야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 어떤 경우에 존 서버가 디싱크/오라우팅/오디스패치
//   되는지 설명하고, 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰 후 answer.md 와 대조)
// ============================================================================

using System;
using System.Net;

// 게이트웨이 워커 스레드들이 공유하는 존 서버 연결.
public class ZoneLink
{
    public void RawSend(byte[] data, int length) { /* NetworkStream.Write(...) (생략) */ }
}

public class Gateway
{
    private readonly ZoneLink _zone;

    public Gateway(ZoneLink zone) { _zone = zone; }

    // 클라에서 수신·재조립이 끝난 "완성된 한 프레임"을 존으로 포워딩한다.
    // clientFrame = [u32 len][u16 msgType][payload], frameLen = 4 + len.
    public void ForwardToZone(ulong sessionId, ushort zoneId, byte[] clientFrame, int frameLen)
    {
        // 클라 프레임 파싱
        uint len = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(clientFrame, 0));
        ushort msgType = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(clientFrame, 4));

        int payloadOffset = 6;
        uint payloadLen = len - 2;   // msgType 2바이트를 뺀 나머지

        // 존 프레임 구성: [u32 zlen][u64 sessionId][u16 zoneId][u16 msgType][payload]
        byte[] outBuf = new byte[4 + 8 + 2 + 2 + (int)payloadLen];
        int off = 0;

        // (A) 길이 프리픽스: 클라 프레임의 len 을 그대로 사용
        WriteU32BE(outBuf, off, len); off += 4;

        // (B) 라우팅 헤더: sessionId/zoneId 를 그대로 복사
        BitConverter.GetBytes(sessionId).CopyTo(outBuf, off); off += 8;
        BitConverter.GetBytes(zoneId).CopyTo(outBuf, off);    off += 2;

        // (C) msgType: 버전 변환 없이 그대로 전달
        WriteU16BE(outBuf, off, msgType); off += 2;

        // payload 복사
        Array.Copy(clientFrame, payloadOffset, outBuf, off, (int)payloadLen); off += (int)payloadLen;

        // (D) 공유 존 연결로 송신
        _zone.RawSend(outBuf, outBuf.Length);
    }

    private static void WriteU32BE(byte[] b, int o, uint v)
    {
        b[o] = (byte)(v >> 24); b[o + 1] = (byte)(v >> 16);
        b[o + 2] = (byte)(v >> 8); b[o + 3] = (byte)v;
    }
    private static void WriteU16BE(byte[] b, int o, ushort v)
    {
        b[o] = (byte)(v >> 8); b[o + 1] = (byte)v;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.