mqtt client从tcp开始实现

模拟mqtt 客户端工作流程

mqtt介绍

mqtt 连接服务器报文格式

在这里插入图片描述

mqtt 发布消息报文协议格式

在这里插入图片描述

mqtt 订阅消息报文格式

类型内容和数据长度
固定报头0x82 + 剩余长度(1-4 byte)
可变报头报文标识符(2 byte)
有效载荷长度(2byte)主题名+qos

mqtt文档,mqtt服务器搭建以及第三方模拟mqtt客户端调试工具

mqtt文档下载链接

mqtt服务器连接

mqtt客户端调试

流程介绍

1:下载服务器连接代码,编译运行
2:使用mqtt客户端进行连接
3:使用下面的自己编译的客户端进行连接,测试

mqtt自己构建的客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "pthread.h"
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <errno.h>

typedef unsigned char  uint8;
typedef unsigned short uint16;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1883

static uint8_t client_id[512] = {"mqtt_client"};
static uint8_t user_name[512] = {"mqtt"};
static uint8_t passwd[512] = {"12345678"};
#define KEEP_ALIVE 20



#define QOS_VALUE0 0
#define QOS_VALUE1 1
#define QOS_VALUE2 2

#define MQTT_DUP_FLAG     1<<3
#define MQTT_QOS0_FLAG    0<<1
#define MQTT_QOS1_FLAG    1<<1
#define MQTT_QOS2_FLAG    2<<1

#define MQTT_RETAIN_FLAG  1

#define MQTT_CLEAN_SESSION  1<<1
#define MQTT_WILL_FLAG      1<<2
#define MQTT_WILL_RETAIN    1<<5
#define MQTT_USERNAME_FLAG  1<<7
#define MQTT_PASSWORD_FLAG  1<<6
#define MQTT_MSG_CONNECT       1<<4
#define MQTT_MSG_CONNACK       2<<4
#define MQTT_MSG_PUBLISH       3<<4
#define MQTT_MSG_PUBACK        4<<4
#define MQTT_MSG_PUBREC        5<<4
#define MQTT_MSG_PUBREL        6<<4
#define MQTT_MSG_PUBCOMP       7<<4
#define MQTT_MSG_SUBSCRIBE     8<<4
#define MQTT_MSG_SUBACK        9<<4
#define MQTT_MSG_UNSUBSCRIBE  10<<4
#define MQTT_MSG_UNSUBACK     11<<4
#define MQTT_MSG_PINGREQ      12<<4
#define MQTT_MSG_PINGRESP     13<<4
#define MQTT_MSG_DISCONNECT   14<<4

#define MSG_CONNECT       1
#define MSG_CONNACK       2
#define MSG_PUBLISH       3
#define MSG_PUBACK        4
#define MSG_PUBREC        5
#define MSG_PUBREL        6
#define MSG_PUBCOMP       7
#define MSG_SUBSCRIBE     8
#define MSG_SUBACK        9
#define MSG_UNSUBSCRIBE  10
#define MSG_UNSUBACK     11
#define MSG_PINGREQ      12
#define MSG_PINGRESP     13
#define MSG_DISCONNECT   14


int g_sockfd = 0;

/*计算mqtt报文剩余长度*/
int calc_due_length(uint8 *data)
{
	int multiplier =1;
	int len = 0 ;
	uint8_t encodedByte;
	do{
		data++;
		encodedByte = *data;
		len+=(encodedByte & 127)*multiplier;
		multiplier*=128;
		if(multiplier > 128*128*128){
			return -1;
		}
	}while((encodedByte & 128)!=0);
	return len;
}

uint8_t g_read_more_num = 0;
uint8_t g_read_more_data[5];
void do_socket(int sockfd)
{
    unsigned char read_more_num= g_read_more_num;
    unsigned char buf[8193] = {0};
    memcpy(buf,g_read_more_data,read_more_num);
	int ret = read(sockfd,&buf[read_more_num],5-read_more_num);
    if (ret < 1 || ret >(5-read_more_num)){
        perror("read");
        printf("read");
        if(errno == EINTR/*读取数据过程中被中断*/ || errno == EAGAIN/*缓存区无数据可读,Resource temporarily unavailable*/ 
            || errno == EWOULDBLOCK/*在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK*/){
		    printf("EINTR,EAGAIN,EWOULDBLOCK");
			return;
		}
        close(sockfd);
        printf("client close and exit");
        exit(0);
    }

    int data_length=calc_due_length(buf);
    int length_byte_num=0;
	if(data_length < 128){
		length_byte_num=1;
	}
    else if(data_length < 16384){
		length_byte_num=2;
	}
    else if(data_length < 2097152){
		length_byte_num=3;
	}
    else if(data_length < 268435456){
		length_byte_num=4;
	}
	if(data_length > sizeof(buf)-1){
		g_read_more_num = 0;
		return;
	}

    int ret2 = ret+read_more_num;//当前Data的数据总量
	int read_num_2= data_length-(ret2- (length_byte_num+1));
	if(read_num_2 < 0 ){ //意外读到了下一条mqtt数据(粘包)
		read_more_num = -read_num_2;
		memcpy(g_read_more_data , &buf[ret2-read_more_num] , read_more_num);
		int n;
		g_read_more_num = read_more_num;
	}
    else{
		g_read_more_num = 0;
	}
    int retry_counts=0;
	while(read_num_2>0){
		int byte_read =  read(sockfd ,&buf[ret2] ,read_num_2);
		if(byte_read==0){
			return;
		}
		if(byte_read < 0){
			printf("Socket Error");
			if(retry_counts++ < 1000){
				printf("Socket revice error2 byteRead=%d",byte_read); 
				usleep(1000);
				byte_read=0;					
			}
			else{
				printf("Socket revice overtime"); 
				return;
			}
		}
		read_num_2 -= byte_read;
		ret2 += byte_read;
	}
	printf("buf :: ");
	int i = 0;
    for (i = 0; i < ret2; i++)
	{
    	printf("%x ", buf[i]);
	}
	
	printf("\n");
    
}


int socket_init(void)
{
    int sockfd = -1;
    int optval=1;
    struct sockaddr_in serveraddr;
  
    /* Create a socket descriptor */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket");
	    return -1;
    }
 
    /* Eliminates "Address already in use" error from bind. */
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&optval , sizeof(int)) < 0){
        perror("setsockopt");
	    return -1;
    }
    return sockfd;
} 

int connect_server(int port ,const char *ipaddr,int sockfd)
{
	struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr=inet_addr(ipaddr);
    
    int ret = connect(sockfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
    if (ret < 0){
        perror("connect");
        // printf("connect server failed");
        return -1;
    }
     /*设置成非阻塞*/
    // unsigned long ul = 1;
    // ioctl(g_sockfd, FIONBIO, &ul); 
    // printf("connect server sucessful");
    return 0;
}
int mqtt_connect(int sockfd)
{
    uint8 flags = 0x00;
	uint8 *packet = NULL;
	uint16 packet_length = 0;	
	uint16 clientidlen = strlen(client_id);
	uint16 usernamelen = strlen(user_name);
	uint16 passwordlen = strlen(passwd);
	uint16 payload_len = clientidlen + 2;
	// Variable header
	uint8 var_header[10] = {
		0x00,0x04,/*len*/
        0x4d,0x51,0x54,0x54,/*mqtt*/
		0x04,/*协议版本*/
	};
	uint8 fixedHeaderSize = 2;    // Default size = one byte Message Type + one byte Remaining Length
	uint8 remainLen = 0;
	uint8 *fixed_header = NULL;
	uint16 offset = 0;
    
	// Preparing the flags
	if(usernamelen) {
		payload_len += usernamelen + 2;
		flags |= MQTT_USERNAME_FLAG;
	}
	if(passwordlen) {
		payload_len += passwordlen + 2;
		flags |= MQTT_PASSWORD_FLAG;
	}
	flags |= MQTT_CLEAN_SESSION;

	var_header[7] = flags;
	var_header[8] = KEEP_ALIVE>>8;
	var_header[9] = KEEP_ALIVE&0xFF;

	remainLen = sizeof(var_header)+payload_len;

	if (remainLen > 127) {
	    fixedHeaderSize++;// add an additional byte for Remaining Length          
	}
	fixed_header = (uint8 *)malloc(fixedHeaderSize);

	// Message Type
	*fixed_header = MQTT_MSG_CONNECT;

	// Remaining Length
	if (remainLen <= 127) {
	    *(fixed_header+1) = remainLen;
	} else {
	    // first byte is remainder (mod) of 128, then set the MSB to indicate more bytes
	    *(fixed_header+1) = remainLen % 128;
	    *(fixed_header+1) = *(fixed_header+1) | 0x80;
	    // second byte is number of 128s
	    *(fixed_header+2) = remainLen / 128;
	}

	packet_length = fixedHeaderSize+sizeof(var_header)+payload_len;
	packet = (uint8 *)malloc(packet_length);
	memset(packet, 0, packet_length);
	memcpy(packet, fixed_header, fixedHeaderSize);
	free(fixed_header);	
	offset += fixedHeaderSize;
	memcpy(packet+offset, var_header, sizeof(var_header));
	offset += sizeof(var_header);
	// Client ID - UTF encoded
	packet[offset++] = clientidlen>>8;
	packet[offset++] = clientidlen&0xFF;
	memcpy(packet+offset, client_id, clientidlen);
	offset += clientidlen;

	if(usernamelen) {
		// Username - UTF encoded
		packet[offset++] = usernamelen>>8;
		packet[offset++] = usernamelen&0xFF;
		memcpy(packet+offset, user_name, usernamelen);
		offset += usernamelen;
	}

	if(passwordlen) {
		// Password - UTF encoded
		packet[offset++] = passwordlen>>8;
		packet[offset++] = passwordlen&0xFF;
		memcpy(packet+offset, passwd, passwordlen);
		offset += passwordlen;
	}
	// Send the packet
    if (write(sockfd,packet, packet_length) < 0){
         free(packet);
        return -1;
    }
    free(packet);
	return 1;
}

int mqtt_connect_init(int sockfd)
{
    int ret =  mqtt_connect(sockfd);
    if(ret < 0){
        // printf("mqtt_connect error");
        return -1;
    }
    // printf("client send connect mqtt sucessful");
    return 0;
}

void *client_do_pthread(void *arg)
{
    pthread_detach(pthread_self());
    g_sockfd = socket_init();
    if (g_sockfd < 0){
        // printf("socket_init error");
        return NULL;
    }
    int ret;//发送三次握手报文
    ret = connect_server(SERVER_PORT,SERVER_IP,g_sockfd);
    if(ret < 0){
        // printf("connect_server error");
        return NULL;
    }
    ret = mqtt_connect_init(g_sockfd); 
    if (ret < 0){
        // printf("mqtt_connect_init failed");
        return NULL;
    }
    
    int i = 0;
    struct pollfd pollfds[1];
    while(1){
    	pollfds[0].fd= g_sockfd;
    	pollfds[0].events=POLLIN;
    	int num = poll(pollfds,1,-1);
        for(i=0; i < num; ++i){
            if(pollfds[i].revents & POLLIN){
    		   // do_socket(pollfds[0].fd);
				int num2 = 0;
				unsigned char buf[1024];
				memset(buf, 0, 1024);
				// while ((num2 = read(pollfds[0].fd, buf, 1024)) >= 0)
				// {
				// 	printf("buf :: ");
				// 	int i = 0;
				// 	for (i = 0; i < num2; i++)
				// 	{
				// 		printf("%x ", buf[i]);
				// 	}
					
				// 	printf("\n");
				// 	memset(buf, 0, 1024);
				// }


				num2 = read(pollfds[0].fd, buf, 1024);
				printf("buf :: ");
				int i = 0;
				for (i = 0; i < num2; i++)
				{
					printf("%x ", buf[i]);
				}
				
				printf("\n");
				
    	    }   
        }
    }
}

void show_help(void)
{
    printf("//\n");
    printf("1 subscribe theme\n");
    printf("2 unsubscribe theme\n");
    printf("3 publish msg\n");
    printf("//\n");
}

uint8_t length_trans_byte_form(int len , uint8_t * len_byte)
{
	uint8_t byte_num=0;
	uint8_t encode_byte =0;
	do{
		encode_byte = len % 128;
		len = len / 128 ;
		if(len > 0){
			encode_byte = encode_byte | 128;
		}
		len_byte[byte_num]=encode_byte;
		byte_num++;
	}while(len > 0);
	return byte_num;
}

int client_send(int fd, void *buf, size_t n) 
{
    size_t nleft = n, i = 0;
    int nwritten;
    unsigned char *bufp = (unsigned char *)buf;
    // printf("client_send ... \n");
    while (nleft > 0) {
        if ((nwritten = write(fd, bufp, nleft)) <= 0) {
            if (errno == EINTR || errno == EAGAIN)  /* interrupted by sig handler return */
                nwritten = 0;    /* and call write() again */
            else {
                // printf("%s", strerror(errno));
                perror("write");
                return -1;       /* errorno set by write() */
            }
        }
        nleft -= nwritten;
        bufp += nwritten;
    }
    return n;
}

static uint16 su_seq = 1;
int mqtt_subscribe_theme(int sockfd,char *Theme , uint8_t Qos)
{
	uint16_t MessageId = su_seq++;
	uint8_t cmd[1024]={0}, i = 0;
    //报文标示符长度2 + 主题长度位占用2字节+主题长度+qos标识
	int data_length = 2+2+strlen(Theme)+1; 
	int playload_len = strlen(Theme);
	uint8_t len_byte[4] ={0x00 , 0x00 ,0x00 ,0x00};
	uint8_t byte_num = length_trans_byte_form(data_length , len_byte);
	cmd[0] = 0x82;
	memcpy(&cmd[1] , len_byte , byte_num);
    printf("byte_num %d strlen(Theme) %d\n", byte_num, strlen(Theme));
	cmd[1+byte_num]=(MessageId & 0xff00) >> 8 ;
	cmd[1+byte_num+1] = MessageId & 0x00ff;		
	cmd[1+byte_num+1+1] = (playload_len & 0xff00) >> 8;
	cmd[1+byte_num+1+1+1] = playload_len & 0x00ff;
	memcpy(&cmd[1+byte_num+1+1+1+1] , Theme , playload_len);
	cmd[1+byte_num+1+1+1+1+playload_len] = Qos;
    printf("cmd %p : ", cmd);

    for(; i < 1+byte_num+1+1+1+1+playload_len+1; i++)
    {
        printf("%x ", cmd[i]);
    }
    printf("\n");
	client_send(sockfd, &cmd[0], 1+byte_num+1+1+1+1+playload_len+1);
}

static uint16  publish_seq = 0;
int mqtt_publish_with_qos(int clientfd,const char* topic, const char* msg, uint16 msgl, uint8 retain, uint8 qos, uint16* message_id) 
{
     printf("qos:%d",qos);
	uint16 topiclen = strlen(topic);
	//uint16 msglen = strlen(msg);
	uint16 msglen = msgl;
	uint8 *var_header = NULL; // Topic size (2 bytes), utf-encoded topic
	uint8 *fixed_header = NULL;
	uint8 fixedHeaderSize = 0,var_headerSize = 0;    // Default size = one byte Message Type + one byte Remaining Length
	uint16 remainLen = 0;
	uint8 *packet = NULL;
	uint16 packet_length = 0;

	uint8 qos_flag = MQTT_QOS0_FLAG;
	uint8 qos_size = 0; // No QoS included
	if(qos == 1) {
		qos_size = 2; // 2 bytes for QoS
		qos_flag = MQTT_QOS1_FLAG;
	}
	else if(qos == 2) {
		qos_size = 2; // 2 bytes for QoS
		qos_flag = MQTT_QOS2_FLAG;
	}
	// printf("qos : %d \n", qos_flag);
	// Variable header
	var_headerSize = topiclen+2+qos_size;
	var_header = (uint8 *)malloc(var_headerSize);
	memset(var_header, 0, var_headerSize);
	*var_header = topiclen>>8;
	*(var_header+1) = topiclen&0xFF;
	memcpy(var_header+2, topic, topiclen);
	if(qos_size) {
        publish_seq++;
        if (publish_seq == 0){
            publish_seq = 1;
        }
		var_header[topiclen+2] = (publish_seq & 0xff00)>>8;
		var_header[topiclen+3] = publish_seq & 0x00ff;
		if(message_id) { 
			*message_id = publish_seq;
		}
	}

	// Fixed header
	// the remaining length is one byte for messages up to 127 bytes, then two bytes after that
	// actually, it can be up to 4 bytes but I'm making the assumption the embedded device will only
	// need up to two bytes of length (handles up to 16,383 (almost 16k) sized message)
	fixedHeaderSize = 2;    // Default size = one byte Message Type + one byte Remaining Length
	remainLen = var_headerSize+msglen;
	if (remainLen > 127) {
		fixedHeaderSize++;          // add an additional byte for Remaining Length
	}
	fixed_header = (uint8 *)malloc(fixedHeaderSize);
    
	// Message Type, DUP flag, QoS level, Retain
	*fixed_header = MQTT_MSG_PUBLISH | qos_flag;
	if(retain) {
		*fixed_header  |= MQTT_RETAIN_FLAG;
	}
	// Remaining Length
	if (remainLen <= 127) {
	   *(fixed_header+1) = remainLen;
	} else {
	   // first byte is remainder (mod) of 128, then set the MSB to indicate more bytes
	   *(fixed_header+1) = remainLen % 128;
	   *(fixed_header+1) = *(fixed_header+1) | 0x80;
	   // second byte is number of 128s
	   *(fixed_header+2) = remainLen / 128;
	}

	packet_length = fixedHeaderSize+var_headerSize+msglen;
	//packet = (uint8 *)malloc(packet_length);
	packet = (uint8 *)malloc(packet_length);
	memset(packet, 0, packet_length);
	memcpy(packet, fixed_header, fixedHeaderSize);
	memcpy(packet+fixedHeaderSize, var_header, var_headerSize);
	memcpy(packet+fixedHeaderSize+var_headerSize, msg, msglen);
	free(var_header);
	free(fixed_header);

	//     printf("cmd %p : ", packet);
	// int i = 0;
    // for(; i < packet_length; i++)
    // {
    //     printf("%x ", packet[i]);
    // }
    // printf("\n");

    client_send(clientfd,packet , packet_length);
	free(packet);
	return 1;
}

int mqtt_publish(int clientfd,const char* topic, const char* msg, uint16 msglen, uint8 retain) 
{
	return mqtt_publish_with_qos(clientfd,topic, msg, msglen, retain, QOS_VALUE0, NULL);
}

void *cmd_do_pthread(void *arg)
{
    int cmd = 0;
       show_help();
		usleep(3*1000 * 1000);
            mqtt_subscribe_theme(g_sockfd,"/public/TEST/#",0);
		usleep(1000 * 1000);
    while(1){
    //    scanf("%d",&cmd); 
    //    if (cmd == 1){
    //    }
    //    else if (cmd == 2){
    //         mqtt_unsubscribe_theme(g_sockfd,"/public/TEST/#");
    //    }
    //    else if (cmd == 3){

            char msg[64] = {"client publish msg"};
            mqtt_publish(g_sockfd,"/public/TEST/ubuntu",msg,strlen(msg), 0);
    //    }
		usleep(3*1000 * 1000);
		continue;
    }
}

int main_loop(void)
{
    pthread_t t1;
    pthread_create(&t1,NULL,client_do_pthread,NULL);

    pthread_t t2;
    pthread_create(&t2,NULL,cmd_do_pthread,NULL);

    while(1){
        sleep(100);
    }
	return 0;
}

int main(void)
{
    main_loop();
    return 0;
}

// gcc  mydemo.c -lpthread -lm -ldl
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MQTT是一种轻量级的消息传输协议,用于物联网等场景下的设备通信。在C语言中,可以使用各种MQTT客户端库来实现MQTT客户端。下面是一个使用Eclipse Paho MQTT C客户端库实现MQTT客户端的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include "MQTTClient.h" #define ADDRESS "tcp://localhost:1883" #define CLIENTID "ExampleClientSub" #define TOPIC "test" #define QOS 1 #define TIMEOUT 10000L volatile MQTTClient_deliveryToken deliveredtoken; void deliveryComplete(void *context, MQTTClient_deliveryToken dt) { printf("Message with token value %d delivery confirmed\n", dt); deliveredtoken = dt; } int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) { printf("Message arrived on topic %s: %.*s\n", topicName, message->payloadlen, (char*)message->payload); MQTTClient_freeMessage(&message); MQTTClient_free(topicName); return 1; } int main(int argc, char* argv[]) { MQTTClient client; MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; MQTTClient_message pubmsg = MQTTClient_message_initializer; MQTTClient_deliveryToken token; int rc; MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); conn_opts.keepAliveInterval = 20; conn_opts.cleansession = 1; MQTTClient_setCallbacks(client, NULL, messageArrived, deliveryComplete, NULL); if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) { printf("Failed to connect, return code %d\n", rc); exit(EXIT_FAILURE); } if ((rc = MQTTClient_subscribe(client, TOPIC, QOS)) != MQTTCLIENT_SUCCESS) { printf("Failed to subscribe, return code %d\n", rc); exit(EXIT_FAILURE); } pubmsg.payload = "Hello world!"; pubmsg.payloadlen = strlen(pubmsg.payload); pubmsg.qos = QOS; pubmsg.retained = 0; MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token); printf("Waiting for up to %d seconds for publication of %s\n" "on topic %s for client with ClientID: %s\n", (int)(TIMEOUT/1000), pubmsg.payload, TOPIC, CLIENTID); rc = MQTTClient_waitForCompletion(client, token, TIMEOUT); printf("Message with delivery token %d delivered\n", token); MQTTClient_disconnect(client, 10000); MQTTClient_destroy(&client); return rc; } ``` 这个示例代码实现了一个MQTT客户端,通过TCP连接到本地的MQTT服务器,订阅了一个名为"test"的主题,发布了一条消息"Hello world!"。在接收到订阅主题的消息时,会调用messageArrived函数进行处理。在发布消息时,会等待一段时间来确认消息是否成功发布,并使用deliveryComplete函数进行处理。 需要注意的是,使用MQTT客户端库时需要根据具体的库提供的API进行调用,并根据实际情况进行配置和处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值