【普中Hi3861开发攻略--基于鸿蒙OS】-- 第 28 章 WIFI 实验-UDP 通信

(1)实验平台:

普中Hi3861鸿蒙物联网WIFI套件https://item.taobao.com/item.htm?id=829136021914(2)资料下载:普中科技-各型号产品资料下载链接


如今物联网市场异常火爆, WIFI 是物联网中非常重要的角色, 现在基本上家家户户都有 WIFI 网络, 通过 WIFI 接入到互联网, 成了智能家居产品普遍的选择。 Hi3861 内部已集成 WIFI 功能, 可以说它就是为 WIFI 无线连接而生的。 本章来学习 WIFI 的 UDP 网络通信, 使用 Hi3861 开发 WIFI 是非常简单而美妙的,让大家在学习物联网中变的简单有趣。 本章分为如下几部分内容:

[28.1 实验介绍](#28.1 实验介绍)

[28.1.1 实验简介](#28.1.1 实验简介)

[28.1.1.1 UDP 协议的特点](#28.1.1.1 UDP 协议的特点)

[28.1.1.2 UDP 通信实现](#28.1.1.2 UDP 通信实现)

[28.1.1.3 UDP 通信应用场景](#28.1.1.3 UDP 通信应用场景)

[28.1.2 实验目的](#28.1.2 实验目的)

[28.1.3 WIFI 函数使用](#28.1.3 WIFI 函数使用)

[28.1.3.1 socket 函数](#28.1.3.1 socket 函数)

[28.1.3.2 htons 函数](#28.1.3.2 htons 函数)

[28.1.3.3 inet_addr 函数](#28.1.3.3 inet_addr 函数)

[28.1.3.4 sendto 函数](#28.1.3.4 sendto 函数)

[28.1.3.5 recvfrom 函数](#28.1.3.5 recvfrom 函数)

[28.1.3.6 closesocket 函数](#28.1.3.6 closesocket 函数)

[28.2 硬件设计](#28.2 硬件设计)

[28.3 软件设计](#28.3 软件设计)

[28.4 实验现象](#28.4 实验现象)


28.1 实验介绍

28.1.1 实验简介

UDP 通信是一种基于用户数据报协议(User Datagram Protocol, UDP) 的网络通信方式。 UDP 是一种无连接的、 不可靠的、 面向数据报的传输层协议, 它主要用于那些对实时性要求较高, 但对数据准确性要求不是非常严格的场景。 以下是对 UDP 通信的详细介绍:

28.1.1.1 UDP 协议的特点

●无连接: UDP 在发送数据之前不需要与对方建立连接, 每个数据报都是一个独立的信息传输单元。

●不可靠传输: UDP 不保证数据的可靠传输, 它只负责将数据报发送出去,不保证对方是否接收到数据, 也不进行数据重传。

●面向数据报: UDP 协议是面向数据报的, 即发送端发送的每个数据报都是一个独立的单元, 接收端也是以数据报为单位进行接收。

●传输效率高: 由于 UDP 不需要建立连接和进行复杂的可靠性控制, 因此其传输效率比 TCP 高。

●支持多对多通信: UDP 支持一对一、 一对多、 多对一和多对多的交互通信。

28.1.1.2 UDP 通信实现

在 Hi3861 上实现 UDP 通信, 通常需要以下几个步骤:

1、 创建套接字(Socket) :

套接字是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。可以通过调用 socket()函数来创建一个 UDP 套接字。

2、 设置服务器地址和端口:

对于 UDP 服务端, 需要设置服务器的 IP 地址和端口号, 并将套接字与这个地址和端口绑定。

对于 UDP 客户端, 需要知道服务端的 IP 地址和端口号, 以便将数据发送到服务端。

3、 发送和接收数据:

使用 sendto()函数发送数据, 该函数允许 UDP 客户端将数据发送到指定的IP 地址和端口。

使用 recvfrom()函数接收数据, 该函数允许 UDP 服务端或客户端接收来自指定 IP 地址和端口的数据。

4、 关闭套接字:

在通信结束后, 应该关闭套接字以释放资源。 可以通过调用 close()函数来关闭套接字。

28.1.1.3 UDP 通信应用场景

由于 UDP 具有传输效率高、 实时性好等特点, 因此它常被用于以下场景:

●实时音视频传输: 如在线视频、 视频会议等场景, 对实时性要求较高, 但对数据丢失的容忍度较高。

●实时游戏通信: 在游戏中, 玩家之间的实时交互对延迟要求很高, UDP 可以提供较低的延迟。

●DNS 查询: DNS 服务器通常使用 UDP 协议来响应客户端的域名查询请求。●广播通信: UDP 支持广播通信, 可以将数据发送给局域网内的所有设备。

28.1.2 实验目的

连接路由器热点, 打开网络调试助手, 选择 UDP 协议连接, 即可与开发板进行 UDP 通信, 开发板作为客户端, 电脑作为服务端。

28.1.3 WIFI 函数使用

28.1.3.1 socket 函数

用于创建新的套接字(socket) 的基础函数。 套接字是网络通信中的端点,它允许两个程序(通常是在不同的主机上) 通过网络进行通信。 函数原型:

cpp 复制代码
int socket(int domain, int type, int protocol);

参数:

domain: 指定套接字使用的协议族。 常用的协议族有 AF_INET(IPv4) 和AF_INET6(IPv6) 。

type: 指定套接字的类型。 常见的类型有 SOCK_STREAM(面向连接的 TCP 套接字) 和 SOCK_DGRAM(无连接的 UDP 套接字) 。

protocol: 指定使用的特定协议。 大多数情况下, 这个参数被设置为 0, 表示选择该类型的默认协议。

返回值:

如果函数调用成功, 它将返回一个非负整数, 即新创建的套接字的描述符(或称为文件描述符) 。 这个描述符用于后续的套接字操作, 如绑定地址(bind) 、监听连接(listen) 、 接受连接(accept) 、 发送数据(send/sendto) 、 接收数据(recv/recvfrom) 等。 如果函数调用失败, 它将返回 -1, 并设置全局变量errno 以指示错误原因。

28.1.3.2 htons 函数

htons 函数是一个在计算机网络编程中广泛使用的函数, 特别是在涉及TCP/IP 协议栈的编程时。 它的主要作用是将一个无符号短整型数值(通常是 16位) 从主机字节顺序(Host Byte Order) 转换为网络字节顺序(Network Byte Order) , 主机字节顺序是特定于计算机体系结构的, 可能是大端模式也可能是小端模式。 而网络字节顺序总是大端模式, 即高位字节存放在内存的低地址处,低位字节存放在内存的高地址处。 函数原型:

cpp 复制代码
#define htons lwip_htons
u16_t lwip_htons(u16_t x);

参数:

x: 16 位无符号整型数据;

返回值: 转换后的数据;

28.1.3.3 inet_addr 函数

主要功能是将一个点分十进制的 IP 地址字符串转换成一个长整型数(在大多数系统中是 u_long 或 unsigned long 类型) , 这个长整型数以网络字节序(大端模式) 表示 IPv4 地址。 inet_addr 函数仅适用于 IPv4 地址。 对于 IPv6 地址,需要使用其他函数(如 inet_pton) 进行处理。 函数原型:

cpp 复制代码
in_addr_t inet_addr(const char *cp);

参数:

cp 是指向以 null 结尾的字符串的指针, 该字符串表示点分十进制的 IPv4地址。

返回值: 返回的 IP 地址是以网络字节序(大端模式) ;

28.1.3.4 sendto 函数

sendto 函数是计算机编程中用于网络编程的一个重要函数, 特别是在处理UDP(用户数据报协议) 通信时。 该函数的作用是将数据发送到指定的目的地,而不需要事先建立连接(与 TCP 协议不同) 。 函数原型:

cpp 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数:

sockfd: 已创建好的套接字文件描述符。

buf: 指向要发送数据的缓冲区的指针。

len: 要发送的数据长度。

flags: 发送选项, 通常设置为 0。

dest_addr: 指向目的地址的结构体指针, 该结构体通常包含 IP 地址和端口号。

addrlen: 目的地址结构体的长度。

返回值: 成功时, 返回发送的字节数; 失败时, 返回-1, 并设置 errno 以指示错误原因;

28.1.3.5 recvfrom 函数

recvfrom 函数是一个在 POSIX 兼容操作系统(如 Linux) 中用于接收数据的系统调用, 也常用于 Windows 系统中的网络编程。 它特别适用于面向无连接的协议, 如 UDP(用户数据报协议) , 函数原型:

cpp 复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数:

sockfd: 已创建并绑定的套接字的文件描述符。

buf: 指向缓冲区的指针, 用于存储接收到的数据。

len: 缓冲区的大小, 以字节为单位。

flags: 一些标志位, 用于修改 recvfrom 的行为, 通常为 0。

src_addr: 指向 sockaddr 结构体的指针, 用于存储发送方的地址信息。 如果不需要获取发送方的地址, 可以设置为 NULL。

addrlen: 指向整型的指针, 用于指定 src_addr 结构体的大小。 在调用recvfrom 之前, 应该将其设置为 src_addr 结构体的大小, 在调用之后, 它将被设置为新接收到的地址的实际大小。

返回值:

如果成功, recvfrom 返回接收到的字节数。如果连接被对方优雅地关闭, 返回 0。

如果发生错误, 返回-1, 并设置全局变量 errno 来指示错误的原因

28.1.3.6 closesocket 函数

释放与套接字相关联的资源, 并关闭套接字, 以确保系统可以回收这些资源供其他应用程序使用, 函数原型:

cpp 复制代码
int lwip_close(int s);

参数:

s: 要关闭的套接字;

返回值: 0 成功, 非 0 错误值;

对于每次成功调用 socket 函数创建的套接字, 应用程序都应确保调用一次closesocket 函数来释放资源。

28.2 硬件设计

由于 Hi3861 内置 WIFI 功能, 所以直接在开发板上使用即可, 无需额外连接。

28.3 软件设计

将前面章节创建好的工程模板, 复制一份, 重命名为 24_wifi_udp, 如下所示

(1) 修改 demo 文件夹下的 BUILD.gn 文件, 如下所示:

(2) 添加工程编译文件路径, 如下所示

(3) 修改 template.c 文件, 代码如下:

cpp 复制代码
/**
 ****************************************************************************************************
 * @file        template.c
 * @author      普中科技
 * @version     V1.0
 * @date        2024-06-05
 * @brief       WIFI UDP实验
 * @license     Copyright (c) 2024-2034, 深圳市普中科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:普中-Hi3861
 * 在线视频:https://space.bilibili.com/2146492485
 * 公司网址:www.prechin.cn
 * 购买地址:
 *
 ****************************************************************************************************
 * 实验现象:开发板连接路由器热点,打开网络调试助手,选择UDP协议连接,即可与开发板进行UDP通信
 *
 ****************************************************************************************************
 */

#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

#include "bsp_led.h"
#include "bsp_wifi.h"
#include "lwip/netifapi.h"
#include "lwip/sockets.h"
#include "lwip/api_shell.h"


//LED任务
osThreadId_t LED_Task_ID; //led任务ID

void LED_Task(void)
{
    led_init();//LED初始化

    while (1) 
    {
        LED(1); 
        usleep(200*1000); //200ms
        LED(0);
        usleep(200*1000); //200ms
    }
}
//LED任务创建
void led_task_create(void)
{
    osThreadAttr_t taskOptions;
    taskOptions.name = "LEDTask";            // 任务的名字
    taskOptions.attr_bits = 0;               // 属性位
    taskOptions.cb_mem = NULL;               // 堆空间地址
    taskOptions.cb_size = 0;                 // 堆空间大小
    taskOptions.stack_mem = NULL;            // 栈空间地址
    taskOptions.stack_size = 1024;           // 栈空间大小 单位:字节
    taskOptions.priority = osPriorityNormal; // 任务的优先级

    LED_Task_ID = osThreadNew((osThreadFunc_t)LED_Task, NULL, &taskOptions); // 创建任务1
    if (LED_Task_ID != NULL)
    {
        printf("ID = %d, Create LED_Task_ID is OK!\n", LED_Task_ID);
    }
}

//控制任务
osThreadId_t WIFI_Task_ID; //任务ID

#define WIFI_SSID "普中科技"
#define WIFI_PAWD "88888888.@"

#define UDP_SERVER_IP "192.168.101.16"
#define UDP_SERVER_PORT 8000

void WIFI_Task(void)
{
    int socket_fd = 0;
    int result;

    // 服务器的地址信息
    struct sockaddr_in send_addr;
    socklen_t addr_length = sizeof(send_addr);
    char recvBuf[512];

    // 连接Wifi
    WiFi_connectHotspots(WIFI_SSID, WIFI_PAWD);

    // 创建socket
    if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 
    {
        printf("create socket failed!\r\n");
        return;
    }

    // 初始化预连接的服务端地址
    send_addr.sin_family = AF_INET;                        // IPV4
    send_addr.sin_port = htons(UDP_SERVER_PORT);           // 远端服务器的端口号
    send_addr.sin_addr.s_addr = inet_addr(UDP_SERVER_IP); // 远端服务器的IP地址
    addr_length = sizeof(send_addr);

    // 发送数据到服务远端
    result = sendto(socket_fd, "hello prechin\r\n", strlen("hello prechin\r\n"), 0,
                    (struct sockaddr *)&send_addr, addr_length);
    if (result) 
    {
        printf("result: %d, sendData:%s\r\n", result, "hello prechin");
    }
    while (1) 
    {
        memset_s(recvBuf, sizeof(recvBuf), 0, sizeof(recvBuf));

        // 接收服务端返回的字符串
        result = recvfrom(socket_fd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&send_addr, &addr_length);
        if (result > 0) 
        {
            // 发送数据到服务远端
            result = sendto(socket_fd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&send_addr, addr_length);
            if (result) 
            {
                printf("result: %d, sendData:%s\r\n", result, recvBuf);
            }
        }
        sleep(1);
    }

    // 关闭这个 socket
    closesocket(socket_fd);
}
//任务创建
void wifi_task_create(void)
{
    osThreadAttr_t taskOptions;
    taskOptions.name = "wifiTask";       // 任务的名字
    taskOptions.attr_bits = 0;               // 属性位
    taskOptions.cb_mem = NULL;               // 堆空间地址
    taskOptions.cb_size = 0;                 // 堆空间大小
    taskOptions.stack_mem = NULL;            // 栈空间地址
    taskOptions.stack_size = 1024*10;           // 栈空间大小 单位:字节
    taskOptions.priority = osPriorityNormal; // 任务的优先级

    WIFI_Task_ID = osThreadNew((osThreadFunc_t)WIFI_Task, NULL, &taskOptions); // 创建任务
    if (WIFI_Task_ID != NULL)
    {
        printf("ID = %d, WIFI_Task_ID Create OK!\n", WIFI_Task_ID);
    }
}

/**
 * @description: 初始化并创建任务
 * @param {*}
 * @return {*}
 */
static void template_demo(void)
{
    printf("普中-Hi3861开发板--WIFI UDP实验\r\n");
    led_task_create();
    wifi_task_create();//任务创建
}
SYS_RUN(template_demo);

28.4 实验现象

将程序下载到开发板内(可参考"2.2.5 程序下载运行"章节) , 打开串口调试助手"\5--开发工具\4-串口调试助手\UartAssist.exe" , 波特率设置为115200, 实验现象: 连接路由器热点, 打开网络调试助手和串口调试助手, 选择UDP 协议连接, 即可与开发板进行 UDP 通信, 开发板作为客户端, 电脑作为服务端。

相关推荐
DIY机器人工房16 小时前
要解决 ESP32 与 STM32 之间 LoRa 通信无应答的问题,可从以下硬件、软件、参数匹配三个维度逐一排查:
stm32·单片机·嵌入式硬件·lora·嵌入式·diy机器人工房
qq_4017004116 小时前
STM32的存储起始地址和运行起始地址为什么一样
stm32·单片机·嵌入式硬件
九江Mgx16 小时前
自由通讯的魔法:Go从零实现UDP/P2P 聊天工具
golang·udp·p2p
-Excalibur-17 小时前
形象解释关于TCP/IP模型——层层封装MAC数据帧的过程
linux·c语言·网络·笔记·单片机·网络协议·tcp/ip
点灯小铭18 小时前
基于单片机的楼道声光人体红外智能控制灯设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
hazy1k19 小时前
51单片机基础-红外遥控(NEC协议)
stm32·单片机·嵌入式硬件·51单片机·1024程序员节
子不语18019 小时前
STM32——按钮实验
stm32·单片机·嵌入式硬件
牛马大师兄20 小时前
STM32实现低功耗管理使用配置知识梳理笔记
笔记·stm32·单片机·嵌入式硬件·物联网·低功耗
大志若愚YYZ21 小时前
STM32——使用定时器+按键中断来实现长按事件检测触发
stm32·单片机·嵌入式硬件