29. 아웃바운드 연결의 완료·실패·타임아웃 미처리 (서버-서버, C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 게이트웨이 서버가 존/인증 서버로 나가는 아웃바운드 연결을 맺는다(서버-서버).
// 다운스트림이 잠깐 죽거나 방화벽에 막히면 connect 가 실패하거나 응답이 없을 수 있다.
// 연결이 되면 그 세션으로 RPC 를 흘려보낸다.
//
// 요구사항:
// - Dial: 대상 주소로 비동기 연결을 시작하고, 연결이 "완료"되면 준비된 세션으로 등록한다.
// - Send: 준비된 세션으로만 메시지를 보낸다.
// - 다운스트림이 응답 없이 매달려도(black-hole) 연결 시도가 무한정 남아선 안 된다.
//
// 과제: (A)(B) 지점을 검토하라. 다운스트림이 죽거나 느릴 때 실패한/미완성 연결로
// 전송하거나 시도가 새어(leak) 쌓일 수 있다. 어떤 상황에서 그런지 밝히고 고쳐라.
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
public sealed class OutboundPool
{
private readonly ConcurrentDictionary<string, Socket> _ready = new();
public void Dial(string key, IPEndPoint remote)
{
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 연결을 시작하고, 준비 맵에 넣는다.
_ = sock.ConnectAsync(remote); // (A) 완료를 기다리거나 성패를 관측하지 않음
_ready[key] = sock; // (B) 완료 전에 "준비됨"으로 등록, 타임아웃 없음
}
public bool Send(string key, byte[] frame)
{
if (!_ready.TryGetValue(key, out var sock)) return false;
sock.Send(frame); // 준비된 세션으로 전송
return true;
}
} 결함 코드 · C++
// 시나리오: 게이트웨이 서버가 존/인증 서버로 나가는 아웃바운드 연결을 논블로킹 소켓으로
// 맺는다(서버-서버). connect() 는 즉시 끝나지 않고 진행 중일 수 있다. 이벤트 루프가
// 소켓이 "쓰기 가능"해지면 연결이 끝난 것으로 보고 준비된 세션으로 등록한다.
//
// 요구사항:
// - startConnect: 논블로킹 connect 를 시작하고 쓰기 가능 이벤트를 기다린다.
// - onWritable : 연결 완료를 처리해 준비 상태로 만든다.
// - 다운스트림이 응답 없이 매달려도(black-hole) 연결 시도가 무한정 남아선 안 된다.
//
// 과제: (A)(B) 지점을 검토하라. 다운스트림이 죽거나 방화벽에 막힐 때 실패한 연결을
// "성공"으로 처리하거나 시도가 영원히 매달릴 수 있다. 조건을 밝히고 고쳐라.
#include <cstdint>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
struct Conn {
int fd = -1;
bool ready = false;
};
class OutboundConn {
public:
// 논블로킹 connect 시작. 반환: 즉시 실패면 false.
bool startConnect(const sockaddr_in& remote) {
conn_.fd = ::socket(AF_INET, SOCK_STREAM, 0);
int fl = ::fcntl(conn_.fd, F_GETFL, 0);
::fcntl(conn_.fd, F_SETFL, fl | O_NONBLOCK);
int rc = ::connect(conn_.fd, (const sockaddr*)&remote, sizeof(remote));
if (rc == 0) { conn_.ready = true; return true; } // 드물게 즉시 성공
if (errno == EINPROGRESS) return true; // 진행 중 → 쓰기 가능 이벤트 대기
::close(conn_.fd); conn_.fd = -1; return false;
}
// 이벤트 루프가 이 fd 를 "쓰기 가능"으로 알릴 때 호출
void onWritable() {
conn_.ready = true; // (A) 쓰기 가능 = 연결 성공으로 간주
}
// 준비되면 전송
bool send(const uint8_t* p, size_t n) {
if (!conn_.ready) return false;
return ::send(conn_.fd, p, n, 0) >= 0; // (B)
}
private:
Conn conn_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.