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++
// ============================================================================
// [코드리뷰 문제] C++ - 세션별 수신 처리의 직렬화 부재 (IOCP/완료 콜백 재진입)
// ----------------------------------------------------------------------------
// 시나리오 (세션/네트워크 · 서버-클라):
// - 각 연결(Session)은 누적 수신 버퍼(recvBuf)와 파싱 상태(recvLen)를 가진다.
// 프레임 형식은 [len:int32][payload...] 다.
// - 비동기 수신이 완료되면 완료 핸들러에서 데이터를 누적·파싱하고, 다음 수신을 건다.
// 처리(parse)는 워커 풀(post)에 던진다.
// - 한 연결에서 데이터가 빠르게/여러 조각으로 들어올 수 있다.
//
// 요구사항:
// - 한 세션의 recvBuf/recvLen 은 동시에 두 곳에서 만지면 안 된다(프레임 경계 보존).
// - 워커에 넘긴 버퍼가 처리 중에 다음 수신으로 덮어써지면 안 된다.
// - 잘못된/부분 수신에도 버퍼가 깨지거나 세션이 비정상 종료되면 안 된다.
//
// 과제:
// 이 수신 경로의 잠재적 문제를 모두 찾고, 한 세션에 조각이 연달아 도착할 때 recvBuf/
// recvLen 와 워커가 참조하는 버퍼에 무슨 일이 일어나는지 설명하고, 수정안과 더 나은
// 설계를 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <cstring>
#include <vector>
#include <functional>
#include <thread>
// --- 최소 워커 풀(완료 처리를 다른 스레드로 넘김) ---
struct ThreadPool {
void post(std::function<void()> job) { std::thread(std::move(job)).detach(); }
};
struct Session {
int fd = -1;
std::vector<uint8_t> recvBuf = std::vector<uint8_t>(8192);
int recvLen = 0; // recvBuf 에 쌓인 바이트 수
uint8_t ioBuf[4096]; // 비동기 수신이 채우는 세션 전용 버퍼
};
class FrameServer {
public:
// 한 조각이 도착했을 때: 누적 → 프레임 파싱 → 잔여 압축
void OnData(Session& s, const uint8_t* data, int n) {
std::memcpy(s.recvBuf.data() + s.recvLen, data, n); // (A) 누적
s.recvLen += n;
int off = 0;
while (s.recvLen - off >= 4) {
int32_t flen;
std::memcpy(&flen, s.recvBuf.data() + off, 4);
if (s.recvLen - off - 4 < flen) break; // 아직 미완성
ProcessFrame(s, s.recvBuf.data() + off + 4, flen); // (B)
off += 4 + flen;
}
std::memmove(s.recvBuf.data(), s.recvBuf.data() + off, s.recvLen - off);
s.recvLen -= off;
}
// 수신 완료 핸들러: ioBuf 에 n 바이트가 채워진 상태로 호출됨
void OnRecvCompleted(Session& s, int n) {
if (n <= 0) { Close(s); return; }
// 처리를 워커 풀에 넘기고, 곧바로 같은 세션 버퍼로 다음 수신을 건다
_pool.post([this, &s, n] { OnData(s, s.ioBuf, n); }); // (C)
BeginReceive(s); // (D) 즉시 다음 수신(ioBuf 재사용)
}
private:
ThreadPool _pool;
void BeginReceive(Session& s) { /* async read into s.ioBuf → OnRecvCompleted */ (void)s; }
void ProcessFrame(Session& s, const uint8_t* p, int len) { (void)s; (void)p; (void)len; }
void Close(Session& s) { (void)s; }
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.