클라이언트가 전송을 마치고, 프록시가 EOF를 보고, 응답이 사라집니다. 프록시의 두 소켓을 즉시 닫으면 정확히 그런 순서가 생길 수 있습니다. TCP 릴레이에서 한 방향의 질서 있는 끝은 다른 방향이 끝났다는 뜻이 아닙니다.

요청을 보내고, 자기 송신 쪽을 닫은 뒤 응답을 기다리는 클라이언트를 생각해 보겠습니다.

client -- request, then FIN --> proxy --> service
client <-------- reply ------- proxy <-- service

프록시에는 두 개의 TCP 연결과 독립적으로 움직이는 두 개의 바이트 스트림이 있습니다. 앞서 가는 바이트를 전달한 뒤에는 입력의 끝이라는 신호도 계속 전달해야 합니다.

EOF를 전달하기 전에 큐를 비우기

TCP 스트림에서 크기가 0이 아닌 수신에 대해 0이 반환되면 그 수신 스트림의 질서 있는 끝을 뜻합니다. 그 읽기 방향을 끝난 것으로 표시합니다. 그 방향에서 더 많은 입력을 기다리지 않되, 반대편 소켓을 위해 이미 큐에 들어간 바이트는 남겨 둡니다.

클라이언트에서 서비스로 가는 큐가 비면 POSIX 소켓에서 shutdown(service_socket, SHUT_WR) 를 호출합니다. 그러면 서비스 쪽으로의 추가 전송이 끝나고, 서비스에서 클라이언트로 가는 방향은 계속 사용할 수 있습니다. 서비스가 응답을 마치면 응답 큐를 비운 뒤 같은 작업을 반대 방향에 적용합니다.

디스크립터를 닫는 것은 두 방향과 그 대기 버퍼가 모두 끝났을 때, 또는 오류나 취소로 종료가 필요할 때입니다. 남은 반쪽을 끝내지 않는 상대를 위해 한계가 있는 타임아웃을 유지하세요.

준비됨은 읽으라는 초대

읽을 수 있는 소켓에는 데이터가 있거나 EOF 상태일 수 있습니다. recv를 호출하고 그 결과를 해석합니다. 양수는 전달할 바이트 수이고, 0은 방향의 끝입니다. 비블로킹 소켓에서 EAGAIN이나 EWOULDBLOCK은 지금은 더 이상의 입력이 없다는 뜻이고, EINTR은 재시도가 필요할 수 있습니다. 둘 다 EOF와 같은 사건은 아닙니다.

반환된 바이트 버퍼에서 FIN 플래그를 찾지 마세요. TCP는 이 API에 순서 있는 바이트 스트림을 전달합니다. 패킷 경계와 FIN 세그먼트는 일반적인 애플리케이션 페이로드가 아닙니다.

이것은 원시 바이트 터널의 수명 주기입니다. 메시지를 파싱하는 HTTP 프록시는 여전히 HTTP 프레이밍을 지켜야 하고, TLS를 종료하는 프록시에도 TLS 종료 프로토콜이 있습니다. 그런 프로토콜 판단은 방향별 TCP 릴레이 위에 두세요.

관련 글