一、简介
在Openharmony的轻量和小型系统中,受限于cpu与内存资源等原因,网络协议一般使用lwip的实现。而且受限资源影响,lwip的socket数与端口数都通过宏定义控制在7-8个以内。在物联IOT实际应用中,经常会出现多台IOT设备在未知对方IP的状态下,需要对多台IOT设备进行控制与通信。此时可以通过组播的方式获取对方设备的IP地址,后续就可以对对方设备进行控制与通信。
另:
1.在IOT设备通信中,关于可信安全的问题,在Openharmony中可以使用hichain的可信安全进行认证。
2.在IOT设备通信中,Openharmony除了采用传统的组播与广播方式控制其它的IOT设备,还可以选择Openharmony的软总线来实现,而软总线的可信认证即是采用的hichain进行安全认证。
此两课题有时间再详述。
本文通过代码示例,在两台不同的L0的轻量系统中通过组播获取对方设备的IP后,发送指定的信息。
二、组播
组播指的是报文从一个源发出,被转发到一组特定的接收者,相同的报文在每条链路上最多有一份。相较于传统的单播和广播,组播可以有效地节约网络带宽、降低网络负载。
组播是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址,组播地址不同于单播地址,它不属于特定某个主机,而是属于一组主机。一个组播地址表示一个群组,需要接收组播报文的接收者都加入这个群组。
广播传输数据源必须与用户在同一网段,组播可以跨网段传输。
为了使组播源和组播组成员进行通信,需要提供网络层组播使用的IP组播地址。
2.1 组播地址
D类地址空间分配给IPv4组播使用,地址范围从224.0.0.0到239.255.255.255.
224.0.0.0~224.0.0.255 永久组地址,为路由协议预留的IP地址,用于标识一组特定的网络设备,供路由协议 、拓扑查找等使用,不用于组播转发。
224.0.1.0~239.255.255.255 均可作为组播地址使用,只是组范围有所不同,具体范围使用可以查找IPv4组播地址规范。
三、Openharmony的两类L0设备
3.1 拓维Niobe开发板
-
代码分支
https://gitee.com/talkweb_oh/niobe
-
wifi连接示例代码
https://gitee.com/talkweb_oh/niobe/tree/master/applications/app/TW301_Network_wifista
-
udp示例
https://gitee.com/talkweb_oh/niobe/tree/master/applications/app/TW305_Network_udpclient
https://gitee.com/talkweb_oh/niobe/tree/master/applications/app/TW305_Network_udpserver
-
tcp示例
https://gitee.com/talkweb_oh/niobe/tree/master/applications/app/TW304_Network_tcpclient
https://gitee.com/talkweb_oh/niobe/tree/master/applications/app/TW304_Network_tcpserver
3.2 小熊派BearPi-HM Nano开发板
- 代码分支
https://gitee.com/bearpi/bearpi-hm_nano
-
wifi连接示例
https://gitee.com/bearpi/bearpi-hm_nano/tree/master/applications/BearPi/BearPi-HM_Nano/sample/D2_iot_wifi_sta_connect
-
udp示例
https://gitee.com/bearpi/bearpi-hm_nano/tree/master/applications/BearPi/BearPi-HM_Nano/sample/D3_iot_udp_client
-
tdp示例
https://gitee.com/bearpi/bearpi-hm_nano/tree/master/applications/BearPi/BearPi-HM_Nano/sample/D4_iot_tcp_server
四、组播获取代码示例
4.1 常量定义
constexpr const char* MULTICAST_DATA = "multicast"; // 组播测试数据
constexpr const char* MULTICAST_IP = "234.3.3.3"; // 组播地址
constexpr uint32_t MULTICAST_PORT = 9000; // 组播端口
constexpr uint32_t SEND_DATA_PORT = 9001; // 发送数据端口
4.2 组播源
void UdpSendThread(void)
{
wifiConnect("wifi name", "wifi password");
int udp_socket = CreateUdpSocket(); // 创建udp socket略过...
if (udp_socket >= 0) {
printf("CreateUdpSocket udp_socket:%d\n", udp_socket);
// 指定发送的组播地址和端口
struct sockaddr_in mcast_addr;
memset(&mcast_addr, 0, sizeof(mcast_addr));
mcast_addr.sin_family = AF_INET;
mcast_addr.sin_addr.s_addr = inet_addr(MULTICAST_IP);
mcast_addr.sin_port = htons(MULTICAST_PORT);
}
while (true) {
// 发送测试数据multicast
int sendLength = sendto(udp_socket, MULTICAST_DATA, 9, 0, (struct socketaddr*)&mcast_addr, sizeof(mcast_addr));
if (sendLength < 0) {
printf("sendto udp_socket:%d fail,sendLength:%d", udp_socket, sendLength);
} else {
g_SendUdpSucc++;
if (g_SendUdpSucc >= 10) {
break; // 此为测试,组播报文只发送10次,实际项目中应在从接收者收到应答数据包时,停止发送。
}
}
Delay(1000);
}
close(udp_socket);
}
4.3 组播接收端
void UdpRecvThread(void)
{
wifiConnect("wifi name", "wifi password");
struct sockaddr_in server_socket;
int upd_socket = CreateUdpSocket();
if (udp_socket >= 0) {
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
server_sock.sin_port = htons(MULTICAST_PORT);
// 调用bind绑定socket地址
if (bind(udp_socket, (struct sockaddr*)&server_socket, sizeof(struct sockaddr)) == -1) {
perror("bind error\r\n");
closesocket(udp_socket);
exit(1);
}
struct ip_mreq mreq;
// 加入组播,接收组播信息
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
int error = setsocket(udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
if (error < 0) {
printf("setsocket fail error:%d\r\n", error);
closesocket(udp_socket);
exit(1);
}
while (true) {
printf("select wait....\r\n");
struct sockaddr_in recvAddr;
memset(recvBuff, 0, sizeof(recvBuff));
memset(&recvAddr, 0, sizeof(struct sockaddr));
int sinSize = sizeof(struct sockaddr_in);
int ret = recvfrom(udp_socket, recvBuff, sizeof(recvBuff)) - 1, 0, (struct sockaddr*)&recvAddr, (socklen_t *)&sinSize);
printf("recv from UDP client IP:%s\r\n", inet_ntoa(recvAddr.sin_addr));
printf("recv data:%s\r\n", recvBuff); // 组播测试数据"multicast"
osDelay(10);
// 校验组播数据
if (strlen(recvBuff) == 9 && memcmp(recvBuff, MULTICAST_DATA, 10) == 0) {
memset(remoteIp, 0, IPADDR_LEN);
strcpy(remoteIp, inet_ntoa(recvAddr.sin_addr));
printf("get peer device IP:%s\r\n", remoteIp);
break;
}
}
closesocket(udp_socket);
}
}
4.4 通过获取的远端地址remoteIp,发送数据到对端设备
void TcpSendThread()
{
struct sockaddr_in sendAddr;
socklen_t addrLen = sizeof(sendAddr);
// tcp socket
int sock_fd = -1;
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
exit(1);
}
sendAddr.sin_family = AF_INET;
sendAddr.sin_port = htons(SEND_DATA_PORT);
sendAddr.sin_addr.s_addr = inet_addr(remoteIp);
int ret = connect(sock_fd, (struct sockaddr*)&sendAddr, sizeof(sendAddr));
if (ret != 0) {
printf("connect server fail ret:%d\r\n", ret);
closesocket(sock_fd);
exit(1);
}
while (true) {
bzero(recvBuff, sizeof(recvBuff));
// 发送数据
printf("send remote device %s:%d=>data:%s\r\n",remoteIp, SEND_DATA_PORT, sendData);
sendto(sock_fd, sendData, strlen(sendData), 0, (struct sockaddr*)&sendAddr, addrLen);
sleep(10);
// 接收服务端返回应答数据
memset(recvBuff, 0, sizeof(recvBuff));
recvfrom(sock_fd, recvBuff, 0, (struct socketaddr*)&sendAddr, &addrLen);
printf("recv remote device %s:%d=>data:%s\r\n", remoteIp, SEND_DATA_PORT, recvBuff);
}
closesocket(sock_fd);
}
4.5 组播端接受对端数据
void TcpRecvThread()
{
struct sockaddr_in serverSock, clientSock, *cliAddr;
int sinSize = sizeof(struct sockaddr_in);
int sock_fd = -1;
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
exit(1);
}
bzero(&serverSock, sizeof(serverSock));
serverSock.sin_family = AF_INET;
serverSock.sin_addr.s_addr = htonl(INADDR_ANY);
serverSock.sin_port = htons(SEND_DATA_PORT);
if (bind(sock_fd, (struct sockaddr*)&serverSock, sizeof(struct sockaddr)) == -1) {
printf("bind fail\r\n");
closesocket(sock_fd);
exit(1);
}
// listen监听
if (listen(sock_fd, TCP_BACKLOG) == -1) {
printf("listen fail\r\n");
exit(1);
}
while (true) {
if ((new_fd = accept(sock_fd, (struct sockaddr*)&clientSock,(socklen_t*)&sinSize)) == -1) {
printf("accept fail\r\n");
closesocket(sock_fd);
exit(1);
}
cliAddr = malloc(sizeof(struct sockaddr));
if (cliAddr != NULL) {
memcpy(cliAddr, &clientSock, sizeof(struct sockaddr));
}
ssize ret;
while (true) {
if ((ret = recv(new_fd, recvBuff, sizeof(recvBuff), 0)) == -1) {
printf("recv fail\r\n");
}
printf("recv data:%s\r\n", recvBuff);
sleep(2);
if ((ret = send(new_fd, sendBuff, strlen(sendBuff) + 1, 0)) == -1) {
printf("send fail\r\n");
}
printf("send data:%s\r\n", sendBuff);
sleep(2);
}
printf("server exit\r\n");
close(new_fd);
}
closesocket(sock_fd);
}
4.6 demo打印结果显示
拓维Niobe开发板:
小熊派BearPi-HM Nano开发板: