LabHub
배우기 러닝패스 코스

TCP 택배가 잘려 도착했다 · 반닫힌 연결에서 마지막 답을 돌려준다 · 이론

반닫힌 연결에서 마지막 답을 돌려준다

LabHub 에서 이어서 보기

한 줄 요약

전송 호출의 성공, 상대가 메시지를 받은 사실, 상대 업무가 완료된 사실은 서로 다릅니다.

왜 이게 필요했나

send가 3을 반환했는데 보내려던 프레임은 20바이트였습니다. 예외가 없었다고 완료로 기록하면 나머지 17바이트는 버려집니다. 직접 반복할 때는 반환한 크기만큼만 이동해야 합니다. send가 0을 반환하면 진전이 없으므로 종료 오류로 처리해야 하며, 그대로 반복하면 무한 루프가 됩니다. Python의 sendall을 쓰면 전체 전송 또는 예외라는 API로 이 반복을 맡길 수 있습니다.

그렇다고 sendall이 “상대의 주문 처리 완료”를 뜻하지는 않습니다. 로컬 전송 경로에서 바이트를 넘기는 일과 원격 프로그램이 프레임을 해석하는 일은 다릅니다. 이 코스에서는 실제 에코 응답이 돌아오는지를 확인합니다. 에코조차 결제·저장 같은 업무의 완료를 뜻하는 것은 아닙니다. 결제의 재시도와 멱등성은 별도 계약이 필요합니다.

어떻게 동작하나

리스닝 소켓은 연결을 받습니다. accept가 반환한 연결 소켓이 데이터를 읽고 씁니다. 마지막 검사기는 127.0.0.1과 포트 0으로 리스닝 소켓을 만들고 운영체제가 고른 포트를 클라이언트에 전달합니다. 특정 포트가 비어 있으리라 가정하지 않으므로 다른 시험과 충돌하기 어렵습니다. loopback은 이 파드 내부이고 외부 인터넷 허용은 필요하지 않습니다.

학습자의 handle_connection은 연결 소켓을 인수합니다. 한 프레임을 읽고 같은 bytes를 응답하는 일을 반복합니다. None이면 새 프레임 없이 정상 종료된 것입니다. b""는 길이가 0인 유효 메시지이므로 그대로 프레임을 만들어 돌려줍니다. if not payload라고 쓰면 이 둘을 혼동해 빈 메시지 이후의 데이터까지 잃습니다. 값의 진릿값 대신 API가 정의한 종료 표식을 비교하세요.

클라이언트는 여러 프레임을 보낸 뒤 shutdown(SHUT_WR)을 호출합니다. 이제 더 보내지는 않지만 응답은 계속 받을 수 있습니다. 서버는 이미 받은 프레임의 응답을 전송한 다음 정상 EOF를 만나 닫습니다. 소켓은 양방향 통신이라는 점 때문에 “상대가 보내기를 끝냈다”와 “내가 응답할 수 없다”가 같지 않습니다. 반닫힘을 이해하지 못하면 마지막 응답을 버리는 버그가 생깁니다.

with sock은 성공과 예외에서 연결 소켓을 닫습니다. 호출자가 소유하던 리스닝 소켓까지 닫는 것이 아닙니다. 어느 함수가 어떤 소켓의 수명을 소유하는지 인터페이스에 쓰는 이유입니다. 열린 파일 디스크립터는 제한된 자원입니다. 예외 때만 누수되는 코드는 정상 요청 시험에서는 멀쩡해도 장애가 반복될 때 자원을 말립니다.

송신 시간도 중요합니다. 이 코스의 send_frame은 호출자가 정한 소켓 timeout을 사용합니다. send_frame 자체가 새 송신 예산을 정하지는 않습니다. recv_frame은 자기 작업이 끝나면 기존 값을 복원하므로, 서버 호출자는 연결을 넘기기 전에 전체 정책에 맞는 기본 timeout을 설정해야 합니다. 수신 시간 제한을 구현했다고 모든 송신 대기까지 제한한 것은 아닙니다. 여러 클라이언트의 느린 소비자 문제와 큐 상한은 다음 단계에서 별도로 다룹니다.

현장에서 만나는 모습

단위 시험은 정확하게 자른 조각을 파서에 공급할 수 있습니다. 실제 TCP 시험은 connect·accept·반닫힘·소켓 회수까지 확인하지만 recv 조각 크기를 강제할 수는 없습니다. 두 시험을 함께 남겨야 경계 계산과 실제 통신을 모두 증명할 수 있습니다. 이 로컬 에코 실습으로 TLS, 인증, 인터넷 지연, 패킷 손실 복구, 대규모 처리량을 검증했다고 주장하지 않습니다.

클라이언트 관측을 “요청 전송을 마쳤다”, “응답 헤더를 받았다”, “응답 본문이 완성됐다”, “정상 EOF를 받았다”로 나누면 실패 위치를 좁힐 수 있습니다. 단순히 연결 성공이라는 로그 하나만 남기면 서버가 요청을 처리했는지 알 수 없습니다. 로그에는 메시지 ID나 바이트 길이처럼 필요한 정보만 남기고 실제 사용자 본문을 무조건 출력하지 않는 습관도 중요합니다. 이 코스의 시험 데이터는 민감 정보가 없는 자체 예시입니다.

응답이 오기 전에 연결이 끊기면 서버가 업무를 안 했는지, 업무는 했지만 응답만 잃었는지 클라이언트는 구분하기 어렵습니다. 그래서 실제 주문 시스템의 재시도에는 같은 작업을 식별할 키와 결과 조회 규칙이 필요합니다. 바이트를 다시 보내는 것은 간단하지만 업무를 중복 처리하지 않는 것은 별도 설계입니다. 에코 프로그램을 완성한 뒤 API 멱등성 코스로 연결할 수 있는 이유입니다.

마지막 검사기는 한 연결을 맡는 handler를 실행합니다. 이것이 무제한 동시 접속 서버의 설계는 아닙니다. 접속을 스레드로 나눌지 이벤트 루프로 다룰지, 대기열과 연결 수를 어떻게 제한할지는 다음 과정에서 실제 부하와 함께 비교합니다. 단일 연결에서 경계와 정리가 틀린 상태로 동시성부터 늘리면 같은 오류가 더 복잡한 모양으로 나타나므로 먼저 작은 계약을 정확하게 만듭니다.

다음 실습에서 할 것

검사기는 작은 프레임·빈 프레임·UTF-8 본문을 한 연결에서 보내고 송신을 반닫습니다. 응답 바이트가 완전히 일치하는지와 서버 종료 후 소켓이 닫혔는지를 확인합니다. 학습자의 함수는 실제로 실행되며 출력만 그럴듯하게 적은 파일로 통과하지 않습니다. 시험 시간을 초과하면 무한 대기부터 점검하세요. 임시 실습 세션이 끝나면 파일은 유지되지 않으므로 필요한 코드는 종료 전에 따로 보관합니다.