> 文档中心 > 华为云14天鸿蒙设备开发-Day9网络应用开发

华为云14天鸿蒙设备开发-Day9网络应用开发

目录

  • 前言
  • 主要API
  • 一、UDP协议
    • 收发API
    • 1. 通信流程
    • 2.客户端实现
    • 3.服务器端实现
  • 二、TCP协议
    • 收发API
    • 1. 通信流程
    • 2.客户端实现
    • 3.服务器端实现
  • 二、MQTT协议
    • Paho MQTT简介
    • Paho MQTT API
    • 开发板实现MQTT客户端
      • 主要代码
    • 测试MQTT客户端

前言

上一篇讲了怎么用开发板使用wifi功能,开启WiFi了要和外部通信的,这篇文章主要写TCP,UDP,MQTT三种通信协议的使用。


主要API

主要使用此文件中的函数third_party/lwip/src/include/lwip/sockets.h
socket()

sock_fd = socket(AF_INET, SOCK_STREAM, 0)) //AF_INT:ipv4, SOCK_STREAM:tcp协议

描述:
在网络编程中所需要进行的第一件事情就是创建一个socket,无论是客户端还是服务器端,都需要创建一个socket,该函数返回socket文件描述符,类似于文件描述符。socket是一个结构体,被创建在内核中。

bind()

bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))

描述:
把一个本地协议地址和套接口绑定,比如把本机的2222端口绑定到套接口。注意:为什么在使用 Socket 实现 UDP 客户端教程中的客户端不需要调用bind函数?这是因为如果没有调用bind函数绑定一个端口的话,当调用connect函数时,内核会为该套接口临时选定一个端口,因此可以不用绑定。而服务器之所以需要绑定的原因就是,所以客户端都需要知道服务器使用的哪个端口,所以需要提前绑定。

listen()

int listen(int s, int backlog)

描述:
此函数返回已经握手完成的连接的套接口。注意:此处的套接口不同于服务器开始创建的监听套接口,此套接口是已经完成连接的套接口,监听套接口只是用来监听。

一、UDP协议

UDP协议概念就不多讲了,属于数据报式通信,无连接不可靠的,但是速度快。

收发API

sendto()

int sendto ( socket s , const void * msg, int len, unsigned int flags,const struct sockaddr * to , int tolen ) ;

描述:
sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket。参数msg指向欲连线的数据内容,参数flags 一般设0。

recvfrom()

int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

描述:
从指定地址接收UDP数据报。

1. 通信流程

华为云14天鸿蒙设备开发-Day9网络应用开发

2.客户端实现

完成Wifi热点的连接后还需要以下几步

  1. 通过 socket 接口创建一个socket,AF_INT表示ipv4,SOCK_STREAM表示使用tcp协议,SOCK_DGRAM表示使用udp协议
  2. 调用 sendto 接口发送数据到服务端。
  3. 调用 recvfrom 接口接收服务端发来的数据
#define _PROT_ 8888//在sock_fd 进行监听,在 new_fd 接收新的链接int sock_fd;int addr_length;static const char *send_data = "Hello! I'm BearPi-HM_Nano UDP Client!\r\n";static void UDPClientTask(void){    //服务器的地址信息    struct sockaddr_in send_addr;    socklen_t addr_length = sizeof(send_addr);    char recvBuf[512];    //连接Wifi    WifiConnect("TP-LINK_65A8", "0987654321");    //创建socket    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)    { perror("create socket failed!\r\n"); exit(1);    }    //初始化预连接的服务端地址    send_addr.sin_family = AF_INET;    send_addr.sin_port = htons(_PROT_);    send_addr.sin_addr.s_addr = inet_addr("192.168.0.175");    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);    }    //关闭这个 socket    closesocket(sock_fd);}

3.服务器端实现

服务器端需要绑定套接字,用来监听客户端的连接请求。

#define _PROT_ 8888#define UDP_BACKLOG 10//在sock_fd 进行监听,在 new_fd 接收新的链接int sock_fd;char recvbuf[512];char *buf = "Hello! I'm BearPi-HM_Nano UDP Server!";static void UDPServerTask(void){//服务端地址信息struct sockaddr_in server_sock;//客户端地址信息struct sockaddr_in client_sock;int sin_size;//连接WifiWifiConnect("TP-LINK_65A8", "0987654321");//创建socketif ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){perror("socket is error\r\n");exit(1);}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(_PROT_);//调用bind函数绑定socket和地址if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1){perror("bind is error\r\n");exit(1);}//处理目标ssize_t ret;while (1){sin_size = sizeof(struct sockaddr_in);bzero(&recvbuf, sizeof(recvbuf));if ((ret = recvfrom(sock_fd, recvbuf, sizeof(recvbuf), 0,(struct sockaddr *)&client_sock,&sin_size)) == -1){printf("recv error \r\n");}printf("recv :%s\r\n", recvbuf);if ((ret = sendto(sock_fd, buf, strlen(buf), 0,(struct sockaddr *)&client_sock,sizeof(client_sock))) == -1){perror("send : ");}}}

二、TCP协议

协议概念就不多讲了,属于流式通信,连接可靠的。

收发API

recv()

int recv( SOCKET s, char *buf, int len, int flags)

描述:
recv函数用来从TCP连接的另一端接收数据

send()

int send( SOCKET s,char *buf,int len,int flags )

描述:
send函数用来向TCP连接的另一端发送数据。

1. 通信流程

华为云14天鸿蒙设备开发-Day9网络应用开发

2.客户端实现

#define _PROT_ 8888//在sock_fd 进行监听,在 new_fd 接收新的链接int sock_fd;int addr_length;static const char *send_data = "Hello! I'm BearPi-HM_Nano TCP Client!\r\n";static void TCPClientTask(void){    //服务器的地址信息    struct sockaddr_in send_addr;    socklen_t addr_length = sizeof(send_addr);    char recvBuf[512];    //连接Wifi    WifiConnect("TP-LINK_65A8", "0987654321");    //创建socket    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)    { perror("create socket failed!\r\n"); exit(1);    }    //初始化预连接的服务端地址    send_addr.sin_family = AF_INET;    send_addr.sin_port = htons(_PROT_);    send_addr.sin_addr.s_addr = inet_addr("192.168.0.175");    addr_length = sizeof(send_addr);    connect(sock_fd,(struct sockaddr *)&send_addr,addr_length); while (1)    { bzero(recvBuf, sizeof(recvBuf)); //发送数据到服务远端if(ret = send(sock_fd, send_data, strlen(send_data), 0) == -1){  perror("send:");} //接收服务端返回的字符串if(ret = recv(sock_fd, recvBuf, sizeof(recvBuf), 0) == -1){  printf("recv error \r\n");}printf("recv :%s\r\n", recvbuf);    }    //关闭这个 socket    closesocket(sock_fd);}

3.服务器端实现

服务器端需要绑定套接字,用来监听客户端的连接请求。
完成Wifi热点的连接后还需要以下几步

  1. 通过 socket 接口创建一个socket,AF_INT表示ipv4,SOCK_STREAM表示使用tcp协议
  2. 调用 bind 接口绑定socket和地址。
  3. 调用 listen 接口监听(指定port监听),通知操作系统区接受来自客户端链接请求,第二个参数:指定队列长度
  4. 调用accept接口从队列中获得一个客户端的请求链接
  5. 调用 recv 接口接收客户端发来的数据
  6. 调用 send 接口向客户端回复固定的数据
#define _PROT_ 8888#define TCP_BACKLOG 10//在sock_fd 进行监听,在 new_fd 接收新的链接int sock_fd, new_fd;char recvbuf[512];char *buf = "Hello! I'm BearPi-HM_Nano TCP Server!";static void TCPServerTask(void){//服务端地址信息struct sockaddr_in server_sock;//客户端地址信息struct sockaddr_in client_sock;int sin_size;struct sockaddr_in *cli_addr;//连接WifiWifiConnect("TP-LINK_65A8", "0987654321");//创建socketif ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("socket is error\r\n");exit(1);}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(_PROT_);//调用bind函数绑定socket和地址if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1){perror("bind is error\r\n");exit(1);}//调用listen函数监听(指定port监听)if (listen(sock_fd, TCP_BACKLOG) == -1){perror("listen is error\r\n");exit(1);}printf("start accept\n");//调用accept函数从队列中while (1){sin_size = sizeof(struct sockaddr_in);if ((new_fd = accept(sock_fd, (struct sockaddr *)&client_sock, (socklen_t *)&sin_size)) == -1){perror("accept");continue;}cli_addr = malloc(sizeof(struct sockaddr));printf("accept addr\r\n");if (cli_addr != NULL){memcpy(cli_addr, &client_sock, sizeof(struct sockaddr));}//处理目标ssize_t ret;while (1){if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1){printf("recv error \r\n");}printf("recv :%s\r\n", recvbuf);sleep(2);if ((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1){perror("send : ");}sleep(2);}close(new_fd);}}

二、MQTT协议

协议概念就不多讲了,之前有总结过。LiteOS SDK oc流程之MQTT
华为云14天鸿蒙设备开发-Day9网络应用开发

Paho MQTT简介

Paho是IBM在2011年建立的Eclipse开源项目,该项目包含多种语言编写的可用客户端。
嵌入式C语言客户端地址:https://github.com/eclipse/paho.mqtt.embedded-c
gitee页面为:bearpi-hm_nano/ third_party / paho_mqtt,在此页面可以更详细了解此库。
鸿蒙系统相关移植文件:MQTTClient-C\src\liteOS\MQTTLiteOS.c

  • MQTTClient:封装MQTTPacket生成的高级别C++客户端程序。
  • MQTTClient-C:封装MQTTPacket生成的高级别C客户端程序
    • samples目录提供FreeRTOS和linux两个例程,分别支持FreeRTOS和Linux系统。
    • src目录提供MQTTClient的代码实现能力,以及用于移植到对应平台的网络驱动
  • MQTTPacket:提供MQTT数据包的序列化与反序列化,以及部分辅助函数。

Paho MQTT API

在MQTTClient.h文件中声明了相关接口函数。我们只需要调用接口即可,不用再自己实现MQTT协议了。

接口名 功能描述
MQTTClientInit 创建一个客户端对象
MQTTConnect 发送MQTT连接数据包
MQTTConnectWithResults 发送MQTT连接数据包并等待返回
MQTTPublish 发送MQTT发布数据包
MQTTSetMessageHandler 发送每个topic消息处理函数
MQTTSubscribe 发送MQTT订阅数据包
MQTTSubscribeWithResults 发送MQTT订阅数据包并等待返回结果
MQTTUnsubscribe 发送MQTT取消数据包
MQTTDisconnect 发送MQTT断开连接数据包并关闭连接

开发板实现MQTT客户端

代码摘自sample中的iot_mqtt工程。

  1. wifi要接入,使开发板处于联网状态
  2. 初始化Network,因为mqtt协议建立在tcp协议上,所以给定套接字,与读写接口
    建立和指定IP地址与1883端口的broker的连接。
typedef struct Network{int my_socket;int (*mqttread) (struct Network*, unsigned char*, int, int);int (*mqttwrite) (struct Network*, unsigned char*, int, int);} Network;int NetworkConnect(Network* n, char* addr, int port)
  1. 使用MQTTClientInit建立一个MQTT客户端
DLLExport void MQTTClientInit(MQTTClient* client, Network* network, unsigned int command_timeout_ms,unsigned char* sendbuf, size_t sendbuf_size, unsigned char* readbuf, size_t readbuf_size);
  1. 配置好client和data,使用MQTTConnect开启MQTT连接。
/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack *  The nework object must be connected to the network endpoint before calling this *  @param options - connect options *  @return success code */DLLExport int MQTTConnect(MQTTClient* client, MQTTPacket_connectData* options);
  1. 开启订阅和发布
    订阅
    需要写一个回调函数作为消息处理函数。
typedef struct MessageData{    MQTTMessage* message;    MQTTString* topicName;} MessageData;/** MQTT Subscribe - send an MQTT subscribe packet and wait for suback before returning. *  @param client - the client object to use *  @param topicFilter - the topic filter to subscribe to *  @param message - the message to send *  @return success code */DLLExport int MQTTSubscribe(MQTTClient* client, const char* topicFilter, enum QoS, messageHandler);

发布

typedef struct MQTTMessage{    enum QoS qos;    unsigned char retained;    unsigned char dup;    unsigned short id;    void *payload;    size_t payloadlen;} MQTTMessage;/** MQTT Publish - send an MQTT publish packet and wait for all acks to complete for all QoSs *  @param client - the client object to use *  @param topic - the topic to publish to *  @param message - the message to send *  @return success code */DLLExport int MQTTPublish(MQTTClient* client, const char*, MQTTMessage*);

主要代码

static unsigned char sendBuf[1000];static unsigned char readBuf[1000];Network network;void messageArrived(MessageData* data){printf("Message arrived on topic %.*s: %.*s\n", data->topicName->lenstring.len, data->topicName->lenstring.data,data->message->payloadlen, data->message->payload);}/* */static void MQTT_DemoTask(void){WifiConnect("Hold","0987654321");printf("Starting ...\n");int rc, count = 0;MQTTClient client;NetworkInit(&network);printf("NetworkConnect  ...\n");begin:NetworkConnect(&network, "192.168.0.176", 1883);printf("MQTTClientInit  ...\n");MQTTClientInit(&client, &network, 2000, sendBuf, sizeof(sendBuf), readBuf, sizeof(readBuf));MQTTString clientId = MQTTString_initializer;clientId.cstring = "bearpi";MQTTPacket_connectData data = MQTTPacket_connectData_initializer;  data.clientID   = clientId;data.willFlag   = 0;data.MQTTVersion= 3;data.keepAliveInterval = 0;data.cleansession      = 1;printf("MQTTConnect  ...\n");rc = MQTTConnect(&client, &data);if (rc != 0) {printf("MQTTConnect: %d\n", rc);NetworkDisconnect(&network);MQTTDisconnect(&client);osDelay(200);goto begin;}printf("MQTTSubscribe  ...\n");rc = MQTTSubscribe(&client, "substopic", 2, messageArrived);if (rc != 0) {printf("MQTTSubscribe: %d\n", rc);osDelay(200);goto begin;}while (++count){MQTTMessage message;char payload[30];message.qos = 2;message.retained = 0;message.payload = payload;sprintf(payload, "message number %d", count);message.payloadlen = strlen(payload);if ((rc = MQTTPublish(&client, "pubtopic", &message)) != 0){printf("Return code from MQTT publish is %d\n", rc);NetworkDisconnect(&network);MQTTDisconnect(&client);goto begin;}osDelay(50);}}

测试MQTT客户端

测试开发板的客户端,首先需要一个服务端也就是broker,再一个就是PC的客户端,用来订阅开发板发布的主题消息。
官方提供了两个软件,可自行尝试:
MQTT消息代理软件mosquitto下载地址: https://mosquitto.org/download/
Eclipse桌面客户端程序下载地址: https://repo.eclipse.org/content/repositories/paho-releases/org/eclipse/paho/org.eclipse.paho.ui.app/1.1.1/
编译调试的截图可参看官方案例页面
官方编译调试步骤截图页面