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



    前言

    上一篇讲了怎么用开发板使用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. 通信流程

    在这里插入图片描述

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    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;
    	//连接Wifi
    	WifiConnect("TP-LINK_65A8", "0987654321");
    
    	//创建socket
    	if ((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 : ");
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    二、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. 通信流程

    在这里插入图片描述

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    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;
    
    	//连接Wifi
    	WifiConnect("TP-LINK_65A8", "0987654321");
    
    	//创建socket
    	if ((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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    二、MQTT协议

    协议概念就不多讲了,之前有总结过。LiteOS SDK oc流程之MQTT
    在这里插入图片描述

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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
    • 2
    • 3
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    发布

    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*);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    主要代码

    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);	
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    测试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/
    编译调试的截图可参看官方案例页面
    官方编译调试步骤截图页面

  • 相关阅读:
    webpack定制化 加载与插件[css加载器、html插件、image打包配置、babel代码兼容、vue加载器及配置]
    嵌入式工程师面试题
    请求 响应
    CSDN博客炫丽图标调整字体大小和颜色
    vscode json 无决找到“e:\...“
    Echarts设置参数
    ffplay.c源码阅读之拉流模块实现原理
    【初学者入门C语言】之二维数组(七)
    语法基础(函数)
    C++模板
  • 原文地址:https://blog.csdn.net/kouqi627/article/details/126049408