12.5. 신뢰성 – 시퀀스, ACK, 재전송¶
프레이밍 계층은 CRC로 손상을 검출합니다. 신뢰성 계층은 패킷이 온전하게 도착하지 않을 때마다 재전송을 협상하여 “검출된 손상”을 “애플리케이션이 절대로 깨진 데이터를 보지 않는” 상태로 바꿉니다.
12.5.1. 시퀀스 번호¶
각 패킷 헤더는 1바이트 시퀀스 번호를 담고 있으며, 이는 각 전송 방향마다 별도로 관리됩니다. 송신자는 전송 전에 카운터를 증가시키고, 수신자는 수신한 각 패킷의 시퀀스가 이전 값에 1을 더한 값(256으로 모듈로)인지 확인합니다.
수신자에는 깔끔한 순서대로의 패킷 대신 다음 세 가지가 나타날 수 있습니다:
유효한 CRC와 함께 예상한 시퀀스 번호가 도착한 경우. 이 패킷은 상위 계층으로 전달됩니다.
잘못된 CRC와 함께 예상한 시퀀스 번호가 도착한 경우. 수신자는 패킷을 폐기하고 (ACK가 협상된 경우) 재전송을 요청하는 NAK를 보냅니다.
유효한 CRC와 함께 예상보다 하나 큰 시퀀스 번호가 도착한 경우. 수신자는 이전 패킷이 누락되었음을 알게 되고, 누락된 시퀀스를 참조하는 NAK를 보낸 뒤 새 패킷을 보관해 둡니다.
중복 사례(원본이 결국 도착한 뒤에 재전송이 도착하는 경우)는 예상 카운터와 대조하여 처리됩니다. 시퀀스가 예상값보다 뒤처져 있으면 그 패킷은 중복이며, 수신자는 송신자가 처음에 받지 못한 것이 분명한 ACK를 보낸 뒤 패킷을 폐기합니다.
12.5.2. ACK와 NAK¶
패킷 헤더의 두 플래그 비트가 신뢰성 트래픽 자체를 담습니다:
나가는 패킷에
ACK_REQ가 설정되어 있으면 “확인 응답을 돌려받고 싶다”는 의미입니다. 데이터 패킷은 보통 이 비트를 설정하지만, 상태 핑이나 일회성 이벤트는 설정하지 않을 수 있습니다.패킷에
ACK가 설정되어 있으면 “이 패킷은 헤더의 시퀀스 번호에 대한 확인 응답”이라는 의미입니다. 이 패킷은 자체 페이로드를 담지 않습니다.NAK가 설정되어 있으면 “이 패킷은 이전 패킷을 거부한다”는 의미이며 – 보통 잘못된 CRC나 시퀀스 번호 간격 때문입니다. 헤더는 송신자에게 어느 시퀀스를 재전송해야 하는지 가리킵니다.
송신자는 정지-대기(stop-and-wait) 루프를 실행합니다. 즉, 확인 응답이 필요한 패킷 하나를 전송한 뒤, 다음 패킷을 보내기 전에 그에 맞는 ACK(또는 NAK)를 기다립니다. 한 번에 하나만 전송하는 모델은 송신자 상태를 제한된 범위 – 가장 작은 캠에서도 수백 바이트 – 안에 유지하며, 처리량에 최적화된 파이프가 아니라 두 엔드포인트 사이의 제어 채널이라는 프로토콜의 역할에 부합합니다. NAK를 받으면 송신자는 동일한 패킷을 RTX 플래그를 설정한 채 재전송하여 수신자가 그것이 재시도임을 알 수 있게 합니다.
12.5.3. 재전송 타이밍¶
재전송 타임아웃 안에 ACK도 NAK도 도착하지 않으면, 송신자는 전송 중이던 패킷을 스스로 재전송합니다. 타임아웃은 기본값이 500 ms 이며 연속 재시도할 때마다 두 배로 늘어납니다(1초, 2초, …). 설정된 재시도 횟수 – 기본값 3회 – 가 지나면 송신자는 포기하고 애플리케이션에 전송 오류를 보고합니다.
타임아웃을 두 배로 늘리는 것은 표준적인 지수 백오프(exponential backoff) 패턴입니다. 짧은 첫 타임아웃은 손실된 패킷을 빠르게 잡아내고, 두 배로 늘리는 방식은 수백 밀리초 동안 바쁜 호스트가 부하를 가중시키는 중복 패킷의 폭주를 일으키지 않게 합니다.
12.5.4. 신뢰성 구성¶
양쪽 끝단은 애플리케이션이 데이터 손실을 감당할 수 있을 때 합의하에 신뢰성 계층의 일부를 끌 수 있습니다:
protocol.init(ack=False)는 패킷별 ACK를 비활성화합니다. 송신자는 보내고 잊어버리며, 수신자는 도착하는 것을 그대로 전달합니다. 오래된 샘플을 받아들일 수 있는 센서 데이터 스트리밍에 적합합니다.protocol.init(seq=False)는 시퀀스 번호 추적을 끄며, 이는 ACK도 함께 끄는 것을 의미합니다. 완벽하게 신뢰할 수 있는 전송 수단에서만 유용합니다.protocol.init(crc=False)는 CRC 검증을 끄지만 나머지 프레이밍은 그대로 둡니다. 전송 수단 자체가 CRC 오류가 발생하지 않을 만큼 견고할 때만 할 만한 일입니다.
기본값 – 모든 것이 켜진 상태 – 은 모든 호스트-캠 디버깅 세션의 올바른 출발점입니다. 일단 애플리케이션이 프로덕션에 들어가면 그 절충점은 데이터와 전송 수단에 따라 구체화됩니다.
12.5.5. 상태 코드¶
전송 오류가 애플리케이션 코드로 전파될 때 그것은 상태 코드로 도착합니다. 프로토콜 라이브러리는 열 가지를 정의합니다:
SUCCESS– 작업이 완료되었습니다.FAILED– 명시되지 않은 이유로 명령이 실패했습니다.INVALID– 수신자가 명령 또는 그 인자 중 하나를 거부했습니다.TIMEOUT– 재시도 타이머가 만료되었습니다.BUSY– 캠이 바쁩니다(일반적으로 채널이 잠긴 경우).CHECKSUM– 헤더 또는 페이로드 CRC가 일치하지 않았습니다.SEQUENCE– 시퀀스 번호가 계층이 복구할 수 있는 범위를 넘어 순서를 벗어났습니다.OVERFLOW– 페이로드가 협상된 최댓값을 초과했습니다.FRAGMENT– 다중 프래그먼트 메시지가 일부 조각이 누락된 채 도착했습니다.UNKNOWN– 정말로 예상치 못한 상황에 대비한 방어적 포괄 항목입니다.
channel_read() 를 호출하는 호스트 코드는 이를 Python 예외로 보게 됩니다. 사용자 정의 오류 처리를 선택한 캠 측 애플리케이션 코드는 이를 백엔드 콜백의 반환 값으로 보게 됩니다. 대부분의 캠 앱은 상태 코드를 전혀 볼 필요가 없습니다 – 라이브러리가 재시도를 처리하며, 정말로 복구 불가능한 실패(예: 전송 수단 자체가 사라진 경우)만 애플리케이션에 도달합니다.
손상을 검출하는 프레이밍과 그로부터 복구하는 신뢰성이 갖춰지면 와이어 수준의 작업은 끝납니다. 애플리케이션 코드는 프레이밍되고 순서가 맞으며 온전한 패킷을 보게 되고, 그 안의 바이트들은 상위 채널이 의도하는 무엇이든 의미할 수 있습니다.