模拟mqtt 客户端工作流程
mqtt介绍
mqtt 连接服务器报文格式
mqtt 发布消息报文协议格式
mqtt 订阅消息报文格式
类型 | 内容和数据长度 |
---|---|
固定报头 | 0x82 + 剩余长度(1-4 byte) |
可变报头 | 报文标识符(2 byte) |
有效载荷 | 长度(2byte)主题名+qos |
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