OSI 七层模型
- 应用层:用户与网络的接口
- 表示层:数据加密、转换、压缩
- 会话层:控制网络连接建立与终止
- 传输层:控制数据传输可靠性
- 网络层:确定目标网络
- 数据链路层:确定目标主机
- 物理层:各种物理设备和标准
TCP 三次握手与4次挥手
TCP 协议
- TCP 属于传输层,基于端口,面向连接
- 主机之间想要通信需要先建立双向数据通道
- TCP 的握手和挥手本质上都是4次
通信事件
- listening 事件:调用 server.listen方法之后触发
- connection 事件:新的连接建立时触发
- close 事件:当 server 关闭时触发
- error 事件:当错误出现的时候触发
- data 事件:当接收到数据时触发该事件
- write 方法:在 socket 上发送数据,默认是 UT8 编码
- end 操作:当 socket 一端发送 FIN 包时触发,结束可读端
TCP 数据粘包处理 — 数据的封包与拆包
class MyTransformCode{
constructor() {
this.packageHeaderLen = 4
this.serialNum = 0
this.serialLen = 2
}
// 编码
encode(data, serialNum) {
const body = Buffer.from(data)
// 01 先按照指定的长度来申请一片内存空间做为 header 来使用
const headerBuf = Buffer.alloc(this.packageHeaderLen)
// 02
headerBuf.writeInt16BE(serialNum || this.serialNum)
headerBuf.writeInt16BE(body.length, this.serialLen)
if (serialNum == undefined) {
this.serialNum++
}
return Buffer.concat([headerBuf, body])
}
// 解码
decode(buffer) {
const headerBuf = buffer.slice(0, this.packageHeaderLen)
const bodyBuf = buffer.slice(this.packageHeaderLen)
return {
serialNum: headerBuf.readInt16BE(),
bodyLength: headerBuf.readInt16BE(this.serialLen),
body: bodyBuf.toString()
}
}
// 获取包长度的方法
getPackageLen(buffer) {
if (buffer.length < this.packageHeaderLen) {
return 0
} else {
return this.packageHeaderLen + buffer.readInt16BE(this.serialLen)
}
}
}
module.exports = MyTransformCode
const net = require('net')
const MyTransform = require('./myTransform.js')
let overageBuffer = null
let ts = new MyTransform()
const client = net.createConnection({
host: 'localhost',
port: 1234
})
client.write(ts.encode('拉勾教育1'))
client.write(ts.encode('拉勾教育2'))
client.write(ts.encode('拉勾教育3'))
client.write(ts.encode('拉勾教育4'))
client.write(ts.encode('拉勾教育5'))
client.on('data', (chunk) => {
if (overageBuffer) {
chunk = Buffer.concat([overageBuffer, chunk])
}
let packageLen = 0
while(packageLen = ts.getPackageLen(chunk)) {
const packageCon = chunk.slice(0, packageLen)
chunk = chunk.slice(packageLen)
const ret = ts.decode(packageCon)
console.log(ret)
}
overageBuffer = chunk
})
Http 协议
代理客户端解决跨域
-
中间代理
const http = require('http') let options = { host: 'localhost', port: 1234, path: '/', method: 'POST' } let server = http.createServer((request, response) => { let req = http.request(options, (res) => { let arr = [] res.on('data', (data) => { arr.push(data) }) res.on('end', () => { // console.log(Buffer.concat(arr).toString()) let ret = Buffer.concat(arr).toString() response.setHeader('Content-type', 'text/html;charset=utf-8') response.end(ret) }) }) req.end('拉勾教育') }) server.listen(2345, () => { console.log('本地的服务端启动了') })
-
服务器端
const http = require('http') const server = http.createServer((req, res) => { // console.log('请求进来了') let arr = [] req.on('data', (data) => { arr.push(data) }) req.on('end', () => { console.log(Buffer.concat(arr).toString()) res.end('111111') }) }) server.listen(1234, () => { console.log('外部服务端启动了') })
Http 静态服务
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime')
const server = http.createServer((req, res) => {
// console.log('请求进来了')
// 1 路径处理
let {pathname, query} = url.parse(req.url)
pathname = decodeURIComponent(pathname)
let absPath = path.join(__dirname, pathname)
// console.log(absPath)
// 2 目标资源状态处理
fs.stat(absPath, (err, statObj) => {
if(err) {
res.statusCode = 404
res.end('Not Found')
return
}
if (statObj.isFile()) {
// 此时说明路径对应的目标是一个文件,可以直接读取然后回写
fs.readFile(absPath, (err, data) => {
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
res.end(data)
})
} else {
fs.readFile(path.join(absPath, 'index.html'), (err, data) => {
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
res.end(data)
})
}
})
})
server.listen(1234, () => {
console.log('server is start.....')
})
静态服务工具
-
创建 lgserve 文件夹
-
在 lgserve 文件夹下新建 bin 目录
-
初始化 package.json
npm init -y
-
在 bin 目录下新建 www.js 文件
#! /usr/bin/env node const {program} = require('commander') // console.log('执行了') // program.option('-p --port', 'set server port') // 配置信息 let options = { '-p --port <dir>': { 'description': 'init server port', 'example': 'lgserve -p 3306' }, '-d --directory <dir>': { 'description': 'init server directory', 'example': 'lgserve -d c:' } } function formatConfig (configs, cb) { Object.entries(configs).forEach(([key, val]) => { cb(key, val) }) } formatConfig(options, (cmd, val) => { program.option(cmd, val.description) }) program.on('--help', () => { console.log('Examples: ') formatConfig(options, (cmd, val) => { console.log(val.example) }) }) program.name('lgserve') let version = require('../package.json').version program.version(version) let cmdConfig = program.parse(process.argv) // console.log(cmdConfig) let Server = require('../main.js') new Server(cmdConfig).start()
-
在项目目录新建 main.js 创建一个 Server 类
const http = require('http') const url = require('url') const path = require('path') const fs = require('fs').promises const {createReadStream} = require('fs') const mime = require('mime') const ejs = require('ejs') const {promisify} = require('util') function mergeConfig (config) { return{ port: 1234, directory: process.cwd(), ...config } } class Server{ constructor(config) { this.config = mergeConfig(config) // console.log(this.config) } start() { let server = http.createServer(this.serveHandle.bind(this)) server.listen(this.config.port, () => { console.log('服务端已经启动了.......') }) } async serveHandle(req, res) { let {pathname} = url.parse(req.url) pathname = decodeURIComponent(pathname) let abspath = path.join(this.config.directory, pathname) // console.log(abspath) try { let statObj = await fs.stat(abspath) if (statObj.isFile()) { this.fileHandle(req, res, abspath) } else { let dirs = await fs.readdir(abspath) dirs = dirs.map((item) => { return{ path: path.join(pathname, item), dirs: item } }) // console.log(dirs) let renderFile = promisify(ejs.renderFile) let parentpath = path.dirname(pathname) let ret = await renderFile(path.resolve(__dirname, 'template.html'), { arr: dirs, parent: pathname == '/' ? false : true, parentpath: parentpath, title: path.basename(abspath) }) res.end(ret) } } catch (err) { this.errorHandle(req, res, err) } } errorHandle(req, res, err) { console.log(err) res.statusCode = 404 res.setHeader('Content-type', 'text/html;charset=utf-8') res.end('Not Found') } fileHandle(req, res, abspath) { res.statusCode = 200 res.setHeader('Content-type', mime.getType(abspath) + ';charset=utf-8') createReadStream(abspath).pipe(res) } } module.exports = Server
-
在项目目录下新建 template.html 用于显示目录列表模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ list-style: none; } </style> </head> <body> <h3>IndexOf <%=title%></h3> <ul> <%if(parent) {%> <li><a href="<%=parentpath%>">上一层</a></li> <%}%> <%for(let i = 0; i < arr.length; i++) {%> <li><a href="<%=arr[i].path%>"><%=arr[i].dirs%></a></li> <%}%> </ul> </body> </html>