是什么
由于TCP是服务器被动通信,无法主动向客户端发送信息,所以有了websocket
可以双向通信
服务端监听自身端口+客户端连接服务器地址和该端口,达成通信成功!(类似于Java的socket通信)
使用的是Http连接来建立:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL(ws://example.com:80/some/path)。
参考:https://www.ruanyifeng.com/blog/2017/05/websocket.html
如何建立
必须由浏览器发起!因为是一个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
注意点:
- 地址以
ws://目标地址:端口
开始 - connection指定连接升级
- upgrade指定升级成websocket协议
- 13是websocket协议版本号
返回值:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
注意:101标识HTTP协议将被替代成websocket协议!标识连接成功!
安全的WebSocket连接机制和HTTPS类似。首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议
websocket 常用 api
// 构造函数
WebSocket WebSocket(url)
// 属性
// 用于指定连接关闭后的回调函数
webSocket.onclose
// 用于指定连接失败后的回调函数
webSocket.onerror
// 用于指定当从服务器接受到信息时的回调函数
webSocket.onmessage
// 用于指定连接成功后的回调函数
webSocket.onopen
// 查询当前连接状况:
// CONNECTING:值为0,表示正在连接。
// OPEN:值为1,表示连接成功,可以通信了。
// CLOSING:值为2,表示连接正在关闭。
// CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
webSocket.readyState
// 事件 使用 addEventListener() 或将一个事件监听器赋值给本接口的 oneventname 属性,来监听下面的事件
// 当一个 WebSocket 连接被关闭时触发。
// 也可以通过 onclose 属性来设置。
"close"
// 当一个 WebSocket 连接因错误而关闭时触发,例如无法发送数据时。
// 也可以通过 onerror 属性来设置。
"error"
// 当通过 WebSocket 收到数据时触发。
// 也可以通过 onmessage 属性来设置。
"message"
// 当一个 WebSocket 连接成功时触发。
// 也可以通过 onopen 属性来设置。
"open"
// 方法 调用该方法执行一些功能
// 关闭当前链接。
WebSocket.close([code[, reason]])
// 对要传输的数据进行排队。
// data类型:utf-8字符串、arraybuffer、blob、arraybufferview
WebSocket.send(data)
向服务器发送数据
发送文本:ws.send('your message');
发送二进制:
var file = document
.querySelector('input[type="file"]')
.files[0];
ws.send(file);
发送arraybuffer:
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
ws.send(binary.buffer);
判断发送进度:实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
处理服务器发过来的数据
发送过来的数据包裹在event.data
中
ws.onmessage = function(event) {
var data = event.data;
// 处理数据
};
ws.addEventListener("message", function(event) {
var data = event.data;
// 处理数据
});
对于接受的数据,需要判断下是什么类型,然后做处理
ws.onmessage = function(event){
if(typeof event.data === String) {
console.log("Received data string");
}
if(event.data instanceof ArrayBuffer){
var buffer = event.data;
console.log("Received arraybuffer");
}
}
可以显式指定二进制的数据类型
// 收到的是 blob 数据 // 收到的是 ArrayBuffer 数据
ws.binaryType = "blob";//ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
console.log(e.data.size);
};
一个完整示例
客户端:
;(function(){
// 获取页面dom节点
const domList = document.querySelector("#list");
const domMsg = document.querySelector("#msg");
const domSend = document.querySelector("#send");
// 判断登陆状况
const username = localStorage.getItem('username');
if(username == null || username == ""){
location.href = '/entry.html';
return;
}
// 连接服务器
const ws = new WebSocket("ws:localhost:8000");
function init(){
bindEvent();
}
// 绑定websocket事件
function bindEvent(){
domSend.addEventListener('click',handleSendMsg,false);
ws.addEventListener('open',onOpen,false);
ws.addEventListener('close',onClose,false);
ws.addEventListener('error',onError,false);
ws.addEventListener('message',onMessage,false); // 也可以: ws.onmessage = onMessage;
}
// 发送数据函数
function handleSendMsg(){
ws.send(JSON.stringify({
"username":username,
"msg":domMsg.value,
"time":new Date()
}))
domMsg.value = "";
}
function onOpen(){
console.log('open')
}
function onClose(){
console.log('close')
}
function onError(){
console.log('error')
}
// 接收数据并处理的函数,这里主要是将接收到的数据插入一个li标签到页面上
function onMessage(e){
console.log('message',e)
domList.appendChild(createMsg(JSON.parse(e.data)))
}
function createMsg(data){
const {username,msg,time} = data;
const li = document.createElement('li');
li.innerHTML = `
<p><span>${username}</span> <i>${time}</i></p>
<p>${msg}</p>
`
return li;
}
init();
})();
服务器:这里使用ws包来监听
const WS = require("ws");
(function () {
const server = new WS.Server({ port: 8000 });
function init() {
bindEvent();
}
function bindEvent() {
server.on("open", onOpen);
server.on("close", onClose);
server.on("error", onError);
server.on("connection", onConnection);
}
function onOpen() {
console.log("open");
}
function onClose() {
console.log("close");
}
function onError() {
console.log("error");
}
function onMessage(msg) {
console.log("message", JSON.parse(msg));
server.clients.forEach((c) => c.send(msg, { binary: false }));
}
function onConnection(clinet) {
console.log("connection");
clinet.on("message", onMessage);
}
init();
})();
后端可以使用nodemon来监控这个index.js文件,并启动服务!
前端可以使用vite来启动服务!
// 后端
"scripts": {
"dev": "nodemon index.js"
},
// 前端
"scripts": {
"dev":"vite"
},