文章目录

  • 前言
  • 运输层
  • UDP简介
    • UDP的数据包格式
      • 校验和
    • 端口号
  • Hi3861的UDP传输
    • 整体思路
    • 套接字(Socket)
      • 简介
      • 主要类型[^3]
      • 工作流程
  • 编程实现
    • Hi3861实现UDP(客户端)
      • 写bug前的准备工作
      • 写bug
      • 效果一览
    • Hi3861实现UDP(服务端)
    • Hi3861实现UDP广播
  • 总结

前言

上一篇记录了Hi3861的无线网络连接的模式,包括AP热点构建局域网以及使用STA模式接入其他路由器或者热点进行组网。使用这两种方式解决了各个终端的物理连接问题,但是没有具体说明网络中的各个成员是如何通信的。借用计算机网络的知识来理解,前面的AP以及STA解决了网络通信中网络层(IPV4)、数据链路层以及物理层的连接问题,但要进行通信还需要完成运输层和应用层。

运输层

运输层提供应用进程之间的逻辑通信,它完成了应用层中进程的数据通信。而前面提到的STA与AP解决的是通信双方主机间的物理连接及数据传输方式。

站在编程的角度来看,运输层解决了通信过程成中数据以什么样的格式传输的问题,类似笔者前面介绍Modbus通信的类比,计算机中所有的信息都是01二进制组成的,在通信过程中,通信的各方需要有一个统一的断句、位定义和字定义,让通信双方都能明白数据包中每个0和1的意义,这样才能实现通信。

而在运输层中的协议主流就是UDP和TCP,本文介绍UDP的接收、发送以及群发,TCP的内容放到下一篇介绍。

UDP简介

UDP是用户数据报协议,通过名称就可以知道他是面向用户的数据报文的,它在传输过程中不需要和通信另一方进行连接,整个传输过程中它不会去检测对方的状态,也不会管对方有没有接收到;它的工作思路就是上面(应用层)让我发啥,我发了就完事儿了,也不用去管对方有没有接收到。这类似日常生活中的发短信,整个发送过程不需要和对方实时沟通。也正是因为这个传输方式,UDP的传输速度会比较快,但是存在丢包风险,提供的是不可靠交付,但是它胜在传输速率,而且得益于UDP传输不需要在软件方面构建通信双方的连接,所以其可用来实现一对一、一对多、多对一、多对多的功能。

UDP的数据包格式

UDP传输过程中的数据包格式如下图所示,大体上说分为两个字段,数据字段首部字段

校验和

这里还有一个伪首部,这个 伪首部只是用来做校验和的计算的,并不参与传输,只是在计算校验和时会添加。
用一个数据包来举个栗子,看看UDP的数据包到底长啥样以及UDP的校验和如何计算。

端口号

上面的数据包的首部中提到了一个端口号,这个端口号是什么东西呢。
应用层中的进程一般都是不唯一的,会有多个应用进程,应用层所有的应用进程都是通过运输层再传送到IP层(网络层),运输层从IP层收到发送给各个不同应用进程的数据后,必须分别交付指明的各应用进程才行,那么运输层是怎么识别各个应用进程的呢,这就需要使用到端口号,可以理解为一个端口号就对应一个进程,当我们要传输数据去对应进程时只需要绑定该进程对应的端口号即可。
端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0-65535,其中,0-1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。
来看一个其他博主的例子:原文链接。
接下来通过一个图例来描述IP地址和端口号的作用,如下图所示。

Hi3861的UDP传输

整体思路

在了解了上面的这些内容后,就已经可以开始使用Hi3861进行UDP的传输了,先来捋一下思路。
首先,要想进行UDP传输必须组建一个局域网,使用Hi3861组建局域网的方式在上一篇中已经介绍了,可以使用AP模式,让Hi3861作为路由来实现,也可以使用STA,将Hi3861接入已有局域网形成组网,这里笔者使用的是后者,使用Hi3861连接家里的wifi进而实现组网,使用STA接入路由器后,路由器会使用DHCP为Hi3861分配一个IP地址,至此物理层和IP层的设置就已经完成;
然后,参考上面的数据包格式,还需要获取和绑定通信另外一端的IP地址及其应用的端口;
最后就是通信了,将所需传输的内容按照UDP数据包的格式进行封装,包括源端口,目的端口,以及源IP和目的IP,虽然IP不包含着数据包内,但是在校验和的计算中也是需要使用的。

套接字(Socket)

有了上面的编程思路后,是不是发现整个打包发送以及接收解析的代码如果纯手撕的话,还是有难度的,而且代码量不会小,涉及的内容比较多也不利于查询错误,那有没有类似其他接口的API函数呢,答案是肯定的。到这就可以请出本文的又一个重量级的东西了——套接字(Socket)。

简介

套接字(Socket),是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。1这就类似前面提到的CMSIS以及鸿蒙的内核抽象层,它的作用就是将网络通信中的底层处理给屏蔽了,预留了接口供编程者使用,在网络通信时不需要再去纠结TCP/IP,UDP这些数据包的打包,校验计算以及解析数据包这些处理。借用大佬的博客来描述一下,原文链接——Socket原理讲解。

套接字Socket=(IP地址:端口号),套接字的表示方法是点分十进制的lP地址后面写上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。例如:如果IP地址是210.37.145.1,而端口号是23,那么得到套接字就是(210.37.145.1:23)2

主要类型3

1.流套接字(SOCK_STREAM)
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议 。
2.数据报套接字(SOCK_DGRAM)
数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理 。
3.原始套接字(SOCK_RAW)
原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接 。

工作流程

要通过互联网进行通信,至少需要一对套接字,其中一个运行于客户端,我们称之为 Client Socket,另一个运行于服务器端,我们称之为 Server Socket]。
以上有关套接字的内容来自百度百科对套接字的解释,原文链接——套接字。
计算机网络告诉我们UDP是不分客户端和服务端的,网络中的任何一个设备都既可以做客户端又可以做服务端,只需要对应IP和端口即可发送数据。显然要使用套接字来实现UDP就有一些矛盾,这里可以将UDP通信双方一个假设为客户端,另外一个假设为服务端,然后运用套接字即可,顺着这个思路,就可以得到下面的一个工作流程图。

编程实现

了解了上面的知识点后,用Hi3861实现UDP通信的代码的思路就有了,接下来分成两部分来编写即可,一个是假设的UDP客户端,一个是假设的UDP服务端。

Hi3861实现UDP(客户端)

写bug前的准备工作

笔者此处使用的是电脑作为服务端,家用的WIFI作为物理连接,使得电脑和Hi3861在同一局域网下。首先需要知道电脑的IP地址和服务的端口号,以便于Hi3861的客户端可以将数据发送过来。
打开电脑的命令行,输入“ipconfiig”回车即可查询到电脑的IP;

然后是端口号,这个可以自行设置的,笔者采用的是8888。

写bug

这里笔者采用的是小熊派的代码,代码中增加了一些注释,代码流程和上面的工作流程一样。部分Socket的函数简介如下图:

详细讲解可以去他们的开源社区查看——开源社区
已下是代码部分:

#include <stdio.h>
#include <unistd.h>#include "ohos_init.h"
#include "cmsis_os2.h"#include "wifi_device.h"
#include "lwip/netifapi.h"
#include "lwip/api_shell.h"
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include "lwip/sockets.h"//套接字的头文件
#include "wifi_connect.h"#define _PROT_ 8888//端口对应上面的“8888”//在sock_fd 进行监听,在 new_fd 接收新的链接
int sock_fd;int addr_length;
static const char *send_data = "Hello! I'm UDP Test!\r\n";static void UDPClientTask(void)
{//初始化服务器的地址信息的结构体struct sockaddr_in send_addr;socklen_t addr_length = sizeof(send_addr);char recvBuf[512];//连接WifiWifiConnect("CU_AXUC", "12345678");//修改成和自己电脑一致的SSID与password//创建本机的套接字socket(一对中的其一)if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){perror("create socket failed!\r\n");exit(1);}//初始化预连接的服务端地址,绑定服务端的IP以及应用进程端口//IPV4send_addr.sin_family = AF_INET;//通信另一端的应用进程端口send_addr.sin_port = htons(_PROT_);//通信另一端的IP地址send_addr.sin_addr.s_addr = inet_addr("192.168.1.7");addr_length = sizeof(send_addr);//总计发送 count 次数据while (1){bzero(recvBuf, sizeof(recvBuf));//发送数据到服务远端sendto(sock_fd, send_data, strlen(send_data), 0, (struct sockaddr *)&send_addr, addr_length);//线程休眠一段时间sleep(10);//接收服务端返回的字符串recvfrom(sock_fd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&send_addr, &addr_length);printf("%s:%d=>%s\n", inet_ntoa(send_addr.sin_addr), ntohs(send_addr.sin_port), recvBuf);}//关闭这个 socketclosesocket(sock_fd);
}static void UDPClientDemo(void)
{osThreadAttr_t attr;attr.name = "UDPClientTask";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 10240;attr.priority = osPriorityNormal;if (osThreadNew((osThreadFunc_t)UDPClientTask, NULL, &attr) == NULL){printf("[UDPClientDemo] Falied to create UDPClientTask!\n");}
}APP_FEATURE_INIT(UDPClientDemo);

效果一览

Hi3861实现UDP(服务端)

根据之前的工作流程图,可以发现UDP的服务端,只是在客户端的基础上增加了bind绑定,以及更换了发送接收的顺序,这里笔者借用传智的元气派来实现,原文链接。
代码如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>#include "ohos_init.h"
#include "cmsis_os2.h"#include "genki_wifi_sta.h"#include "lwip/sockets.h"#define WIFI_SSID "itheima"
#define WIFI_PASSWORD "12345678"
#define HOSTNAME "itcast"static void udp_task(void) {wifi_sta_connect(WIFI_SSID, WIFI_PASSWORD, HOSTNAME);//链接WIFI// udp create创建套接字int sock_fd;int ret;sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (sock_fd < 0) {perror("sock_fd create error\r\n");return;}// config receive addr初始化服务端配置struct sockaddr_in recvfrom_addr;socklen_t recvfrom_addr_len = sizeof(recvfrom_addr);memset((void *) &recvfrom_addr, 0, recvfrom_addr_len);// 写IP与端口recvfrom_addr.sin_family = AF_INET;recvfrom_addr.sin_addr.s_addr = htonl(INADDR_ANY);recvfrom_addr.sin_port = htons(8080);// bind receive addr// bindret = bind(sock_fd, (struct sockaddr *) &recvfrom_addr, recvfrom_addr_len);if (ret == -1) {perror("bind error\r\n");return;}char recv_buf[1024];int recv_len;while (1) {struct sockaddr_in sender_addr;int sender_addr_len;recv_len = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &sender_addr,sender_addr_len);if (recv_len <= 0) {continue;}char recv_data[recv_len];memcpy(recv_data, recv_buf, recv_len);printf("len: %d data: %s\r\n", recv_len, recv_data);}
}static void start(void) {osThreadAttr_t attr;attr.name = "udp_recv";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 1024 * 4;attr.priority = 25;if (osThreadNew((osThreadFunc_t) udp_task, NULL, &attr) == NULL) {printf("Create udp recv task Failed!\r\n");}
}APP_FEATURE_INIT(start);

Hi3861实现UDP广播

前面提到过,UDP是可以进行广播的,这里可以参考元气派的教程,使用UDP群发信息,可以实现同步控制多个终端的进程。原文链接——UDP广播。

总结

有关Hi3861的UDP通信介绍就记录至此,文章如有不妥之处欢迎批评指正。
OpenHarmony学习笔记——南向开发环境搭建
OpenHarmony学习笔记——编辑器访问Linux服务器进行编译
OpenHarmony学习笔记——点亮你的LED
OpenHarmony学习笔记——多线程的创建
OpenHarmony学习笔记——I2C驱动0.96OLED屏幕
OpenHarmony学习笔记——Hi3861使用DHT11获取温湿度
OpenHarmony学习笔记——Hi3861接入OneNET
手把手教你OneNET数据可视化
OpenHarmony学习笔记——Hi386+ASR-01的语音识别助手


  1. 王雷,TCP/IP网络编程基础教程,北京理工大学出版社,2017.02,第4页 ↩︎

  2. 潘伟编著,计算机网络 理论与实验,厦门大学出版社,2013.12,第145页 ↩︎

  3. 创客诚品,刘慧欣,孟令一编著,C语言从入门到精通 全新精华版,北京希望电子出版社,2017.10,第377页~第378页 ↩︎

Hi3861网络通信——UDP收发相关推荐

  1. C#实现的UDP收发请求工具类实例

    本文实例讲述了C#实现的UDP收发请求工具类.分享给大家供大家参考,具体如下: 初始化: ListeningPort = int.Parse(ConfigurationManager.AppSetti ...

  2. day31 网络通信udp,ip地址,端口port

    网络通信概述 1. 什么是网络 说明 网络就是一种辅助双方或者多方能够连接在一起的工具 如果没有网络可想单机的世界是多么的孤单 单机游戏(不能和远在他乡的朋友一起玩) 2. 使用网络的目的 就是为了联 ...

  3. android udp 收发例子_网络协议之TCP和UDP

    首先强调一点,TCP/IP协议是一个协议簇.里面包括很多协议的,UDP只是其中的一个, 之所以命名为TCP/IP协议,因为TCP.IP协议是两个很重要的协议,就用他两命名了. 两个协议的区别实际使用时 ...

  4. jmeter测试udp收发数据

    jemter本身不支持udp测试,需要下载安装第三方插件,或者下载一个插件管理器(下面那个蝴蝶一样的图标),里面有各种插件可以供你下载 然后点击插件管理器,下载udp请求插件,设置如下配置你要测试的服 ...

  5. java udp传输文件_JAVA使用UDP收发文件

    java使用UDP发送文件 环境 maven 3.6 jdk 1.8 udp-nio 分支支持批量发送 服务端源码(接收文件) package com.banywl.file.transfer.udp ...

  6. 【W5500】STM32 H743驱动W5500进行UDP收发

    前景提要 STM32 H743确实是个好芯片,但是这个MAC只有一个真是让我觉得不太够,想整双MAC的A核芯片玩玩,奈何实在也是没得精力弄Linux,虽然imx6ull也是一个好芯片.... 外挂MA ...

  7. delphi 使用UDP收发数据

    2019独角兽企业重金招聘Python工程师标准>>> udpclient.Active:=true; udpclient.Sendln(cmd,#$D); if udpclient ...

  8. android udp 收发例子_如何利用光衰减器来测试光纤收发器的灵敏度?

    光纤收发器的灵敏度可以说是光纤收发器的一个重要指标,了解如何测试光纤接收器的灵敏度是一项很重要的技能.当光输入功率在一定范围内时,光纤接收器的性能最佳.但是如何来判断光纤收发器是否会在最低光输入功率时 ...

  9. 基于Hi3861的听话的狗子

    听话的狗子 一.项目介绍 二.硬件介绍 (1)Hi3861最小系统电路 (2)电源管理电路 (3)USB转串口电路+烧录电路 (4)外设接口电路 三.PCB设计 四.软件部分简介 (1)代码整体框架 ...

  10. [立创传智黑马程序员CSDN]训练营——仿生机械狗

    听话的狗子 功能描述 成品展示 视频展示 硬件介绍 电源部分 手册参考电路 实际使用电路 主控电路 下载电路 舵机驱动模块 语音识别模块 其他注意事项 软件简介 主体框架 语音识别代码 蓝牙APP 机 ...

最新文章

  1. 测序仪的序列:DNA测序的历史
  2. 【deeplab】Semantic Image Segmentation with Deep Convolutional Nets and Fully
  3. 进程间通信 IPC 的本地过程调用 LPC(Local Procedure Call)和远程过程调用 RPC(Remote Procedure Call)
  4. jQuery 遍历
  5. NET问答: Log4Net 无法将日志写入到 log 文件的求助.....
  6. 静态多态之泛型编程(模板)
  7. 快手内测10分钟长视频 以吸引更多MCN、用户入驻
  8. YUV格式学习:YUV422P、YV16、NV16、NV61格式转换成RGB24
  9. 离线身份证OCR识别
  10. 计算 KL距离 (相对熵)
  11. xcode9 免证书调试
  12. linux加载scsi硬盘驱动程序使用,linux scsi硬盘的安装
  13. ubuntu18.04配置静态ip和动态ip
  14. 李小铭计算机专业应聘书作文,应聘申请书英语作文
  15. 3d在c语言中3的作用,c语言中%3d是什么意思?
  16. 回文数五位和六位c语言,特殊回文数
  17. PV值、UV值和IP值
  18. 11 计算机组成原理第七章 输入/输出系统 I/O系统基本概念 外部设备
  19. 大数据查询分析引擎比较
  20. Hibernate的几种查询方式 HQL,QBC,QBE,离线查询,复合查询,分页查询

热门文章

  1. 蓝桥杯:合唱队形(C语言)
  2. React中setState() 函数的三种用法
  3. Linq中Skip和Take用法(TakeWhile和SkipWhile用法)
  4. 瞎琢磨先生のJava笔记之Java代码远程调用shell脚本
  5. DDoS 报告攻击类型占比
  6. aws----文件存储efs的全面了解
  7. 【Python】可视化台风路径轨迹图
  8. MySQL菜鸟学习日志——0001
  9. element-ui tree全部展开和全部折叠
  10. java 微信图片上传_后台Java代码加前端微信小程序实现图片上传案例(学习)