ZYNQ|LWIP协议栈学习(4)

上一篇当中用freertos系统,Socket API接口和UDP协议完成了数据从开发板发送到上位机。

今天的任务是学习数据的接收,做一个echo服务器,让开发板把接收的数据再发回来给上位机。

然后学习如何将DDR内存中的数据发送到上位机当中。

一、数据的接收

主函数main.c:

在主函数当中,创建了main thread主线程,然后创建网络线程。

#include <sleep.h>
#include "netif/xadapter.h"
#include "platform_config.h"
#include "xil_printf.h"
#include "lwip/init.h"
#include "lwip/inet.h"

static int complete_nw_thread;

void print_app_header();
void start_application();

#define THREAD_STACKSIZE 1024

#define DEFAULT_IP_ADDRESS "192.168.1.10"
#define DEFAULT_IP_MASK "255.255.255.0"
#define DEFAULT_GW_ADDRESS "192.168.1.1"

struct netif server_netif;

/*  按照标准格式打印IP地址函数  */
static void print_ip(char *msg, ip_addr_t *ip)
{
	xil_printf(msg);
	xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip),
				ip4_addr3(ip), ip4_addr4(ip));
}
/*  打印相关参数设置,IP地址,子网掩码,网关   */
static void print_ip_settings(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
	print_ip("Board IP:       ", ip);
	print_ip("Netmask :       ", mask);
	print_ip("Gateway :       ", gw);
}
/*  设置默认参数,IP地址,子网掩码,网关   */
static void assign_default_ip(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
	int err;

	xil_printf("Configuring default IP %s \r\n", DEFAULT_IP_ADDRESS);

	err = inet_aton(DEFAULT_IP_ADDRESS, ip);
	if(!err)
		xil_printf("Invalid default IP address: %d\r\n", err);

	err = inet_aton(DEFAULT_IP_MASK, mask);
	if(!err)
		xil_printf("Invalid default IP MASK: %d\r\n", err);

	err = inet_aton(DEFAULT_GW_ADDRESS, gw);
	if(!err)
		xil_printf("Invalid default gateway address: %d\r\n", err);
}

void network_thread(void *p)
{
	/* the mac address of the board. this should be unique per board
	 * 设置开发板的MAC地址*/
	u8_t mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };

	/* Add network interface to the netif_list, and set it as default
	 * 设置网络接口并设置为默认接口*/
	if (!xemac_add(&server_netif, NULL, NULL, NULL, mac_ethernet_address,
		PLATFORM_EMAC_BASEADDR)) {
		xil_printf("Error adding N/W interface\r\n");
		return;
	}

	netif_set_default(&server_netif);

	/* specify that the network if is up
	 * 开启网络接口 */
	netif_set_up(&server_netif);

	/* start packet receive thread - required for lwIP operation
	 * 开始线程,相当于那个必须要有的一句input*/
	sys_thread_new("xemacif_input_thread",
			(void(*)(void*))xemacif_input_thread, &server_netif,
			THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);

	complete_nw_thread = 1;

	vTaskDelete(NULL);

}

int main_thread()
{
	xil_printf("\n\r\n\r");
	xil_printf("-----lwIP Socket Mode UDP Server Application------\r\n");

	/* initialize lwIP before calling sys_thread_new
	 * 初始化LWIP库 */
	lwip_init();

	/* any thread using lwIP should be created using sys_thread_new
	 * 创建网络线程*/
	sys_thread_new("nw_thread", network_thread, NULL,
			THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);

	while(!complete_nw_thread)
		usleep(50);
	/* 设置默认IP地址等参数(这里设置的是开发板的参数) */
	assign_default_ip(&(server_netif.ip_addr), &(server_netif.netmask),
				&(server_netif.gw));
	/* 打印参数,方便调试观察 */
	print_ip_settings(&(server_netif.ip_addr), &(server_netif.netmask),
				&(server_netif.gw));
	xil_printf("\r\n");


	/* start the application
	 * 开启网络传输*/
	start_application();

	vTaskDelete(NULL);
	return 0;
}

int main()
{
	/* 创建主线程 */
	sys_thread_new("main_thread", (void(*)(void*))main_thread, 0,
			THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
	vTaskStartScheduler();
	while(1);
	return 0;
}

udp_perf_server.c:


/** Connection handle for a UDP Server session */

#include "udp_perf_server.h"

extern struct netif server_netif;
static struct perf_stats server;


/** Receive data on a udp session */
static void udp_recv_perf_traffic(int sock)
{
	int count;
	char recv_buf[UDP_RECV_BUFSIZE];
	struct sockaddr_in from;
	socklen_t fromlen = sizeof(from);

	while (1) {
		/* 接收数据并且会存到recv_buf缓冲区当中,count表示接收到的字节数 */
		count = lwip_recvfrom(sock, recv_buf, UDP_RECV_BUFSIZE, 0,
				(struct sockaddr *)&from, &fromlen) ;

		if (count > 0) {
		    // 数据接收成功
		    // 这里只是简单地将接收到的数据原封不动地发送回去
		   // sendto(sock, recv_buf, count, 0,
		    //		(struct sockaddr *)&from, fromlen);
		    lwip_sendto(sock, recv_buf, count, 0,
                                (struct sockaddr *)&from, fromlen);

		        }
		}
}

void start_application(void)
{
	err_t err;
	int sock;
	struct sockaddr_in addr;
	/* 创建套接字 */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		xil_printf("UDP server: Error creating Socket\r\n");
		return;
	}
	/* 清零addr的空间 */
	memset(&addr, 0, sizeof(struct sockaddr_in));
	/* 设置IPV4模式,端口号,
	 * 设置网络套接字地址的IP地址部分为任意可用的地址。
	 * `INADDR_ANY`是一个特殊的常量,表示任意主机地址
	 * 所以它可以接收到任意主机发来的数据 */
	addr.sin_family = AF_INET;
	addr.sin_port = htons(UDP_CONN_PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	/* 连接到远程主机 */
	err = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
	if (err != ERR_OK) {
		xil_printf("UDP server: Error on bind: %d\r\n", err);
		close(sock);
		return;
	}
	//接收数据的函数
	udp_recv_perf_traffic(sock);
}

二、具体函数解析:

首先main.c函数和上次的发送测试实验是一样的。

在udp_perf_server.c文件当中,前面也都一样,除了设置addr的地方,原来addr.sin_addr.s_addr = htonl(INADDR_ANY);第三句里设置的是个确定的参数,是电脑的IP地址,但是现在改成INADDR_ANY,意味着不论从哪个IP地址发来的数据都可以接收

	/* 创建套接字 */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		xil_printf("UDP server: Error creating Socket\r\n");
		return;
	}
	/* 清零addr的空间 */
	memset(&addr, 0, sizeof(struct sockaddr_in));
	/* 设置IPV4模式,端口号,
	 * 设置网络套接字地址的IP地址部分为任意可用的地址。
	 * `INADDR_ANY`是一个特殊的常量,表示任意主机地址
	 * 所以它可以接收到任意主机发来的数据 */
	addr.sin_family = AF_INET;
	addr.sin_port = htons(UDP_CONN_PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	/* 连接到远程主机 */
	err = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
	if (err != ERR_OK) {
		xil_printf("UDP server: Error on bind: %d\r\n", err);
		close(sock);
		return;
	}

 关键是这个函数    udp_recv_perf_traffic(sock);

在socket API接口当中,不需要使用回调函数,直接调用lwip_recvfrom来接收数据,就可以把数据存到recv_buf当中,然后可以在做一些想要的操作

lwip_recvfrom函数:

  • 1. `sock`: 这是接收端套接字的文件描述符(socket descriptor),它是之前使用 `socket` 函数创建的套接字的标识符。
  • 2. `recv_buf`: 这是用于接收数据的缓冲区。当函数成功接收到数据时,接收到的数据将被存储在这个缓冲区中。
  • 3. `UDP_RECV_BUFSIZE`: 这是接收缓冲区的大小,表示 `recv_buf` 缓冲区能够存储的最大字节数。这个值应该根据你的需求和系统资源来确定。
  • 4. `0`: 这是一个用于设置接收数据的标志位参数。在这里,0 表示没有特殊的接收选项。
  • 5. `(struct sockaddr *)&from`: 这是一个指向 `struct sockaddr` 类型的指针,用于指定发送端的地址信息。在函数成功接收到数据后,发送端的地址信息将被填充到这个结构体中。
  • 6. `&fromlen`: 这是发送端地址结构体的大小,以字节为单位。在调用函数之前,它应该被设置为 `sizeof(struct sockaddr_in)`,表示接收端地址结构体 `from` 的大小。在函数成功接收到数据后,这个值将被更新为发送端地址结构体的实际大小。

`lwip_recvfrom` 函数用于从指定的套接字接收数据,并将数据存储到指定的接收缓冲区中。在接收数据之后,还可以通过发送端的地址信息来确定数据的来源。

count表示的是接收到的字节个数

count = lwip_recvfrom(sock, recv_buf, UDP_RECV_BUFSIZE, 0,
				(struct sockaddr *)&from, &fromlen) ;

lwip_sendto函数:

原型函数各个参数的含义如下:

  • `s`:表示要发送数据的套接字(socket),是一个文件描述符或套接字描述符。 
  • `dataptr`:表示指向要发送数据的缓冲区的指针,即待发送数据的起始地址。 
  • `size`:表示要发送数据的字节数。
  •  `flags`:表示发送操作的标志,通常设置为 0。
  • `to`:表示指向目标地址的指针,即数据发送的目的地地址。对于 UDP,目标地址是一个结构体 `struct sockaddr`,通常为 `struct sockaddr_in` 类型。
  • `tolen`:表示目标地址结构体的长度,通常为 `sizeof(struct sockaddr_in)`。

在给定的函数调用中,`lwip_sendto` 函数用于向指定的目标地址发送 UDP 数据包。具体来说:

  •  `sock` 是套接字描述符,用于标识数据将通过哪个套接字发送。
  •  `recv_buf` 是一个指向接收到的数据的缓冲区的指针,它包含了要发送的数据。
  •  `count` 是接收到的数据的字节数,指示了要发送的数据的大小。
  •  `0` 表示没有特殊的标志位设置。
  •  `(struct sockaddr *)&from` 是一个指向目标地址的指针,它是一个 `struct sockaddr_in` 类型的指针,表示数据将发送到接收方的地址。在这里,`from` 表示数据的发送方地址。
  •  `fromlen` 是目标地址结构体的长度,通常是 `sizeof(struct sockaddr_in)`。

这个函数调用的作用是将接收到的数据通过 UDP 原封不动地发送回给发送方。

//函数原型
err_t lwip_sendto(int s, const void *dataptr, size_t size, int flags,
                  const struct sockaddr *to, socklen_t tolen);
//调用后的
lwip_sendto(sock, recv_buf, count, 0,
         (struct sockaddr *)&from, fromlen);

三、上板测试

一开始怎么也接收不到

后来我试着改了一下下面的端口号,把它改成了5001之后,就可以收到了!

那这个时候定义的5001这个端口就是开发板的了?

好奇怪,没懂。

这时候我再修改一下电脑的端口号,不受影响

修改电脑的IP地址,也可以正确的收发了。

 问题:

端口号这个到底设置的是哪一个,没弄明白,需要再确认一下。

现在发送的数据量很小,后面应该是很大的,怎么能全部按顺序发送上去?

怎么取DDR的数?

  • 31
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Zynq是一种嵌入式处理器,具备高度可编程的特性。要实现TCP协议,需要在Zynq的硬件平台上设计和实现一套完整的网络协议。 首先,需要使用硬件描述语言(HDL)或者可编程逻辑门阵列(FPGA)设计和实现以太网控制器的硬件。这个以太网控制器负责物理层和数据链路层的操作,包括帧的发送和接收,MAC地址的解析,以及硬件的初始化和配置。 其次,需要在Zynq的处理器系统上运行一套网络协议的软件。这个软件可以使用开源的网络协议库,比如lwIP(lightweight IP)等。lwIP是一个轻量级的TCP/IP协议,适用于嵌入式系统。通过在软件层面实现网络协议,可以实现IP层、传输层和应用层的功能,包括IP数据包的路由、TCP连接管理、数据传输等。 通过在硬件和软件层面的协同工作,可以在Zynq平台上实现TCP协议。具体的实现过程包括:配置和初始化硬件以太网控制器,然后通过软件加载网络协议库,并进行必要的配置和初始化。接下来,可以通过对网络协议的API和接口进行编程,实现具体的应用逻辑和功能,如网络通信、数据传输、网络协议的处理等。 Zynq实现TCP协议的优势在于,它的硬件和软件资源可以高度定制和配置,可以根据具体的应用需求进行优化和扩展。同时,Zynq平台的可编程性使得实现和调试网络协议更加灵活和方便。然而,在实际应用中,还需要考虑到资源消耗、性能优化、网络安全等方面的问题,以确保整个系统的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值