博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
NodeJS的底层通信
阅读量:5867 次
发布时间:2019-06-19

本文共 11348 字,大约阅读时间需要 37 分钟。

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() . 说起来,这个应该算是最奇葩的一个方法了。他接受的参数,真的可以玩死你。

      1. listen(port,hostname[,callback])

        用来设置监听的端口.server.listen(3000,'http://localhost'). 当然,如果你不设置hostname的话,nodeJS会默认以IP6的方式进行获取,如果IP6也没有的话,则会给你随机分配一个,即采用,0.0.0.0 方式来。其中要说的是最后一个callback,当你的server端口开启监听时,就是触发该函数。其实他就是listening事件的回调函数,有兴趣的同学可以翻上去看看。

      2. server.listen(options[,callback])

        options是一个Object类型,里面包含的参数有:

         port 
        :端口号  host
        : 主机  backlog
        : log号  path
        监听路径  exclusive
        是否共享handle函数

        callback: 和上面说的callback是一个东西

    不过一样就是用第一个就可以了。这些我也不知道什么时候会使用,写的时候突然就用上了。

    1. server.listen(path,backlog)

      这个主要针对的是本地(Unix)的socket server的监听。举一个demo吧:server.listen('/tmp/echo.sock',cb); 里面的路径就是Unix下的sock文件。

    2. 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里面的参数,就是allowHalfOpenpauseOnConnect 默认都是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. 目前的兼容性是. 对于目前兼容IE8+来说,还是有一定的距离的。所以这里推荐使用一个库--socket.io,可以实现双向的通信. 他的降级方式这式样的.

  • websocket

  • Socket over Flash API

  • XHR Polling 长连接

  • XHR Multipart Streaming

  • Forever Iframe //持续刷新iframe

  • JSONP Polling //跨域的长连接

正式由于实现了这一强大的兼容性,所以,在通信时,非常的火。而且,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:80var 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~ 然后就是调整一下格式就行了。

转载请注明作者和原文链接

链接地址:

你可能感兴趣的文章
跳台阶
查看>>
eclipse Unable to execute dex: GC overhead limit exceeded GC overhead limit exceeded解决办法
查看>>
wchar_t在linux是4个字节
查看>>
pip安装更换镜像源
查看>>
linux CentOS 7 中LVM讲解配置实例及磁盘扩容等
查看>>
ApplicationListener与ApplicationContext的结合使用
查看>>
用tarball实现liferay自动安装部署11-读取configure-helper.sh文件
查看>>
问题记录:想要替换别人的代码,但是没办法或不能修改别人的代码
查看>>
mongodb整合入spring
查看>>
阻塞队列
查看>>
2016年开源软件TOP20 公布了,你用过哪几个?
查看>>
springboot2.0 redis配置
查看>>
rabbitmq学习记录(三)工作队列-轮询分配
查看>>
2. 红黑树
查看>>
开发者可在WindowsCoreOS创建自己的应用了
查看>>
eclipse常用快捷键
查看>>
iOS——拍照后的照片保存到本地
查看>>
es6对象笔记
查看>>
Java 堆内存与栈内存异同(Java Heap Memory vs Stack Memory Difference)
查看>>
初入博客
查看>>