前言
前篇NodeJS基础篇—01从全局开始讲了NodeJS的全局模块等基础知识,那NodeJS还有什么呢?比如,网络(UDP、TCP、HTTP)、线程&进程管理等等;
本篇主要讲一下网络这部分,毕竟 API I/O 是一个服务的关键部分。话不多说,开始吧,有任何表述不准确的地方,欢迎各位大大们指正。
共涉及到的模块
- dgram
- net
- socket对象
- http模块:
- Reqeust对象:获取请求数据信息
- Response对象:响应反馈
- url模块:分析处理 url
- queryString模块:处理提交数据
- URL
- URLSearchParam
- stream模块:处理body提交数据
- path模块:本地文件路径处理
- fs模块:文件资源处理模块
一、dgram模块(UDP 数据报)
dgram模块提供了 UDP 数据包 socket 的实现
socket又称"套接字",应用程序通常通过"套接字" 向网络发出请求或者应答网络请求,其本质上就是一套用于实现网络数据交换的接口(API)
搭建简单的dgram服务器
【1】获取核心模块dgram
# Code
const dgram = require('dgram')
【2】取 dgram.Socket 类,并创建dgram.socket服务
创建一个特定 type 的dgram.Socket 对象,用来处理网 络数据的一个标准 API 对象,可以对网络数据进行读取和输出。
# 法1:
const serverSocket = new dgram.Socket()
# 法2:
const serverSocket = dgram.createSocket(type[, callback])
# type: 'udp4' 或 'udp6' //udp4 => ipv4
# Code
const serverSocket = dgram.createSocket('udp4');
【3】dgram(UDP)监听事件 .on()
closeerrorlistening
# 服务器开启成功,等待数据
# Code
serverSocket.on('listening', () => {
console.log('服务器开启成功,等待数据:');
});
message
# 当接收到数据的时候触发
# Code
serverSocket.on('message', data => {
console.log('接收到了数据:', data.toString());
})
【4】绑定(监听)指定的地址及端口 .bind()
绑定(监听)指定的地址及端口
serverSocket.bind([port][, address][, callback])
- port: 未指定则由系统分配
- address: 默认 0.0.0.0,表示所有地址
- IP callback: 绑定成功后的回调
# Code
serverSocket.bind(12345, '127.0.0.1');
【5】关闭服务 .close()
# Code
serverSocket.close()
【6】发送数据 .send()
发送数据:UDP,无连接协议,不需要连接到服务器,然后再发 数据。
serverSocket.send(msg, port, [address])
- msg: 发送的数据(字符串/Buffer)
直接发送数据触到 前面指定ip:port的serverSocket接收信息
# Code
clientSocket.send('hello', 12345, '127.0.0.1');
二、net模块(TCP)
在node中,tcp 协议,基于 net 模块来实现的 net 模块提供了创建基于流的 TCP 或 IPC 服务器。
(net.createServer())和客户端(net.createConnection()) 的异步 网络 API
- 服务端:提供服务,被连接,被请求的一方
- 客户端:获取服务,发起连接,请求的一方
搭建简单的net服务器 【net.Server类】
注: 接收到的数据是buffer,二进制 流数据传输
【1】获取核心模块net
# Code
const net = require('net')
【2】取 net.Server 类,并创建服务实例
# 法1:
const serverNet = new net.Server()
# 法2:
const serverNet = net.createServer([port[, host]])
# Code
const server = net.createServer(
() => {
// 这个函数其实就是connection事件绑定的函数
}
);
【3】监听端口 .listen()
监听端口,处理请求
server.listen(端口, [ip])
端口:
ip:默认为0.0.0.0,表示所有通配
【4】net事件 .on()
closeerrorconnection
当有客户端连接的时候触发
回调函数的第一个参数是一个net.Socket实例对象,
数据的传输就是通过socket对象来实现,回调函数参数是传送的数据
# !!!socket对象中包含所有的socket实例方法,
# 比如socket.write向客户端发送数据
server.on('connection', socket => {
// socket => 当前连接的 socket 对象
})
搭建简单的客户端 【net.Socket类】
TCP:先连接connection,后传输
【1】取 net.Socket 类,创建客户端socket对象
# 法一
const socketNetClient = new net.Socket()
socketNetClient.connect(options[, connectListener])
# 法二
const socketNetClient = net.createConnection(port[, host][, connectListener])
// connectListener连接回调
// 参数是 要连接的目标主机的地址以及端口号
# Code
const clientSocket = net.createConnection(12345, '10.220.17.238');
【2】在套接字上发送数据 .write()
在 socket 上发送数据。第二个参数指定了字符串的编码
.write(data[, encoding][, callback])
如果整个数据被成功刷新到内核缓冲区,则返回 true。 如果所有或部分数据在用户内存中排队,则返回 false。 当缓冲区再次空闲时,将触发 'drain'。
可选的 callback 参数将在数据最终写完时执行(可能不会立即执行)。
【3】半关闭套接字 .end()
半关闭 socket。即,它发送一个 FIN 数据包。 服务器可能仍会发送一些数据。
如果end()中指定了 data,则相当于调用 socket.write(data, encoding) 之后再调用 socket.end()
【4】net.Socket类 事件
end和data主要是基于stream,才触发的
data回调函数参数是buffer数据end需要另一方socket.end( )来触发connect已连接到服务器error
数据包
在数据传输过程中不仅仅只有主体数据(你要发送的主要内容),还包括了一些其他的数据信息(一层一层包装),比如发送端的IP、端口等,以方便接受者对数据进行处理与回复反馈信息给对方。
如果发送的数据比较大的话,还会按照一定的规则对发送的数据进行分包,每一个包中包含有一部分主体数据以及 上面提到的额外信息,接收方在接收到数据以后会数据包进行整合等一系列操作。
这种传输规则就是数据传输协议中的规定,不同的协议对传输规则有不同的规定。
socket.remoteAddress//对方发过来的数据的地址socket.remotePort//对方发过来数据的端口
net服务及客户端代码实例
# Server.js
const server = net.createServer();
server.on('connection', socket=>{ // !!!!net.Socket类!!!!
//有client连接时触发 console.log('有client连接')
socket.on('data',data=>{ //data事件接收数据
console.log(`接收到了客户端发来的消息${data}`)
socket.write('hello client')
// 向客户端发送数据
})
})
server.listen(12345,'127.0.0.1')
# Client.js
const socketNetClient = new net.Socket()
socketNetClient.connect(12345,'127.0.0.1')
// const clientSocket = net.createConnection(12345,'127.0.0.1')
socketNetClient.on('connect',()=>{
//已连接到server
console.log('已连接上服务器')
socketNetClient.write('hello,server')
socketNetClient.on('data',data=>{
//二进制数据
console.log('接收到了服务器发来消息'+data)
})
})
三、HTTP模块
搭建简单的HTTP服务器
【1】获取核心模块http
# Code
const http = require('http')
【2】取 http.server 类,http.server服务
该类继承自 net.server,http基于tcp,request本质是 net.socket+http协议增加的一些内容。
request.socket 基本等价 net.socket
console.log(request.socket.remoteAddress );
# 法1:
const server = new http.server()
# 法2:
const server = http.createServer([options][, requestListener])
# Code
const server = http.createServer(
() => {
// 这个函数其实就是connection事件绑定的函数
}
);
【3】监听端口 .listen()
监听端口,处理请求
server.listen(端口, [ip])
端口:
ip:默认为0.0.0.0,表示所有通配
【4】http事件 .on()
closeerrorconnection
当有客户端连接的时候触发
回调函数的第一个参数是一个http.Socket实例对象,
数据的传输就是通过socket对象来实现,回调函数参数是传送的数据
# !!!socket对象中包含所有的socket实例方法,
# 比如socket.write向客户端发送数据
server.on('connection', socket => {
// socket => 当前连接的 socket 对象
socket.write('xxxx')
})
requestHTTP对Request和Response的数据格式进行规定以及包装。- request
- request.setHeader(name, value)
- request.getHeader(name)
- request.path
- request.protocal
- request.host
- request.url
- response
- response.setHeader(name, value)
- response.getHeader(name)
- response.end([data[, encoding]][, callback]) //表明所有响应头和正文都已发送,如果指定了
data,则其效果类似于调用 response.write(data, encoding)后跟 response.end(call back) - response.write(chunk[, encoding][, callback])//向客户端返回响应内容
- response.statusCode
- response.writeHead(statusCode[, statusMessage][, headers])向请求指定状态码发送响应头。
- request
server.on('request', (request, response) => {
# request: 客户端请求对象,
# 保存了与当前这次请求的客户端相关的信息http.IncomingMessage 类
# response: 服务器输出对象,提
# 供了服务端输出(响应) 有关的一些方法http.ServerResponse 类
}
搭建简单的HTTP客户端
也可以是一个服务器向另一个服务器请求 http.clientRequest - options
- protocol : 使用的协议。默认为 http:
- host : 请求发送至的服务器的域名或 IP 地址。默认为 localhost
- family : 当解析 host 和 hostname 时使用的 IP地址族时有效,值是 4 或 6,未指定时,则同时使用 IP v4 和 v6 port : 远程服务器的端口。默认为 80
- method : 指定 HTTP 请求方法的字符串。默认为'GET'
- path : 请求的路径。默认为 '/'。 应包括查询字符 串(如有的话)。如 '/index.html?page=12'
- headers : 包含请求头的对象
【1】http.ClientRequest类,创建客户端实例
# 法一
const client = new http.ClientRequest()
# 法二
const client = http.request(options[, callback])
# Code
const client = http.request( {
//创建一个客户端(能发http请求)的对象
// tcp/ip
host: 'www.baidu.com', port: 80,
// http
protocol: 'http:',
method: 'get',
path: '/img/bd_logo1.png'
}, res => {
// 这个回调函数会在服务器响应的时候触发
// !!!!! res => socket !!!!!
let content = Buffer.alloc(0);
res.on('data', data => {
content = Buffer.concat([content, data], content.length + data.length);
});
res.on('end', () => {
fs.writeFileSync('./baidu.png', content); });
}
);
http服务器代码实例
# server.js
const http = require('http');
const path = require('path');
const fs = require('fs');
const mime = require('./mime.json');
http.createServer((req,res)=>{
fs.readFile(path.join(__dirname,'www',req.url), (err,fileContent)=>{
if(err){
// 没有找到对应文件 404Notfind
res.writeHead(404,{
'Content‐Type':'text/plain; charset=utf8'
});
res.end('页面被狗狗叼走了');
}else{
let dtype = 'text/html'; //默认类型
// 获取请求文件的后缀\
let ext = path.extname(req.url);
// 如果请求的文件后缀合理,就从 json文件 里 获取到标准的响应格式
if(mime[ext]){
dtype = mime[ext];
}
// 如果响应的内容是文本,就设置成utf8编码
if(dtype.startsWith('text')){
dtype += ';
charset=utf8'
}
// 设置响应头信息
res.writeHead(200,{
'Content‐Type':dtype
});
res.end(fileContent);
}
});
}).listen(3000, ()=>{
console.log('running...');
});
四、参数传递与获取
核心模块url
node:url 模块提供用于网址处理和解析的实用工具
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ href │
├──────────┬──┬─────────────────────┬────────────────────────┬───────────────────────────┬───────┤
│ protocol │ │ auth │ host │ path │ hash │
│ │ │ ├─────────────────┬──────┼──────────┬────────────────┤ │
│ │ │ │ hostname │ port │ pathname │ search │ │
│ │ │ │ │ │ ├─┬──────────────┤ │
│ │ │ │ │ │ │ │ query │ │
" https: // user : pass @ sub.example.com : 8080 /p/a/t/h ? query=string #hash "
│ │ │ │ │ hostname │ port │ │ │ │
│ │ │ │ ├─────────────────┴──────┤ │ │ │
│ protocol │ │ username │ password │ host │ │ │ │
├──────────┴──┼──────────┴──────────┼────────────────────────┤ │ │ │
│ origin │ │ origin │ pathname │ search │ hash │
├─────────────┴─────────────────────┴────────────────────────┴──────────┴────────────────┴───────┤
│ href │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
("" 行中的所有空格都应被忽略。它们纯粹是为了格式化。)
旧版
const url = require('url');
// 用于 URL 处理与解析
const querystring = require('querystring');
// querystring 模块提供了一些实用函数,用于解析与格式化 URL 查询字符串
const myURL =
url.parse('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash');
.parse('url',flag)把URL字符串转化为对象,加flag = true之后, 其中的query分割成一个对象,querystring处理.format()就是把对象转化为标准的URL字符串.search获取和设置网址的序列化的查询部分。- querystring.stringify()
.query解析的query对象-
querystring.parse()
-
新版
const myURL =
new URL('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash');
new URL(input[, base]).format()就是把对象转化为标准的URL字符串.search获取和设置网址的序列化的查询部分。.searchParams获取表示网址查询参数的URLSearchParams对象。 此属性是只读的。new URLSearchParams()参数可以是search也可以是queryObj,类似querystring核心库.toString()序列化.append(name, value).delete(name).set(name, value).get(name)
五、发起HTTP请求的方法
-
HTTP 标准库
- 无需安装外部依赖
- 需要以块为单位接受数据,自己监听 end 事件
- HTTP 和 HTTPS 是两个模块,需要区分使用
-
Request 库
- 使用方便
- 有 promise 版本
request-promise
-
Axios
- 既可以用在浏览器又可以用在 NodeJS
- 可以使用 axios.all 并发多个请求
-
SuperAgent
- 可以链式使用
-
node-fetch
- 浏览器的 fetch 移植过来的