28. 연결 종료 시 graceful half-close 부재로 인한 송신 데이터 절단(RST) (C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: 요청-응답형 세션. 클라이언트가 로그아웃(또는 거래 정산 확정)을 요청하면
// 서버는 결과 ack 를 보내고 연결을 닫는다. 이 ack 에는 "정산 결과/종료 사유" 같은
// 마지막으로 꼭 전달돼야 하는 정보가 담긴다.
//
// 요구사항:
//   - 서버가 마지막으로 보낸 ack 가 상대에게 확실히 도달한 뒤 연결이 닫혀야 한다.
//   - 클라이언트는 같은 연결로 추가 요청을 파이프라이닝했을 수 있다(인바운드 잔여 가능).
//
// 과제: (A)(B) 지점을 검토하라. 특정 타이밍에서 방금 보낸 ack 가 상대에게
//       도달하지 못하고 잘릴 수 있다. 어떤 조건에서 그런지 밝히고 고쳐라.

using System.Net.Sockets;
using System.Threading.Tasks;

public enum PacketType { Move = 1, Logout = 7 }
public struct Packet { public PacketType Type; }

public sealed class Session
{
    public Socket Sock = null!;

    // 아래 헬퍼는 정상 동작한다고 가정(구현 생략)
    public Task<Packet[]> RecvAndParseAsync() => Task.FromResult(new Packet[0]);
    public byte[] BuildAck(Packet p) => new byte[8];
    public Task SendAllAsync(byte[] buf) => Task.CompletedTask;
    public void Dispatch(Packet p) { }

    public void CloseSession()
    {
        Sock.Dispose();                          // (A)
    }

    public async Task OnReadableAsync()
    {
        Packet[] pkts = await RecvAndParseAsync();
        foreach (var p in pkts)
        {
            if (p.Type == PacketType.Logout)
            {
                await SendAllAsync(BuildAck(p));  // 종료 ack 전송(소켓 송신버퍼까지)
                CloseSession();                   // (B)
                return;
            }
            Dispatch(p);
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.