lwIP로 TCP bring-up 하면 “가끔 멈춘다”는 말이 꼭 나온다.
- 연결은 유지된다(끊기진 않는다)
- 처음엔 데이터가 잘 오다가 어느 순간부터 더 이상 안 온다
- 상대는 같은 데이터를 재전송한다(Wireshark에 retransmission만 쌓인다)
이 상황에서 제일 먼저 의심하는 포인트가 있다.
RAW API에서
tcp_recved()를 치고 있나?
tcp_recved() 한 줄 정리
tcp_recved()는
“내가 이만큼 데이터를 소비했으니, 그만큼 receive window를 다시 열어도 된다”
를 lwIP TCP 스택에 알려주는 호출이다.
이걸 안 치면 lwIP는 이렇게 행동한다.
- 앱이 데이터를 안 읽는다고 판단
- receive window를 다시 안 늘림
- 상대는 더 보낼 수가 없어서 멈춤
즉 네트워크가 죽은 게 아니라, 흐름제어(flow control)가 막힌 것이다.
어디서 호출해야 하나: tcp_recv 콜백 안에서
RAW API를 쓰면 보통 구조가 이렇다.
tcp_recv(pcb, recv_cb)
recv_cb(arg, pcb, pbuf *p, err)
콜백으로 pbuf가 들어오면,
- pbuf를 처리(복사 or 스트림 파서에 투입)
- 처리한 만큼
tcp_recved()호출 - pbuf 해제(
pbuf_free())
이 3개를 “빠짐없이” 해야 한다.
가장 흔한 안전 템플릿(개념용)
프로젝트마다 버퍼/스레딩 구조가 다르니 그대로 복붙용은 아니고,
흐름만 보자.
static err_t recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
if (p == NULL) {
// 상대가 FIN 보냄(연결 종료)
return ERR_OK;
}
if (err != ERR_OK) {
pbuf_free(p);
return err;
}
// (1) 데이터 처리(예: 복사)
app_consume_bytes(p);
// (2) "내가 소비한 바이트 수"를 스택에 알려줌
tcp_recved(pcb, (u16_t)p->tot_len);
// (3) pbuf 반환
pbuf_free(p);
return ERR_OK;
}
포인트는 p->len이 아니라 p->tot_len이다.
p->len: 현재 pbuf 조각의 길이p->tot_len: 체인 전체 길이(현재 pbuf부터 끝까지)
RAW TCP에서 pbuf가 체인으로 들어오는 케이스가 많아서,
p->len만큼만 tcp_recved()를 치면 윈도우가 천천히 새서 결국 막힌다.
“바로 처리 못하는 구조”면 언제 tcp_recved()를 치나
recv 콜백에서 바로 소비를 못 하고,
다른 태스크로 넘겨서 나중에 처리하는 구조도 많다.
이때 선택지는 둘 중 하나다.
1) 콜백에서 복사하고 바로 tcp_recved()
가장 안전하고 디버깅이 쉬운 선택이다.
- 복사 비용은 있지만
- 수명주기/소유권 사고가 줄어든다
2) pbuf를 들고 가고, “진짜로 소비한 시점”에 tcp_recved()
zero-copy에 가까운 구조를 원하면 이 방향인데,
대신 규칙이 생긴다.
- pbuf를 잡고 있는 동안은
tcp_recved()를 늦춘다(윈도우가 안 열리니 상대가 느려질 수 있음) - 처리 완료 후
tcp_recved()+pbuf_free()를 정확히 수행한다 - 호출 컨텍스트(보통
tcpip_thread) 규칙을 어기면 또 다른 “가끔” 이슈가 생긴다
실무에서는 1)로 먼저 안정화하고, 2)로 천천히 옮기는 편이 덜 아프다.
이 증상이 “가끔”으로 보이는 이유
tcp_recved 누락은 사실 재현이 꽤 잘 되는 버그인데,
현장에서는 “가끔”으로 표현되는 경우가 많다.
이유는 보통 이렇다.
- 전송량이 적은 테스트(몇 백 바이트)에서는 티가 안 난다
- 특정 기능(로그 업로드/파일 전송/긴 DID 읽기)에서만 멈춘다
- pbuf 체인/윈도우 크기/상대 구현에 따라 “멈추는 지점”이 달라진다
그래서 팀 내에서는 “랜덤”처럼 느껴지지만,
Wireshark로 보면 대개 흐름이 똑같다.
Wireshark에서 보이는 힌트(빠르게)
아래 중 하나가 보이면 의심할 가치가 충분하다.
- 수신측(내 장비)이 advertise하는 TCP window가 점점 줄다가 작아짐
- 어느 순간 window가 거의 안 열림(실제로는 앱이 안 읽는 걸로 해석됨)
- 상대는 retransmission만 반복
즉 “링크/라우팅 문제가 아니라, 수신 윈도우가 막혔다”는 시그널이다.
빠른 디버깅 체크리스트
tcp_recv()콜백에서tcp_recved()호출이 실제로 있는지 grepp->tot_len을 쓰는지 확인(체인 대비)- 에러/드롭/early return 경로에서도
pbuf_free()가 빠지지 않았는지 확인 - “다른 태스크로 넘기는 구조”면
tcp_recved()호출 시점을 표로 정리 - 필요하면 recv 콜백에서
p->tot_len을 로그로 찍고, 누적 소비량이 증가하는지 확인
한 줄 요약
lwIP RAW TCP에서 전송이 멈추고 재전송만 쌓이면, 먼저 tcp_recved() 누락(또는 p->tot_len 처리 실수)으로 TCP receive window가 안 열리는지부터 의심하는 게 제일 싸고 빠르다.
추천 키워드
lwIP, RAW TCP, tcp_recved, TCP window, pbuf, 네트워크 디버깅
참고 자료
DevBJ | 오늘을살자, Log Today