理解 NodeJS 中基于事件驱动的架构

事件驱动最简单的形式是常见的 Node.js 函数回调,例如: fs.readFile 。事件被触发时,Node 就会调用回调函数,所以回调函数可视为事件处理程序。

让我们来探究一下这个基础形式。

Node,在你准备好的时候调用我吧!

以前没有原生的 promise、async/await 特性支持,Node 最原始的处理异步的方式是使用回调。

回调函数从本质上讲就是作为参数传递给其他函数的函数,在 JS 中这是可能的,因为函数是一等公民。

回调函数并不一定异步调用,这一点非常重要。在函数中,我们可以根据需要同步/异步调用回调函数。

例如,在下面例子中,主函数 fileSize 接收一个回调函数 cb 为参数,根据不同情况以同步/异步方式调用 cb :

理解 NodeJS 中基于事件驱动的架构

请注意,这并不是一个好的实践,它也许会带来一些预期外的错误。最好将主函数设计为始终同步或始终异步地使用回调。

我们再来看看下面这种典型的回调风格处理的异步 Node 函数:

理解 NodeJS 中基于事件驱动的架构

readFileAsArray 以一个文件路径和回调函数 callback 为参,读取文件并切割成行的数组来当做参数调用 callback。

这里有一个使用它的示例,假设同目录下我们有一个 numbers.txt 文件中有如下内容:

要找出这个文件中的奇数的个数,我们可以像下面这样调用 readFileAsArray 函数:

理解 NodeJS 中基于事件驱动的架构

这段代码会读取数组中的字符串,解析成数字并统计奇数个数。

在 NodeJS 的回调风格中的写法是这样的:回调函数的第一个参数是一个可能为 null 的错误对象 err,而回调函数作为主函数的最后一个参数传入。 你应该永远这么做,因为使用者们极有可能是这么以为的。

现代 JavaScript 中回调函数的替代品

在 ES6+ 中,我们有了 Promise 对象。对于异步 API,它是 callback 的有力竞争者。不再需要将 callback 作为参数传递的同时处理错误信息,Promise 对象允许我们分别处理成功和失败两种情况,并且链式的调用多个异步方法避免了回调的嵌套(callback hell,回调地狱)。

如果刚刚的 readFileAsArray 方法允许使用 Promise,它的调用将是这个样子的:

理解 NodeJS 中基于事件驱动的架构

作为调用 callback 的替代品,我们用 .then 函数来接受主方法的返回值, .then 中我们可以和之前在回调函数中一样处理数据,而对于错误我们用 .catch 函数来处理。

现代 JavaScript 中的 Promise 对象,使主函数支持 Promise 接口变得更加容易。我们把刚刚的readFileAsArray 方法用改写一下以支持 Promise:

理解 NodeJS 中基于事件驱动的架构

现在这个函数返回了一个 Promise 对象,该对象包含 fs.readFile 的异步调用,Promise 对象暴露了两个参数: resolve 函数和 reject 函数。reject 函数的作用就和我们之前 callback 中处理错误是一样的,而 resolve 函数也就和我们正常处理返回值一样。剩下唯一要做的就是在实例中指定 reject resolve 函数的默认值,在 Promise 中,我们只要写一个空函数即可,例如 () => {} .

在 async/await 中使用 Promise

当你需要循环异步函数时,使用 Promise 会让你的代码更易阅读,而如果使用回调函数,事情只会变得混乱。

Promise 是一个小小的进步,generator 是更大一些的小进步,但是 async/await 函数的到来,让这一步变得更有力了,它的编码风格让异步代码就像同步一样易读。

我们用 async/await 函数特性来改写刚刚的调用 readFileAsArray 过程:

理解 NodeJS 中基于事件驱动的架构

首先我们创建了一个 async函数,只是在定义 function 的时候前面加了 async 关键字。在async 函数里,使用关键字 await 使 readFileAsArray 函数好像返回普通变量一样,这之后的编码也好像 readFileAsArray是同步方法一样。

async 函数的执行过程非常易读,而处理错误只需要在异步调用外面包上一层 try/catch 即可。

在 async/await 函数中我们我们不需要使用任何特殊 API(像: .then 、 .catch ),我们仅仅使用了特殊关键字,并使用普通 JavaScript 编码即可。

我们可以在支持 Promise 的函数中使用 async/await 函数,但是不能在回调风格的异步方法中使用它,比如 setTimeout 等等。

EventEmitter 模块

EventEmitter 是 Node.js 中基于事件驱动的架构的核心,它用于对象之间通信,很多 Node.js 的原生模块都继承自这个模块。

模块的概念很简单,Emitter 对象触发已命名事件,使之前已注册的监听器被调用,所以 Emitter 对象有两个主要特征:

  • 触发已命名事件

  • 注册和取消注册监听函数

如何使用呢?我们只需要创建一个类来继承 EventEmitter 即可:

理解 NodeJS 中基于事件驱动的架构

例化前面我们基于 EventEmitter 创建的类,即可得到 Emitter 对象:

const myEmitter = new MyEmitter();

在 Emitter 对象的生命周期中的任何一点,我们都可以用 emit 方法发出任何已命名的事件:

myEmitter.emit('something-happened');

触发一个事件即某种情况发生的信号,这些情况通常是关于 Emitter 对象的状态改变的。

我们使用 on 方法来注册,然后这些监听的方法将会在每一个 Emitter 对象 emit 它们对应名称的事件的时候执行。

事件 != 异步

让我们看一个例子:

理解 NodeJS 中基于事件驱动的架构

WithLog 类是一个 event emitter。它有一个 excute 方法,接收一个 taskFunc 任务函数作为参数,并将此函数的执行包含在 log 语句之间,分别在执行之前和之后调用了 emit 方法。

执行结果如下:

理解 NodeJS 中基于事件驱动的架构

我们需要注意的是所有的输出 log 都是同步的,在代码里没有任何异步操作。

  • 第一步 “Before executing”;

  • 命名为 begin 的事件 emit 输出了 “About to execute”;

  • 内含方法的执行输出了“*** Executing task ***”;

  • 另一个命名事件输出“Done with execute”;

  • 最后“After executing”。

如同之前的回调方式,events 并不意味着同步或者异步。

这一点很重要,假如我们给 excute 传递异步函数 taskFunc ,事件的触发就不再精确了。

可以使用 setImmediate来模拟这种情况:

理解 NodeJS 中基于事件驱动的架构

会输出:

理解 NodeJS 中基于事件驱动的架构

这明显有问题,异步调用之后不再精确,“Done with execute”、“After executing”出现在了“***Executing task***”之前(应该在后)。

当异步方法结束的时候 emit 一个事件,我们需要把 callback/promise 与事件通信结合起来,刚刚的例子证明了这一点。

使用事件驱动来代替传统回调函数有一个好处是:在定义多个监听器后,我们可以多次对同一个 emit 做出反应。如果要用回调来做到这一点的话,我们需要些很多的逻辑在同一个回调函数中,事件是应用程序允许多个外部插件在应用程序核心之上构建功能的一个好方法,你可以把它们当作钩子点来允许利用状态变化做更多自定义的事。

异步事件

我们把刚刚的例子修改一下,将同步改为异步方式,让它更有意思一点:

理解 NodeJS 中基于事件驱动的架构

WithTime 类执行 asyncFunc 函数,使用 console.time 和 console.timeEnd 来返回执行的时间,它 emit 了正确的序列在执行之前和之后,同样 emit error/data 来保证函数的正常工作。

我们给 withTime emitter 传递一个异步函数 fs.readFile 作为参数,这样就不再需要回调函数,只要监听 data 事件就可以了。

执行之后的结果如下,正如我们期待的正确事件序列,我们得到了执行的时间,这是很有用的:

理解 NodeJS 中基于事件驱动的架构

请注意我们是如何将回调函数与事件发生器结合来完成的,如果 asynFunc 同样支持 Promise 的话,我们可以使用 async/await 特性来做到同样的事情:

理解 NodeJS 中基于事件驱动的架构

这真的看起来更易读了呢! async/await 特性使我们的代码更加贴近 JavaScript 本身,我认为这是一大进步。

事件参数及错误

在之前的例子中,我们使用了额外的参数触发了两个事件。

error 事件使用了 error 对象。

this.emit('error', err);

data 事件使用了 data 对象。

this.emit('data', data);

我们可以在命名事件之后使用任何需要的参数,这些参数将在我们为命名事件注册的监听器函数内部可用。

例如: data 事件执行的时候,监听函数在注册的时候就会允许我们的接收事件触发的 data 参数,而 asyncFunc 函数也实实在在暴露给了我们。

时间:10个月前 (02/26) / 阅读:1663 / 评论:0

理解 NodeJS 中基于事件驱动的架构

Wireshark抓包分析TCP协议

版权声明:本文为作者原创文章,可以随意转载,但必须在明确位置表明出处!!!

之前有一篇文章介绍了http协议初识http协议」, http协议协议是基于tcp协议的,所以作者觉得有必要针对tcp协议做一个介绍,希望各位读者能够静下心来认真阅读,也可以自己去看看TCP/IP协议详解这本书,一定要让自己成为那20%的人。

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,对TCP协议的文章网上已经很成熟了,今天我只是想总结一下知识,加深印象,所谓好记心不如烂笔头麻。

TCP/IP分层结构

TCP/IP协议栈主要分为4层:应用层,传输层,网络层,数据链路层

  • 应用层

应用层负责处理特定的应用程序细节,像远程登陆,FTP传输,SMTP邮件传输,SNMP简单网络管理。

  • 传输层

运输层主要提供两台主机之间端到端的通信,在TCP/IP协议族中,TCP和UPD是两种截然不同的传输协议,TCP(传输控制协议)为主机之间提供可靠传输,它把从应用层得到的数据分成适当的数据包交给下面的(IP)网络层,确定接收到的分组,设置发送最后确认分组的超时时钟等,因运输层提供了高可靠性的端到端的通信,应用层就不需要再去关注这些细节,而UDP(用户数据协议)提供的是不可能性的传输,它只负责从一台主机发送到另一台主机,并不保证此数据一定到达另一端主机,任何必需的可靠性必须由应用层来提供。

  • 网络层

网络层主要处理分组在网络中的活动,网络层协议包涵IP,ICMP,IGMP协议。

  • 链路层

链路层组要包涵网卡驱动程序,它处理和电缆或者其它传输介质的物理接口细节。链路层的主要目的有三个

  1. 为IP模块接收和发送IP数据报

  2. 为ARP模块发送ARP请求和接收ARP应答

  3. 为RARP模块接收RARP应答和发送RARP请求

Wireshark抓包分析TCP协议

所谓的协议就是通信双方都需要遵守的规则,这样才能明白对方要表达什么,就像两个人打电话一样,A说的是重庆话,B说的是广东话,这两人打电话肯定不知道对方说的是什么,这就叫他们没有遵守协议,若是都让他们说普通话这样俩儿人就都能听懂对方说的是什么意识了,普通话这里就相当于协议大家都要遵守。下面我将结合Wireshark抓包工具来分析TCP/IP协议

封装

数据进入协议栈的封装过程

Wireshark抓包分析TCP协议

当经过以太网层的封装后,就要通过网线或者其它传输介质把此封装好的数据报文发送到另一端去,另一段收到数据报后最先接触的是以太网层也就是我们的数据链路层协议,该层协议复制把以太网首部解析掉,让后把解析后的数据报上送到IP层,IP层把IP首部解析掉,然后上传到TCP层,依次类推每层协议解析其首部并判断其首部中的协议标识以确定接收数据的上层协议,然后上送到他的上一层。这就是封层结构的好处之一,每层协议只做自己的事,不是自己的事就交给别人去做。

TCP报文格式

Wireshark抓包分析TCP协议

Wireshark的抓包结果

Wireshark抓包分析TCP协议

原端口/目的端口(16bit):

我们都知道网络之前的通信是不通主机之间的通信,就windows系统而言通过查看任务管理器我们可以知道一台主机有许多进程,当我们发送数据时怎么知道要发送到对方主机那个进程里呢,所以这就是端口号的作用,在TCP报文中包涵了源端口/目的端口,源端口标识了发送进程,目的端口标识了接收方进程。在此报文中我们的源端口号是0x8572 = 34162, 目的端口是0x01bb = 443如下图所示

Wireshark抓包分析TCP协议

序列号(32bit)

Sequence Number这个是发送序列号,用来标识从源端向目的端发送的数据字节流,它表示在这个报文端中的第一个数据字节的顺序号,系列好是32位的无符号类型,序号表达达到2^32 - 1后又从0开始, 当建立一个新的连接时,SYN标志为1,系列号将由主机随机选择一个顺序号ISN(Initial Sequence Number)。此报文中的序列号是0x9e546d6b早已超过了2^32 - 1 所以这里的序列号为0,如下图

Wireshark抓包分析TCP协议

确认号(32bit)

Acknowledgment Number它包涵了发送确认一端所期望收到的下一个顺序号。因此确认序列号应当是上次成功接收到数据的顺序号加1。只有ACK标志为1时确认序号字段才有效。TCP为应用层提供全双工服务,这意味着数据能在两个方向上独立的进行传输,因此连接的两断必须要保证每个方向上的传输数据顺序。

偏移(4bit)

这里的偏移实际指的是TCP首部的长度,它用来表明TCP首部中32bit字的数目,通过它可以知道一个TCP包它的用户数据从哪里开始,这个字段占4bit,若此字段的值为1000,则说明TCP首部的长度是8 * 4 = 32字节,所以TCP首部的最大长度是该字段的值为1111 = 15, 15 * 4 =60字节。此报文我们的偏移量在0x80中,又因它占4bit,0x80等于二进制的1000 0000 所以我们的偏移量是 1000 = 8,所以我们的TCP报文头为8 * 4 = 32字节。

Wireshark抓包分析TCP协议

Reserved(6bit)

目前没有使用,它的值都为0

标志(6bit)

在TCP首部中有6个标志比特,他们中的多个可同时被置为1

  • URG(Urgent Pointer Field Significant):紧急指针标志,用来保证TCP连接不被中断,并且督促中间设备尽快处理这些数据

  • ACK(Acknowledgement Field Signigicant):确认号字段,该字段为1时表示应答字段有效,即TCP应答号将包含在TCP报文中。

  • PSH(Push Function): 推送功能,所谓推送功能指的是接收端在接收到数据后立即推送给应用程序,而不是在缓冲区中排队。

  • RST(Reset the connection): 重置连接,不过一搬表示断开一个连接,如下图,主机192.168.3.27 访问主机211.150.84.8;但主机84.8并没有监听对于端口,这时它会向主机3.27发送一个RST位置的TCP包断开连接。

Wireshark抓包分析TCP协议

  • SYN(Synchronize sequence numbers):同步序列号,用来发起一个连接请求

  • FIN(No more data from sender):表示发送端发送任务已经完成(既断开连接)

窗口大小(16bit)

表示源主机最大能接收多少字节。

校验和(16bit)

包含TCP首部和TCP数据段,这是一个强制性的字段,一定是由发送端计算和存储,由接收端进行验证

紧急指针(16bit)

只有当URG标志置为1时该字段才有效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一段发送紧急数据的一种方式。

TCP选项

至少1个字节的可变长字段,标识哪个选项有效。Kind=0:选项表结束, Kind=1:无操作, Kind=2:最大报文段长度,Kind=3:窗口扩大因子, Kind=8:时间戳。

TCP的三次握手和四次挥手

整个过程如下图所示

Wireshark抓包分析TCP协议

TCP的三次握手

TCP的三次握手过程如下图所示,我们通过数据包来分析一下握手过程是不是和图中所画一致。

Wireshark抓包分析TCP协议

第一次握手

主机192.168.3.27向主机111.13.100.91发起连接请求,可以看在这时的SYN被置为1了,序列号Seq = 0,如下图

Wireshark抓包分析TCP协议

第二次握手

主机111.13.100.91应答主机192.168.3.27,可以看到这个时候的应答包含了SYN,ACK,ACK = Seq + 1 = 1, 这里的Seq是第一次握手发起请求的Seq值,并不是下图报文中红框表示的Seq值。

Wireshark抓包分析TCP协议

第三次握手

主机192.168.3.27应答主机111.13.100.91,可以看到这个时候的应答包是ACK, ACK = Seq + 1 = 1,这里的Seq是第二次握手主机111.13.100.91产生的序列值

Wireshark抓包分析TCP协议

在回头看看我们的svr4.1037主机和bsdi.discard之前的连接建立是不是和我们的报文分析的一致,第一次握手 SYN Seq = 1415531521; 第二次握手 SYN ack = 1415531521 + 1 = 1415531522 Seq = 1823083521;第三次握手 ack = 1823083521 + 1 = 1823083522;到这里就可以看出此过程和我们的报文分析是一致的。

可以看到三次握手后确定了双方包的序列号,最大接收数据的大小以及MSS(Maximum Segment Size)最大分片大小 MSS = MTU - IP头部长度 - TCP头部长度,MTU最大传输单元一班为1500字节,若TCP/IP报文都不带选项时MSS = 1500 - 20 - 20 = 1460,MSS的意思是最大分片大小,这里若是1460的话,那么若是应用程序发送大于1460个字节那么超过1460个字节数会分片为下一个包,下图是应用层发送4096个字节。

时间:10个月前 (02/26) / 阅读:1638 / 评论:0

Wireshark抓包分析TCP协议

用Wireshark解密https流量

在本文中,作者揭开了网络流量加密和解密的神秘面纱,并翻译了诸如https,ssh,sftp等神秘术语。

Wireshark,一个有趣的开源网络嗅探器,不仅可以读取网络流量,还可以进一步解密https流量,前提是您拥有私钥! 让我们学习更多关于使用这个工具解密https流量。

为了防止网络流量嗅探器泄露登录凭证,可以使用安全协议,如https、ssh、sftp等,而不是http、telnet和ftp等各自的明文协议。安全协议加密在两个端点之间传输的流量;因此网络上的所有可用流量都是不可读的,除非直到解密。

为了理解安全协议,让我试着用简单的术语解释加密和解密的基本知识。

加密和解密

加密技术是一种用来制造可读技术(明文)的数据读取(密文),通常使用复杂的数学算法。解密与加密过程完全相反。

加密的两种主要类型

对称密钥加密:一个密钥用于加密和解密。对称密钥算法的例子有:先进的加密标准(AES)、数据加密标准(DES)等。密钥的典型长度为128字节,加密解密速度快。但是,对于成功的操作,双方都必须知道密钥。但是,对于成功的操作,双方都必须知道密钥。

非对称密钥加密:密钥对用于加密和解密。由一个密钥加密的文本只能由第二个密钥解密,反之亦然。因此,相同的密钥不能执行加密和相应的解密。非对称密钥加密算法的实例包括:Rivest、Shamir Adleman(RSA)、ElGamal、椭圆曲线密码学等。

非对称密钥加密的一些要点是:

1.与对称密钥加密相比,非对称密钥加密和解密速度较慢。 例如,使用1024位密钥的RSA加密比使用128位密钥的AES加密慢约250倍。

2.一个密钥(私钥)被保密,另一个密钥(公共密钥)被分发。

3.由发件人的私钥加密的文本可以由发件人的公钥解密。 因此,实际上任何人都可以解密这个密钥,因为这个密钥是分布式的。 但是,请注意,这确保了发件人的真实性 - 私钥被相应的发件人保密。

由接收者的公钥加密的文本只能由接收者的私钥解密; 因此使用接收者的公钥加密的消息只能由相应的接收者用相应的私钥解密。 https协议使用此功能。

用Wireshark解密https流量

时间:10个月前 (02/26) / 阅读:2094 / 评论:0

用Wireshark解密https流量