Node.js之网络通讯模块实现浅析
前言
想必我们在用Node.js用的最多的应该是创建http服务,所以对于每个Web开发工程师而言,Node.js的网络相关模块学习是必不可少。
Node.js的网络模块架构
在Node.js的模块里面,与网络相关的模块有Net、DNS、HTTP、TLS/SSL、HTTPS、UDP/Datagram,除此之外,还有v8底层相关的网络模块有tcp_wrap.cc
、udp_wrap.cc
、pipe_wrap.cc
、stream_wrap.cc
等等,在Javascript层以及C++层之间通过process.binding进行桥接相互通信。
Net模块
Net模块提供了一些用于底层的网络通信接口,包括创建服务器以及客户端,其中HTTP模块也是基于Net模型的上层封装,在Net模块里面主要提供net.Server以及net.Socket
创建TCP服务端
创建一个TCP服务器,可以通过使用构造函数new net.Server
或者使用工厂方法net.createServer
,这两个方法都会返回一个net.Server类,可接收两个可选参数。
var net = require('net'); var server = net.createServer(function(socket){ socket .on('data',function(data){ console.log('socket data',data.toString()); socket.write( data.toString() ); }) .on('end',function(){ console.log('socket end') }) .on('error',function(error){ console.log('socket error',error); }); }); server.listen(56200,function(){ console.log('server run at ',server.address()); }); server.on('error',function(err){ throw err; }); // 执行后:server run at { address: '::', family: 'IPv6', port: 56200 }
在listen监听的时候没有指定端口的话会自动随意监听一个端口,创建完成一个TCP服务器后,使用tenlent 0.0.0.0 56200
,链接后可与服务器进行数据通信。通过createServer
实例化一个服务后,服务会去监听客户端请求,与客户端建立了链接之后会在回调里面抛出建链的net.Socket
对象。
创建TCP客户端
创建一个TCP客户端链接可以使用构造函数new net.Socket
或者其工厂方法net.createConnection
,创建成功后都会返回一个net.Socket实例。
var net = require('net'); var client = net.createConnection({port:56200,host:'localhost'}); client.on('connect',function(){ console.log('client connect'); }); client.on('data',function(data){ console.log('client data',toString()); }); client.on('error',function(error){ throw error; }); client.on('close',function(){ console.log('client close'); });
Socket
socket是啥这里就不做详细的阐述了,下面主要了解下net.Socket这个构造体主要有提供一些什么方法、监听事件的使用。
相关事件
- connect : 当客户端与服务端成功建立链接之后触发,如果链接不上服务器直接抛出error事件错误然后退出node进程。
- data : 当客户端收到服务器传送过来的数据或者是客户端传送给服务器的数据的时候触发回调。
- end : 当另外一侧发送FIN包断开的时候触发,默认情况下面 (
allowHalfOpen == false
)socket会自我销毁(如果写入待处理队列里面还没正式响应回包),但是我们可以设置allowHalfOpen
参数为true,这样可以继续往该socket里面写数据,但是我们需要自己去调用 end 方法去消耗这个socket,不然可能会造成句柄泄漏。 - close : 链接断开的时候触发,但是如果在传输的过程中有错误的话这里会在回调函数里面抛出 error。
- timeout : 当socket超时空闲的时候触发,如果要在队列里面销毁需要手动去调close方法。
- lookup : 域名解析完成的时候触发。
- drain : 写完缓存的时候触发,可使用在上传大小限制中。
相关方法
- write() : 服务端给客户端发送数据或者是客户端给服务端发送数据。
- address() : 获取服务绑定的socket的IP地址,返回对象有三个属性,分别为端口、host以
- 及IPvX版本。
- end() : 半关闭socket,会发送一个FIN包,服务器仍然可能发送一些数据,也可以这样调用socket.end(data,encoding)。
- pause() : 暂停读取数据,可以用作对数据上传限制。
- resume() : 继续数据读取。
- setEncoding() : 设置数据流的获取格式。
- setKeepAlive() : 允许/禁止keep-alive功能。
- setNoDelay() : 禁止Nagele算法,TCP链接默认使用Nagle算法,它们在发送之前数据会被缓存。这是为true的话在每次socket.write()的时候会立即发送数据,默认为true。
- setTimeout() : 当一个空闲的socket在多少秒后不活跃会被接受到timeout事件,但是该socket不会停止销毁,需要手动调用end()或者destroy()。表示禁止空闲超时。
相关属性
- bufferSize : 当前缓存的等待被发送的字符串的数量。
- bytesRead : 收到的字节的数量。
- bytesWritten : 发送的字节的数量
- destroyed : 标识链接是否已经被破坏,一旦被破环,就不用使用该链接来传输数据。
- localAddress : 远程客户端链接本地地址的host。如果我们监听服务的host是0.0.0.0,而客户端链接的是'192.168.1.1',最后的值是后者。
- localPort : 本地的端口。
- remoteAddress : 客户端IP,如果socket已经是destryed的话,该值为
undefined
。 - remoteFamily : 客户端是IPvX
回包异常处理
服务器从客户端接受到需要处理的数据后进入处理环节,再业务逻辑处理完成之前如果socket以外断开的话,待服务器再给客户端回报的时候会直接响应error
事件并报错Error : This socket has benn ended by the other part
,所以在回报之前服务端需要先判断该socket是否被销毁,如果没有被销毁则回包,如果已经断开则销毁:
var net = require('net'); var biz = require('./biz'); var server = net.createServer(function(socket){ socket .on('data',function(data){ biz.do(data) .then(function(){ if( !socket.destroyed ) { socket.write( data.toString() ); } else { // do some report socket.destry(); } }) .catch(function(){ !socket.destroyed && socket.end('server handler error'); }); }) .on('end',function(){ console.log('socket end') }) .on('error',function(error){ console.log('socket error',error); }); }); server.listen(56200,function(){ console.log('server run at ',server.address()); }); server.on('error',function(err){ throw err; });
限制客户端数据大小
对请求大小限制是服务安全里面比不可少的一个环节,服务端不能无限大小的去接受客户端发送过来的所有数据,而限制大小就是第一道门槛。
var net = require('net'); var MAX_REQUEST_BYTES = 2 * 1024 * 1024; // 2M var server = net.createServer(function(socket){ socket .on('data',function(data){ if(data.bytesRead > MAX_REQUEST_BYTES) { socket.pause(); socket.end('data is too big, forbidden'); // do some report } }) .on('end',function(){ console.log('socket end') }) .on('error',function(error){ console.log('socket error',error); }); }); server.listen(56200,function(){ console.log('server run at ',server.address()); }); server.on('error',function(err){ throw err; });