前两篇我们学习了TCP的发送,本文学习如何处理接收数据。本文使用TCP设计一个echo服务器,开发板将来自所有IP地址和端口的数据原路发送回去,功能和本系列第15篇的UDP echo服务器相同。
本文实例与SDK提供的“lwip echo server”例程相比要简化许多,没有使用DHCP协议。本文主要是学习TCP的接收回调,DHCP的内容会在后面专门讲述。
SDK程序设计
让Zynq工作在TCP server模式。与上一个TCP发送“Hello World”实例的主要差别体现在user_udp.c文件中, 其余文件代码基本相同(main.c的while循环中无需调用send_data函数发送数据)。
#include "user_tcp.h"
#define local_port 8080
static struct tcp_pcb *connected_pcb = NULL;
err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
/* 处于未建立状态则不要读取数据包 */
if (!p) {
tcp_close(tpcb);
xil_printf("tcp connection closed\r\n");
tcp_recv(tpcb, NULL);
return ERR_OK;
}
tcp_recved(tpcb, p->len); //已收到数据包
if (tcp_sndbuf(tpcb) > p->len) { //检测可用空间字节数
send_data(p); //echo
}
else
xil_printf("no space in tcp_sndbuf\n\r");
pbuf_free(p); //释放pbuf
return ERR_OK;
}
//--------------------------------------------------
// TCP连接成功的回调函数
//--------------------------------------------------
err_t connect_accept_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
{
xil_printf("tcp_server: Connection Accepted\r\n");
connected_pcb = tpcb; //存储连接的TCP状态
tcp_nagle_disable(connected_pcb);
tcp_recv(connected_pcb, tcp_recv_callback);
return ERR_OK;
}
//--------------------------------------------------
// TCP PCB初始化函数
//--------------------------------------------------
int tcp_send_init()
{
struct tcp_pcb *pcb;
err_t err;
/* 创建新的TCP PCB */
pcb = tcp_new();
if (!pcb) {
xil_printf("txperf: Error creating PCB. Out of Memory\r\n");
return -1;
}
/* 绑定本地端口 */
err = tcp_bind(pcb, IP_ADDR_ANY, local_port);
if (err != ERR_OK) {
xil_printf("tcp_server: Unable to bind to port %d: err = %d\r\n", local_port, err);
return -2;
}
/* 监听连接 */
tcp_arg(pcb, NULL);
pcb = tcp_listen(pcb);
if (!pcb) {
xil_printf("tcp_server: Out of memory while tcp_listen\r\n");
return -3;
}
/* 设置accept回调函数 */
tcp_accept(pcb, connect_accept_callback);
return 0;
}
//--------------------------------------------------
// TCP数据发送函数
//--------------------------------------------------
void send_data(struct pbuf* p)
{
err_t err;
struct tcp_pcb *tpcb = connected_pcb;
if (!connected_pcb)
return;
err = tcp_write(tpcb, p->payload, p->len, 3);
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_write: %d\r\n", err);
connected_pcb = NULL;
return;
}
err = tcp_output(tpcb);
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_output: %d\r\n",err);
return;
}
}
在建立连接的accept回调函数connect_accept_callback中,使用tcp_recv函数绑定当TCP连接收到数据时的回调函数tcp_recv_callback。
接收回调函数的主要流程如下:
1. 检查连接是否建立,如果未建立则不要读取数据包
2. 应用程序收到数据后,使用tcp_recved函数增大窗口大小
3. 判断PCB用于发送的缓冲区是否有足够的空间,有则发送数据
4. 释放pbuf
在输出数据时,我们可以用pbuf的payload和len字段作为发送数据指针和长度。如果对某些tcp相关函数不清楚,可查看本系列第11篇。测试结果如下:
至此,我们已经学习了lwIP中所有TCP连接、发送、接收相关的函数用法。下一篇最后再介绍一下TCP的轮询机制。
---------------------