NodeJS使用koa并集成WS

NodeJS使用koa搭建道路检测平台服务器

1:koa使用概述

1.1:koa简介

koa是由Express原班人马打造的,致力于成为一个更小更富有表现力、更健壮的Web框架。使用koa编写web应用,通过组合不同的generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写Web应用变得得心应手。

跟express比起来就是难点但是更轻量,市场占用率也是六四开,显然express的市场占用率还是略高一点。对express或者nodeJS基础不太明白的可以看NodeJs教程_Addam Holmes的博客-CSDN博客_nodejs教程

1.2:koa初始化项目

新建一个目录,打开对应目录下的终端然后初始化项目

// 初始化项目
npm init
// package name: 你的项目名字叫啥
// version: 版本号
// description: 对项目的描述
// entry point: 项目的入口文件(一般你要用那个js文件作为node服务,就填写那个文件)
// test command: 项目启动的时候要用什么命令来执行脚本文件(默认为node app.js)
// git repository: 如果你要将项目上传到git中的话,那么就需要填写git的仓库地址(这里就不写地址了)
// keywirds: 项目关键字(我就不写了)
// author: 作者的名字(也就是你叫啥名字)
// license: 发行项目需要的证书(就不写了)

之后安装koa的包

npm i koa

然后新建一个koa的js文件,在根目录新建app.js

之后引入koa,初始化koa,之后监听3000,注册应用级中间件

const Koa = require("koa")

const app = new Koa()

app.use((ctx,next)=>{
    
    
    // 输出请求路径
    console.log(ctx.request.path)
    ctx.response.body = "Hello World"
})
app.listen(3000)

第一个参数就是把express的req和res合并了。

ctx.req

Node 的request对象。

ctx.res

Node 的response对象。

  1. res.statusCode()
  2. res.writeHead()
  3. res.write()
  4. res.end()

绕过Koa的response 处理是不被支持的,应避免使用以下node 属性,下面才是ctx的参数核心

ctx.request()

koa的 Request对象。

ctx.response()

koa的 Respose对象。

1.3:koa精简写法

前面的输出请求路径console.log(ctx.request.path)可以直接写成ctx.path

输出页面数据ctx.response.body = "Hello World"也可以直接写成ctx.body = "Hello World"

ctx封装了ctx.request的函数如下(以下访问器和Requst别名等效)

  1. ctx.header
  2. ctx.headers
  3. ctx.method
  4. ctx.method=
  5. ctx.url
  6. ctx.url=
  7. ctx.originalUrl
  8. ctx. origin
  9. ctx.hrefctx.path
  10. ctx. path=
  11. ctx.query
  12. ctx.query=
  13. ctx. querystring
  14. ctx. querystring=
  15. ctx.host
  16. ctx.hostname

ctx封装了ctx.response的函数如下(以下访问器和Response别名等效)

  1. ctx.body
  2. ctx.body=
  3. ctx. status
  4. ctx.status=
  5. ctx. message
  6. ctx.message=
  7. ctx.length=
  8. ctx.length
  9. ctx.type=
  10. ctx.type
    ctx.headerSent
  11. ctx.redirect()
  12. ctx.attachment()
  13. ctx.set()
  14. ctx. append ()
  15. ctx.remove ()
  16. ctx.lastModified=
  17. ctx.etag=

1.4:koa和express

koa和express区别

(1)更轻量
koa不提供内置的中间件;
koa不提供路由,而是把路由这个库分离出来了(koa/router)
(2)Context对象
koa增加了一个Context的对象,作为这次请求的上下文对象(在koa2中作为中间件的第一个参数传入)。同时Context上也挂载了Request和Response两个对象。和Express类似,这两个对象都提供了大量的便捷方法辅助开发,这样的话对于在保存一些公有的参数的话变得更加合情合理。
(3)异步流程控制

express采用callback来处理异步,koa v2采用async/await。

asyncl await使用同步的写法来处理异步,明显好于callback和promise。

(4)中间件模型
express基于connect中间件,线性模型;

koa中间件采用洋葱模型(对于每个中间件,在完成了一些事情后,可以非常优雅的将控制权传递给下一个中间件,并能够等待它完成,当后续的中间件完成处理后,控制权又回到了自己《非常类似于函数调用》)

koa洋葱模型同步调用案例
const Koa = require("koa")
const app = new Koa()

app.use((ctx,next)=>{
    
    
    // 判断请求路径
    if(ctx.path =="/favicon.ico") return
    console.log("11111")
    next()
    console.log("33333")
    ctx.body = "Hello World"
})
app.use((ctx,next)=>{
    
    
    console.log("22222")
})
app.listen(3000)
koa洋葱模型异步调用案例

接下来异步的调用案例,对于Promise,或者async 和 await忘了就看这个

https://blog.csdn.net/m0_55534317/article/details/127775678

const Koa = require("koa")
const app = new Koa()

app.use(async (ctx, next) => {
    
    
    if (ctx.path == "/favicon.ico") return
    console.log("11111")
    let Token = await next()
    console.log("44444", ctx.token, Token)
    ctx.body = "Hello World"
})
app.use(async (ctx, next)=>{
    
    
    console.log("22222")
    await delay(1000)
    ctx.token = "uyguhyugiujhiuyhokyughkj4esaf4s"
    console.log("33333")
    return "uyguhyugiujhiuyhokyughkj4esaf4s"
})
function delay(time){
    
    
    return new Promise((resolve,reject)=>{
    
    
        setTimeout(resolve,time)
    })
}
app.listen(3000)

输出就是,页面会等待一秒之后再输出helloworld。

11111
22222
33333
44444	uyguhyugiujhiuyhokyughkj4esaf4s	uyguhyugiujhiuyhokyughkj4esaf4s

区别就是koa是再调用next是会把控制权交出去,从哪交出去从哪拿回来

1.5:koa路由

koa的路由需要单独下载

npm i koa-router

之后引入试用一下

const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()


router.get("/list",(ctx,next)=>{
    
    
    ctx.body = ["Hello World1","Hello World2","Hello World3","Hello World4"]
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)

app.use(router.routes())把所有路由加到app上。

.use(router.allowedMethods())这个是可以当前端路由请求错误可以页面提示

路由有很多种

router.get("/list",(ctx,next)=>{
    
    
})
router.post("/list",(ctx,next)=>{
    
    
})
router.put("/list:id",(ctx,next)=>{
    
    
})
router.del("/list:id",(ctx,next)=>{
    
    
})

也可以链式写法

router.get("/list",(ctx,next)=>{
    
    
})
.post("/list",(ctx,next)=>{
    
    
})
.put("/list/:id",(ctx,next)=>{
    
    
})
.del("/list/:id",(ctx,next)=>{
    
    
})

1.6:koa路由的规范化

(1)规范api的创建

把所有东西放在一个路由里肯定是不合适的,比如现在有user请求和list请求,那我们就可以建立一个routes文件夹下存放user.js和list.js,然后使用app.js去注册这两个组件即可,app.js需要先引入,先注册成路由级组件,再注册成应用级组件

文件夹结构

在这里插入图片描述

user.js

const Router = require("koa-router")
const router = new Router()

router.get("/",(ctx,next)=>{
    
    
    ctx.body = "user get success"
})
router.post("/",(ctx,next)=>{
    
    
    ctx.body = "user post success"
})
router.put("/:id",(ctx,next)=>{
    
    
    ctx.body = {
    
    
        ok : 1,
        data : "user put success"
    }
})
router.del("/:id",(ctx,next)=>{
    
    
    ctx.body = {
    
    
        ok : 1,
        data : "user delete success"
    }
})

module.exports = router

list.js

const Router = require("koa-router")
const router = new Router()

router.get("/",(ctx,next)=>{
    
    
    ctx.body = "list get success"
})
router.post("/",(ctx,next)=>{
    
    
    ctx.body = "list post success"
})
router.put("/:id",(ctx,next)=>{
    
    
    ctx.body = {
    
    
        ok : 1,
        data : "list put success"
    }
})
router.del("/:id",(ctx,next)=>{
    
    
    ctx.body = {
    
    
        ok : 1,
        data : "list delete success"
    }
})

module.exports = router

app.js

const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()
// 引入两个路由子类
const userRouter = require('./routes/user.js')
const listRouter = require('./routes/list.js')
// 先注册成路由级组件
router.use("/user",userRouter.routes(), userRouter.allowedMethods())
router.use("/list",listRouter.routes(), listRouter.allowedMethods())
// 再注册成应用级组件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
(2)批量注册成路由组建

可以单独写一个文件index.js吧user.js和list.js注册成路由组件,然后app.js仅需引入那一个然后祖册成应用级组件即可。

文件结构(在index.js中组成路由组件),user.js和list.js不变

在这里插入图片描述

index.js

const Router = require("koa-router")
const router = new Router()

// 引入两个路由子类
const userRouter = require('./user.js')
const listRouter = require('./list.js')
// 先注册成路由级组件
router.use("/user",userRouter.routes(), userRouter.allowedMethods())
router.use("/list",listRouter.routes(), listRouter.allowedMethods())
module.exports = router

app.js

const Koa = require("koa")
const app = new Koa()

const router = require('./routes/index.js')
// 直接注册成应用级组件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
(3)统一加前缀

首先需要记住一点,user.js、list.js、index.js、app.js的router其实不是同一个router

统一加前缀的命令

router.prefix("/api")

比如如下加在index.js

在这里插入图片描述

那么现在user和list的api在访问的时候均需要加/api前缀

但是如果加在user.js中,那就user的api在访问的时候均需要加/api前缀

(4)重定向
router.redirect('/',"/user")

这样127.0.0.1:3000和127.0.0.1:3000/user都可以访问user的get和post

(5)请求静态资源

这个其实我觉得一般用不到现在都前后端分离了

就需要先npm install koa-static

之后再app.js中声明一个文件为静态资源文件夹

const static = require('koa-static')
app.use(static(
    path.join(__diname,"public")
))
(6)获取请求参数

GET请求获取请求参数

console.log(ctx.querystring)//获取GET请求的参数字符串
console.log(ctx.query)//获取解析之后的GET请求的参数

POST请求获取请求参数

对于POST请求的处理,koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中

先安装

npm i koa-bodyparser

再引入(index.js中引入)

const bodyParser = require('koa-bodyparser')
// 使用ctx.body解析中间件
app.use(bodyParser())

之后user.js和list.js中就可以输出前端传过来的参数了

console.log(ctx.request.body)

无论前端传form表单还是json格式数据都可以用这个

(7)koa-ejs模板

先安装依赖包

npm install koa-views
npm install ejs

额,又是写前端的,现在都是前后端分离了,就不想写了

2:Socket编程

2.1:WebSocket介绍

image-20220421084242097

WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。我们来看看WebSocket连接是如何创建的。

首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求。,格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

该请求和普通的HTTP请求有几点不同:

  1. GET请求的地址不是类似/path/,而是以ws://开头的地址;
  2. 请求头Upgrade: websocketConnection: Upgrade表示这个连接将要被转换为WebSocket连接;
  3. Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

随后,服务器如果接受该请求,就会返回如下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用WebSocket的API,就不需要关心这些。

现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。

为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP协议的请求-应答机制限制了全双工通信。WebSocket连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧。

安全的WebSocket连接机制和HTTPS类似。首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议。

服务器支持

由于WebSocket是一个协议,服务器具体怎么实现,取决于所用编程语言和框架本身。Node.js本身支持的协议包括TCP协议和HTTP协议,要支持WebSocket协议,需要对Node.js提供的HTTPServer做额外的开发。已经有若干基于Node.js的稳定可靠的WebSocket实现,我们直接用npm安装使用即可。

2.2:ws模块

(1)ws模块创建客户端并接收到消息群发出去

服务端:

/***
 * WebSocket模块
 */
const  WebSocket = require("ws")
WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({
    
     port: 8800 });
// 一旦有客户端进来这个
wss.on('connection', function connection(ws) {
    
    
    // 监听客户端发过来的消息
    ws.on('message', function message(data, isBinary) {
    
    
        console.log(data.toString())
        wss.clients.forEach(function each(client) {
    
    
            if (client !== ws && client.readyState === WebSocket.OPEN) {
    
    
                client.send(data, {
    
     binary: isBinary });
            }
        });
    });
    ws.send('欢迎加入聊天室');
});

这里面几个注意点

1:创建WebSocket服务端

const wss = new WebSocketServer({ port: 8800 });

2:注意wss是我们创建的WebSocket服务器,ws使我们当前连接的客户端

// 一旦有客户端进来这个
wss.on('connection', function connection(ws) {
    
    
    // 监听客户端发过来的消息
    ws.on('message', function message(data, isBinary) {
    
    
        console.log(data.toString())
        wss.clients.forEach(function each(client) {
    
    
            if (client !== ws && client.readyState === WebSocket.OPEN) {
    
    
                client.send(data, {
    
     binary: isBinary });
            }
        });
    });
    ws.send('欢迎加入聊天室');
});

3:client.readyState === WebSocket.OPEN判断当前客户端是连接的状态

4:wss.clients是我们创建的WebSocket服务器一个方法,wss.clients.forEach(function each(client) 就是遍历全部的WebSocket客户端

客户端具体细节见WebSocket学习笔记_Addam Holmes的博客-CSDN博客_await websocket

var ws = new WebSocket("ws://localhost:8080")
ws.onopen = ()=>{
    
    
    console.log("open")
}
ws.onmessage = (evt)=>{
    
    
    console.log(evt.data)
}
(2)在第一次连接的时候记录用户信息
/***
 * WebSocket模块
 */
const  WebSocket = require("ws")
WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({
    
     port: 8800 });
// 一旦有客户端进来这个
wss.on('connection', function connection(ws,req) {
    
    
    // 连接时得操作
    const websiteType = new URL(req.url,"http://127.0.0.1:3000")
    console.log(websiteType.searchParams.get("name"))
    if (websiteType.searchParams.get("name") === "website"){
    
    
        // 引擎端
        ws.name = "website"
        ws.send('欢迎使用');
    }else {
    
    
        // 引擎端
        ws.name = "car"
    }
    // 监听客户端发过来的消息
    ws.on('message', function message(data, isBinary) {
    
    
        // console.log(data.toString())
        wss.clients.forEach(function each(client) {
    
    
            if (client.name == "website" && client.readyState === WebSocket.OPEN) {
    
    
                client.send(data, {
    
     binary: isBinary });
            }
        });
    });
});

在使用的时候可以在初次连接的时候根据连接的路由不同打上标签

const websiteType = new URL(req.url,"http://127.0.0.1:3000")
console.log(websiteType.searchParams.get("name"))
if (websiteType.searchParams.get("name") === "website"){
    
    
    // 引擎端
    ws.name = "website"
    ws.send('欢迎使用');
}else {
    
    
    // 引擎端
    ws.name = "car"
}

所以客户端连接的时候就需要注意了

ws://127.0.0.1:8800
// websiteType.searchParams.get("name") == null
ws://127.0.0.1:8800?name=website
// websiteType.searchParams.get("name") == website

此时我们可以判断连接路由为ws://127.0.0.1:8800?name=website得ws加个属性,属性名为name,属性值就是website,那其他的客户端的name属性属性值就是car

// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
    
    
    // console.log(data.toString())
    wss.clients.forEach(function each(client) {
    
    
        if (client.name == "website" && client.readyState === WebSocket.OPEN) {
    
    
            client.send(data, {
    
     binary: isBinary });
        }
    });
});

在转发数据的时候只转发给name为website的客户端

}


所以客户端连接的时候就需要注意了

```js
ws://127.0.0.1:8800
// websiteType.searchParams.get("name") == null
ws://127.0.0.1:8800?name=website
// websiteType.searchParams.get("name") == website

此时我们可以判断连接路由为ws://127.0.0.1:8800?name=website得ws加个属性,属性名为name,属性值就是website,那其他的客户端的name属性属性值就是car

// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
    
    
    // console.log(data.toString())
    wss.clients.forEach(function each(client) {
    
    
        if (client.name == "website" && client.readyState === WebSocket.OPEN) {
    
    
            client.send(data, {
    
     binary: isBinary });
        }
    });
});

在转发数据的时候只转发给name为website的客户端

猜你喜欢

转载自blog.csdn.net/m0_55534317/article/details/127793172