序言
浏览器实现长连接
在过去到现在,浏览器需要实现长连接有以下几种方式:
第一种是基于http协议来可以有两种方式:
1.在客户端使用轮询的方式,缺点是:增加不必要的http请求,浪费服务器的带宽
2.HTTP/1.0通过Connection:Keep-alive来实现长连接。
HTTP/1.1为了尽可能的提高HTTP性能,1.1规定所有连接必须是持久的,已经不需要在头部加上Connection:Keep-alive了。
想要短连接可以,Connection:close 但一般没人会主动去使用。仔细观察一下你的请求,有时候你可以在响应头上看到这个参数。
3.使用websocket连接方式,客户端和服务器可以保持持久的连接,也可以一方进行中断,然后就可以断开连接
原理
websockt 和 http 都是基于socket来的,而socket 是基于 tcp/ip 协议来的。
socket
1.客户端连接服务器(TCP / IP),三次握手,建立了连接通道
2.客户端和服务器通过socket接口发送消息和接收消息,任何一端在任何时候,都可以向另一端发送任何消息
3.有一端断开了,通道销毁
http
1.客户端连接服务器(TCP / IP),三次握手,建立了连接通道
2.客户端发送一个http格式的消息(消息头 消息体),服务器响应http格式的消息(消息头 消息体)
3.客户端或服务器断开,通道销毁
实时性的问题:
1.轮询
2.长连接
websocket
专门用于解决实时传输的问题
1.客户端连接服务器(TCP / IP),三次握手,建立了连接通道
2.客户端发送一个http格式的消息(特殊格式),服务器也响应一个http格式的消息(特殊格式),称之为http握手
3.双发自由通信,通信格式按照websocket的要求进行
4.客户端或服务器断开,通道销毁
在websocket的http握手阶段,服务器响应头中需要包含如下内容:
Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: [key]
其中,Sec-WebSocket-Accept的值来自于以下算法:
base64(sha1(Sec-WebSocket-Key) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
在node中可以使用以下代码获得:
const crypto = require("crypto"); const hash = crypto.createHash("sha1"); hash.update(requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); const key = hash.digest("base64");
其中,requestKey来自于请求头中的Sec-WebSocket-Key
代码实现
使用原生的方式实现
node 服务端实现
const net = require("net"); const server = net.createServer((socket) => { console.log("收到客户端的连接"); socket.once("data", (chunk) => { const httpContent = chunk.toString("utf-8"); let parts = httpContent.split("\r\n"); parts.shift(); parts = parts .filter((s) => s) .map((s) => { const i = s.indexOf(":"); return [s.substr(0, i), s.substr(i + 1).trim()]; }); const headers = Object.fromEntries(parts); const crypto = require("crypto"); const hash = crypto.createHash("sha1"); hash.update( headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ); const key = hash.digest("base64"); // 响应 socket.write(`HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: ${key} `); socket.on("data", (chunk) => { console.log(chunk,'来自浏览器的数据'); }); }); }); server.listen(5008);
客户端浏览器实现方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <button>发送数据到服务器</button> <script> // 客户端(浏览器)websocket const ws = new WebSocket("ws://localhost:5008"); // 创建一个websocket,同时,发送连接到服务器 ws.onopen = function () { // http握手完成 console.log("连接已建立"); }; ws.onmessage = function (e) { console.log("来自服务器的数据", e.data); }; ws.onclose = function () { console.log("通道关闭"); }; document.querySelector("button").onclick = function () { ws.send("123"); }; // ws.close(); //客户端主动断开连接 </script> </body> </html>
使用插件来进行实现
npm install --save socket.io 这个库既可以用户客户端,也可以用于服务端 传送门
node端
const express = require("express"); const socketIO = require("socket.io"); const http = require("http"); const path = require("path"); // express const app = express(); const server = http.createServer(app); app.use(express.static(path.resolve(__dirname, "public"))); // websocket const io = socketIO(server); io.on("connection", (socket) => { // 当有一个新的客户端连接到服务器成功之后,触发的事件 console.log("新的客户端连接进来了"); socket.on("msg", (chunk) => { // 监听客户端的msg消息 console.log(chunk.toString("utf-8")); }); const timer = setInterval(function () { //每隔两秒钟,发送一个消息给客户端,消息为test socket.emit("test", "test message from server"); }, 2000); // 断开连接 socket.on("disconnect", () => { clearInterval(timer); console.log("closed"); }); }); // 监听端口 server.listen(5008, () => { console.log("server listening on 5008"); });
客户端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <button>发送数据到服务器</button> // 可以使用sdn,或者是直接安装 <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/2.3.0/socket.io.js"></script> <script> // 创建一个连接 const socket = io.connect(); document.querySelector("button").onclick = function () { // 发送消息 socket.emit("msg", "msg from client"); }; // 获取服务端test的消息 socket.on("test", (chunk) => { console.log(chunk); }); // 断开连接 socket.on("disconnect", () => { console.log("closed"); }); </script> </body> </html>