【兆易GD32H759I-EVAL】 FreeRTOS_tcpudp例程学习与TCP测试
[复制链接]
本帖最后由 尹小舟 于 2024-6-11 22:37 编辑
前面分析了main函数,我们现在继续学习分析LWIP;
/*!
\brief initializes the LwIP stack
\param[in] none
\param[out] none
\retval none
*/
void lwip_stack_init(void)
{
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
/* create tcp_ip stack thread */
tcpip_init( NULL, NULL );
/* IP address setting */
#ifdef USE_DHCP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else
IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
#endif /* USE_DHCP */
#ifdef USE_ENET0
/* - netif_add(struct netif *netif, ip_addr_t *ipaddr,
ip_addr_t *netmask, ip_addr_t *gw,
void *state, err_t (* init)(struct netif *netif),
err_t (* input)(struct pbuf *p, struct netif *netif))
Adds your network interface to the netif_list. Allocate a struct
netif and pass a pointer to this structure as the first argument.
Give pointers to cleared ip_addr structures when using DHCP,
or fill them with sane numbers otherwise. The state pointer may be NULL.
The init function pointer must point to a initialization function for
your ethernet netif interface. The following code illustrates it's use.*/
netif_add(&g_mynetif0, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* registers the default network interface */
netif_set_default(&g_mynetif0);
netif_set_status_callback(&g_mynetif0, lwip_netif_status_callback);
/* when the netif is fully configured this function must be called */
netif_set_up(&g_mynetif0);
#endif /* USE_ENET0 */
#ifdef USE_ENET1
/* - netif_add(struct netif *netif, ip_addr_t *ipaddr,
ip_addr_t *netmask, ip_addr_t *gw,
void *state, err_t (* init)(struct netif *netif),
err_t (* input)(struct pbuf *p, struct netif *netif))
Adds your network interface to the netif_list. Allocate a struct
netif and pass a pointer to this structure as the first argument.
Give pointers to cleared ip_addr structures when using DHCP,
or fill them with sane numbers otherwise. The state pointer may be NULL.
The init function pointer must point to a initialization function for
your ethernet netif interface. The following code illustrates it's use.*/
netif_add(&g_mynetif1, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* registers the default network interface */
netif_set_default(&g_mynetif1);
netif_set_status_callback(&g_mynetif1, lwip_netif_status_callback);
/* when the netif is fully configured this function must be called */
netif_set_up(&g_mynetif1);
#endif /* USE_ENET1 */
}
函数定义
void lwip_stack_init(void)
{
// ...
}
变量定义
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
这三个变量用于存储 IP 地址、子网掩码和网关地址。
lwIP 栈初始化
tcpip_init( NULL, NULL );
tcpip_init() 函数用于初始化 lwIP 的 TCP/IP 核心模块。它通常创建一个或多个线程来处理 lwIP 的内部操作。在这个例子中,tcpip_init() 被调用时没有传递任何参数(NULL, NULL),这可能意味着使用了默认的参数,这些参数对于当前配置不是必需的。
IP 地址设置
#ifdef USE_DHCP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else
IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
#endif /* USE_DHCP */
如果定义了 USE_DHCP ,则 IP 地址、子网掩码和网关地址被初始化为 0,表示这些值将通过 DHCP 协议自动获取。
如果没有定义 USE_DHCP ,则使用静态配置的 IP 地址、子网掩码和网关。这里使用 IP4_ADDR 宏来设置这些值。
网络接口添加
#ifdef USE_ENET0
netif_add(&g_mynetif0, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
netif_set_default(&g_mynetif0);
netif_set_status_callback(&g_mynetif0, lwip_netif_status_callback);
netif_set_up(&g_mynetif0);
#endif /* USE_ENET0 */
如果定义了 USE_ENET0,则使用 netif_add() 函数将网络接口 g_mynetif0 添加到 lwIP 的网络接口列表中。这个函数需要网络接口结构体的指针、IP 地址、子网掩码、网关地址、一个用户定义的状态指针(在这里是 NULL)、初始化函数 ethernetif_init 和输入函数 tcpip_input。然后,netif_set_default() 函数将 g_mynetif0 设置为默认的网络接口。这意味着在没有明确指定网络接口的情况下,lwIP 将使用此接口发送数据。netif_set_status_callback() 函数用于设置网络接口的状态回调函数 lwip_netif_status_callback。当网络接口的状态发生变化时(例如,链接上或链接下),这个回调函数将被调用。最后,netif_set_up() 函数将网络接口设置为启动状态,使其能够发送和接收数据包。
lwip_stack_init() 函数通过调用 tcpip_init() 初始化 lwIP 栈,并根据是否定义了 USE_DHCP 来选择使用 DHCP 还是静态配置来设置 IP 地址、子网掩码和网关。然后,如果定义了 USE_ENET0,它将添加网络接口 g_mynetif0 到 lwIP 的网络接口列表中,并设置其默认状态和状态回调函数。最后,它将网络接口设置为启动状态。
lwip_netif_status_callback
该函数是LWIP(Lightweight IP)网络栈中网络接口状态变化时的回调函数。当网络接口完全配置并启动后,该函数会被调用以初始化telnet服务器、TCP客户端和UDP回显服务。
/*!
\brief after the netif is fully configured, it will be called to initialize the function of telnet, client and udp
\param[in] netif: the struct used for lwIP network interface
\param[out] none
\retval none
*/
void lwip_netif_status_callback(struct netif *netif)
{
if(((netif->flags & NETIF_FLAG_UP) != 0) && (0 != netif->ip_addr.addr)) {
/* initilaize the tcp server: telnet 8000 */
hello_gigadevice_init();
/* initilaize the tcp client: echo 10260 */
tcp_client_init();
/* initilaize the udp: echo 1025 */
udp_echo_init();
}
}
- 函数名:
- void lwip_netif_status_callback(struct netif *netif):函数没有返回值(void),并接受一个指向netif结构的指针作为参数。netif结构包含了网络接口的配置信息。
- 函数体:
- 首先,函数检查netif的两个状态标志:
- NETIF_FLAG_UP:确保网络接口是启动的(up)。
- netif->ip_addr.addr:确保网络接口已分配了一个非零的IP地址。
- 如果上述两个条件都满足,函数会初始化以下服务:
-
hello_gigadevice_init():这个函数用于初始化TCP服务器:,监听在8000端口上。
-
tcp_client_init():这个函数是用于初始化一个TCP客户端的,端口10260
-
udp_echo_init():这个函数用于初始化一个UDP回显服务,监听在1025端口上。UDP回显服务是一个简单的服务,它接收客户端发送的UDP数据包,并将完全相同的数据包发送回客户端。
hello_task
/*!
\brief hello task
\param[in] arg: user supplied argument
\param[out] none
\retval none
*/
static void hello_task(void *arg)
{
int ret;
int sockfd = -1, newfd = -1;
uint32_t len;
int tcp_port = 8000;
int recvnum;
struct sockaddr_in svr_addr, clt_addr;
char buf[50];
/* bind to port 8000 at any interface */
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(tcp_port);
svr_addr.sin_addr.s_addr = htons(INADDR_ANY);
name_recv.length = 0;
name_recv.done = 0;
while(1) {
/* create a TCP socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
continue;
}
ret = bind(sockfd, (struct sockaddr *)&svr_addr, sizeof(svr_addr));
if(ret < 0) {
lwip_close(sockfd);
sockfd = -1;
continue;
}
/* listen for incoming connections (TCP listen backlog = 1) */
ret = listen(sockfd, 1);
if(ret < 0) {
lwip_close(sockfd);
continue;
}
len = sizeof(clt_addr);
/* grab new connection */
newfd = accept(sockfd, (struct sockaddr *)&clt_addr, (socklen_t *)&len);
if(-1 != newfd) {
send(newfd, (void *)&GREETING, sizeof(GREETING), 0);
}
while(-1 != newfd) {
/* reveive packets, and limit a reception to MAX_NAME_SIZE bytes */
recvnum = recv(newfd, buf, MAX_NAME_SIZE, 0);
if(recvnum <= 0) {
lwip_close(newfd);
newfd = -1;
break;
}
hello_gigadevice_recv(newfd, buf, recvnum);
}
lwip_close(sockfd);
sockfd = -1;
}
}
这个hello_task函数是一个基于LWIP(Lightweight IP)库的TCP服务器任务。它监听在特定端口(8000)上的TCP连接,并在连接建立后发送一个问候消息(GREETING,这个变量在函数外部定义,没有在此段代码中给出),然后接收并处理来自客户端的数据。以下是对该函数的详细解释:
-
变量定义:
- sockfd 和 newfd:分别用于监听套接字和与客户端通信的套接字。
- tcp_port:服务器监听的TCP端口号(8000)。
- svr_addr 和 clt_addr:分别用于存储服务器和客户端的地址信息。
- buf:用于存储从客户端接收到的数据。
- recvnum:表示从客户端接收到的数据字节数。
- len:用于accept函数,表示客户端地址结构的长度。
- ret:用于存储系统调用的返回值。
-
设置服务器地址:
- 使用AF_INET(IPv4)作为地址族。
- 监听端口设置为8000。
- 绑定到任何可用的网络接口(INADDR_ANY)。
-
无限循环:
- 在这个循环中,服务器不断地尝试创建套接字、绑定、监听,并接受新的连接。
-
创建套接字:
-
绑定套接字:
- 使用bind函数将套接字绑定到指定的地址和端口。
- 如果绑定失败,关闭套接字并继续下一次循环。
-
监听连接:
- 使用listen函数开始监听连接。
- 如果监听失败,关闭套接字并继续下一次循环。
-
接受连接:
-
接收和处理数据:
- 在内部循环中,使用recv函数从客户端接收数据。
- 如果接收到的数据长度小于或等于0(表示连接已关闭或出错),则关闭与客户端的套接字并退出内部循环。
- 否则,调用hello_gigadevice_recv函数(这个函数在代码段中没有给出,可能是处理接收到的数据的自定义函数)。
-
关闭套接字:
- 这是为了重新进入循环并尝试再次绑定和监听。但通常,服务器只需要一个监听套接字,并在整个运行期间保持打开状态。这里的关闭和重新创建可能是出于某种特定的需求或测试目的。
实验现象
|