DoIP 디버깅하다 보면 timeout처럼 보이는데 실제로는 그렇지 않은 경우가 있다.
- 요청은 나간다
- TCP 연결도 살아 있다
- ECU도 바로 뭔가 응답한다
- 그런데 최종 positive response는 안 오고
7F xx 21만 반복된다
이때 재전송을 세게 걸면 오히려 더 오래 막힌다.
오늘 메모는 NRC 0x21 Busy Repeat Request를 timeout과 분리해서 봐야 하는 이유다.
먼저 의미부터 짧게
0x21은 보통 이런 뜻으로 읽는다.
지금 그 요청을 바로 처리하기 어렵다. 같은 요청을 조금 뒤에 다시 시도해라.
중요한 건 이거다.
- 무응답이 아니다
- 통신 단절도 아니다
- ECU가 요청 내용을 이해 못한 것도 아니다
즉 0x21은 “아예 못 받았다”보다
“받았지만 지금 처리 순서가 안 맞는다”에 가깝다.
timeout처럼 다루면 왜 꼬이나
현장 코드에서 자주 보는 흐름이 이렇다.
request 전송
-> NRC 0x21 수신
-> 즉시 retry
-> 또 0x21
-> retry 횟수 초과
-> 세션 재시작 또는 재연결
이 패턴은 겉으로는 합리적으로 보인다.
그런데 실제로는 ECU가 비워질 시간을 전혀 주지 못하는 경우가 많다.
특히 아래 상황에서 자주 보인다.
- 이전 RoutineControl이 아직 끝나지 않음
- 다른 테스터가 같은 자원을 이미 점유 중
- 같은 세션 안에서 직전 요청의 후처리가 남아 있음
- 게이트웨이 뒤 ECU가 순차 처리라서 다음 요청을 바로 못 받음
즉 0x21은 네트워크 timeout이 아니라,
ECU 쪽 작업 큐 또는 자원 점유 상태 신호로 보는 편이 맞다.
Response Pending 0x78과도 다르다
0x21과 0x78을 같은 재시도 정책으로 묶으면 헷갈린다.
0x78: 같은 요청을 ECU가 이미 처리 중이니 계속 기다리는 흐름0x21: 지금은 그 요청을 받기 곤란하니, 나중에 다시 넣으라는 흐름
둘 다 “지금 당장 최종 응답이 아니다”라는 점은 같지만,
테스터 동작은 달라야 한다.
0x78 -> 같은 transaction 안에서 기다림
0x21 -> 새 retry transaction을 간격 두고 다시 시도
이걸 섞어 버리면,
테스터 로그에는 둘 다 timeout처럼 보이기 쉽다.
자주 터지는 구현 실수
1) 0x21을 수신 에러처럼 올린다
상위 레이어가 0x21을 받았는데도
그냥 “실패” 하나로 뭉개면 retry 정책을 세밀하게 못 짠다.
그러면 결국:
- timeout과 같은 backoff
- socket reconnect
- 세션 재진입
같이 너무 큰 동작으로 번진다.
2) retry 간격이 0에 가깝다
바로 다시 보내면 ECU가 비워질 시간을 못 준다.
0x21 수신 시각
-> 5ms 뒤 재전송
-> 또 0x21
이건 “재시도”라기보다 사실상 “압박”에 가깝다.
실무에서는 짧더라도 의도적인 간격을 두는 편이 낫다.
핵심은 고정 숫자보다 같은 자원 요청을 직렬화하고, 너무 빠른 재전송을 막는 것이다.
3) 멀티 서비스 요청을 동시에 날린다
예를 들어:
- DID 읽기
- RoutineControl
- Security Access 이후 후속 요청
을 서로 독립 스레드에서 동시에 던지면,
ECU 입장에서는 같은 내부 자원을 순차 처리해야 해서 0x21이 반복될 수 있다.
이 경우 네트워크를 고치는 게 아니라
테스터 요청 큐를 정리해야 한다.
로그를 이렇게 남기면 빨라진다
0x21 이슈는 패킷만 보면 “응답은 있으니 통신은 정상” 정도로 끝나기 쉽다.
그래서 나는 아래 항목을 같이 묶어 본다.
- 요청 서비스 ID와 서브기능
- 직전 요청이 끝난 시각
0x21수신 시각과 retry 간격- 같은 ECU에 동시에 걸린 요청 개수
- 세션/보안 상태 전환 직후인지 여부
이 다섯 줄이 있으면,
“ECU가 바빠서 그런지”와
“테스터가 너무 빨리 다시 던지는지”가 금방 갈린다.
구현 쪽에서 안전한 패턴
가장 무난한 방향은 이렇다.
request 전송
-> final response면 종료
-> NRC 0x78이면 같은 transaction에서 대기
-> NRC 0x21이면 요청 큐에 재스케줄
-> retry 간격과 최대 횟수는 서비스별 정책 적용
그리고 같은 ECU, 같은 자원을 건드리는 요청은
가능하면 직렬화하는 편이 덜 아프다.
예를 들면:
- RoutineControl 수행 중에는 같은 리소스 DID read를 뒤로 미룸
- Download/Transfer 계열 중에는 보조 진단 요청을 잠시 막음
- unlock 직후 후속 요청을 한 큐에서 순서대로 실행
빠른 체크리스트
0x21을 timeout과 같은 에러 코드로 뭉개고 있지 않은지 확인- retry 간격이 너무 짧지 않은지 확인
- 같은 ECU 대상 요청을 여러 태스크가 동시에 보내고 있지 않은지 확인
0x78대기 정책과0x21재시도 정책을 분리했는지 확인- 재시도 실패 시 바로 재연결하지 말고 세션/큐 상태부터 보도록 했는지 확인
한 줄 요약
DoIP에서 NRC 0x21이 반복되면 네트워크 timeout보다 ECU 자원 점유와 요청 직렬화 문제일 가능성이 크므로, 0x78과 분리된 retry 간격과 큐 정책으로 다루는 편이 안전하다.
추천 키워드
DoIP, UDS, NRC, Busy Repeat Request, retry policy, timeout debugging
DevBJ | 오늘을살자, Log Today