网络模块
NS 提供了 net,dgram,http,https 分别用于处理 TCP,UDP,HTTP,HTTPS, 适用于客户端和服务端.
因为 net 和 dgram 是比较底层的功能模块, 所以我只是简单说些方法和示例, 重点放在 http 和 https 上.
Node.JS 网络模块(上) - Net 与 Dgram 模块
Node.JS 网络模块(下) - HTTP 与 HTTPS 模块
前置知识
网络协议基础 - TCP,UDP,SOCKET 与参考模型
Node.JS 的 Stream 对象
Net
net 模块提供了创建基于流的 TCP 或 IPC 服务器 (net.createServer()) 和客户端(net.createConnection()) 的异步网络 API.
net 模块在 Windows 上支持命名管道 IPC, 在其他操作系统上支持 UNIX 域套接字.
因为主要提供的都是一些用于底层的网络通信, 所以了解一下就好.
net 模块提供了两大类分别是
net.Server 类
这个类用于创建 TCP 或 IPC server.
事件
事件 | 作用 |
---|---|
close([callback]) | connections 关闭触发,返回 server。 |
connection(socket) | 当一个新的 connection 建立的时候触发. socket 是一个 net.Socket 的实例对象. |
error | 当错误出现的时候触发. 不同与 net.Socket, 'close' 事件不会在这个事件触发后继续触发 除非 server.close() 是手动调用 |
listening | 当服务被绑定后调用 server.listen(). |
方法
address
操作系统返回绑定的地址, 协议族名和服务器端口.
close
服务器关闭时会触发. 注意, 如果存在连接, 这个事件不会被触发直到所有的连接关闭.
getConnections(callback)
异步获取服务器的当前并发连接数. 当 socket 被传递给子进程时工作.
回调函数的两个参数是 err 和 count.
listen
当服务器调用 server.listen 绑定后会触发.
用法很多, 具体看文档吧.
- server.listen(handle[, backlog][, callback])
- server.listen(options[, callback])
- server.listen(path[, backlog][, callback]) for IPC servers
- server.listen([port][, host][, backlog][, callback]) for TCP servers
- (还有更多方法可自行查询文档, 我就不贴了)
net.Socket 类
这个类是 TCP 或 UNIX Socket 的抽象(在 Windows 上使用命名管道, 而 UNIX 使用域套接字). 一个 net.Socket 也是一个 duplex stream, 所以它能被读或写, 并且它也是一个 EventEmitter.
net.Socket 可以被用户创建并直接与 server 通信. 举个例子, 它是通过 net.createConnection()返回的, 所以用户可以使用它来与 server 通信.
当一个连接被接收时, 它也能被 Node.JS 创建并传递给用户. 比如, 它是通过监听在一个 net.Server 上的'connection'事件触发而获得的, 那么用户可以使用它来与客户端通信.
事件
事件 | 作用 |
---|---|
close | 一旦 socket 完全关闭就发出该事件。参数 had_error 是 boolean 类型,表明 socket 被关闭是否取决于传输错误。 |
connection | 当一个 socket 连接成功建立的时候触发该事件。 |
error | 当错误发生时触发。'close' 事件也会紧接着该事件被触发。 |
listening | 当服务被绑定后调用 server.listen(). |
data | 当接收到数据的时触发该事件。data 参数是一个 Buffer 或 String。数据编码由 socket.setEncoding() 设置。注意当 Socket 发送 data 事件的时候,如果没有监听者数据将会丢失。 |
drain | 当写入缓冲区变为空时触发。可以用来做上传节流。 |
end | 当 socket 的另一端发送一个 FIN 包的时候触发,从而结束 socket 的可读端。 |
lookup | 在找到主机之后创建连接之前触发。不可用于 UNIX socket。 |
ready | 当 socket 准备好之后发射,在 connect 之后立刻触发 |
timeout | 当 socket 超时的时候触发。该事件只是用来通知 socket 已经闲置。用户必须手动关闭。 |
方法
太多了, 不想搬, 自行查询吧
例子
1, 创建服务器发送信息;
2, 连接服务器再断开连接;
服务端 net.createServer([options][, connectionListener])
参数 | 描述 |
---|---|
options <Object> | allowHalfOpen <boolean> 表示是否允许一个半开的 TCP 连接。 默认值: false pauseOnConnect <boolean> 一旦来了连接,是否暂停套接字。 默认值: false |
connectionListener <Function> | 为'connection' 事件自动设置一个监听器。 |
如果 allowHalfOpen 被设置为 true, 那么当 socket.end() 被显式调用时, 如果对边套接字发送了一个 FIN 包, 服务只会返回一个 FIN 数据包, 这会持续到后来连接处在半闭状态 (不可读但是可写). 请查看'end' 事件和 RFC 1122 (4.2.2.13 节) 获取更多信息.
如果 pauseOnConnect 被设置为 true, 那么与连接相关的套接字都会暂停, 也不会从套接字句柄读取数据. 这样就允许连接在进程之间传递, 避免数据被最初的进程读取. 如果想从一个暂停的套接字开始读数据, 请调用 socket.resume()
服务可以是一个 TCP 服务或者一个 IPC 服务, 这取决于 listen() 监听什么
上面看不懂就算了, 我们直接写个简单示例看看就知道了.
常规的创建一个服务端, 分别监听 8124 端口, 错误处理和发送数据.
- const.NET = require('net')
- const server = net.createServer(socket => {
- socket.on('end', () => {
- console.log('你已被列入黑名单!')
- })
- socket.write('请问需要贷款么?!')
- socket.pipe(socket)
- }).on('error', (err) => {
- console.log(err)
- throw err
- }).listen(8124, () => {
- console.log('你已向对方发起好友请求!')
- })
客户端
net.createConnection()
这个是创建 net.Socket 的工厂函数, 立即使用 socket.connect() 初始化链接, 然后返回启动连接的 net.Socket.
当连接建立之后, 在返回的 socket 上将触发一个'connect' 事件. 若制定了最后一个参数 connectListener, 则它将会被添加到'connect' 事件作为一个监听器.
用法有三种:
net.createConnection(options[, connectListener])
net.createConnection(path[, connectListener]) 用于 IPC 连接.
net.createConnection(port[, host][, connectListener]) 用于 TCP 连接.
这里选择第一种作为例子讲解
入参 | 描述 |
---|---|
options <Object> | 调用 new.NET.Socket([options]) 和 socket.connect(options[, connectListener]) 方法都会传入。 |
connectListener <Function> | net.createConnection() 方法的通用参数。如果制定了,将被添加为返回 socket 上的'connect' 事件上的监听器。 |
我们也可以采用 createConnection 的别名 connect
net.connect(options[, connectListener])
net.connect(path[, connectListener]) 用于 IPC 连接.
net.connect(port[, host][, connectListener]) 用于 TCP 连接.
具体用法入参自行看文档, 这里选择第一种.
- const.NET = require('net')
- const client = net.connect({port: 8124}, function () {
- console.log('已同意好友请求 \ n 你是?')
- }).on('data', function (data) {
- console.log(data.toString())
- client.end()
- }).on('end', function () {
- console.log('你滚!')
- })
输出结构
// 服务端
你已向对方发起好友请求!
你已被列入黑名单!
// 客户端
已同意好友请求
你是?
请问需要贷款么?!
你滚!
dgram (数据报)
net 是创建基于流的 TCP 或 IPC, 而 dgram 就是提供了 UDP 数据包 socket 的实现. 只提供一个 dgram.Socket 类.
dgram.Socket 类
dgram.Socket 对象是一个封装了数据包函数功能的 EventEmitter.
dgram.Socket 实例是由 dgram.createSocket()创建的. 创建 dgram.Socket 实例不需要使用 new 关键字.
事件
事件 | 作用 |
---|---|
close | 'close'事件将在使用 close() 关闭一个 socket 之后触发。该事件一旦触发,这个 socket 上将不会触发新的'message'事件。 |
error | 当有任何错误发生时,'error'事件将被触发。事件发生时,事件处理函数仅会接收到一个 Error 参数。 |
listening | 当一个 socket 开始监听数据包信息时,'listening'事件将被触发。该事件会在创建 UDP socket 之后被立即触发。 |
message | 当有新的数据包被 socket 接收时,'message'事件会被触发。msg 和 rinfo 会作为参数传递到该事件的处理函数中。 |
方法
socket.createSocket(type[, callback])
参数 | 作用 |
---|---|
type <string> | 'udp4' 或'udp6' |
callback <Function> | 为'message' 事件添加一个监听器。 |
创建一个特定 type 的 dgram.Socket 对象. type 参数是 udp4 或 udp6. 可选传一个回调函数, 作为'message' 事件的监听器.
一旦套接字被创建. 调用 socket.bind()会指示套接字开始监听数据报消息. 如果 address 和 port 没传给 socket.bind(), 那么这个方法会把这个套接字绑定到 "全部接口" 地址的一个随机端口(这适用于 udp4 和 udp6 套接字). 绑定的地址和端口可以通过 socket.address().address 和 socket.address().port 来获取.
socket.address
返回一个包含 socket 地址信息的对象. 对于 UDP socket, 该对象将包含 address,family 和 port 属性.
socket.bind([port][, address][, callback])
参数 | 作用 |
---|---|
port <number> | 整数 |
address <string> | 地址 |
callback <Function> | 没有参数。当绑定完成时会被调用。 |
对于 UDP socket, 该方法会令 dgram.Socket 在指定的 port 和可选的 address 上监听数据包信息. 若 port 未指定或为 0, 操作系统会尝试绑定一个随机的端口. 若 address 未指定, 操作系统会尝试在所有地址上监听. 绑定完成时会触发一个'listening'事件, 并会调用 callback 方法.
注意, 同时监听'listening'事件和在 socket.bind()方法中传入 callback 参数并不会带来坏处, 但也不是很有用.
一个被绑定的数据包 socket 会令 Node.JS 进程保持运行以接收数据包信息.
若绑定失败, 一个'error'事件会被触发. 在极少数的情况下(例如尝试绑定一个已关闭的 socket), 一个 Error 会被抛出.
一个监听 41234 端口的 UDP 服务器的例子:
- const dgram = require('dgram');
- const server = dgram.createSocket('udp4');
- server.on('error', (err) => {
- console.log(` 服务器异常:\n${err.stack}`);
- server.close();
- });
- server.on('message', (msg, rinfo) => {
- console.log(` 服务器收到:${msg} 来自 ${rinfo.address}:${rinfo.port}`);
- });
- server.on('listening', () => {
- const address = server.address();
- console.log(` 服务器监听 ${address.address}:${address.port}`);
- });
- server.bind(41234);
- // 服务器监听 0.0.0.0:41234
- socket.send(msg, [offset, length,] port [, address] [, callback])
参数 | 作用 |
---|---|
msg < Buffer,Uint8Array,string,Array> | 要发送的消息。 |
offset <number> | 整数。指定消息的开头在 buffer 中的偏移量。 |
length <number> | 整数。消息的字节数。 |
port <number> | 整数。目标端口。 |
address <number> | 目标主机名或 IP 地址。 |
callback <Function> | 当消息被发送时会被调用。 |
在 socket 上发送一个数据包. 目标 port 和 address 须被指定.
msg 参数包含了要发送的消息. 根据消息的类型可以有不同的做法.
1)如果 msg 是一个 Buffer 或 Uint8Array, 则 offset 和 length 指定了消息在 Buffer 中对应的偏移量和字节数.
2)如果 msg 是一个 String, 那么它会被自动地按照 utf8 编码转换为 Buffer. 对于包含了多字节字符的消息, offset 和 length 会根据对应的 byte length 进行计算, 而不是根据字符的位置.
3)如果 msg 是一个数组, 那么 offset 和 length 必须都不能被指定.
address 参数是一个字符串.
1)若 address 的值是一个主机名, 则 DNS 会被用来解析主机的地址.
2)若 address 未提供或是非真值, 则'127.0.0.1'(用于 udp4 socket)或'::1'(用于 udp6 socket)会被使用.
若在之前 socket 未通过调用 bind 方法进行绑定, socket 将会被一个随机的端口号赋值并绑定到 "所有接口" 的地址上(对于 udp4 socket 是'0.0.0.0', 对于 udp6 socket 是'::0').
可以指定一个可选的 callback 方法来汇报 DNS 错误或判断可以安全地重用 buf 对象的时机. 注意, 在 Node.JS 事件循环中, DNS 查询会对发送造成至少 1 tick 的延迟.
确定数据包被发送的唯一方式就是指定 callback. 若在 callback 被指定的情况下有错误发生, 该错误会作为 callback 的第一个参数. 若 callback 未被指定, 该错误会以'error'事件的方式投射到 socket 对象上.
偏移量和长度是可选的, 但如其中一个被指定则另一个也必须被指定. 另外, 他们只在第一个参数是 Buffer 或 Uint8Array 的情况下才能被使用.
一个发送 UDP 包到 localhost 上的某个随机端口的例子:
- const dgram = require('dgram');
- const message = Buffer.from('Some bytes');
- const client = dgram.createSocket('udp4');
- client.send(message, 41234, 'localhost', (err) => {
- client.close();
- });
一个发送包含多个 buffer 的 UDP 包到 127.0.0.1 上的某个随机端口的例子:
- const dgram = require('dgram');
- const buf1 = Buffer.from('Some');
- const buf2 = Buffer.from('bytes');
- const client = dgram.createSocket('udp4');
- client.send([buf1, buf2], 41234, (err) => {
- client.close();
- });
发送多个 buffer 的速度取决于应用和操作系统. 逐案运行基准来确定最佳策略是很重要的. 但是一般来说, 发送多个 buffer 速度更快.
关于 UDP 包大小的注意事项
IPv4/v6 数据包的最大尺寸取决于 MTU(Maximum Transmission Unit, 最大传输单元)与 Payload Length 字段大小.
Payload Length 字段有 16 位宽, 指一个超过 64K 的包含 IP 头部和数据的负载 (65,507 字节 = 65,535 − 8 字节 UDP 头 − 20 字节 IP 头部); 通常对于环回地址来说是这样, 但这个长度的数据包对于大多数的主机和网络来说不切实际.
MTU 指的是数据链路层为数据包提供的最大大小. 对于任意链路, IPv4 所托管的 MTU 最小为 68 个字节, 推荐为 576(典型地, 作为拨号上网应用的推荐值), 无论它们是完整地还是分块地抵达.
对于 IPv6,MTU 的最小值是 1280 个字节, 然而, 受托管的最小的碎片重组缓冲大小为 1500 个字节. 现今大多数的数据链路层技术(如以太网), 都有 1500 的 MTU 最小值, 因而 68 个字节显得非常小.
要提前知道数据包可能经过的每个链路的 MTU 是不可能的. 发送大于接受者 MTU 大小的数据包将不会起作用, 因为数据包会被静默地丢失, 而不会通知发送者该包未抵达目的地.
socket.close([callback])
关闭该 socket 并停止监听其上的数据. 如果提供了一个回调函数, 它就相当于为'close'事件添加了一个监听器.
(还有更多方法可自行查询文档, 我就不贴了)
例子
我们只用上面讲到的方法简单说说
1, 我们分别创建两个 dgram.Socket 对象, 服务端绑定 8000, 客户端绑定 8001;
2, 分别做些监听信息, 错误, 关闭事件;
3, 先启动服务端, 接收到的信息后会发送关闭指示到 8001 端口(客户端);
4, 再启动客户端, 监听信息里有个接收 exit 指令会自动关闭的设置, 主动发送信息到 8000(服务端);
服务端
- const dgram = require('dgram')
- const server = dgram.createSocket('udp4')
- server.on('message', (msg, rinfo) => {
- console.log(` 服务器收到:${msg} 来自 ${rinfo.address}:${rinfo.port}`)
- server.send('exit', 0, 4, 8001, 'localhost')
- }).on('listening', function () {
- const address = server.address()
- console.log(` 服务器监听 ${address.address}:${address.port}`)
- }).on('error', (err) => {
- console.log(` 服务器异常:\n${err.stack}`)
- server.close()
- }).on('close', (err) => {
- console.log(` 服务器关闭 `)
- server.close()
- }).bind(8000)
客户端
- const dgram = require('dgram')
- const client = dgram.createSocket('udp4')
- client.on('message', (msg, rinfo) => {
- console.log(` 客户端接收来自 ${rinfo.address}:${rinfo.port}`)
- if (msg == 'exit') client.close()
- }).on('error', (err) => {
- console.log(` 客户端异常:\n${err.stack}`)
- client.close()
- }).on('close', () => {
- console.log('客户端关闭')
- }).bind(8001)
- const messages = 'Hello World'
- client.send(messages, 0, messages.length, 8000, 'localhost')
输出结构
// 服务端
服务器监听 0.0.0.0:8000
服务器收到: Hello World 来自 127.0.0.1:8001
// 客户端
客户端接收来自 127.0.0.1:8000
客户端关闭
来源: http://www.qdfuns.com/article/40831/d36f207d3f0c4087db7defdcb181f429.html