物联网协议应用


前言

  本文主要介绍一下物联网协议如NTP协议HTTP协议MQTT协议的接口使用。在了解物联网协议之前需要了解一下TCP/IP协议lwIP协议,参考以下链接:https://blog.youkuaiyun.com/weixin_44567668/article/details/139619797


一、NTP协议

1.1 NTP简介

  NTP(Network Time Protocol)网络时间协议基于UDP,是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS 等等)做同步化,它可以提供高精准度的时间校正(LAN 上与标准间差小于 1 毫秒,WAN 上几十毫秒),且可介由加密确认的方式来防止恶毒的协议攻击。时间按 NTP 服务器的等级传播。按照离外部 UTC 源的远近把所有服务器归入不同的 Stratum(层)中。
  NTP数据报文格式,如下图所示:
在这里插入图片描述
  NTP数据报文格式的各个字段的作用,如下表所示:
在这里插入图片描述
在这里插入图片描述
  从上表可知,NTP 报文的字段非常多,这些字段并不是每一个都必须设置的,可以根据项目的需要来构建 NTP 请求报文。
在这里插入图片描述

1.2 NTP实现

  由上可以知道获取 NTP 实时时间步骤了:

① 以 UDP 协议连接阿里云 NTP 服务器
② 发送 NTP 报文到阿里云 NTP 服务器
③ 获取阿里云 NTP服务器返回的数据,取第 40 位到 43 位的十六进制数值。
④ 把 40 位到 43 位的十六进制数值转成十进制
⑤ 把十进制数值减去1900-1970 的时间差(2208988800 秒)
⑥ 数值转成年月日时分秒

在这里插入图片描述

  1. lwip_demo.h头文件
      主要创建两个结构体,一个用来获取参数,一个用来显示时间
#define NTP_DEMO_RX_BUFSIZE   2000  /* 定义udp最大接收数据长度 */
#define NTP_DEMO_PORT         123   /* 定义udp连接的本地端口号 */

typedef struct _NPTformat
{
   
    char    version;            /* 版本号 */
    char    leap;               /* 时钟同步 */
    char    mode;               /* 模式 */
    char    stratum;            /* 系统时钟的层数 */
    char    poll;               /* 更新间隔 */
    signed char  precision;     /* 精密度 */
    unsigned int   rootdelay;   /* 本地到主参考时钟源的往返时间 */
    unsigned int   rootdisp;    /* 统时钟相对于主参考时钟的最大误差 */
    char    refid;              /* 参考识别码 */
    unsigned long long  reftime;/* 参考时间 */
    unsigned long long  org;    /* 开始的时间戳 */
    unsigned long long  rec;    /* 收到的时间戳 */
    unsigned long long  xmt;    /* 传输时间戳 */
} NPTformat;

typedef struct _DateTime  /*此结构体定义了NTP时间同步的相关变量*/
{
   
    int  year;        /* 年 */
    int  month;       /* 月 */
    int  day;         /* 天 */
    int  hour;        /* 时 */
    int  minute;      /* 分 */
    int  second;      /* 秒 */
} DateTime;

#define SECS_PERDAY     86400UL         /* 一天中的几秒钟 = 60*60*24 */
#define UTC_ADJ_HRS     8               /* SEOUL : GMT+8(东八区北京)  */
#define EPOCH           1900            /* NTP 起始年  */
#define HOST_NAME  "ntp1.aliyun.com"    /*阿里云NTP服务器域名 */
  1. lwip_demo.c源文件
#define NTP_TIMESTAMP_DELTA  2208988800UL
const char g_days[12] = {
   31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
NPTformat g_ntpformat;                                                        /* NT数据包结构体 */
DateTime g_nowdate;                                                           /* 时间结构体 */
uint8_t g_ntp_message[48];                                                    /* 发送数据包的缓存区 */  
uint8_t g_ntp_demo_recvbuf[NTP_DEMO_RX_BUFSIZE];                              /* NTP接收数据缓冲区 */

uint8_t g_lwip_time_buf[100];

/**
 *@brief     计算日期时间
 *@param     secondsUTC 世界标准时间
 *@retval    无
*/
void lwip_calc_date_time(unsigned long long time)
{
   
   unsigned int Pass4year;
    int hours_per_year;
    if (time <= 0)
    {
   
        time = 0;
    }

    g_nowdate.second = (int)(time % 60);   /* 取秒时间 */
    time /= 60;

    g_nowdate.minute = (int)(time % 60);   /* 取分钟时间 */
    time /= 60;
    
    g_nowdate.hour = (int)(time % 24);     /* 小时数 */

    Pass4year = ((unsigned int)time / (1461L * 24L));/* 取过去多少个四年,每四年有 1461*24 小时 */

    g_nowdate.year = (Pass4year << 2) + 1970;    /* 计算年份 */

    time %= 1461 * 24;     /* 四年中剩下的小时数 */

    for (;;)               /* 校正闰年影响的年份,计算一年中剩下的小时数 */
    {
   
        hours_per_year = 365 * 24;         /* 一年的小时数 */

        if ((g_nowdate.year & 3) == 0) /* 判断闰年 */
        {
   

            hours_per_year += 24;          /* 是闰年,一年则多24小时,即一天 */
        }

        if (time < hours_per_year)
        {
   
            break;
        }

        g_nowdate.year++;
        time -= hours_per_year;
    }

    time /= 24;   /* 一年中剩下的天数 */

    time++;       /* 假定为闰年 */

    if ((g_nowdate.year & 3) == 0)      /* 校正闰年的误差,计算月份,日期 */
    {
   
        if (time > 60)
        {
   
            time--;
        }
        else
        {
   
            if (time == 60)
            {
   
                g_nowdate.month = 1;
                g_nowdate.day = 29;
                return ;
            }
        }
    }

    for (g_nowdate.month = 0; g_days[g_nowdate.month] < time; g_nowdate.month++)   /* 计算月日 */
    {
   
        time -= g_days[g_nowdate.month];
    }

    g_nowdate.day = (int)(time);

    return;

}

/**
 *@brief     从NTP服务器获取时间
 *@param     buf:存放缓存
 *@param     idx:定义存放数据起始位置
 *@retval    无
*/
void lwip_get_seconds_from_ntp_server(uint8_t *buf, uint16_t idx)
{
   
    unsigned long long atk_seconds = 0; 
    uint8_t i = 0;

    for (i = 0; i < 4; i++)  /* 获取40~43位的数据 */
    {
   
        atk_seconds = (atk_seconds << 8) | buf[idx + i]; /* 把40~43位转成16进制再转成十进制 */
    }

    atk_seconds -= NTP_TIMESTAMP_DELTA;/* 减去减去1900-1970的时间差(2208988800秒) */
    lwip_calc_date_time(atk_seconds);       /* 由UTC时间计算日期 */
}

/**
 *@brief     初始化NTP Client信息
 *@param     无
 *@retval    无
*/
void lwip_ntp_client_init(void)
{
   
    uint8_t flag;

    g_ntpformat.leap = 0;           /* leap indicator */
    g_ntpformat.version = 3;        /* version number */
    g_ntpformat.mode = 3;           /* mode */
    g_ntpformat.stratum = 0;        /* stratum */
    g_ntpformat.poll = 0;           /* poll interval */
    g_ntpformat.precision = 0;      /* precision */
    g_ntpformat.rootdelay = 0;      /* root delay */
    g_ntpformat.rootdisp = 0;       /* root dispersion */
    g_ntpformat.refid = 0;          /* reference ID */
    g_ntpformat.reftime = 0;        /* reference time */
    g_ntpformat.org = 0;            /* origin timestamp */
    g_ntpformat.rec = 0;            /* receive timestamp */
    g_ntpformat.xmt = 0;            /* transmit timestamp */

    flag = (g_ntpformat.version << 3) + g_ntpformat.mode; /* one byte Flag */
    memcpy(g_ntp_message, (void const *)(&flag), 1);
}

/**
 * @brief  lwip_demo程序入口
 * @param  无
 * @retval 无
*/
void lwip_demo(void)
{
   
    err_t err;
    static struct netconn *udpconn;
    static struct netbuf  *recvbuf;
    static struct netbuf  *sentbuf;
    ip_addr_t destipaddr;
    uint32_t data_len = 0;
    struct pbuf *q;
    lwip_ntp_client_init();
    /* 第一步:创建udp控制块 */
    udpconn = netconn_new(NETCONN_UDP);
    /* 定义接收超时时间 */
    udpconn->recv_timeout = 10;

    if (udpconn != NULL) /* 判断创建控制块释放成功 */
    {
   
        /* 第二步:绑定控制块、本地IP和端口 */
        err = netconn_bind(udpconn, IP_ADDR_ANY, NTP_DEMO_PORT);
        /* 域名解析 */
        netconn_gethostbyname((char *)(HOST_NAME), &(destipaddr));
        /* 第三步:连接或者建立对话框 */
        netconn_connect(udpconn, &destipaddr, NTP_DEMO_PORT); /* 连接到远端主机 */

        if (err == ERR_OK) /* 绑定完成 */
        {
   
            while (1)
            {
   
                sentbuf = netbuf_new();
                netbuf_alloc(sentbuf, 48);
                memcpy(sentbuf->p->payload, (void *)g_ntp_message, sizeof(g_ntp_message));
                err = netconn_send(udpconn, sentbuf); /* 将sentbuf中的数据发送出去 */
                if (err != ERR_OK)
                {
   
                    printf("发送失败\r\n");
                    netbuf_delete(sentbuf); /* 删除buf */
                }
                netbuf_delete(sentbuf);      /* 删除buf */

                /* 第五步:接收数据 */
                netconn_recv(udpconn, &recvbuf);
                vTaskDelay(1000);     /* 延时1s */
                if (recvbuf != NULL)  /* 接收到数据 */
                {
   
                    memset(g_ntp_demo_recvbuf, 0, NTP_DEMO_RX_BUFSIZE); /*数据接收缓冲区清零 */

                    for (q = recvbuf->p; q != NULL; q = q->next) /*遍历完整个pbuf链表 */
                    {
   
                        /* 判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,如果大于 */
                        /* 的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据 */
                        if (q->len > (NTP_DEMO_RX_BUFSIZE - data_len)) memcpy(g_ntp_demo_recvbuf + data_len, q->payload, (NTP_DEMO_RX_BUFSIZE - data_len)); /* 拷贝数据 */
                        else memcpy(g_ntp_demo_recvbuf + data_len, q->payload, q->len);

                        data_len += q->len;

                        if (data_len > NTP_DEMO_RX_BUFSIZE) break;          /* 超出TCP客户端接收数组,跳出 */
                    }

                    data_len = 0;                                           /* 复制完成后data_len要清零 */
                    lwip_get_seconds_from_ntp_server(g_ntp_demo_recvbuf,40);   /* 从NTP服务器获取时间 */
                    printf("北京时间:%02d-%02d-%02d %02d:%02d:%02d\r\n",  
                           g_nowdate.year, 
                           g_nowdate.month + 1,
                           g_nowdate.day,
                           g_nowdate.hour + 8,
                           g_nowdate.minute,
                           g_nowdate.second);
                    sprintf((char*)g_lwip_time_buf,"BJ time:%02d-%02d-%02d %02d:%02d:%02d", g_nowdate.year, 
                                                                                               g_nowdate.month + 1,
                                                                                               g_nowdate.day,
                                                                                               g_nowdate.hour + 8,
                                                                                               g_nowdate.minute,
                                                                                               g_nowdate.second);
                    lcd_show_string(5, 170, lcddev.width, 16, 16, (char*)g_lwip_time_buf, RED);
                    
                    netbuf_delete(recvbuf);             /* 删除buf */
                }
                else vTaskDelay(5);                     /* 延时5ms */
            }
        }
        else printf("NTP绑定失败\r\n");
    }
    else printf("NTP连接创建失败\r\n");
}

  在此文件下定义了四个函数,这些函数的作用如下表所示:

函数 描述
lwip_demo() 实现UDP连接,使用NETCONN接口
lwip_ntp_client_init() 构建NTP请求报文
lwip_get_seconds_from_ntp_server() 获取NTP服务器的数据
lwip_calc_date_time() 计算日期时间

二、HTTP协议

2.1 HTTP协议简介

  HTTP(Hypertext Transfer Protocol)协议,即超文本传输协议,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。HTTP 协议是基于TCP/IP 协议的网络应用层协议。默认端口为80端口。HTTP 协议是一种请求/响应式的协议。一个客户端与服务器建立连接之后,发送一个请求给服务器。服务器接收到请求之后,通过接收到的信息判断响应方式,并且给予客户端相应的响应,完成整个 HTTP数据交互流程。
在这里插入图片描述
  HTTP定义了与服务器交互的不同方法,其最基本的方法是 GET、PORT 和 HEAD。如下图所示。
在这里插入图片描述

  • GET:从服务端获取数据。
  • PORT:向服务器传送数据。
  • HEAD:检测一个对象是否存在。

  互联网通过URL来定位,URL全称是 Uniform Resource Locator,是互联网上用来标识某一处资源的绝对地址,大部分 URL 都会遵循 URL 的语法,一个 URL 的组成有多个不同的组件,一个 URL的通用格式如下:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

在这里插入图片描述
在这里插入图片描述

  HTTP 报文是由 3 个部分组成,分别是:对报文进行描述的“起始行”,包含属性的“首部”,以及可选的“数据主体”,对于请求报文与应答报文,只有“起始行”的格式是不一样的。起始行和首部就是由行分隔的 ASCII 文本组成,每行都以由两个字符组成的行终止序列作为结束,其中包括一个回车符(ASCII 码 13)和一个换行符(ASCII 码 10), 这个行终止序列可以写做 CRLF。

# HTTP请求报文
<method> <request-URL> <version> //起始行
<headers> 						//首部
 
<entity-body> 					//数据主体

# HTTP应答报文
<version> <status> <reason-phrase> //起始行
<headers> 							//首部

<entity-body> 						//数据主体

  下面就对这两种 HTTP 报文的各个部分简单描述一下:

  • 方法(method):HTTP 请求报文的起始行以方法作为开始,方法用来告知服务器要做些什么,常见的方法有 GET、POST、HEAD 等,比如“GET /forum.php HTTP/1.1” 使用的就是 GET 方法。
  • 请求 URL(request-URL):指定了所请求的资源。
  • 版本(version):指定报文所使用的 HTTP 协议版本,其中指定了主要版本号, 指定了次要版本号,它们都是整数,其格式如下:
HTTP/<major>.<minor>
  • 状态码(status)&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别问,问就是全会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值