00、前言
? 最近在写一个网页,需要用到后端的websocket服务,由于不需要太复杂的功能,后端选择使用nodejs的nodejs-websocket 模块。在开发过程中发现还需要http服务,但nodejs-websocket 无法实现http服务,于是便开始寻求可以解决两个协议共用同一端口的方法。
01、有关问题
? 一般来说,每个服务启动时都需占用一个端口。例如上面所提到的nodejs-websocket ,如下代码所示(代码来源:nodejs-websocket ):
const ws = require("nodejs-websocket")
const server = ws.createServer(function (conn) {
console.log("New connection")
conn.on("text", function (str) {
console.log("Received "+str)
conn.sendText(str.toUpperCase()+"!!!")
})
conn.on("close", function (code, reason) {
console.log("Connection closed")
})
}).listen(8001)
? 运行以上代码开启了一个websocket服务,监听8001 端口。
? 如果这时再运行一个http服务,如express ,同样监听8001 端口就会出现端口冲突,如以下代码(代码来源:express 中文网):
const express = require('express')
const app = express()
const port = 8001
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
? 将上述两个服务同时运行,则会出现:Error: listen EADDRINUSE: address already in use :::8001 错误提示。
? 如果你不太明白端口的意义或者用途,可以参考计算机网络相关书籍。简要来说,端口在计算机通讯中用以标记一个服务或应用,以便传输层知道将某数据包具体给应用层的哪个应用。
? 以上启动了两个服务,而这两个服务又是独立的,且同时监听了同一端口,此时就会出现端口冲突的情况。
02、如何实现复用端口
? 在客户端发起websocket连接时,客户端会向浏览器发送一个http请求,该请求的请求头包含Connection:Upgrade 和Upgrade:websocket ,向服务器请求修改协议为websocket,如果服务器同意修改协议,则会响应一个响应码为101的HTTP报文,至此HTTP的职责已经完成,下面的通讯则会采用websocket协议。(注意:只有在客户端使用实例化的websocket实例传输数据才会采用websocket协议,在此期间客户端仍可以发起其他的http请求,两者并不冲突)。
? 由此可得,只需让http服务处理所有的请求,在遇到Connection:Upgrade 和Upgrade:websocket 时换用websocket服务即可,此时只需http监听端口,而websocket不必对外监听,而由http服务直接转发。
? 下面给出采用express 、http 、ws 模块实现"复用"端口的代码:
const express = require("express");
const WS_MODULE = require("ws");
const http = require("http");
const app = express();
const port = 80;
app.get("/hello", (req, res) => {
res.send("hello world");
});
const server = http.createServer(app);
ws = new WS_MODULE.Server({ server });
ws.on("connection", (conn) => {
conn.on("message", (str) => {
});
});
server.listen(port, () => {
console.log("服务器已开启,端口号:" + port);
});
? 以上代码中,该应用可同时提供websocket和http服务,且域名下任何的请求路径在切换协议为websocket时都是一样的处理方式。即:在这种情况下连接ws://localhost 和ws://localhost/walekj/awe/dacz 没有任何区别。
? 如果想对不同的websocket请求路径采用不同的websocket服务,可以使用以下代码:
const express = require("express");
const WS_MODULE = require("ws");
const http = require("http");
const app = express();
const port = 80;
app.get("/hello", (req, res) => {
res.send("hello world");
});
const server = http.createServer(app);
const chatWS = new WS_MODULE.Server({ noServer: true });
chatWS.on("connection", (conn) => {
console.log("新的客户端连接 chatWS进行处理");
});
const fileWS = new WS_MODULE.Server({ noServer: true });
fileWS.on("connection", (conn) => {
console.log("新的客户端连接 fileWS进行处理");
});
server.on("upgrade", (req, socket, head) => {
if (req.url === "/chat") {
chatWS.handleUpgrade(req, socket, head, (conn) => {
chatWS.emit("connection", conn, req);
});
} else if (req.url === "/file") {
fileWS.handleUpgrade(req, socket, head, (conn) => {
fileWS.emit("connection", conn, req);
});
} else {
socket.destroy();
}
});
server.listen(port, () => {
console.log("服务器已开启,端口号:" + port);
});
? 现在可以连接ws://localhost/chat 和ws://localhost/file ,而其他的URI则无法建立连接。
? 采用这种方式则可以在连接前做其他的事情,如用户身份验证
03、参考链接&&API
ws
express
http
|