学会Zynq(20)TCP echo服务器(接收回调)

前两篇我们学习了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篇。测试结果如下:

学会Zynq(20)TCP echo服务器(接收回调)_第1张图片
至此,我们已经学习了lwIP中所有TCP连接、发送、接收相关的函数用法。下一篇最后再介绍一下TCP的轮询机制。

你可能感兴趣的:(FPGA,Zynq)