Buffer: 处理二进制数据
二进制数据的获取
- 文件数据
- 网络数据
二进制数据的可读性:base64
Base64是一种基于64个可打印字符来表示二进制数据的方法。
转换前 11111111,11111111,11111111
转换后 00111111, 00111111, 00111111, 00111111
十进制 63 63 63 63
对应码表中的值 ////
let buf = Buffer.alloc(3, 0xff)
/* 输出 //// */
console.log(
buf.toString('base64')
)
二进制数据的显示
显示成base64
buffer.toString('base64')
显示成utf8编码的字符串
buffer.toString('utf8')
显示成16进制
buffer.toString('hex')
创建自定义Buffer
通过utf8编码的字符串创建
Buffer.from('text', 'utf8')
通过base64创建
Buffer.from('MTIz', 'base64')
生成并初始化一个Buffer
Buffer.alloc(3, 0xff)
数据格式转换
实现16进制、base64、utf8的自由转换
Buffer.from('text', 'utf8').toString('base64')
Stream
Writable 可写流
向目标写入数据
const fs = require('fs')
let writable = fs.createWriteStream('./log')
writable.write('some text\n')
writable.end('end\n')
注意:如果写入数据时,writable.write(data)
返回false
说明缓冲区已满,需要等待drain
事件触发后,再继续写入。
Readable 可读流
从目标读取数据
两种读取数据的模式
主动读取
注意:缓冲区数据为空时readable.read()
会返回null
,建议配合readable
事件表示。
被动接收
只要缓冲区中存在数据,就会触发data
事件
const fs = require('fs')
let readable = fs.createReadStream('./log')
let bufs = []
// 绑定data事件后,流对象才会触发该事件
readable.on('data', buf => {
bufs.push(buf)
})
readable.on('end', () => {
console.log(Buffer.concat(bufs).toString())
})
pipe
连接可读流和可写流
const http = require('http')
const fs = require('fs')
http.createServer((req, res) => {
fs.createReadStream('./index.html').pipe(res)
}).listen(3001)
Duplex and Transform Streams
gulp.src('client/templates/*.jade')
.pipe(jade())
.pipe(minify())
.pipe(gulp.dest('build/minified_templates'));
http模块
发送请求
const http = require('http')
// 可写流
let writable = http.request({
protocol: 'http:',
host: 'localhost',
method: 'POST',
headers: {
'content-type': 'application/json'
},
path: '/test.json'
}, onResponse)
// 与请求头中的数据类型保持一致
writable.write(
JSON.stringify({
a: 1,
b: 2
})
)
// 结束流
writable.end()
/**
* @param {Stream.Readable} readable
*/
function onResponse (readable) {
let bufs = []
readable.on('data', buf => {
// 不能转成字符
bufs.push(buf)
})
readable.on('end', () => {
console.log(Buffer.concat(bufs).toString())
})
}
http服务器
const http = require('http')
const server = http.createServer((readable, writable) => {
// 从readable中获取数据
writable.writeHead(200, {
'content-type': 'application/json'
})
writable.write(
JSON.stringify({
code: 200,
msg: 'success'
})
)
writable.end()
})
server.listen(3001)
bigpipe
可以让浏览器在请求完全结束前提前开始页面渲染
const http = require('http')
const server = http.createServer(async (readable, writable) => {
writable.writeHead(200, {
'content-type': 'text/html'
})
await sleep(1000)
writable.write('a')
await sleep(1000)
writable.write('b')
await sleep(1000)
writable.write('c')
writable.end()
})
server.listen(3001)
function sleep (ms) {
return new Promise(resolve => {
setTimeout(() => resolve(), ms)
})
}
express
每个中间件是一个函数。
每个到达服务器的请求都会交给声明的中间件依次处理。
每个中间件都可以选择直接向客户端返回数据或是将请求继续交给下一个中间件处理。
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log(req.originalUrl)
next()
})
app.use((req, res, next) => {
res.writeHead(200, {
'content-type': 'text/html'
})
res.write('hello world')
res.end()
})
app.use((req, res) => {
console.log('不会执行')
})
app.listen(3001)
koa
每个中间件都可以选择直接返回上一个中间件或是将请求继续交给下一个中间件处理。
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
let start = Date.now()
await next()
console.log(`${ctx.request.url}:${Date.now() - start}ms`)
})
app.use(async (ctx, next) => {
ctx.response.status = 200
ctx.response.headers['content-type'] = 'text/html'
await sleep(1000)
ctx.body = 'hello world'
})
app.use(async (ctx, next) => {
console.log('不会执行')
})
app.listen(3001)
function sleep(ms) {
return new Promise(resolve => {
setTimeout(() => resolve(), ms)
})
}
常用中间件
为服务器增加静态资源访问和缓存的能力
File Serving
解析HTTP请求体中的数据
Body Parsing
日志记录
Logging
多进程
单个nodejs进程无法有效利用多核CPU
// index.js
const cp = require('child_process')
for (let i = 0; i < 5; i++) {
let ins = cp.fork('./sub.js')
ins.on('message', msg => {
console.log('master get message: ' + JSON.stringify(msg))
})
ins.send({
text: 'message sent by master'
})
}
// sub.js
console.log('sub: ' + process.pid)
process.on('message', msg => {
console.log('sub get message: ' + JSON.stringify(msg))
process.send({
text: 'message sent by ' + process.pid
})
})
cluster
解决的问题:多个nodejs无法监听同一个端口。
模块实现了多个进程同时监听同一个端口的功能。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
pm2
多进程管理
根据服务器CPU的核数启动多个实例
实例意外退出时会自动重启实例
$ pm2 start index.js -i -1
进程监控
$ pm2 monit
开发模式
$ pm2 start index.js --watch
部署
提供远程部署nodejs服务器的功能。
pm2 通过ssh登录远程服务器,执行部署操作。
/* deploy.json*/
{
"apps": [
{
"name": "server",
"script": "index.js",
"instances": 1,
"exec_mode": "cluster"
}
],
"deploy": {
"server1": {
"user": "root",
"host": [
"127.0.0.1"
],
"ref": "origin/master",
"repo": "https://github.com/user/project.git",
"path": "/root/code",
"post-deploy": "git pull && yarn && pm2 startOrRestart deploy.json",
"pre-setup": "rm -rf /root/code"
}
}
}
第一次部署,pm2 会执行pre-setup
,删除指定目录
pm2 deploy deploy.json server1 setup
再次部署,pm2 会执行post-deploy
,从git拉取最新代码、安装依赖、重新启动程序
pm2 deploy deploy.json server1
eggjs
eggjs
继承自Koa, 增加了一组目录规范和一些常见问题的解决方案,包括应用部署、定时任务、日志、单元测试、CSRF防御、数据库操作等。
多进程模型
服务器性能
wrk
一个http压测工具
Mac中安装方式:brew install wrk
8个线程、建立200个连接,连续请求30s
$ wrk -t8 -c200 -d30s --latency "http://localhost:3001"
测试服务器在一段时间内持续接收到大量请求时的延迟情况。