写在前面
为了较好了解LWIP的相关内容,这里阅读了LWIP141版本的源代码的核心指文档(rawapi.txt)进行了解相关函数的用法以及相关概念。
线程
lwIP 开始针对单线程环境。在添加多线程支持时,不是使核心线程安全,而是选择了另一种方法:有一个主线程运行 lwIP 核心(也称为“tcpip_thread”)。原始 API 只能从此线程使用!使用顺序或套接字 API 的应用程序线程通过消息传递与此主线程进行通信。
因此,可以从其他线程或 ISR 调用的函数列表非常有限! 只有来自这些 API 头文件的函数是线程安全的:
- api.h - netbuf.h - netdb.h - netifapi.h - sockets.h - sys.h
此外,内存(解除)分配函数可以从多个线程(不是 ISR!)以 NO_SYS=0 调用,因为它们受SYS_LIGHTWEIGHT_PROT 和信号量的保护。
回调
程序执行由回调驱动。每个回调都是从 TCP/IP 代码中调用的普通 C 函数。每个回调函数都将当前 TCP 或 UDP 连接状态作为参数传递。此外,为了能够保持程序特定的状态,回调函数是使用独立于 TCP/IP 状态的程序指定参数调用的。
设置应用程序连接状态的函数是:
void tcp_arg(struct tcp_pcb *pcb, void *arg)
指定应传递给所有其他回调函数的程序特定状态。 “pcb”参数是当前的 TCP 连接控制块,“arg”参数是将传递给回调的参数。
TCP连接设置
用于建立连接的函数类似于顺序 API 和 BSD 套接字 API 的函数。一个新的 TCP 连接标识符(即协议控制块 -PCB)是用tcp_new() 函数。然后可以将此 PCB 设置为侦听新的传入连接或明确连接到另一台主机。
struct tcp_pcb *tcp_new(void)
创建新的连接标识符 (PCB)。如果没有内存可用于创建新的 pcb,则返回 NULL。
err_t tcp_bind(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
将 pcb 绑定到本地 IP 地址和端口号。 IP 地址可以指定为 IP_ADDR_ANY,以便将连接绑定到所有本地 IP 地址。如果另一个连接绑定到同一个端口,函数将返回 ERR_USE,否则返回 ERR_OK。
struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
命令 pcb 开始侦听传入连接。当传入连接被接受时,将调用由 tcp_accept() 函数指定的函数。 pcb 必须使用 tcp_bind() 函数绑定到本地端口。
tcp_listen() 函数返回一个新的连接标识符,作为参数传递给函数的标识符将被释放。这种行为的原因是正在侦听的连接需要更少的内存,因此 tcp_listen() 将回收原始连接所需的内存并为侦听连接分配一个新的更小的内存块。如果没有内存可用于侦听连接,tcp_listen() 可能会返回 NULL。如果是这样,与作为参数传递给 tcp_listen() 的 pcb 关联的内存将不会被释放。
struct tcp_pcb *tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
与 tcp_listen 相同,但将侦听队列中的未完成连接数限制为 backlog 参数指定的值。要使用它,您需要在 lwipopts.h 中设置 TCP_LISTEN_BACKLOG=1。
void tcp_accepted(struct tcp_pcb *pcb)
通知 lwIP 已接受传入连接。这通常会从接受回调中调用。这允许 lwIP 执行内务管理任务,例如允许进一步传入的连接在侦听积压中排队。注意:传入的PCB必须是监听PCB,而不是传入accept回调PCB!
void tcp_accept(struct tcp_pcb *pcb, err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err))
指定当新连接到达侦听连接时应调用的回调函数。
err_t tcp_connect(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port, err_t (* connected)(void *arg, struct tcp_pcb *tpcb, err_t err));
设置 pcb 以连接到远程主机并发送打开连接的初始 SYN 段。
tcp_connect() 函数立即返回;它不会等待连接正确设置。相反,它会在建立连接时调用指定为第四个参数(“connected”参数)的函数。如果连接不能正常建立,要么是因为对方主机拒绝连接,要么因为对方没有回答,这个pcb的“err”回调函数(注册到tcp_err,见下文)将被调用。
如果没有可用于排队 SYN 段的内存,tcp_connect() 函数可以返回 ERR_MEM。如果 SYN 确实成功入队,tcp_connect() 函数将返回 ERR_OK。
发送 TCP 数据
TCP 数据是通过调用 tcp_write() 将数据排入队列来发送的。当数据成功传输到远程主机时,应用程序将通过调用指定的回调函数得到通知。
err_t tcp_write(struct tcp_pcb *pcb, const void *dataptr, u16_t len, u8_t apiflags)
将参数 dataptr 指向的数据排入队列。数据的长度作为 len 参数传递。 apiflags 可以是以下一项或多项:
TCP_WRITE_FLAG_COPY:指示是否应该为要复制的数据分配新的内存。如果未给出此标志,则不应分配新内存,数据应仅由指针引用。这也意味着在数据被远程主机确认之前,dataptr 后面的内存不能改变 。
TCP_WRITE_FLAG_MORE:表示后面有更多数据。如果给出,则在此调用 tcp_write 创建的最后一个段中设置 PSH 标志。如果给出此标志,则不会设置 PSH 标志。
如果数据长度超过当前发送缓冲区大小或传出段队列的长度大于 lwipopts.h 中定义的上限,则 tcp_write() 函数将失败并返回 ERR_MEM。可以使用 tcp_sndbuf() 函数检索输出队列中可用的字节数。
使用此函数的正确方法是使用最多 tcp_sndbuf() 字节的数据调用该函数。如果该函数返回ERR_MEM,则应用程序应等待,直到其他主机成功接收到当前排队的某些数据,然后重试。
void tcp_sent(struct tcp_pcb *pcb, err_t (* sent)(void *arg, struct tcp_pcb *tpcb, u16_t len))
指定当远程主机成功接收(即确认)数据时应调用的回调函数。传递给回调函数的 len 参数给出了上次确认所确认的字节数。
接收TCP数据
TCP 数据接收是基于回调的 -当新数据到达时调用应用程序指定的回调函数。当应用程序获取数据后,它必须调用 tcp_recved() 函数来指示 TCP 可以通告增加接收窗口。
void tcp_recv(struct tcp_pcb *pcb, err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err))
设置新数据到达时将调用的回调函数。回调函数将传递一个 NULL pbuf 以指示远程主机已关闭连接。如果没有错误并且回调函数返回ERR_OK,那么它必须释放pbuf。否则,它不能释放 pbuf 以便 lwIP 核心代码可以存储它。
void tcp_recved(struct tcp_pcb *pcb, u16_t len)
必须在应用程序收到数据时调用。 len 参数表示接收数据的长度。
申请轮询
当连接空闲时(即没有数据传输或接收),lwIP 将通过调用指定的回调函数重复轮询应用程序。这既可以用作看门狗定时器,用于终止空闲时间过长的连接,也可以用作等待内存可用的方法。例如,如果对 tcp_write() 的调用由于内存不可用而失败,则应用程序可能会在连接空闲一段时间后使用轮询功能再次调用 tcp_write()。
void tcp_poll(struct tcp_pcb *pcb, err_t (* poll)(void *arg, struct tcp_pcb *tpcb), u8_t interval)
指定轮询间隔和应调用以轮询应用程序的回调函数。间隔以 TCP 粗粒度计时器的数量指定,通常每秒发生两次。间隔 10 表示每 5 秒轮询一次应用程序。
关闭和中止连接
err_t tcp_close(struct tcp_pcb *pcb)
关闭连接。如果没有内存可用于关闭连接,该函数可能会返回 ERR_MEM。如果是这样,应用程序应等待并使用确认回调或轮询功能重试。如果关闭成功,函数返回 ERR_OK。 pcb 在调用 tcp_close() 后由 TCP 代码释放。
void tcp_abort(struct tcp_pcb *pcb)
通过向远程主机发送 RST(重置)段来中止连接。 pcb 被释放。这个功能永远不会失效。
注意:从 TCP 回调之一调用此方法时,请确保始终返回 ERR_ABRT(否则永远不要返回 ERR_ABRT,否则您将面临访问已释放内存或内存泄漏的风险!如果连接因错误而中止,则应用程序会通过 err 回调收到此事件的警报。可能会中止连接的错误发生在内存不足时。要调用的回调函数是使用 tcp_err() 函数设置的。
void tcp_err(struct tcp_pcb *pcb, void (* err)(void *arg, err_t err))
错误回调函数不会将 pcb 作为参数传递给它,因为 pcb 可能已经被释放。
底层TCP接口
TCP 为系统的低层提供了一个简单的接口。在系统初始化期间,必须在调用任何其他 TCP 函数之前调用函数 tcp_init()。系统运行时,必须定期调用两个定时器函数 tcp_fasttmr() 和 tcp_slowtmr()。 tcp_fasttmr() 应每 TCP_FAST_INTERVAL 毫秒(在 tcp.h 中定义)调用一次,tcp_slowtmr() 应每 TCP_SLOW_INTERVAL 毫秒调用一次。