上一篇当中用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的数?