9. C# half-open 연결 감지: 단방향 경로 장애와 송신 결과 처리
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - half-open 연결 감지: 단방향 경로 장애와 송신 결과 처리
// ----------------------------------------------------------------------------
// 시나리오:
// - 실시간 대전 서버(비동기 소켓). 서버는 매 틱 상태 스냅샷을 각 세션에
// 비동기 송신(SendAsync)한다. 클라는 입력을 올려보낸다.
// - 모바일/와이파이 환경에서 "단방향 경로 장애"가 발생한다: 서버→클라
// 방향만 죽고(패킷이 블랙홀로 빠짐), 클라→서버는 잠시 살아있거나 그 반대.
// 또는 클라 단말이 갑자기 사라져 FIN/RST 가 끝내 오지 않는다(half-open).
// - 송신(틱) 스레드와 수신 완료 콜백이 같은 세션을 동시에 만진다.
//
// 요구사항:
// - half-open / 단방향 장애를 수십 초 내에 감지해 세션을 정리한다.
// - 송신이 실제로 상대에게 도달하는지(또는 막혔는지)를 근거로 판정한다.
// - 살아있는 정상 세션을 오인 끊김 시키지 않는다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Net.Sockets;
using System.Threading.Tasks;
public sealed class Conn
{
private readonly Socket _socket;
public volatile bool Closed = false;
// (A) 마지막으로 "송신을 시도한" 시각 (수신 시각이 아님에 유의)
public DateTime LastSendAttempt = DateTime.Now;
public Conn(Socket socket) { _socket = socket; }
// 매 틱 호출: 상태 스냅샷을 비동기 송신
public void SendSnapshot(byte[] data)
{
if (Closed) return;
LastSendAttempt = DateTime.Now;
// 비동기 송신. 반환값/완료결과를 확인하지 않는다. (B)
_ = _socket.SendAsync(data, SocketFlags.None);
}
// 수신 완료 콜백
public void OnRecv(byte[] data, int len)
{
if (len <= 0) { Close(); return; }
// 클라 입력 처리 ...
// (C) 수신했다는 사실만으로 "연결 살아있음"으로 간주
}
// (D) 죽음 판정: 별도 워치독 스레드가 주기적으로 호출
// "오랫동안 송신을 시도하지 않았으면" 죽었다고 본다.
public bool LooksDead(TimeSpan idleLimit)
{
var now = DateTime.Now;
return (now - LastSendAttempt) > idleLimit; // (E)
}
public void Close()
{
if (Closed) return;
Closed = true;
// _socket.Close() ...
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - half-open 연결 감지: 단방향 경로 장애와 송신 결과 처리
// ----------------------------------------------------------------------------
// 시나리오:
// - 실시간 대전 서버(IOCP/epoll 비동기). 서버는 매 틱 상태 스냅샷을 각
// 세션에 비동기 송신(WSASend/send)한다. 클라는 입력을 올려보낸다.
// - 모바일/와이파이 환경에서 "단방향 경로 장애"가 발생한다: 서버→클라
// 방향만 죽고(패킷이 블랙홀로 빠짐), 클라→서버는 잠시 살아있거나 그 반대.
// 또는 클라 단말이 갑자기 사라져 FIN/RST 가 끝내 오지 않는다(half-open).
// - 송신 스레드 풀(N)과 수신 완료 콜백이 같은 세션을 동시에 만진다.
//
// 요구사항:
// - half-open / 단방향 장애를 수십 초 내에 감지해 세션을 정리한다.
// - 송신이 실제로 상대에게 도달하는지(또는 막혔는지)를 근거로 판정한다.
// - 살아있는 정상 세션을 오인 끊김 시키지 않는다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <atomic>
#include <cstdint>
#include <chrono>
class Conn {
public:
int fd;
std::atomic<bool> closed{false};
// (A) 마지막으로 "송신을 시도한" 시각 (수신 시각이 아님에 유의)
std::atomic<int64_t> lastSendAttemptMs{0};
// 매 틱 호출: 상태 스냅샷을 비동기 송신
void SendSnapshot(const char* data, int len) {
if (closed.load()) return;
lastSendAttemptMs.store(NowMs());
// 비동기 송신. 반환값/완료결과를 확인하지 않는다. (B)
::send(fd, data, len, 0);
}
// 수신 완료 콜백
void OnRecv(const char* data, int len) {
if (len <= 0) { Close(); return; }
// 클라 입력 처리 ...
// (C) 수신했다는 사실만으로 "연결 살아있음"으로 간주
}
// (D) 죽음 판정: 별도 워치독 스레드가 주기적으로 호출
// "오랫동안 송신을 시도하지 않았으면" 죽었다고 본다.
bool LooksDead(int64_t idleLimitMs) {
int64_t now = NowMs();
return (now - lastSendAttemptMs.load()) > idleLimitMs; // (E)
}
void Close() {
if (closed.exchange(true)) return;
// close(fd) ...
}
static int64_t NowMs() {
using namespace std::chrono;
return duration_cast<milliseconds>(
steady_clock::now().time_since_epoch()).count();
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.