nodejs tcp socket发送多个数据事件

我试图通过用tcp包编码来演示http服务器如何工作的最简单的方法。 我之前做了几次,但今天我面临一个意想不到的行为,因为来自套接字对象的data事件被随机触发一个或多个类似的请求,我想知道为什么,以及如何正确地解决它。

请注意,我知道我应该使用stream数据处理方式,这是我在第二次演示中所做的。 每一次都会增加复杂性,使演示文稿更容易​​遵循。

这是服务器。 正如你所看到的,它很简单,容易得到。

 const net = require('net') const response = `HTTP/1.1 200 OK Access-Control-Allow-Origin: * Foo: Bar foobar ` net.createServer(socket => { socket.on('data', buffer => { console.log('----- socket data', Date.now()) console.log(buffer.toString()) socket.write(response) socket.end() console.log('-----') }) socket.on('end', () => console.log('----- socket end.')) socket.on('close', () => console.log('----- socket close.', '\n')) }).listen(2000) 

为了testing我的服务器,我只要打开任何Web浏览器到http://localhost:2000并获得响应; 但是当使用下面的有效负载(使用浏览器的JavaScript控制台)时, 有时候数据事件会被触发两次,最终导致一个错误,因为write/end进程不能再次进行。

 var xhr = new XMLHttpRequest(); xhr.open("POST", "/"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify({ foo: "bar" })); 

如果有什么可以帮助的话,这是来自服务器的日志的快照:

 ----- socket data 1479133993862 POST / HTTP/1.1 Host: localhost:2000 Connection: keep-alive Content-Length: 13 Pragma: no-cache Cache-Control: no-cache Origin: http://localhost:2000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 Content-Type: application/json Accept: */* Referer: http://localhost:2000/ Accept-Encoding: gzip, deflate, br Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,id;q=0.2,ms;q=0.2,ko;q=0.2 {"foo":"bar"} ----- ----- socket end. ----- socket close. ----- socket data 1479133994515 POST / HTTP/1.1 Host: localhost:2000 Connection: keep-alive Content-Length: 13 Pragma: no-cache Cache-Control: no-cache Origin: http://localhost:2000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 Content-Type: application/json Accept: */* Referer: http://localhost:2000/ Accept-Encoding: gzip, deflate, br Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,id;q=0.2,ms;q=0.2,ko;q=0.2 {"foo":"bar"} ----- ----- socket end. ----- socket close. ----- socket data 1479133995166 POST / HTTP/1.1 Host: localhost:2000 Connection: keep-alive Content-Length: 13 Pragma: no-cache Cache-Control: no-cache Origin: http://localhost:2000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 Content-Type: application/json Accept: */* Referer: http://localhost:2000/ Accept-Encoding: gzip, deflate, br Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,id;q=0.2,ms;q=0.2,ko;q=0.2 ----- ----- socket data 1479133995167 {"foo":"bar"} events.js:154 throw er; // Unhandled 'error' event ^ Error: write after end at writeAfterEnd (_stream_writable.js:167:12) at Socket.Writable.write (_stream_writable.js:212:5) at Socket.write (net.js:624:40) at Socket.<anonymous> (/Users/julien/Temp/foo.js:14:12) at emitOne (events.js:90:13) at Socket.emit (events.js:182:7) at readableAddChunk (_stream_readable.js:153:18) at Socket.Readable.push (_stream_readable.js:111:10) at TCP.onread (net.js:529:20) 

正如你所看到的,第一个2请求是好的,但第三个被分成2个不同的部分。 请求的标题将在一个数据事件中,而主体在另一个数据事件中。

我和很less的开发人员讨论过这个问题,我们猜测它可能与我的操作系统的TCP堆栈有关,如果这可能很重要的话,那就是OSX Sierra。

我看不到任何其他的方法来修补它,而不是将缓冲区累积到一个在上层作用域中声明的variables中,然后使用丑陋的定时器技巧,最终得到类似于可取消的setImmediate 类似的东西

 var timer = false, data = ''; socket.on('data', buffer => { data += buffer.toString(); clearTimeout(timer); timer = setTimeout(() => process(socket, data), 1) }) 

问题很简单:我知道这个修复在很多方面都是非常错误的,但是我不能在没有使用stream或者http包的情况下看到其他修复。 你能照亮我吗?

这就是TCP的工作原理。 TCP是一个字节stream。 在应用层上没有包含边界(甚至是请求)的数据包。 连接一端的n个字节的写入调用可能导致在另一端读取n个字节的读取调用。 你必须做好准备,每一次读取产生一个任意数量的字节(直到读取调用的缓冲区大小 – 然而当你获得数据在node.js推送,你不能影响)。 如果您需要应用程序级别的数据包,则需要自己处理数据包,例如将长度前缀的数据包写入数据stream。

然而,HTTP不需要数据包的概念,因为它已经由HTTP协议定义了一个标题和正文结束的地方。