36. 송신 백프레셔 부재: 무제한 송신 큐와 블로킹 send (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 게임 서버 — 세션 송신 경로(브로드캐스트/푸시)
//
// 서버는 존(zone) 안의 모든 세션에 상태 업데이트를 브로드캐스트한다.
// 각 세션은 자기 송신 큐를 갖고, 워커가 큐를 비우며 소켓으로 내보낸다.
// 느린(혹은 악의적으로 느린) 클라이언트가 데이터를 잘 받아가지 않을 수 있다.
//
// 요구사항:
// - 한 느린 클라이언트가 서버 전체(메모리/이벤트 루프)를 위협하면 안 된다.
// - 브로드캐스트를 호출하는 게임 로직 스레드가 소켓 I/O 때문에 막히면 안 된다.
// - 필요 시 뒤처진 세션은 스로틀/드롭/연결종료로 격리한다.
//
// 아래 세션 송신 코드를 검토하라.
using System;
using System.Collections.Generic;
using System.Net.Sockets;
public sealed class Session
{
private readonly Socket _socket;
private readonly Queue<byte[]> _sendQueue = new();
public Session(Socket socket) { _socket = socket; }
// 게임 로직 스레드가 호출: 브로드캐스트할 프레임을 큐에 넣는다.
public void Enqueue(byte[] frame)
{
lock (_sendQueue)
{
_sendQueue.Enqueue(frame); // (A)
}
Flush();
}
// 큐를 소켓으로 밀어낸다.
private void Flush()
{
while (true)
{
byte[] frame;
lock (_sendQueue)
{
if (_sendQueue.Count == 0) return;
frame = _sendQueue.Peek();
}
int sent = _socket.Send(frame, 0, frame.Length, SocketFlags.None); // (B)
lock (_sendQueue)
{
_sendQueue.Dequeue();
}
}
}
}
// 브로드캐스트: 게임 로직 스레드에서 존의 모든 세션에 푸시
public static class Zone
{
public static void Broadcast(List<Session> sessions, byte[] frame)
{
foreach (var s in sessions) // (C)
s.Enqueue(frame);
}
} 결함 코드 · C++
// 시나리오: 게임 서버 — 세션 송신 경로(브로드캐스트/푸시) (C++)
//
// 서버는 존 안의 모든 세션에 상태 업데이트를 브로드캐스트한다. 각 세션은 자기
// 송신 큐를 갖고, 큐를 소켓으로 내보낸다. 느린(악의적) 클라이언트가 있을 수 있다.
//
// 요구사항:
// - 느린 클라이언트 하나가 서버 전체(메모리/틱 루프)를 위협하면 안 된다.
// - 브로드캐스트를 호출하는 게임 로직 스레드가 소켓 I/O 로 막히면 안 된다.
// - 뒤처진 세션은 스로틀/드롭/연결종료로 격리한다.
//
// 아래 세션 송신 코드를 검토하라.
// (send() 는 POSIX 소켓 send 를 흉내낸 자리표시자 선언이다.)
#include <deque>
#include <vector>
#include <mutex>
#include <cstddef>
extern int send(int fd, const void* buf, size_t n, int flags); // 블로킹 소켓
struct Frame { const char* data; size_t len; }; // 브로드캐스트 버퍼를 가리킴
class Session {
public:
explicit Session(int fd) : fd_(fd) {}
// 게임 로직 스레드가 호출: 브로드캐스트 프레임을 큐에 넣는다.
void Enqueue(const Frame& f) {
{
std::lock_guard<std::mutex> lk(m_);
queue_.push_back(f); // (A) 포인터를 그대로 저장
}
Flush();
}
private:
void Flush() {
while (true) {
Frame f;
{
std::lock_guard<std::mutex> lk(m_);
if (queue_.empty()) return;
f = queue_.front();
}
send(fd_, f.data, f.len, 0); // (B) 블로킹 send, 반환값 무시
{
std::lock_guard<std::mutex> lk(m_);
queue_.pop_front();
}
}
}
int fd_;
std::mutex m_;
std::deque<Frame> queue_;
};
// 브로드캐스트: 로직 스레드에서 존의 모든 세션에 푸시
void Broadcast(std::vector<Session*>& sessions, const std::vector<char>& buf) {
Frame f{ buf.data(), buf.size() }; // (C) 지역/임시 버퍼를 가리킴
for (Session* s : sessions)
s->Enqueue(f);
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.