20. 송신 측 부분 전송(short write)과 동시 전송으로 인한 프레임 경계 붕괴 (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 송신 측 부분 전송(short write)과 동시 전송으로 인한 프레임 경계 붕괴
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 · 서버-클라/서버-서버):
// - 메시지는 길이 프리픽스로 프레이밍한다: 와이어 포맷 [u32 len][len 바이트 payload].
// - Socket.Send 는 OS send() 처럼 "요청한 바이트보다 적게" 보낼 수 있고(송신 버퍼가 찼을 때),
// 실제로 보낸 바이트 수를 반환한다.
// - 한 연결(Socket)에는 여러 스레드/소스가 메시지를 보낼 수 있다(브로드캐스트, 존 서버가
// 같은 게이트웨이 연결로 여러 플레이어 패킷을 포워딩 등).
//
// 요구사항:
// - 한 프레임([len][payload])은 와이어에 "통째로, 끊김 없이" 나가야 한다(부분 전송 시 나머지를
// 마저 보내야 하고, 다른 메시지의 바이트가 중간에 끼면 안 된다).
// - 길이 프리픽스의 바이트 순서(엔디안)는 송수신이 합의한 고정 포맷이어야 한다.
// - 송신 버퍼가 차서 다 못 보낼 때(백프레셔)도 프레임이 깨지거나 유실되면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 수신 측에서 프레임 경계가 어긋나(메시지가
// 섞이거나 길이가 틀어져) 디싱크/오파싱이 일어나는지 설명하고, 수정안과 더 나은 설계를
// 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Net.Sockets;
public class MessageSender
{
private readonly Socket _sock;
public MessageSender(Socket sock) { _sock = sock; }
// 길이 프리픽스 프레이밍으로 한 메시지 전송
public void SendMessage(byte[] payload)
{
// (D) 길이 프리픽스의 바이트 표현(엔디안)을 검토하라
byte[] hdr = BitConverter.GetBytes((uint)payload.Length);
// (C) 헤더와 페이로드의 전송 방식을 검토하라
_sock.Send(hdr, 0, 4, SocketFlags.None); // (A) 이 전송 호출의 반환값 처리를 검토하라
_sock.Send(payload, 0, payload.Length, SocketFlags.None); // (A) 이 전송 호출의 반환값 처리를 검토하라
// (B) 여러 스레드가 같은 _sock 에 동시 호출하는 경우를 검토하라
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 송신 측 부분 전송(short write)과 동시 전송으로 인한 프레임 경계 붕괴
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 · 서버-클라/서버-서버):
// - 메시지는 길이 프리픽스로 프레이밍한다: 와이어 포맷 [u32 len][len 바이트 payload].
// - 저수준 전송 rawSend 는 OS send() 처럼 "요청한 바이트보다 적게" 보낼 수 있고
// (송신 버퍼가 찼을 때), 실제로 보낸 바이트 수(>=0) 또는 오류(-1)를 반환한다.
// - 한 연결(Socket)에는 여러 스레드/소스가 메시지를 보낼 수 있다(브로드캐스트, 존 서버가
// 같은 게이트웨이 연결로 여러 플레이어 패킷을 포워딩 등).
//
// 요구사항:
// - 한 프레임([len][payload])은 와이어에 "통째로, 끊김 없이" 나가야 한다(부분 전송 시 나머지를
// 마저 보내야 하고, 다른 메시지의 바이트가 중간에 끼면 안 된다).
// - 길이 프리픽스의 바이트 순서(엔디안)는 송수신이 합의한 고정 포맷이어야 한다.
// - 송신 버퍼가 차서 다 못 보낼 때(백프레셔)도 프레임이 깨지거나 유실되면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 수신 측에서 프레임 경계가 어긋나(메시지가
// 섞이거나 길이가 틀어져) 디싱크/오파싱이 일어나는지 설명하고, 수정안과 더 나은 설계를
// 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <cstring>
#include <vector>
// 저수준 전송 추상화. rawSend 는 ::send() 처럼 부분 전송이 가능하다.
struct Socket {
long rawSend(const char* data, size_t len); // 보낸 바이트 수(>=0) 또는 -1(오류)
};
class MessageSender {
public:
explicit MessageSender(Socket& sock) : sock_(sock) {}
// 길이 프리픽스 프레이밍으로 한 메시지 전송
void sendMessage(const std::vector<uint8_t>& payload) {
uint32_t len = static_cast<uint32_t>(payload.size());
char hdr[4];
std::memcpy(hdr, &len, 4); // (D) 길이 프리픽스의 바이트 표현(엔디안)을 검토하라
// (C) 헤더와 페이로드의 전송 방식을 검토하라
sock_.rawSend(hdr, 4); // (A) 이 전송 호출의 반환값 처리를 검토하라
sock_.rawSend(reinterpret_cast<const char*>(payload.data()), // (A) 이 전송 호출의 반환값 처리를 검토하라
payload.size());
// (B) 여러 스레드가 같은 sock_ 에 동시 호출하는 경우를 검토하라
}
private:
Socket& sock_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.