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++
// ============================================================================
// [코드리뷰 문제] C++ - accept 루프와 연결 폭주(connection storm) 방어
// ----------------------------------------------------------------------------
// 시나리오:
// - epoll(엣지 트리거) 기반 단일 acceptor 스레드가 리슨 소켓을 감시하고,
// 새 연결을 accept 해서 IO 스레드 풀에 분배한다.
// - 점검 종료 직후 / 인기 이벤트 오픈 시각에 수만 클라가 동시에 재접속을
// 시도한다(connection storm). 봇/공격성 재시도 트래픽도 섞인다.
// - 한 IP/한 계정이 짧은 시간에 수백 연결을 여는 비정상 패턴도 관측된다.
//
// 요구사항:
// - 폭주 상황에서도 acceptor 가 새 연결을 흘리지 않고(누락 없이) 처리한다.
// - 파일 디스크립터(fd) 고갈로 서버가 마비되면 안 된다.
// - 한 IP/계정의 과도한 동시 연결로 정상 유저가 밀려나면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>
#include <cstdio>
class Acceptor {
public:
int listenFd;
int epollFd;
IoThreadPool* pool; // IO 스레드 풀 (정의 생략)
// (A) acceptor 스레드: epoll_wait 로 리슨 소켓 readable 이벤트를 받으면 호출
void OnListenReadable() {
sockaddr_in addr{};
socklen_t len = sizeof(addr);
// 하나의 readable 이벤트당 한 번만 accept 한다. (B)
int fd = accept(listenFd, (sockaddr*)&addr, &len);
if (fd < 0) {
perror("accept"); // (C)
return;
}
// 논블로킹 설정
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
// 세션 생성 후 IO 스레드 풀에 분배 (per-IP/계정 제한 없음) (D)
Session* s = new Session(fd, addr);
pool->Assign(s);
}
void Run() {
epoll_event ev{};
ev.events = EPOLLIN | EPOLLET; // (E) 엣지 트리거로 리슨 소켓 등록
ev.data.fd = listenFd;
epoll_ctl(epollFd, EPOLL_CTL_ADD, listenFd, &ev);
epoll_event events[256];
while (true) {
int n = epoll_wait(epollFd, events, 256, -1);
for (int i = 0; i < n; ++i) {
if (events[i].data.fd == listenFd)
OnListenReadable();
// ... 다른 fd 이벤트 처리
}
}
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.