时间:2020-02-24来源:电脑系统城作者:电脑系统城
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
socket是在http基础上,对http进行升级,让连接用socket来完成。
一个典型的Websocket握手请求如下:
客户端请求
GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: example.com Origin: http://example.com Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
服务器回应
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s= Sec-WebSocket-Location: ws://example.com/
const http = require('http'); const net = require('net');//TCP 原生socket const crypto = require('crypto'); let server = net.createServer(socket => { //握手只有一次 socket.once('data',(data)=>{ console.log('握手开始') let str = data.toString() let lines = str.split('\r\n') //舍弃第一行和最后两行 lines = lines.slice(1,lines.length-2) //切开 let headers ={} lines.forEach(line=>{ let [key,value]= line.split(`: `) headers[key.toLowerCase()]=value }) if(headers[`upgrade`]!='websocket'){ console.log('其他协议',headers[`upgrade`]) socket.end() }else if(headers[`sec-websocket-version`] != '13'){ console.log('版本不对',headers[`upgrade`]) socket.end() }else { let key = headers['sec-websocket-key'] let mask = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' let hash =crypto.createHash('sha1') hash.update(key+mask) let key2 =hash.digest('base64') socket.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${key2}\r\n\r\n`) //服务器响应,握手结束 //真正的数据 socket.on('data',data1 => { console.log(data1)//帧 let FIN = data1[0]&0x001//位运算 let opcode=data1[0]&0xF0 }) } }) //断开 socket.on('end',() =>{ console.log('客户端断开') }) }); server.listen(8080);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script> let socket = new WebSocket('ws://localhost:8080/') socket.emit = function(name,...args){ socket.send(JSON.stringify({name,data:args})) alert(JSON.stringify({name,data:args})) } socket.onopen = function (event) { console.log('WebSocket is open') socket.emit('msg',12,34) } socket.onmessage = function (event) { console.log('有消息过来') } socket.onclose = function () { console.log('断开连接') } </script> </head> <body> </body> </html>
原生socket较复杂,一般都通过框架来使用websocket,socket.io封装了websocket。
socket.io文档
安装:
npm install socket.io -s
io = require('socket.io')(httpServer);
io.on('connection',function(socket))
on(name,function(data){})
:绑定监听emit(name,data)
: 发送消息let app = require('express')(); let httpServer = require('http').createServer(app); //得到IO对象 let io = require('socket.io')(httpServer); //监视连接,(当有一个客户连接上时回调) io关联多个socket io.on('connection',function (socket) { console.log('socketio connected'); //绑定sendMsg监听,接受客户端发送的消息 socket.on('sendMsg',function (data) { console.log('服务端接受到浏览器的信息'); /*io.emit 发送给所有连接服务器的客户端, socket.emit 发送给当前连接的客户端*/ socket.emit('receiveMsg',data.name + '_'+ data.date); console.log('服务器向浏览器发送消息',data) }) }) http.listen(4000,function(){ console.log('listening on:4000') })
socket.io-client
库io(url)
连接服务端,得到socket对象(如果不指定url,将会连接默认主机地址)<script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script> <script> //连接服务器,得到代表连接的socket对象 const socket = io('ws://localhost:4000'); //绑定'receiveMessage的监听,来接受服务器发送的数据 socket.on('receiveMsg',function(data){ console.log('浏览器端接受消息'); }) //向服务器发送消息 socket.emit('sendMsg',{name: 'Tom',date: Date.now()}) console.log('浏览向服务器发送消息') </script>
上面服务端如果使用socket.emit
实现的是服务端和客户端的一对一发送数据,那么如何将服务端收到的数据发送给其他用户,来实现聊天室效果呢?
这里就需要io.emit
发送数据给当前连接此服务器的所有用户。
服务端
let app = require('express')(); let httpServer = require('http').createServer(app); //得到IO对象 let io = require('socket.io')(httpServer); //监视连接. io.on('connection',function (socket) { socket.on('chat message', function(msg){ console.log('connected',msg) io.emit('chat message', msg); }); socket.on('disconnected',(res)=>{ console.log('disconnected',res) }) }) http.listen(4000,function(){ console.log('listening on:4000') })
客户端
<head> <script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script> $(function () { let socket = io("ws://localhost:4000"); $('form').submit(function(e){ e.preventDefault(); //阻止刷新 socket.emit('chat message', $('#m').val()); $('#m').val(''); return false; }); socket.on('chat message', function(msg){ $('#messages').append($('<li>').text(msg)); }); }); </script> </head> <body> <ul id="messages"></ul> <form action=""> <input id="m" autocomplete="off" /><button>Send</button> </form> </body>
接下来根据官网的方案进行优化
Here are some ideas to improve the application:
在服务端,通过io.on('connection')
监听用户连接。
socket.on('disconnect')
监听用户断开。
通过回调向客户端传递提示信息。 socket.id
可以用来独一无二的表示当前会话的客户端id
//服务端 socket.on('add user',function (msg) { //在用户第一次加入时,触发add user,提示所有用户 io.emit('user joined',{id: socket.id}); }) socket.on('disconnect',(reason)=>{ console.log('disconnect',socket.id,reason); io.emit('user left',{id: socket.id}) }) //客户端 socket.on('user joined',function(data){ let {id} = data; $('#messages').append($('<li>').text(id+'加入聊天室').addClass('log')); }) socket.on('user left',function(data){ let {id} = data; $('#messages').append($('<li>').text(id+'离开聊天室').addClass('log')); })
//在客户端提供添加昵称的输入框,当输入完信息后,传递昵称给服务端 socket.emit('add user', username); //在服务端重构 socket.on('add user', function (username) { socket.username = username; io.emit('user joined', { username: socket.username }); })
通过监听keydown
事件,判定 event.which
的值是否为 13(enter的Unicode码是13)。如果是则emit 消息
通过监听input事件,来更新type信息
//update $inputMessage.on('input', function () { updateTyping(); }); function updateTyping() { if (connected) { if (!typing) {//如果当前没在输入,则更改标志,并发送正在输入的消息消息 typing = true; socket.emit('typing'); } lastTypingTime = (new Date()).getTime(); setTimeout(function () { var typingTimer = (new Date()).getTime(); var timeDiff = typingTimer - lastTypingTime; if (timeDiff >= TYPING_TIMER_LENGTH && typing) {//如果停止输入超时,则发送停止消息 socket.emit('stop typing'); typing = false; } }, TYPING_TIMER_LENGTH); } } //服务端 传递当前正在输入或停止输入的用户名,用于让客户端显示或消失 is Typing的信息 socket.on('typing', function () { io.emit('typing', { username: socket.username }); }); socket.on('stop typing', function () { io.emit('stop typing', { username: socket.username });
更多案例在官方仓库中查找
namespace允许用户去分配路径,这个的好处是可以减少TCP资源,同时进行通道隔离
默认的namespace是/
通过 of
方法可以自定义namespace
//服务端 const nsp = io.of('/my-namespace'); nsp.on('connection', function(socket){ console.log('someone connected'); }); nsp.emit('hi', 'everyone!'); //客户端 const socket = io('/my-namespace');
对于每个namespace,都可以定义多个频道,也就是room,用户可以 join
和 left
//服务端 io.on('connection', function(socket){ socket.join('some room'); }); //当要向某个房间传数据时,使用 to io.to('some room').emit('some event');
有的时候需要将数据从一个进程发送到令一个进程,可以通过redis adapter
//一个服务端 可以应用redis adapter const io = require('socket.io')(3000); const redis = require('socket.io-redis'); io.adapter(redis({ host: 'localhost', port: 6379 })); //另一个服务端可以通过连接给服务,从另一个进程的任意频道发送 const io = require('socket.io-emitter')({ host: '127.0.0.1', port: 6379 }); setInterval(function(){ io.emit('time', new Date); }, 5000);
2022-02-14
canvas贪食蛇 canvas实现贪食蛇的实践2022-02-14
bootstrapv4轮播图去除两侧阴影及线框的方法 bootstrapv4轮播图2021-03-20
前端Html5如何实现分享截图的示例代码Canvas实现放大镜效果完整案例分析(附代码),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值...
2020-11-26