﻿Как узнать, что TCP полностью завершился?

В raw API прямого callback'а вида:

on_connection_closed()

нет.

Обычно используют собственное состояние:

struct conn {
    bool closing;
};

и после успешного:

tcp_close(pcb);
conn->closing = true;

считают соединение закрытым для приложения.

Сам TCP завершится позже внутри lwIP.

А вызовется ли err callback?

Обычно нет.

tcp_err()

вызывается при ненормальном завершении:

RST
abort
потеря PCB
сетевые ошибки

но не при штатном FIN/ACK закрытии.

Нужно ли освобождать память после tcp_close()?

Сам pcb освобождать нельзя:

tcp_close(pcb);

/* НЕЛЬЗЯ */
mem_free(pcb);

lwIP сделает это самостоятельно.

Но ваши данные (arg) нужно освободить самостоятельно.

Обычно так:

struct my_conn *c = arg;

tcp_arg(pcb, NULL);

free(c);

tcp_close(pcb);

или

free(c);
tcp_close(pcb);
pcb = NULL;

если после этого код больше не обращается к c.

Безопасная практика

После успешного tcp_close():

if (tcp_close(pcb) == ERR_OK) {
    pcb = NULL;
}

и считать объект недействительным.

Потому что lwIP может уничтожить его в любой момент по мере завершения TCP state machine.

Типичный шаблон
static void close_conn(struct my_conn *c)
{
    err_t err;

    err = tcp_close(c->pcb);

    if (err == ERR_OK) {
        c->pcb = NULL;
        free(c);
    } else {
        /* ERR_MEM -> попробуем позже */
    }
}

Но если free(c) делается сразу, нужно быть уверенным, что никакие callback'и lwIP больше не используют arg. Часто перед закрытием сначала снимают callback'и:

tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_err(pcb, NULL);

а затем вызывают tcp_close().

Это особенно важно в сложных приложениях с собственными структурами соединений.
