22. 세션별 수신 처리의 직렬화 부재 (동시/재진입 수신 콜백)

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 세션별 수신 처리의 직렬화 부재 (동시/재진입 수신 콜백)
// ----------------------------------------------------------------------------
// 시나리오 (세션/네트워크 · 서버-클라):
//   - 각 연결(Session)은 누적 수신 버퍼(RecvBuf)와 거기서 프레임을 잘라내는 파싱
//     상태(RecvLen, 오프셋)를 가진다. 프레임 형식은 [len:int32][payload...] 다.
//   - 비동기 소켓 수신이 완료되면 콜백에서 데이터를 누적·파싱하고, 다음 수신을 건다.
//   - 한 연결에서 데이터가 빠르게/여러 조각으로 들어올 수 있다.
//
// 요구사항:
//   - 한 세션의 수신 버퍼/파싱 상태는 동시에 두 곳에서 만지면 안 된다(프레임 경계가
//     깨지거나, 같은 바이트가 두 번/누락되어 처리되면 안 된다).
//   - 잘못된/부분 수신에도 버퍼가 깨지거나 예외로 세션이 죽으면 안 된다.
//
// 과제:
//   이 수신 경로의 잠재적 문제를 모두 찾고, 한 세션에 조각이 연달아 도착할 때 RecvBuf/
//   RecvLen 에 무슨 일이 일어나는지(프레임이 어떻게 꼬이는지) 설명하고, 수정안과 더 나은
//   설계를 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Net.Sockets;
using System.Threading;

public class Session
{
    public Socket Sock;
    public byte[] RecvBuf = new byte[8192];
    public int    RecvLen;          // 현재 RecvBuf 에 쌓인 바이트 수
}

public class FrameServer
{
    // 한 조각이 도착했을 때: 누적 → 프레임 파싱 → 잔여 압축
    public void OnData(Session s, byte[] data, int n)
    {
        Array.Copy(data, 0, s.RecvBuf, s.RecvLen, n);        // (A) 누적
        s.RecvLen += n;

        int off = 0;
        while (s.RecvLen - off >= 4)
        {
            int flen = BitConverter.ToInt32(s.RecvBuf, off);
            if (s.RecvLen - off - 4 < flen) break;           // 아직 프레임이 다 안 옴
            ProcessFrame(s, s.RecvBuf, off + 4, flen);
            off += 4 + flen;
        }

        Array.Copy(s.RecvBuf, off, s.RecvBuf, 0, s.RecvLen - off);  // (B) 잔여 앞으로
        s.RecvLen -= off;
    }

    // 수신 완료 콜백
    public void OnReceiveCompleted(Session s, byte[] buf, int n)
    {
        if (n <= 0) { Close(s); return; }

        // 처리를 스레드풀에 던지고, 곧바로 다음 수신을 건다
        ThreadPool.QueueUserWorkItem(_ => OnData(s, buf, n));      // (C)
        BeginReceive(s);                                           // (D) 즉시 다음 수신
    }

    private void BeginReceive(Session s) { /* s.Sock.BeginReceive(...) → OnReceiveCompleted */ }
    private void ProcessFrame(Session s, byte[] buf, int off, int len) { /* 디스패치 */ }
    private void Close(Session s) { /* ... */ }
}
내 리뷰 · C#
내 답안 · 자동 저장

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