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

本文介绍了一个简单的TCP Echo服务器实现,该服务器能够接收任意IP地址和端口的数据,并将其原样返回。通过具体代码示例详细讲解了如何使用lwIP进行TCP连接、接收及发送数据的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前两篇我们学习了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的轮询机制。

<think>嗯,用户问的是如何在Vitis中实现两个ZYNQ开发板PS端的以太网TCP通信。我需要先回想一下ZYNQ的基本结构。ZYNQ的PS部分确实有以太网控制器,所以应该是通过PS的GEM接口来连接。首先,硬件配置应该要正确,比如时钟、MIO引脚分配,还有DDR配置,这些在Vivado里设置。然后,软件部分在Vitis里需要用lwIP库,因为lwIP适合嵌入式系统的TCP/IP协议栈。 接下来,用户可能需要知道具体的步骤。首先是硬件设计部分,每个ZYNQ的PS需要配置以太网外设,检查MIO是否正确,可能还要注意PHY的时钟源。然后生成硬件描述文件导出到Vitis。 然后是软件部分,创建两个应用工程,一个作为服务器,一个作为客户端。使用lwIP的API,比如tcp_new()来创建TCP控制块,绑定端口,监听连接。客户端那边需要知道服务器的IP地址,发起连接。发送和接收数据可能需要用到回调函数处理接收的数据。 可能需要注意的点是,两个开发板的IP要在同一子网,服务器和客户端的端口号要一致。还有PHY的硬件连接是否正确,比如通过网线直连可能需要交叉线,或者通过交换机。另外,lwIP的配置比如内存池大小,可能需要调整,防止数据量大时出问题。 用户可能会遇到的常见问题包括连接不上,这时候需要检查IP设置,防火墙,或者物理连接。还有数据传输出错,可能涉及到数据接收不完整,需要正确处理回调函数,或者调整缓冲区大小。调试的话,可以用打印信息或者Wireshark抓包。 需要确保步骤清晰,分硬件和软件部分,每个部分的关键点都提到。可能用户对Vitis和Vivado的流程不太熟,所以需要说明导出硬件到Vitis的步骤。另外,代码示例部分,给出服务器和客户端的框架,但要注意可能需要根据具体板子调整IP和端口。 最后,常见问题解答部分可以帮助用户自行排查问题,比如IP配置、物理连接、lwIP参数等。需要提醒用户根据实际硬件调整代码中的IP地址,比如192.168.1.10和192.168.1.20这样的例子。</think>在Vitis中实现两个ZYNQ开发板PS端的以太网TCP通信,可分为以下步骤: 一、硬件环境准备 1. 确保两个ZYNQ开发板: - 通过RJ45网线直连(需交叉线)或连接同一局域网 - PS端以太网控制器(GEM)已启用(通常为GEM0) - 在Vivado中正确配置: * MIO引脚分配(例如GEM0_RGMII接口) * 时钟配置(125MHz REF_CLK) * DDR参数设置 二、Vivado硬件设计 1. 为每个ZYNQ核配置: ```Block Design - 启用PS-PL配置中的GEM0 - 设置MIO Bank电压与PHY芯片匹配 - 配置DDR控制器参数 ``` 2. 生成比特流后导出硬件描述文件(.xsa) 三、Vitis软件工程配置 1. 创建独立应用工程(服务器端/客户端) 2. 添加lwIP库依赖: ```BSP Settings - 在Board Support Package中勾选lwIP库 - 配置lwIP参数: mem_size = 16000 # 内存池大小 pbuf_pool_size = 16 # 缓冲区数量 ``` 四、TCP通信实现框架 1. 服务器端代码结构: ```c // 创建TCP控制块 struct tcp_pcb *pcb = tcp_new(); tcp_bind(pcb, IP_ADDR_ANY, 5001); // 绑定端口 tcp_listen(pcb); // 设置接收回调 tcp_accept(pcb, server_accept); // 接收处理函数 static err_t server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err){ if(p != NULL){ // 处理接收数据 tcp_write(tpcb, p->payload, p->len, 1); pbuf_free(p); } return ERR_OK; } ``` 2. 客户端代码结构: ```c // 建立连接 struct tcp_pcb *pcb = tcp_new(); ip_addr_t server_ip; IP4_ADDR(&server_ip, 192,168,1,10); // 服务器IP tcp_connect(pcb, &server_ip, 5001, client_connected); // 发送数据 void send_data(){ struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, strlen(data), PBUF_RAM); memcpy(p->payload, data, p->len); tcp_write(pcb, p->payload, p->len, TCP_WRITE_FLAG_COPY); } ``` 五、关键配置注意事项 1. IP地址配置原则: - 两个设备需在同一子网 - 例:192.168.1.10/24 和 192.168.1.20/24 - 通过以下API设置: ```c netif_set_addr(netif, &ipaddr, &netmask, &gw); ``` 2. 物理层验证: - 使用PHY芯片寄存器检查链路状态 - 确保Auto-Negotiation完成(查看NSR寄存器) 六、调试技巧 1. 使用Wireshark抓包验证TCP三次握手 2. 通过串口打印关键状态: ```c xil_printf("TCP connection established, remote IP: %s\r\n", ipaddr_ntoa(&pcb->remote_ip)); ``` 3. 错误处理: - 检查lwIP返回状态码(ERR_OK表示成功) - 注意内存泄漏(及时释放pbuf) 常见问题解决方案: 1. 连接超时: - 确认防火墙已关闭 - 检查硬件PHY芯片供电和复位信号 2. 数据传输不稳定: - 增大lwIP内存池参数 - 添加流量控制机制 3. 性能优化: - 启用TCP窗口缩放选项 - 使用零拷贝API(tcp_send_data()) 实际开发中需根据具体硬件型号(如ZC702/ZCU102)调整PHY驱动参数,建议参考Xilinx官方示例工程(lwIP TCP Echo Server)进行二次开发。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值