46. 세션 송신 펌프의 동시 진입 (같은 소켓 중첩 SendAsync·프레임 오염, C#)

난이도 상 해설 보기 →

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

결함 코드 · C#
// 시나리오: 세션 비동기 송신 펌프 (C# 게임서버)
// - 각 세션은 송신 큐를 가진다. 여러 게임플레이 스레드가 Enqueue 로 패킷을 넣는다.
// - 소켓 쓰기는 한 번에 하나의 SendAsync 만 진행돼야 한다(겹치면 안 됨).
//   그래서 "지금 송신 중인가"를 _sending 플래그로 표시하고, 송신 중이 아니면
//   펌프를 깨워(kick) 큐를 비운다. 송신 완료 콜백도 다음 청크를 이어 보내려고
//   같은 경로(TrySend)를 다시 호출한다.
//
// 요구사항: 같은 소켓에 대해 SendAsync 가 동시에 두 번 진행되지 않아야 한다.
//
// 과제: 두 스레드가 거의 동시에 Enqueue 하거나, Enqueue 와 완료 콜백이 겹치면
//       같은 소켓에 SendAsync 가 중첩 실행돼 바이트가 뒤섞이고 프레임이 깨진다.
//       (A),(B) 를 근거로 원인·수정안을 설명하라.

using System;
using System.Collections.Concurrent;
using System.Net.Sockets;

public sealed class Session
{
    private readonly Socket _socket;
    private readonly ConcurrentQueue<byte[]> _sendQueue = new();
    private bool _sending;

    public Session(Socket s) { _socket = s; }

    public void Enqueue(byte[] packet)
    {
        _sendQueue.Enqueue(packet);
        TrySend();
    }

    private void TrySend()
    {
        if (_sending)                  // (A)
            return;
        if (!_sendQueue.TryDequeue(out var packet))
            return;

        _sending = true;               // (B)
        BeginSend(packet);
    }

    private void BeginSend(byte[] packet)
    {
        var args = new SocketAsyncEventArgs();
        args.SetBuffer(packet, 0, packet.Length);
        args.Completed += OnSendCompleted;
        if (!_socket.SendAsync(args))
            OnSendCompleted(this, args);
    }

    private void OnSendCompleted(object? sender, SocketAsyncEventArgs args)
    {
        _sending = false;
        TrySend();                      // 다음 청크 이어 보내기
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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