IP network에서는 packet이 손상되거나 사라지고, 중복되거나 순서가 바뀔 수 있다. 신뢰적 데이터 전송은 이런 channel 위에서 receiver가 손상되지 않은 data를 중복 없이 순서대로 받도록 checksum, sequence number, acknowledgment, timeout과 retransmission을 조합하는 문제다.
TCP는 이 원리를 byte stream에 구현한다. UDP는 datagram과 checksum을 제공하지만 ACK, 재전송과 순서 복구를 제공하지 않는다. 어느 transport를 쓰든 애플리케이션 처리 완료와 network delivery는 별개의 보장이다.
필요한 mechanism을 failure와 연결한다
| 발생할 수 있는 문제 | 필요한 정보·동작 |
|---|---|
| bit 손상 | checksum으로 오류 감지 |
| packet loss | ACK 대기와 timeout, 재전송 |
| ACK loss | sequence number로 중복 판별 |
| 순서 뒤바뀜 | sequence number와 receive buffer |
| 여러 packet 동시 전송 | sliding window와 in-flight 범위 관리 |
| receiver buffer 부족 | advertised receive window 기반 flow control |
하나의 mechanism이 모든 문제를 해결하지 않는다. checksum이 맞는다는 사실은 packet이 한 번만 도착했거나 순서가 맞다는 뜻이 아니다. ACK가 왔다는 사실도 상대 application이 data를 처리하거나 disk에 저장했다는 뜻은 아니다.
체크섬은 손상을 감지한다
UDP와 TCP header에는 checksum field가 있다. sender는 header와 data를 바탕으로 값을 계산하고 receiver는 다시 계산해 전송 중 bit error를 찾는다. 다만 UDP over IPv4에서는 checksum 값 0으로 미사용을 표시할 수 있으므로, 모든 UDP datagram이 payload 오류 검사를 거쳤다고 가정하면 안 된다.
checksum은 cryptographic integrity나 sender authentication이 아니다. 의도적인 변조를 막거나 누가 보냈는지 증명하지 않는다. 그런 요구에는 TLS 같은 상위 보안 protocol이 필요하다.
UDP checksum에서 오류를 찾으면 datagram은 폐기될 수 있지만 UDP가 스스로 다시 보내지는 않는다. application이 응답을 기다리고 timeout 뒤 재요청한다면 request ID와 중복 처리 규칙도 함께 설계해야 한다.
순서 번호는 무엇을 식별할까
generic reliable protocol에서는 packet마다 sequence number를 붙여 새 data와 재전송·중복을 구분할 수 있다. TCP에서는 segment 번호가 아니라 byte마다 sequence number 공간을 사용한다.
sender → receiver: Seq=1000, Len=500
receiver → sender: Ack=1500
data는 byte 1000부터 1499까지를 차지한다. Ack=1500은 1499까지 연속해서 받았고 다음에 1500을 기대한다는 cumulative acknowledgment다.
중간 범위가 빠지고 뒤 data가 먼저 오면 receiver는 다음에 필요한 byte를 계속 ACK할 수 있다.
받음: 1000-1499
빠짐: 1500-1999
받음: 2000-2499
누적 ACK: 1500
같은 ACK가 반복되면 sender는 gap이 있을 가능성을 알 수 있다. 다만 packet reordering도 duplicate ACK를 만들 수 있으므로 구현은 표준화된 loss detection과 recovery 규칙을 따른다.
ACK가 유실되면 왜 중복이 생길까
sender가 data를 보냈고 receiver는 정상적으로 받았지만 ACK만 사라질 수 있다. sender는 timeout 뒤 같은 sequence 범위를 재전송한다. receiver는 sequence number를 보고 이미 받은 data임을 알아야 application에 두 번 전달하지 않고 ACK를 다시 보낼 수 있다.
sender receiver
| ---- data #7 ------> |
| <---- ACK #8 --- X |
| timeout |
| ---- data #7 ------> | 중복 판별
| <---- ACK #8 ------- |
그러므로 재전송만 있고 sequence number가 없으면 ACK loss에서 중복 delivery를 막기 어렵다.
Timeout은 고정 숫자가 아니다
timeout이 너무 짧으면 단지 RTT가 늘어난 packet을 loss로 오인해 불필요하게 재전송한다. 너무 길면 실제 loss 복구가 늦다. TCP는 측정한 RTT와 변동을 바탕으로 RTO(Retransmission Timeout)를 계산한다.
RFC 6298은 SRTT와 RTTVAR를 이용한 RTO 계산, timer 만료 뒤 exponential backoff를 정의한다. 재전송된 segment의 ACK로 RTT를 재면 원본과 재전송 중 무엇에 대한 응답인지 모호해질 수 있어 Karn algorithm의 제약도 적용한다.
application-level retry timeout을 정할 때 TCP RTO와 별개라는 점도 중요하다. application timeout이 먼저 끝나 새 request를 보내면 원래 요청이 server에서 계속 처리될 수 있다. 결제·생성 API는 idempotency key와 결과 조회 경로가 필요하다.
Stop-and-wait에서 sliding window로
한 packet을 보내고 ACK가 올 때까지 기다리는 stop-and-wait은 이해하기 쉽지만 bandwidth-delay product가 큰 경로에서 link를 충분히 사용하지 못한다.
보냄 1 → ACK 1 → 보냄 2 → ACK 2
sliding window는 확인받지 않은 data를 일정 범위까지 연속해서 보낸다.
보냄 1, 2, 3, 4 → ACK 진행에 따라 window 이동 → 보냄 5, 6, ...
TCP sender가 실제로 유지할 수 있는 in-flight data는 receiver의 rwnd와 network congestion을 반영한 cwnd 등 여러 제한을 받는다. flow control과 congestion control은 둘 다 window를 쓰지만 보호 대상이 다르다.
누적 ACK와 SACK의 차이
기본 TCP ACK는 연속해서 받은 마지막 범위 다음 byte를 알린다. 앞의 gap 때문에 뒤쪽 data를 이미 받았다는 정보를 충분히 전달하지 못할 수 있다.
SACK(Selective Acknowledgment) option을 협상하면 receiver가 받은 비연속 block을 sender에 알릴 수 있다.
누적 ACK: 1500
SACK block: 2000-2500을 이미 받음
sender는 이 정보를 이용해 빠진 범위를 선택적으로 재전송할 수 있다. SACK이 application message 단위를 확인하는 것은 아니며 TCP sequence space의 byte range를 다룬다.
UDP와 TCP의 보장을 비교한다
| 항목 | UDP | TCP |
|---|---|---|
| data model | datagram | byte stream |
| checksum | 제공 | 제공 |
| delivery 보장 | 없음 | loss를 재전송해 신뢰적 stream 제공, 영구 장애는 error로 끝날 수 있음 |
| 순서 복구 | 없음 | byte 순서 복구 |
| 중복 억제 | application 책임 | TCP sequence 처리 |
| message 경계 | 보존 | 보존하지 않음 |
| congestion control | application·상위 protocol 책임 | TCP가 제공 |
TCP도 무한한 전달 보장은 아니다. host crash, network partition과 user timeout으로 connection이 실패할 수 있다. 성공한 write()는 local send buffer에 data를 받아들였다는 뜻일 수 있고, 상대 application의 business transaction 완료를 증명하지 않는다.
애플리케이션에서 한 단계 더 필요한 것
TCP가 신뢰적 byte stream을 제공해도 다음은 application protocol이 정의해야 한다.
- message boundary와 최대 크기
- request와 response를 연결할 ID
- retry 시 중복 side effect를 막는 idempotency
- application 처리 완료 ACK와 persistence 기준
- connection이 끊긴 뒤 resume 위치
- timeout과 cancellation 뒤 늦게 도착한 결과 처리
TCP 연결 설정부터 종료까지의 전체 흐름은 TCP 연결 동작 순서, port와 socket으로 flow를 나누는 방식은 전송 계층 멀티플렉싱, network 혼잡에 따른 window 조절은 TCP 혼잡 제어에서 이어진다.
참고 자료
'배움과 성장 > 네트워크·보안' 카테고리의 다른 글
| 네트워크 혼잡은 왜 생길까: 큐·손실·ECN과 TCP 반응 (0) | 2023.04.03 |
|---|---|
| TCP 연결 동작 순서: 3-way handshake부터 ACK·재전송·종료까지 (0) | 2023.04.01 |
| 전송 계층 멀티플렉싱·역다중화: 포트와 소켓으로 이해하기 (0) | 2023.03.31 |
| TCP·UDP 소켓 프로그래밍: Python 클라이언트·서버 흐름 (0) | 2023.03.31 |
| P2P 파일 배포 원리: 클라이언트-서버와 BitTorrent 비교 (0) | 2023.03.31 |
댓글