WebSocket 是什么?
WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
Websocket是什么样的协议?
Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说。
WebSocket 与 HTTP
HTTP 有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个 HTTP 请求合并为一个,但是 Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器,所以在握手阶段使用了 HTTP 。
为什么需要WebSocket?
用以实现高效的实时Web应用
轮询(polling)
这是最早的一种实现实时 Web 应用的方案。客户端以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。这种同步方案的最大问题是,当客户端以固定频率向服务器发起请求的时候,服务器端的数据可能并没有更新,这样会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。
轮询:客户端按规定时间定时像服务端发送ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
const getting = {
url:'localhost:8080',
dataType:'json',
success:function(res) {
console.log(res);
}
};
//关键在这里,Ajax定时访问服务端,不断获取数据 ,这里是1秒请求一次。
window.setInterval(() => {$.ajax(getting)},1000);
长轮询(Long Polling)
长轮询是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提高。
Ajax长轮询属于Ajax轮询的升级版,在客户端和服务端都进行了一些改造,使得消耗更低,速度更快。不间断的通过Ajax查询服务端。
var getting = {
url:'localhost:8080',
dataType:'json',
success:function(res) {
console.log(res);
$.ajax(getting); //关键在这里,回调函数内再次请求Ajax
}
//当请求时间过长,就再次调用ajax长轮询
error:function(res){
$.ajax($getting);
}
};
$.ajax(getting);
流
流技术方案通常就是在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。这种机制在用户体验上有一点问题,需要针对不同的浏览器设计不同的方案来改进用户体验,同时这种机制在并发比较大的情况下,对服务器端的资源是一个极大的考验。
WebSocket-简单实例
WebSocket 客户端
在客户端,没有必要为 WebSockets 使用 JavaScript 库。实现 WebSockets 的 Web 浏览器将通过 WebSockets 对象公开所有必需的客户端功能(主要指支持 Html5 的浏览器)。
//客户端代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input id="sendText" type="text"/>
<button id="wsSend">发送</button>
<script>
// 初始化一个 WebSocket 对象
var ws = new WebSocket("ws://localhost:8001/");
function showMessage(msg) {
var div = document.createElement("div")
div.innerHTML = msg.msg
if (msg.type === "enter") {
div.style.color = "red"
} else if (msg.type === "left") {
div.style.color = "blue"
}
if (msg.type === "text") {
div.innerHTML = msg.name + ":" + msg.msg
}
document.body.appendChild(div)
}
// 建立 web socket 连接成功触发事件
ws.onopen = function () {
// 使用 send() 方法发送数据
document.getElementById("wsSend").onclick = function () {
var txt = document.getElementById("sendText").value;
if (txt) {
ws.send(txt)
}
}
};
// 接收服务端数据时触发事件
ws.onmessage = function (msg) {
var received_msg = JSON.parse(msg.data);
if (received_msg) {
showMessage(received_msg);
}
};
// 断开 web socket 连接成功触发事件
ws.onclose = function () {
alert("连接已关闭...");
};
</script>
</body>
</html>
//基于nodejs-websocket
var ws = require("nodejs-websocket")
var clientCount = 0
// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) { //连接后执行
console.log("New connection")
clientCount++;
conn.name = "user" + clientCount
var msg = {}
msg.name = conn.name
msg.msg = conn.name + "进入了"
msg.type = "enter"
broadCast(JSON.stringify(msg))
conn.on("text", function (str) { //接收到消息出发
msg.msg = str
msg.type = "text"
broadCast(JSON.stringify(msg))
})
conn.on("close", function (code, reason) { //连接断开后出发
console.log("Connection closed")
msg.type = "left"
msg.msg = conn.name + "离开了"
broadCast(JSON.stringify(msg))
})
conn.on("error", function (err) { //异常时出发
console.log("error")
console.log(err)
})
}).listen(8001)
function broadCast(str) {
server.connections.forEach(function (connection) {
connection.sendText(str)
})
}
启动服务
npm run server
通过代码不难发现当交互数据较复杂时,单纯使用webSocket存在对数据的大量处理,而这部分逻辑并不建议出现在业务代码中。可使用现有的库处理这些逻辑,这里使用socket.io 来代替单纯使用webSocket。
Socket.io
socket.io是一个跨浏览器支持WebSocket的实时通讯的JS库。https://socket.io/docs/
使用socket.io
以下为官方简单例子
npm install --save socket.io
Server (app.js)
var app = require('http').createServer()
var io = require('socket.io')(app);
app.listen(80);
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
Client (index.html)
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
socket.io使websocket的交互可以直接传递对象,通过标识符区分消息的类型,不再需要在客户端对交互数据进行处理。
使用webpack
等打包编译工具时,客户端引入socket.io
npm install --save socket.io-client
import io from "socket.io-client"
个人实验—简单聊天室
以下为个人基于react、webpack、socket.io实现的一个简单聊天室功能。
首先搭建一个react+webpack的项目,这里略过...
npm install --save socket.io-client
在jsx中引入
import * as React from "react";
import io from "socket.io-client"
//实例化socket对象
const socket = io('ws://localhost:8001/'); //连接地址 这里为本地服务地址
//socket核心逻辑代码
initSocket = () => {
socket.on('enter', (data) => { //用户进入时触发
this.showMessage(data, "enter")
});
socket.on('message', (data) => { //接收到消息是触发
this.showMessage(data, "message")
});
socket.on('leave', (data) => { //用户离开时触发
this.showMessage(data, "leave")
});
socket.on('enterSelf', (data) => { //用以区分是当前用户发送消息或其他用户
this.setState({personName: data.name})
});
}
send = (inputText) => {
if (inputText) {
socket.emit('message', inputText); //发送消息
this.setState({sendValue: null})
}
}
render() {
const {messageBox} = this.state
return (<div style={{overflow: "hidden"}}>
<Chatting>
<p className="title">J聊天室</p>
{this.renderMessageBox(messageBox)}
<Search
placeholder="请输入要发送的消息"
enterButton="发送"
value={this.state.sendValue}
onChange={(e) => this.setState({sendValue: e.target.value})}
onSearch={value => this.send(value)}
/>
</Chatting>
</div>
)
}
以上省略了样式及部分页面渲染代码。
server部分
npm install --save socket.io
var app = require('http').createServer()
var io = require('socket.io')(app);
app.listen(8001);
var clientCount = 0
io.on('connection', function (socket) {
clientCount++;
socket.name = 'user' + clientCount
io.emit('enter', {name: socket.name}); //用户连接成功时触发发送登陆消息
socket.emit('enterSelf', {name: socket.name})
socket.on('message', function (msg) { //服务端接受消息,返回各个客户端消息
io.emit('message', {name: socket.name, message: msg});
});
socket.on("disconnect", function () { //用户断开连接时,触发离开消息
io.emit('leave', {name: socket.name});
})
});
npm run dev,npm run server
服务启动后页面效果如下