图像比文字来的更直白!
物联网(Internet of things):顾名思义,所有的物体都能够联网。
那么怎么建立一个这样的通信网络呢?我们今天以搭建提供tcp实时通信服务的服务器为例子。
有人感觉到物联网很神秘,也有人感觉到tcp很陌生,提到http大家可能都很熟悉,其实http就是建立在tcp基础上的一层协议,今天跟着本节课的教程,大家甚至可以自己创建属于自己的通信协议。
让我们开始吧!
-
开发准备
- [x] vscode or sublime 码农的武器
- [x] Node.js 基础知识
- [x] es6 相关语法
- [x] 善学、勤思的良好品质
-
搭建一个最基础的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服务器搭建。
-
我们写一个简单的客户端来验证一下
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) }) 复制代码
-
我们运行下服务端和客户端的代码,发现服务端收到了客户端发来的数据
from client> <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64> 复制代码
或许很多人都对这种格式的数据很陌生,其实这是一最基础的底层数据表达方式,即字节流,又称字节数组。
上述的每一个字节都可以通过查询ascii码表来解释,转换成字符串就是
hello world
-
说了这么多难道就是为了让我们搞个
hello world
?说好的自定义协议呢?
okok!I 服了 U!在设计自定义协议之前,我需要缺人你们是否了解 大端和小端的概念!楼主不是在讲天书!
在计算机系统中,大家都知道有
int8
,int16
,int32
等等这些数字不同形式,但是在计算机中是怎么表示这些数字的,大家清楚吗?如果不清楚的话,请跟我来。 -
大端?小端?不再神秘!
所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
纸上得来终觉浅,绝知此事要躬行!我们来写几个例子!我们直接用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写完
自定义协议
-
知道了大小端,对我们自定义协议有什么帮助呢?【首先我们明确使用小端模式】
假设我们要和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通信服务器已经搞定了,善于思考和实践的同学可能会应用到各种智能硬件中,或者想着去应用了。但是我们的这段代码离真正的应用级还差的很远,可能会碰到很多很多的坑,下回我在带你们一起填坑。
近期评论