网站如何发布和推广,wordpress编辑器哪个好用,模版型网站,河南省实名举报使用 lwIP 协议栈进行 TCP 裸机编程#xff0c;其本质就是编写协议栈指定的各种回调函数。将你的应用逻辑封装成函数#xff0c;注册到协议栈#xff0c;在适当的时候#xff0c;由协议栈自动调用#xff0c;所以称为回调。 注#xff1a;除非特别说明#xff0c;以下内…使用 lwIP 协议栈进行 TCP 裸机编程其本质就是编写协议栈指定的各种回调函数。将你的应用逻辑封装成函数注册到协议栈在适当的时候由协议栈自动调用所以称为回调。 注除非特别说明以下内容针对 lwIP 2.0.0 及以上版本。 向协议栈注册回调函数有专门的接口如下所示
tcp_err(pcb, errf); //注册 TCP 接到 RST 标志或发生错误回调函数 errf
tcp_connect(pcb, ipaddr, port, connected); //注册 TCP 建立连接成功回调函数 connecter
tcp_accept(pcb, accept); //注册 TCP 处于 LISTEN 状态时监听到有新的连接接入
tcp_recv(pcb, recv); //注册 TCP 接收到数据回调函数 recv
tcp_sent(pcb, sent); //注册 TCP 发送数据成功回调函数 sent
tcp_poll(pcb, poll, interval); //注册 TCP 周期性执行回调函数 poll本节讲述 recv 回调函数。
recv 回调函数
在 TCP 控制块中函数指针 recv 指向用户实现的函数当接收到有效数据时由协议栈调用此函数通知用户处理接收到的数据。 函数指针 recv 的类型为 tcp_recv_fn 该类型定义在 tcp.h 中
/** Function prototype for tcp receive callback functions. Called when data has* been received.** param arg Additional argument to pass to the callback function (see tcp_arg())* param tpcb The connection pcb which received data* param p The received data (or NULL when the connection has been closed!)* param err An error code if there has been an error receiving* Only return ERR_ABRT if you have called tcp_abort from within the* callback function!*/
typedef err_t (*tcp_recv_fn)(void *arg, struct tcp_pcb *tpcb,struct pbuf *p, err_t err);协议栈通过宏 TCP_EVENT_RECV(pcb,p,err,ret) 调用 pcb-recv 指向的函数。宏 TCP_EVENT_RECV 定义在 tcp_priv.h 中
#define TCP_EVENT_RECV(pcb,p,err,ret) \do { \if((pcb)-recv ! NULL) { \(ret) (pcb)-recv((pcb)-callback_arg,(pcb),(p),(err));\} else { \(ret) tcp_recv_null(NULL, (pcb), (p), (err)); \} \} while (0)以关键字 TCP_EVENT_RECV 搜索源码可以搜索到 2 处使用
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
TCP_EVENT_RECV(pcb, refused_data, ERR_OK, err);1 由 tcp_input 函数调用
指针 recv_data 是一个 struct pbuf 类型的指针定义在 tcp_in.c 文件中是一个静态变量
static struct pbuf *recv_data;经过 tcp_process 函数处理后如果接收到有效数据则指针 recv_data 指向数据 pbuf 此时协议栈通过宏 TCP_EVENT_RECV 调用用户编写的数据处理函数。
简化后的代码为
void
tcp_input(struct pbuf *p, struct netif *inp)
{// 经过一系列检测,没有错误/* 在本地找到有效的控制块 pcb */if (pcb ! NULL) {err tcp_process(pcb);/* A return value of ERR_ABRT means that tcp_abort() was calledand that the pcb has been freed. If so, we dont do anything. */if (err ! ERR_ABRT) {if (recv_data ! NULL) {/* Notify application that data has been received. */TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err ERR_ABRT) {goto aborted;}/* If the upper layer cant receive this data, store it */if (err ! ERR_OK) {pcb-refused_data recv_data;LWIP_DEBUGF(TCP_INPUT_DEBUG, (tcp_input: keep incoming packet, because pcb is \full\\n));}}/* Try to send something out. */tcp_output(pcb); // --- 注意这里调用了发送函数所以 recv 回调函数就没必要再调用这个函数}}
}从以上代码中可以看出
回调函数有返回值若发现异常用户层可以主动调用 tcp_abort 函数终止连接然后返回 ERR_ABRT 错误码协议栈会完成后续的操作
/* Notify application that data has been received. */
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
if (err ERR_ABRT) {goto aborted;
}如果正确的处理了数据回调函数必须返回 ERR_OK 错误码否则协议栈会认为用户没有接收这包数据就会对它进行缓存
/* If the upper layer cant receive this data, store it */
if (err ! ERR_OK) {pcb-refused_data recv_data;LWIP_DEBUGF(TCP_INPUT_DEBUG, (tcp_input: keep incoming packet, because pcb is \full\\n));
}所以上层如果来不及处理数据可以让协议栈暂存。这里暂存数据使用了指针 pcb-refused_data 需要注意一下因为接下来会再次看到它。
注意这里会调用 TCP 发送函数
/* Try to send something out. */
tcp_output(pcb);在 recv 回调函数中处理完接收到的数据后通常我们还会调用 tcp_write 函数回送数据。函数原型为
/*** ingroup tcp_raw* Write data for sending (but does not send it immediately).** It waits in the expectation of more data being sent soon (as* it can send them more efficiently by combining them together).* To prompt the system to send data now, call tcp_output() after* calling tcp_write().* * This function enqueues the data pointed to by the argument dataptr. The length of* the data is passed as the len parameter. The apiflags can be one or more of:* - TCP_WRITE_FLAG_COPY: indicates whether the new memory should be allocated* for the data to be copied into. If this flag is not given, no new memory* should be allocated and the data should only be referenced by pointer. This* also means that the memory behind dataptr must not change until the data is* ACKed by the remote host* - TCP_WRITE_FLAG_MORE: indicates that more data follows. If this is omitted,* the PSH flag is set in the last segment created by this call to tcp_write.* If this flag is given, the PSH flag is not set.** The tcp_write() function will fail and return ERR_MEM if the length* of the data exceeds the current send buffer size or if the length of* the queue of outgoing segment is larger than the upper limit defined* in lwipopts.h. The number of bytes available in the output queue can* be retrieved with the tcp_sndbuf() function.** The proper way to use this function is to call the function with at* most tcp_sndbuf() bytes of data. If the function returns ERR_MEM,* the application should wait until some of the currently enqueued* data has been successfully received by the other host and try again.** param pcb Protocol control block for the TCP connection to enqueue data for.* param arg Pointer to the data to be enqueued for sending.* param len Data length in bytes* param apiflags combination of following flags :* - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack* - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will not be set on last segment sent,* return ERR_OK if enqueued, another err_t on error*/
err_t
tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags)通过注释可以得知这个函数会尽可能把发送的数据组合在一起然后一次性发送出去因为这样更有效率。换句话说调用这个函数并不会立即发送数据如果希望立即发送数据需要在调用 tcp_write 函数之后调用 tcp_output 函数。
而现在我们又知道了在 tcp_input 函数中调用 recv 回调函数后协议栈会执行一次 tcp_output 函数这就是我们在 recv 回调函数中调用 tcp_write 函数能够立即将数据发送出去的原因
2 由 tcp_process_refused_data 函数调用
在上一节提到 “上层如果来不及处理数据可以让协议栈暂存。这里暂存数据使用了指针 pcb-refused_data ”而 tcp_process_refused_data 函数就是把暂存的数据重新提交给应用层处理。提交的方法是调用 recv 回调函数简化后的代码为
err_t
tcp_process_refused_data(struct tcp_pcb *pcb)
{/* set pcb-refused_data to NULL in case the callback frees it and thencloses the pcb */struct pbuf *refused_data pcb-refused_data;pcb-refused_data NULL;/* Notify again application with data previously received. */TCP_EVENT_RECV(pcb, refused_data, ERR_OK, err);if (err ERR_ABRT) {return ERR_ABRT;} else if(err ! ERR_OK){/* data is still refused, pbuf is still valid (go on for ACK-only packets) */pcb-refused_data refused_data;return ERR_INPROGRESS;}return ERR_OK;
}协议栈会在两处调用 tcp_process_refused_data 函数。 2.1 在 tcp_input 函数中调用
void
tcp_input(struct pbuf *p, struct netif *inp)
{// 经过一系列检测,没有错误/* 在本地找到有效的控制块 pcb */if (pcb ! NULL) {/* If there is data which was previously refused by upper layer */if (pcb-refused_data ! NULL) {if ((tcp_process_refused_data(pcb) ERR_ABRT) || // --- 这里((pcb-refused_data ! NULL) (tcplen 0))) {/* pcb has been aborted or refused data is still refused and the new segment contains data */if (pcb-rcv_ann_wnd 0) {/* this is a zero-window probe, we respond to it with current RCV.NXTand drop the data segment */tcp_send_empty_ack(pcb);}goto aborted;}}err tcp_process(pcb);/* A return value of ERR_ABRT means that tcp_abort() was calledand that the pcb has been freed. If so, we dont do anything. */if (err ! ERR_ABRT) {if (recv_data ! NULL) {/* Notify application that data has been received. */TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err ERR_ABRT) {goto aborted;}/* If the upper layer cant receive this data, store it */if (err ! ERR_OK) {pcb-refused_data recv_data;}}/* Try to send something out. */tcp_output(pcb);}}
}通过以上代码可以知道
在处理接收数据之前先检查一下是否有上次暂存的数据如果有则调用 tcp_process_refused_data 函数将暂存数据上报给应用层处理。无论上层有多少数据没有处理协议栈只暂存最后一次接收且上层没有处理的数据
/* If the upper layer cant receive this data, store it */
if (err ! ERR_OK) {pcb-refused_data recv_data;
}2.2 在 tcp_fasttmr 函数中调用 协议栈每隔 TCP_TMR_INTERVAL 默认 250毫秒调用一次 tcp_fasttmr 函数在这个函数中会检查 TCP_PCB 是否有尚未给上层应用处理的暂存数据如果有则调用 tcp_process_refused_data 函数将暂存数据上报给应用层处理。简化后的代码为
void
tcp_fasttmr(void)
{tcp_timer_ctr;tcp_fasttmr_start:pcb tcp_active_pcbs;while (pcb ! NULL) {if (pcb-last_timer ! tcp_timer_ctr) {next pcb-next;/* If there is data which was previously refused by upper layer */if (pcb-refused_data ! NULL) {tcp_active_pcbs_changed 0;tcp_process_refused_data(pcb); // --- 这里if (tcp_active_pcbs_changed) {/* application callback has changed the pcb list: restart the loop */goto tcp_fasttmr_start;}}pcb next;} else {pcb pcb-next;}}
}3 recv 函数的复用行为
前面看到了错误回调函数、连接成功回调函数、接收到数据回调函数后面还会看到发送成功回调函数等。那么我们合理推测应该也有连接关闭回调函数。在连接关闭时协议栈确实回调了一个函数但这个函数也是 recv 回调函数协议栈并没有提供单独的连接关闭回调函数而是复用了 recv 回调函数。协议栈使用宏 TCP_EVENT_CLOSED 封装了这一过程代码为
#define TCP_EVENT_CLOSED(pcb,ret) \do { \if(((pcb)-recv ! NULL)) { \(ret) (pcb)-recv((pcb)-callback_arg,(pcb),NULL,ERR_OK);\} else { \(ret) ERR_OK; \} \} while (0)注意调用 recv 函数时第 3 个参数为 NULL 这很重要。我们又知道recv 的原型为
typedef err_t (*tcp_recv_fn)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);所以第三个参数是 struct pbuf 型指针。 也就是说我们必须在 recv 回调函数中处理 pbuf 指针为 NULL 的特殊情况这表示远端主动关闭了连接这时我们应主动调用 tcp_close 函数关闭本地连接。一个典型的 recv 回调函数框架为
static err_t
app_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{if (p NULL) {// 连接关闭前的处理可选tcp_close(pcb);} else {if (err ! ERR_OK) {// 目前还没有使用 ERR_OK 之外的回调参数这里兼容以后的协议栈pbuf_free(p);return err;}// 更新窗口值必须调用tcp_recved(pcb,p-tot_len);// 在这里处理接收到的数据// 释放 pbuf必须 pbuf_free(p);}return ERR_OK;
}协议栈在 tci_input 函数中调用宏 TCP_EVENT_CLOSED 简化后的代码为
void
tcp_input(struct pbuf *p, struct netif *inp)
{// 经过一系列检测,没有错误/* 在本地找到有效的控制块 pcb */if (pcb ! NULL) {err tcp_process(pcb);if (err ! ERR_ABRT) {if (recv_flags TF_RESET) {// 收到 RST 标志回调 errf 函数TCP_EVENT_ERR(pcb-state, pcb-errf, pcb-callback_arg, ERR_RST);tcp_pcb_remove(tcp_active_pcbs, pcb);tcp_free(pcb);} else {if (recv_acked 0) {// 收到数据 ACK 应答回调 sent 函数TCP_EVENT_SENT(pcb, (u16_t)acked16, err);if (err ERR_ABRT) {goto aborted;}recv_acked 0;}if (recv_data ! NULL) {// 收到有效数据 回调 recv 函数TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err ERR_ABRT) {goto aborted;}}if (recv_flags TF_GOT_FIN) {// 收到 FIN 标志回调 recv 函数远端关闭连接TCP_EVENT_CLOSED(pcb, err); // --- 这里if (err ERR_ABRT) {goto aborted;}}/* Try to send something out. */tcp_output(pcb);}}}
}读后有收获资助博主养娃 - 千金难买知识但可以买好多奶粉 (〃‘▽’〃)