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++
// 시나리오: 요청-응답형 세션. 클라이언트가 로그아웃(또는 거래 정산 확정)을 요청하면
// 서버는 결과 ack 를 보내고 연결을 닫는다. 이 ack 에는 "정산 결과/종료 사유" 같은
// 마지막으로 꼭 전달돼야 하는 정보가 담긴다.
//
// 요구사항:
// - 서버가 마지막으로 보낸 ack 가 상대에게 확실히 도달한 뒤 연결이 닫혀야 한다.
// - 클라이언트는 같은 연결로 추가 요청을 파이프라이닝했을 수 있다(인바운드 잔여 가능).
//
// 과제: (A)(B) 지점을 검토하라. 특정 타이밍에서 방금 보낸 ack 가 상대에게
// 도달하지 못하고 잘릴 수 있다. 어떤 조건에서 그런지 밝히고 고쳐라.
#include <sys/socket.h>
#include <unistd.h>
#include <cstdint>
#include <vector>
enum { LOGOUT = 7 };
struct Packet { int type; };
struct Session { int fd; };
// 아래 헬퍼는 정상 동작한다고 가정(구현 생략)
void flushSendQueue(Session& s); // 앱 송신 큐 → 커널 송신버퍼
std::vector<Packet> recvAndParse(Session& s); // 수신·프레이밍·파싱
std::vector<uint8_t> buildAck(const Packet& p);
bool sendAll(int fd, const std::vector<uint8_t>& buf); // 전량 전송
void dispatch(Session& s, const Packet& p);
void closeSession(Session& s) {
flushSendQueue(s);
::close(s.fd); // (A)
}
void onReadable(Session& s) {
std::vector<Packet> pkts = recvAndParse(s);
for (const Packet& p : pkts) {
if (p.type == LOGOUT) {
sendAll(s.fd, buildAck(p)); // 종료 ack 전송(커널 버퍼까지)
closeSession(s); // (B)
return;
}
dispatch(s, p);
}
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.