Node 核心编程 -- 网络通信

12 篇文章 1 订阅

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>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值