起因
最近在用 Node.js 写一些网络请求相关的代码时,频繁在一些开源代码中看到 drain 事件的使用,于是我也依葫芦画瓢写到了自己的代码里面:
1 2 3 4 5 |
socket.on('drain', function(){ console.log('drain event fired.'); }); socket.write('some data.'); |
实际放到线上测试的时候发现,在一些情况下,drain 事件真的会被触发,那到底什么时候会触发 drain 事件呢?drain 事件能用来做什么呢?本着打破砂锅问到底的精神,我决定探究一番。
TLDR
请直接跳到小结
部分。
探究
最简单的办法就是查文档。因为我写的是网络请求相关的代码,那么我就先翻越了 net 和 socket 相关的部分。在 Node.js 官方文档中,对于 socket.write 有这么一部分描述:
Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of the data was queued in user memory. ‘drain’ will be emitted when the buffer is again free.
也就是说,drain 事件是和 socket.write 的返回值强关联的,那么我们可以做一个简单的实验(只写关键部分):
1 2 3 4 5 6 7 |
socket.on('drain', function(){ console.log('drain event fired.'); }); var ret = socket.write('some data.'); console.log('write data returned %s.', ret); |
可是无论我怎么运行这部分代码,返回值总是 true,drain 事件没有被触发。那为啥线上就能触发呢?按照文档所说,只有全部或者部分数据被缓冲在了内存里面才会返回 false。那问题又来了,什么时候数据才会被缓冲呢?
既然是被缓冲了,那最先猜测到就是数据流量太大。就像每天上下班高峰期的文一西路那样,一旦车流量太大,前面的路口塞满了,交警就会让后面的车停下来。
好,那我们加大“车”流量(为节省篇幅,部分代码省略):
服务端代码:
1 2 3 4 |
var net = require('net'); net.createServer(function(socket){ // do something }).listen(6666, '127.0.0.1'); |
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var net = require('net'); function writeToRemote(){ var client = new net.Socket(); client.connect(6666, '127.0.0.1', function(){ if(!client.write('some data by ziying.')){ console.log('write returned false.'); } }); client.on('drain', function(){ console.log('drain event fired.'); }); client.on('data', function(){ client.destroy(); }); } // 加大流量 for(var i = 0; i < 1000; i++){ writeToRemote(); } |
但运行多次之后发现仍旧没有看到任何 drain 事件的迹象。难道次数不够?随着我继续增大 i 的最大值,直到遇到(libuv) kqueue(): Too many open files in system
的错误时候,我仍旧没看到 drain 事件。
逼我用绝招。看 Node.js 源代码!
因为 socket.write 实际上是调用的 Stream.write(参考此处源代码),最后我们在 Stream.write 的代码中找到了一丝端倪:
1 2 3 4 5 6 7 8 |
var ret = state.length < state.highWaterMark; // we must ensure that previous needDrain will not be reset to false. if (!ret) state.needDrain = true; // something else return ret; |
可以看到当要写的数据的长度大于 highWaterMark (字面理解:高水位线)的时候,那么 Stream.write 就会返回 false,也就会触发 drain 事件了。
那这个高水位线具体是多少呢?可以继续看代码: