net模块其实是对TCP协议的一层封装。 在前端,我们经常打交道的无非是HTTP/HTTPS 协议。他们只是高层的通信协议(单向),主要应对的是网络不稳定的情况,当客户端请求数据时,服务器便返回相应的信息即可,当完成数据交流之后,便会自动断开连接。当然,HTTP也只是对TCP的一层封装而已。在nodeJS 里面我们可以实现底层TCP,我们可以自定义任何我们想要的效果,比如,我们可以实现 可持续(keep-alive)的效果,形式实时通信。而里面用到的主要通讯技术就是"套字节 socket"的双向通信.
核心API
net主要有两大类,一个是Server,一个是Socket.
Server相关
Serve继承了EventEmitter, 也应该说是JS 最精华的所在。 当然Server也有他自己的一些方法。
-
事件
close: 当服务器关闭的时候回触发。要注意是当且仅当所有的连接关闭的时候才会触发。
server.on('close',cb)
connection: 这应该是server中最重要的一个事件。 表示当一个新连接建立时触发。 即,通常处理的具体业务逻辑,我们都是写在connection里面的。回调函数里的参数就是socket的实例.
server.on("connection",function(socket){...})
当使用createServer直接创建时,就可以省略connection事件的监听了。 回调函数里自带一个socket对象
listening: 当使用server.listen()方法成功绑定端口的时候触发,不过没什么卵用.
error: 这个同理,表示出现什么错误的时候会触发。并且close事件,会立即触发。
-
方法
-
address()[Object]: 返回服务器绑定的地址和端口号,以及一些相应信息.
{ port: 12346, family: 'IPv4', address: '127.0.0.1'}
具体demo:server.listen(() => { address = server.address(); console.log(`opened server on ${address}`); });
-
listen() . 说起来,这个应该算是最奇葩的一个方法了。他接受的参数,真的可以玩死你。
listen(port,hostname[,callback])
用来设置监听的端口.server.listen(3000,'http://localhost')
. 当然,如果你不设置hostname的话,nodeJS会默认以IP6的方式进行获取,如果IP6也没有的话,则会给你随机分配一个,即采用,0.0.0.0
方式来。其中要说的是最后一个callback,当你的server端口开启监听时,就是触发该函数。其实他就是listening事件的回调函数,有兴趣的同学可以翻上去看看。-
server.listen(options[,callback])
options是一个Object类型,里面包含的参数有: port <Number>:端口号  host <String>: 主机  backlog <Number>: log号  path <String> 监听路径  exclusive <Boolean> 是否共享handle函数
callback: 和上面说的callback是一个东西
不过一样就是用第一个就可以了。这些我也不知道什么时候会使用,写的时候突然就用上了。
server.listen(path,backlog)
这个主要针对的是本地(Unix)的socket server的监听。举一个demo吧:server.listen('/tmp/echo.sock',cb);
里面的路径就是Unix下的sock文件。server.listen(handle,backlog)
本来可以在createServer里面直接指定handle不过,这里在listen里面指定也是可以的。
即
function handleRequest(request, response){ response.end('It Works!! Path Hit: ' + request.url); } server.listen(handleRequest);
不过,这个真的没什么卵用。。。
server.maxConnections 用来设置最大连接数。
-
Socket相关
创建完Server之后,就到了具体的实现逻辑。Socket来帮助我们实现信息的通信.
-
事件
close 当发生传输错误的时候会触发close事件
connect: 当socket连接成功创建的时候,会触发该事件
data: 非常重要的一个事件,用来接收数据。
end: 当完成数据发送的时候,会触发end事件。 但是如果你设置了比如allowHalfOpen=true的时,这时候情况就有些复杂,需要你手动使用下文end()方法进行中断.
timeout: 如果 你长期没有进行数据的传输(inactivity), 这时候,你可以手动的关闭连接。而,这里的事件是通过socket.setTimteout()来进行设置的。
-
方法
address(); 同样,返回绑定的地址,和server.address()是一样的效果
bufferSize: 检查现在已用的内存大小
bytesRead: 已经接受的字节数
bytesWritten 已经发送的总字节数
localAddress/localPort: 就是返回 你的地址(比如127.0.0.1)和端口号(比如:87)
setEncoding([encoding]): 设置接受数据的格式,有('ascii', 'utf8', 'base64').通常设置
utf8
就可以了end(data): 调用这个方法,并不是立即发送FIN包,该会在data传送完之后发送.
setTimeout(timeout[, callback]) 当有多长时间你不活动是(inactivity),就会触发callback,当然你可以手动关闭连接。
write(data, encoding): 用来写入信息,如果写入成功,会返回true, 否则返回false,这此时data会排在内存中,当完成是,Buffer重新为空,此时触发drain事件,表示可以继续写入。
connect(): 这同样和server.listen一样,也是一个要死人的方法. 他的格式和listern的格式很接近(废话,不都是TCP连接吗?).
destroy(): 确保没有额外的I/O操作,然后关闭连接即可。
-
socket.connect(port, host)
同样,里面的参数,我就不介绍了,后面的connnectionListerner就是替代connect事件的回调。 不过一般使用createConnection代替,这个实际生产中并没有什么卵用。socket.connect(options[, connectListener])
options里面的参数,和上面还是有些区别的。这里说一下大致的参数吧。port(端口),host,localAddress,localPort,family,lookup. 就这几个,不会的请google.
socket.connect(path[, connectListener])
这个和server.listen就是一个道理了。好累,,不解释了
-
net相关
上面说的就是net里面全部的内容,但是,我们应该怎么创建Socket或是Server呢?
-
方法
net.createServer(options)
options里面的参数,就是allowHalfOpen
和pauseOnConnect
默认都是false.这里我就不详述了。
因为,这。。。确实没什么用(maybe 以后有用呢?那有需求再说呗). 好了,我们直接来说connectionListener. 其实,他就是connection事件的回调函数,这里,方便书写,就直接放在createServer里面来了。
并且该listen还会自动创建一个socket对象,用来进行和数据的传输.
具体来看一个demo
var net = require('net');
//自动创建socket
var server = net.createServer(function(socket) { //'connection' listener
console.log('server connected');
socket.on('end', function() {
console.log('server disconnected');
});
socket.on('data', function(){
socket.end('hello\r\n');
});
});
//开启端口的监听
server.listen(8124, function() { //'listening' listener
console.log('server bound');
});
其实探究源码,我们知道net其实就是两个部分,一个socket一个是Server,其实在使用createServer创建的源码其实应该是这样的.
//from 阎王
net.createServer = function(callback){
// 每次客户端连接都会新建一个 socket
var socket = new Socket();
callback && callback(socket);
};
net.createConnection(port, host)
用来创建一个socket连接,该和socket.connect()方法一样变态,也有很多的变形参数传入,不过这里就只介绍这一种,有兴趣(找虐)的童鞋可以去参考具体文档。如果你的host没有设置,则默认为localhost. 当然,这不是最常用的,因为太长了--最常用的是connect
net.connect(port, host)
使用方法同上,这里就不赘述了。
ok~ 基本的就是这些了。(md~ 好累呀~) 经过我认认真真的板书,大概知道了net内部模块的相关的API了吧(快,夸一下宝宝). 开头说道,TCP 其实是靠底层的通信协议,而且他有支持双工的 socket的诶~
这里俺们来实践一下,如果造一个实时通信的轮子。
轮子哥--双工通信
首先,肯定是需要先造一个服务器的。 很简单啦。直接上代码:
const net = require('net');
const server = net.createServer(function(socket) { //'connection' listener
console.log('server connected');
socket.on('end', function() {
console.log('server disconnected');
});
socket.on('data', function(data){
console.log("[Client]:"+data); //输出返回的信息
setTimeout(()=>{socket.write("Hi, I'm your Server");},1000);
});
});
server.listen(3133, function() { //'listening' listener
console.log('server bound');
});
上面主要用来接受客户端信息的返回,并且打印接受的信息。
其实你也可以这么写》
var net = require('net');
var server = net.createServer();
server.on('connnection',function(){
//... 上面createServer的内部逻辑
});
//下面就是一样的了。
对照API看看,大家应该就知道了。
来,接着写一下客户端》
const net = require('net');
const client = new net.Socket();
client.connect(3133, function() {
console.log('Connect to Server');
// 建立连接后立即向服务器发送数据,服务器将收到这些数据
client.write('Hi Server');
});
// 为客户端添加“data”事件处理函数
// data是服务器发回的数据
client.on('data', function(data) {
console.log('[Server] ' + data);
setTimeout(()=>{client.write("Hi Server");},1000)
//我们暂时不关闭:client.destroy();
});
// 为客户端添加“close”事件处理函数
client.on('close', function() {
console.log('Connection closed');
});
首先运行server.js,然后运行client.js。 正常情况下, 会每2s输出内容》
比如:
//Client控制台
[Client]:Hi Server
...
//Server 控制台
[Server] Hi, I'm your Server
...
当你手动断开时,就会触发close事件。 当然,你也可以使用client.destroy或者client.end() 进行关闭。 而在服务端可以使用server.close(cb)进行关闭。
艹,大哥,这么简单,你还写出来,简直浪费呀~~~~
恩,小兄弟,你有所不知,我想说的,下面才是干货。23333
冷门双工通信
亲,你有没有考虑这个问题嘞?
当你网络情况比较慢的,时候,你的请求可能还在路上,而Server看你还不来,就把连接断了。
还有
当你看上次请求过时了,你又发一次请求,那该请求和上次请求谁会被处理呢?
OK~ 现在是答题解惑时间
熟悉TCP 协议的童鞋,应该知道TCP的3次握手,挥手以及相应的header. 母鸡? OK, 传送门.
这里,我们针对上面的问题做一些解答
在数据发送过程时,A向B发送一次请求. 如果B就收到包后会向A发送ACK 确认包。如果A在指定时间没有接收到的话则会重新发送包。 当然, 如果你的时间过期的过分的话,老子就不和你说话啦!!!(直接断开连接)
其实,就涉及到两个时间的设定。
重发超时时间
连接超时时间
这里主要利用到NodeJS的两个API 一个是setKeepAlive(Boolean,time), 一个是setTimeout(time[,cb]);
setKeepAlive
这个API主要是给我们一种途径,防止对方的时限过短而断开连接。使用setKeepAlive 会向对方发送 一个空的ACK包,来保持通信。
const server = net.createServer(function(socket) {
socket.setKeepAlive(true,3000); //如果3s内没有包的交流的话,会发送空包,进行交互
});
这里,你的时间当然可以设置高一点,比如20s/30s。
setTimeout
当你的inactivity时间过长的话,则作为服务器,我可以断你的线,我可以发送一个包提醒你。 这里,也可以模仿setKeepAlive. 不过,这个API灵活性更高,当设置的时间到了,则会触发timeout事件。不过一般我们就写在回调函数里,这样方便一些。
const server = net.createServer(function(socket) {
socket.setTimeout(3000,()=>{
socket.end("you gonna be killed");
});
});
其实,上面两个API,本人最喜欢用的还是setTimeout(大部分情况). 而且,如果系统不设置timeout的话,他是不会断线的。
另外,还有一个比较冷门的API,我这里也顺带提一提吧。
socket.setNoDelay([noDelay])
该API主要涉及的是关于网络传输的一个算法--Nagle algorithm
. 该算法主要针对的是网络传输时资源节省的算法。 有时候,当你在发送数据包的时候,有时候传输的数据很小,就几个b,老子TCP都比你大不止10倍(通常一个TCP大小是40 byt)。所以为了针对这样的数据包,使用Nagle 可以将小包 缓存起来,等到下一次 打包来的时候,一起发送过去。 but!!! 我们得想想这个算法出来的背景,那时候我们还在使用核桃机,用的是2G网。 能节约1b 是1b 啊,针对那时候 这个算法,是极其有价值的。 but now, 我觉得应该被淘汰了,因为现在4G 已经风靡, 我已经不想再放那张洗脑的2G,3G,4G图了。 大家可以自行脑补。 不过,系统是默认开启的,所以,这样造成的后果是,信息的延迟过大。当今,推荐是关闭该算法,在nodeJS中可以使用setNoDelay(true)
来实现。
const server = net.createServer(function(socket) {
socket.setNoDelay(true);
});
ok~ 我们可以使用上述的API就可以构建一个强健的 双通道通信了。
当然,有的童鞋,可能会吐槽了。md~
别别别别别别别别别... 骂宝宝
艹~ 你这是前端诶, front-end 端都不给,差评~~
墨迹, 我正要介绍嘞, 目前前端界最火的一个插件--socket.io.js 这 应该算是很牛逼的一个库了。ok~ 我们现在来具体学习一下吧.
socket.io
以前,在前端要做到双向通信,只能使用长轮询(ajax,iframe)等 相关的trick. 不过H5 推出了一个新的通信方式websocket. 目前的兼容性是IE10+. 对于目前兼容IE8+来说,还是有一定的距离的。所以这里推荐使用一个库--socket.io,可以实现双向的通信. 他的降级方式这式样的.
websocket
Socket over Flash API
XHR Polling 长连接
XHR Multipart Streaming
Forever Iframe //持续刷新iframe
JSONP Polling //跨域的长连接
正式由于socket.io实现了这一强大的兼容性,所以,在通信时,非常的火。而且,socket.io有这一套的强大的通信机制,从前端到后端,使用的API和事件都是完全一致。 这里我们先浅浅的舔一舔socket.io的脚毛。
很简单,两个端的代码如下:
//浏览器
const io = require('socket.io'),
socket = io('http://localhost:8080');
socket.on('connection', function (data) {
console.log(data);
socket.emit('communicate', { my: 'data' });
});
//nodeJS端
const app = require('http').createServer(handler);
const io = require('socket.io')(app);
app.listen(8080,'http://localhost');
function handler (req, res) {
res.writeHead(200, {'content-type': 'text/plain'});
res.end(data);
}
//使用io监听connection事件, 用来处理当有连接连上时发生的事.
io.on('connection', function (socket) {
//这里的connection其实是自定义事件,相当于,另外定义一个端口,进行通信
socket.emit('connection', { hello: 'baby' });
socket.on('communicate', function (data) {
console.log(data);
});
});
差不多就是这样,细心的同学,可能会看见nodeJS端,为什么会写两个绑定的请求嘞? 一方面是由于降级的原因,另外一方面,由于HTTP进行的是短连接,不能很好的解决长连接的问题,而另外加的一个监听(connection),其实就是对net模块的封装,以及完美的API转化。 如果我们抛开socket.io, 通常的做法就是在页面中再嵌套一个iframe 然后,实现长连接的效果。
ok,我们继续。 总结一下,其实socket.io最核心的API 其实就两个一个是on.一个是emit. on用来接收信息,emit用来发送信息。比如,我们现在想要进行多端口通信的话,应该怎么做呢?
这里有两种方法,一个是使用socket.io提供的API--of,另外是使用自定义事件。 两者应用不同的场景,比如我们要进行多对多的通信,那么,就需要使用of来进行区分。
of 通信
其实,就和express框架是一样的。使用of,就可以很方便的定义一个路由的通信.
//客户端
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connection', function () {
chat.emit('hi!'); //针对不同路由,这里emit直接跟data
});
news.on('news', function () {
news.emit('woot');
});
//服务器
var io = require('socket.io')(80); //相当于http://localhost:80
var chat = io
.of('/chat') //这里监听的就是http://localhost:80/chat 路由
.on('connection', function (socket) {
socket.emit('a message', { //只能一对一的传信息
that: 'only'
, '/chat': 'will get'
});
chat.emit('a message', { //给chat下面所有监听的a message的事件发送信息
everyone: 'in',
'/chat': 'will get'
});
});
var news = io
.of('/news')
.on('connection', function (socket) {
socket.emit('item', { news: 'item' }); //发送信息
});
推荐,不要使用这个多路由通信,因为实在没有自定义事件用起来方便,容易理解。
自定义事件通信
很简单,就相当于自定义事件一样,如果大家使用过订阅发布这模式,这个理念就很简单了。
//客户端
socket.on('connection',()=>{
socket.on('firstTunnel',(data)=>{
console.log(`the first data is ${data}`);
socket.emit('firstTunnel',{info:"[Client] Hi Server, this is first Client"});
});
socket.on('secondTunnel',(data)=>{
console.log(`the second data is ${data}`);
socket.emit('secondTunnel',{info:'[Client] Hi Server, this is second Client'});
})
});
//服务端
io.on('connection', (socket) => {
socket.on('connection', () => {
socket.on('firstTunnel', (data) => {
console.log(`the first data is ${data}`);
});
socket.on('secondTunnel', (data) => {
console.log(`the second data is ${data}`);
})
//...
socket.emit('firstTunnel', { info: "[Client] Hi Server, this is first Client" });
socket.emit('secondTunnel', { info: '[Client] Hi Server, this is second Client' });
});
})
OK, 现在你可以使用firstTunnel和secondTunnel进行信息的传递了。 我之所以喜欢socket.io 是因为 他的理念就是.
code once run anywhere
其实,这个nodeJS的理念是一木一样的,前端的代码,在后端可以复用。 举个例子吧, 就比如我们写验证的时候,有个原则就是永远不要相信用户的数据。 所以,导致的结果解释,前端检测之后,后端还需要自己自定义一套一模一样的检测机制---真· 鸡肋. 如果我们前端使用nodeJS 自己写后端的话,结果又不一样了,只需要做两件事,copy+paste. Over~ 然后就是调整一下格式就行了。
转载请注明作者和原文链接
链接地址:https://segmentfault.com/a/1190000004598004
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。