7. C# accept 루프와 연결 폭주(connection storm) 방어

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - accept 루프와 연결 폭주(connection storm) 방어
// ----------------------------------------------------------------------------
// 시나리오:
//   - 단일 acceptor 태스크가 리슨 소켓에서 AcceptAsync 로 새 연결을 받아
//     IO 처리 풀에 분배한다.
//   - 점검 종료 직후 / 인기 이벤트 오픈 시각에 수만 클라가 동시에 재접속을
//     시도한다(connection storm). 봇/공격성 재시도 트래픽도 섞인다.
//   - 한 IP/한 계정이 짧은 시간에 수백 연결을 여는 비정상 패턴도 관측된다.
//
// 요구사항:
//   - 폭주 상황에서도 acceptor 가 새 연결을 흘리지 않고(누락 없이) 처리한다.
//   - 소켓 핸들(fd) 고갈로 서버가 마비되면 안 된다.
//   - 한 IP/계정의 과도한 동시 연결로 정상 유저가 밀려나면 안 된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

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

public sealed class Acceptor
{
    private readonly Socket _listenSocket;
    private readonly IoPool _pool;   // IO 처리 풀 (정의 생략)

    public Acceptor(Socket listenSocket, IoPool pool)
    {
        _listenSocket = listenSocket;
        _pool = pool;
    }

    // (A) acceptor: 새 연결을 받아 처리한다
    public async Task AcceptOnceAsync()
    {
        Socket conn;
        try
        {
            // 한 번의 호출당 한 번만 accept 한다. (B)
            conn = await _listenSocket.AcceptAsync();
        }
        catch (SocketException ex)
        {
            Console.WriteLine($"accept error: {ex.SocketErrorCode}");   // (C)
            return;
        }

        // 세션 생성 후 IO 풀에 분배 (per-IP/계정 제한 없음) (D)
        var ep = (IPEndPoint)conn.RemoteEndPoint;
        var session = new Session(conn, ep.Address);
        _ = _pool.AssignAsync(session);   // (E) fire-and-forget
    }

    public async Task RunAsync()
    {
        while (true)
        {
            // 매 루프마다 한 연결씩 받는다. (B)
            await AcceptOnceAsync();
        }
    }
}

public sealed class Session
{
    public Socket Socket;
    public IPAddress RemoteIp;
    public Session(Socket s, IPAddress ip) { Socket = s; RemoteIp = ip; }
}

public interface IoPool
{
    Task AssignAsync(Session s);
}
내 리뷰 · C#
내 답안 · 자동 저장

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