华为云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网络应用开发_第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);
}

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 : ");
		}
	}
}

二、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张图片

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;

	//连接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);
	}
}

二、MQTT协议

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

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

你可能感兴趣的:(鸿蒙设备开发,嵌入式,华为云,harmonyos,华为)