zhengrenzhe's blog   About

NodeJS传输二进制数据

最近在用NodeJS写一个东西,需要直接使用TCP协议传输数据,本文就讨论一下使用NodeJS进行TCP数据传输的一些姿势。

在NodeJS中有对于TCP协议封装好的模块net,通过它可以方便的创建TCP连接。更多关于net模块的信息,可在NodeJS的API文档中查询。

接着要建立一个TCP Client,可以使用以下代码建立:

let socket = new net.Socket();

socket.connect({
    'port': 22,
    'host': ip
});

这样就会建立起一个TCP连接,在这个socket上会有若干种事件,对应一个TCP连接生命周期的不同部分,按需使用即可,通常connect, data, end是比较常用的,分别代表TCP连接已建立, 收到数据, 连接中断三种情况。

传输字符串

下面就该说到本文的重点:传输数据。这看似是很简单的,因为只要调用socketwrite方法,即可发送数据:

socket.write('Hello, World!');

通过Wireshark抓包可以看到发送的数据。

两个蓝色的选区就是相对应的部分,左边为数据的Hex数据,右边为ASCII转储。这里每个ASCII字符都以ASCII码的形式被传输。通过write方法传输的字符串数据,都会自动被转化为Buffer形式在传输。Buffer是NodeJS专门为处理二进制数据而开发的模块,弥补了JavaScript在操作二进制数据方面的不足。所以把字符串数据转为Buffer对象再传输与上面的效果是相同的。

socket.write(new Buffer('Hello, World!'));

传输整形数据

对于字符串很简单,但是对于数字来说就有点麻烦了,因为write方法只能接受StringBuffer类型的数据,所以对于数字来说需要直接构造Buffer类型的数据,除非你真的想把数字当作字符串来使用。

那么该如何构造只包含Int形Buffer呢?查看官方API可以看到Buffer有以下几种构造方式:

new Buffer([0x62])   // 'b'

通过这种方法构造出的数据本质上与直接传入字符串的效果是相同的,这显然是不适用于构造Int形Buffer。

new Uint16Array([1,2,1,-2])    // Uint16Array { '0': 1, '1': 2, '2': 1, '3': 65534 }
new Int16Array([1,2,1,-2])     // Int16Array { '0': 1, '1': 2, '2': 1, '3': -2 }

arrayBuffer与普通array很相似,大部分属性与array是相同的,但最大的区别就是arrayBuffer一旦创建,长度就是不可变的,它没有push, pop等方法,详细信息可以看MDN文档。通过arrayBuffer创建的buffer结构将与arrayBuffer相同,两者也会占用同样的内存空间。

new Buffer(new Uint16Array([12]))  // <Buffer 0c>

从上面的代码可以看见,将arrayBuffer传给Buffer,arrayBuffer中的12就会变成二进制的数据,使用这种方法就可以将数字转化为二进制数据进行传输,抓包看一下,最后的 0c 就是数字12的二进制形式。

let a = new Buffer(2);  // <Buffer 01 00>
a.fill(0);  // <Buffer 00 00>

现在有了一个空的buffer,该如何向内部写入数据呢?buffer实例有相当多的方法可以向内部写入数据:

这些方法对功能都是类似的,就是向buffer内部写入不同固定长度的数据,并且每种固定长度都有大小端之分。那该如何使用呢?以writeUIntBE为例,它是向buffer内写入无符号整形数据的,接受的参数为writeUIntBE(value, offset, byteLength[, noAssert]),分别为待写入数据,偏移量,字节长度,是否跳过检查value与offset。

let a = new Buffer(2).fill(0);  // <Buffer 00 00>
a.writeUIntBE(12, 0, 1);   // <Buffer 0c 00>

可以看到buffer的索引为0的位置的数据被填充为了0c,也就是12。所以通过这种方法也是可以构造Int形Buffer,相比通过arrayBuffer来构造有更高的自由度。例如要创建一个4字节长度的buffer,内部数据为183:

let a = new Buffer(4).fill(0);
a.writeUIntBE(183, 0, 4);    //  <Buffer 00 00 00 b7>

抓包看一下上面数据的传输情况:

可以看到这一段的数据占用了4字节,空白地方以00填充。

综上,将Int型数据填充至buffer有两种方法:

传输字符串整形混合数据

有了上面的填充整形与字符型数据的方法,混合型数据就简单多了,只要把不同类型的buffer合并到一起即可,Buffer的concat方法可以为我们完成这个操作:

let a = new Buffer('A');   // <Buffer 41>
let b = new Buffer('B');   // <Buffer 42>
let c = new Buffer('C');   // <Buffer 43>
let d = new Buffer(1).fill(0);
d.writeUIntBE(12, 0, 1);   // <Buffer 0c>
let r = Buffer.concat([a, b, c, d]);   // <Buffer 41 42 43 0c>

这时发送的数据就是buffer中的内容了。

← WTF的“火星坐标”  Selection & Range 基础 →