物联网时代——Node.js能做些什么

9c6833e49b567c3f7e5ff88248d27dc7

图像比文字来的更直白!

物联网(Internet of things):顾名思义,所有的物体都能够联网。

那么怎么建立一个这样的通信网络呢?我们今天以搭建提供tcp实时通信服务的服务器为例子。

有人感觉到物联网很神秘,也有人感觉到tcp很陌生,提到http大家可能都很熟悉,其实http就是建立在tcp基础上的一层协议,今天跟着本节课的教程,大家甚至可以自己创建属于自己的通信协议。

让我们开始吧!

github

  1. 开发准备

    • [x] vscode or sublime 码农的武器
    • [x] Node.js 基础知识
    • [x] es6 相关语法
    • [x] 善学、勤思的良好品质
  2. 搭建一个最基础的tcp服务器

     import net from 'net'
    
     var server = net.createServer((conn) => {
         conn.on('data', (data) => {
             console.log('from client> ', data)
         })
     })
    
     server.on('error', (err) => {
         console.log(err)
         return process.exit(0)
     })
    
     server.listen(3000)
    复制代码

    10行代码完成了一个最基础的tcp服务器搭建。

  3. 我们写一个简单的客户端来验证一下

     import net from 'net'
    
     var client = net.createConnection({port: 3000}, (err) => {
         if (err) {
             console.log(err)
         } else {
             client.write('hello world')
         }
     })
    
     client.on('data', (data) => {
         console.log('from server> ', data)
     })
    复制代码
  4. 我们运行下服务端和客户端的代码,发现服务端收到了客户端发来的数据

     from client>  <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
    复制代码

    或许很多人都对这种格式的数据很陌生,其实这是一最基础的底层数据表达方式,即字节流,又称字节数组。

    上述的每一个字节都可以通过查询ascii码表来解释,转换成字符串就是 hello world

  5. 说了这么多难道就是为了让我们搞个 hello world?说好的自定义协议呢?
    okok!I 服了 U!在设计自定义协议之前,我需要缺人你们是否了解 大端和小端的概念!

    3f2d1e15b3ccae3298c13143d65dbaf6

    楼主不是在讲天书!

    在计算机系统中,大家都知道有int8,int16,int32等等这些数字不同形式,但是在计算机中是怎么表示这些数字的,大家清楚吗?如果不清楚的话,请跟我来。

  6. 大端?小端?不再神秘!

    所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

    所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。

    纸上得来终觉浅,绝知此事要躬行!我们来写几个例子!我们直接用Node.js 的repl功能即可,命令行输入 node,进入该模式 (该模式下enter代表一次输入)

     var a = 16
     var b = Buffer.alloc(2) // 给b分配2个字节,表示uint16数字
     var c = Buffer.alloc(2) // 同样给c分配2个字节,表示uint16数字
     b.writeUInt16LE(a)  // 以小端写入 
     c.writeUInt16BE(a)  // 以大端写入
    复制代码

    大家能才到b、c的运行结果分别是什么吗?这里不卖官子。

     b: <buffer 10 00>
     c: <buffer 00 10>
    复制代码

    细心聪明的大家肯定发现了,对照我们刚才讲的大端小端的概念,I think you got it!

2017.9.20 有点困了


明天继续ok,早起上班,先把这篇blog写完

自定义协议

  1. 知道了大小端,对我们自定义协议有什么帮助呢?【首先我们明确使用小端模式】

    假设我们要和gps模块[具有4g或者无线通信功能]

字段 字段描述 字节 备注
lat 纬度 4 放大10000000倍传输,int32
lng 经度 4 放大10000000倍传输 ,int32
sat_num 卫星数量 1 无,uint8
alt 卫星高度 2 放大10倍传输,int16

2.那么我们该怎么定义协议呢?这里我仅提供一个简单的协议模版供参考。

序号 内容 字节 必需 描述
1 de1e 2 协议头[固定]
2 C 1 数据指令,代表数据来源,这里我们规定0x01为我们的gps模块
3 L 1 数据包长度,最长255
4 D 可变长 数据包内容
5 K 2 CRC校验字节
crc校验码是为了对数据完整性进行校验的一种方式。网上有很多教程,大家自行查阅。

这里我提供一个简单的生成crc16校验码程序


```
function crc16 (buffer) {
    var wCRC_Table = [
        0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
        0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
    ]

    var crcWord = 0xFFFF
    var result = Buffer.alloc(2)
    var start = 0
    var length = buffer.length

    function Drv_Crc_Acc(data) {
        var temp

        temp = wCRC_Table[(data ^ crcWord) & 15] ^ (crcWord >> 4)
        crcWord = wCRC_Table[((data >> 4) ^ temp) & 15] ^ (temp >> 4)
    }
    while (start < length) {
        Drv_Crc_Acc(buffer[start]);
        start++
        if (start == length) {
            result.writeUInt16LE(crcWord)
            return result
        }
    }
}
```
复制代码

3.hi,小伙伴们,一份简单的自定义的协议就是如此简单哟!下面让我们简单的来验证一下吧。

//server.js
import net from 'net'


function crc16 (buffer) {
    var wCRC_Table = [
        0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
        0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
        ]

    var crcWord = 0xFFFF
    var result = Buffer.alloc(2)
    var start = 0
    var length = buffer.length

    function Drv_Crc_Acc(data) {
        var temp

        temp = wCRC_Table[(data ^ crcWord) & 15] ^ (crcWord >> 4)
        crcWord = wCRC_Table[((data >> 4) ^ temp) & 15] ^ (temp >> 4)
    }


    while (start < length) {
        Drv_Crc_Acc(buffer[start]);
        start++
        if (start == length) {
            result.writeUInt16LE(crcWord)
            return result
        }
    }
}

function parser(buffer) {
    let head = buffer.slice(0,2)
    let gps = buffer.slice(2,3)
    let length = buffer.slice(3,4)
    let data_buffer = buffer.slice(4,15)
    let crc = buffer.slice(15)
    let crc_vali = crc16(buffer.slice(0,15))
    if (crc.compare(crc_vali) == 0) {
        console.log('crc校验成功')
        let lat = data_buffer.readInt32LE(0) / 10000000
        let lng = data_buffer.readInt32LE(4) / 10000000
        let sat_no = data_buffer.readUInt8(8)
        let alt = data_buffer.readInt16LE(9) / 10

        console.log(`gps location is lat:${lat},lng:${lng},height:${alt}m,current sat Num: ${sat_no}`)
    }
} 

var server = net.createServer((conn) => {
    conn.on('data', (data) => {
        parser(data)
    })
})


server.on('error', (err) => {
    console.log(err)
    return process.exit(0)
})

server.listen(4000, (e) => {
    console.log(e, `running`)
})
复制代码
//client.js
import net from 'net'

function crc16 (buffer) {
    var wCRC_Table = [
        0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
        0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
        ]

    var crcWord = 0xFFFF
    var result = Buffer.alloc(2)
    var start = 0
    var length = buffer.length

    function Drv_Crc_Acc(data) {
        var temp

        temp = wCRC_Table[(data ^ crcWord) & 15] ^ (crcWord >> 4)
        crcWord = wCRC_Table[((data >> 4) ^ temp) & 15] ^ (temp >> 4)
    }


    while (start < length) {
        Drv_Crc_Acc(buffer[start]);
        start++
        if (start == length) {
            result.writeUInt16LE(crcWord)
            return result
        }
    }
}

var client = net.createConnection({port: 4000}, () => {
    let head = new Buffer('de1e','hex')  // 固定头
    let gps = new Buffer('01','hex') // gps协议 0x01

    let length = new Buffer('0b','hex') // 数据段长度为11,16进制表示就是 0x0b

    let lat = 32.083  // 模拟纬度
    let lng = 118.32019  // 模拟经度 
    let sat_num = 12 // 模拟搜星数
    let alt = 5.3 // 模拟高度

    let data_buffer = Buffer.alloc(11) //根据gps协议,总共有11个字节的数据段长度
    data_buffer.writeInt32LE(lat * 10000000,0)    
    data_buffer.writeInt32LE(lng * 10000000,4)
    data_buffer.writeUInt8(sat_num,8)
    data_buffer.writeInt16LE(alt * 10, 9)

    let crc = crc16(Buffer.concat([head, gps, length ,data_buffer]))

    let cmd = Buffer.concat([head,gps,length,data_buffer,crc])
    console.log(cmd)
    client.write(cmd)

})

client.on('data', (data) => {
    console.log('from server> ', data)
})
复制代码

别偷懒,跟着我撸撸代码,你将会提升更多。

到这里,我们的自定义协议的tcp通信服务器已经搞定了,善于思考和实践的同学可能会应用到各种智能硬件中,或者想着去应用了。但是我们的这段代码离真正的应用级还差的很远,可能会碰到很多很多的坑,下回我在带你们一起填坑。