其实,这篇文章我主要想分享 Node.js 的 Stream API 的实现的。。。讲着讲着跑题了
这几天在写一个小玩意,需要在 Android 上将一份一份的数据通过 WiFi 传输到电脑。电脑上的服务端使用 Node.js 搭建。
众所周知,TCP 中传输的数据会被切分成较小的包,客户端往 Socket 一次写入的数据,在另一头的服务器端并不是一次读出的。具体在 Node.js 中的现象就是,多次发生 'data'
事件,每次只带了很小一片数据。因此如何将这些小片数据重新组合在一起成了一个问题。
由于客户端运行在手机上,因此决定封一个极其简单的协议:thuck(好吧项目名字已经代表了我当时的心情了)。
首先,分析一下现在遇到的问题:大块数据被切分成了很小的包,小块数据有可能被和前面或后面的小包合并了。一份数据在哪里结束?下一份数据由从哪里开始?
目前我的解决方案是这样的:
客户端传递数据前,先传一个标志,以及接下来这份数据的长度;服务器端捕获到标志及长度后,开始收集这么一段长度的数据,收集完毕后合并输出。为了简单,这里有几个约定:
- 为了不用遍历整个数据包,约定第一个包的一开始就应该是标志。
- 标志约定就只有一个字节,经跟着是 32 位(4 字节)无符号整数表示的数据长度。
- 标志和长度组成了 5 个字节的头,假设这 5 个字节的头部不会被系统切断。
理想中的一份数据是这样的:
[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
[BBBBBBBBBBBBBBB]
[CCCCCCCCCCCCCCCCCCCCC]
[DDDDDDD]
于是,按照我们的这个「协议」加上头后的数据是这样的:
[FA][LENGTHAA][AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
[FB][LENGTHBB][BBBBBBBBBBBBBBB]
[FC][LENGTHCC][CCCCCCCCCCCCCCCCCCCCC]
[FD][LENGTHDD][DDDDDDD]
实际情况中,假设分包的大小不可知,传输的数据可能会是这样的:
[FA][LENGTHAA][AAAAAAAAAAAAAAAAAAAAAAA]
[AAAAAAAAAA][FB][LENGTHBB][BBBBBBBBBBB]
[BBBB][FC][LENGTHCC]
[CCCCCCCC]
[CCCCCCCCCCCCC][FD][LENGTHDD]
[DDDDDDD]
可能更悲催的,会把你的头也截断。。。这,想太多了,RP 不好就默哀吧。
貌似讲太多了,直接操家伙动手吧
TCP Socket 在 Node.js 中被封装成了 Stream 接口的一个衍生;而我对这些数据的使用也是流式使用。进去出来都是 Stream,因此我们可以借助 stream 模块的 Transport 抽象类实现一个转换器。
stream.Transform 是一个抽象类,意思就是本身不能直接使用,而是需要继承并重写对应的方法。在 Node.js 中类的继承可以用 util 模块中的 inherits
函数:
var inherits = require('util').inherits
, Transform = require('stream').Transform
module.exports = Thuck
inherits(Thuck, Transform)