一. 为什么要使用WebSocket?
- 对于我们第一次接触WebSocket的同学,其实大家都有一个疑问🤔️,我们已经有了HTTP,为什么还要使用WebSocket,它的优势是什么?其实很简单,HTTP只能由客户端发送请求到服务器端,是单向的。而WebSocket是双向的,服务器可以主动向客户端推送信息。
二. WebSocket是什么?
- WebSocket是HTML5出的东西,它就是一个新的协议,它和HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充,咱们可以通过这样一张图理解。
三. 那WebSocket是什么样的协议,具体有什么优点?
- 如果你想知道WebSocket有怎样的优点,首先咱们得了解HTTP有哪些的痛点,你说是不是这个理儿?
- 那我们就先来说说HTTP请求
- HTTP的生命周期通过Request来界定,也就是一个Request,一个Response,那么在HTTP1.0中,这次HTTP请求就结束了。
- 在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。
- 但是请记住 Request = Response ,在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。
- 还有一点,HTTP是无状态的协议,通俗的说就是,服务器因为每天要和太多的人打交道了,比较健忘,你一走,他就把你的事全忘光了。你再来还得告诉它你是谁?
- 那我们用人话,再说的简单一点
- HTTP是单向,只能由客户端发送请求到服务器端。但是WebSocket是双向的,可以由服务器直接推送消息到客户端。
- HTTP只能是一个请求,对应一个响应。但是WebSocket可以是一个请求,对应多个响应。
- HTTP是无状态的协议,每次建立TCP连接,经历过一个request和一个response之后,就会断开连接。再次发送请求还需要重新建立TCP连接,再次告诉服务器,你是谁。但是Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的无状态性,服务端会一直知道你的信息,直到你关闭请求。
- WebSocket天生自带跨域,天然加密。
四. 说了这么多好的地方,WebSocket是如何实现握手的呢?
- 让我们先来看看WebSocket的请求头
GET / HTTP/1.1 Host: localhost:8080 Origin: http://127.0.0.1:3000 Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw== - 重点请求首部意义如下:
- Connection: Upgrade:表示要升级协议
- Upgrade: WebSocket:告诉服务器,虽然我一开始给你发http,但是后续咱们升级成WebSocket吧,如何?
- Sec-WebSocket-Version: 13:表示WebSocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
- Sec-WebSocket-Key:验证你是不是WebSocket,直接甩给你一个WebSocket才懂的一句话,如果你正确回复了,那就说明你确实是WebSocket服务器,如果你回不过来,说明你是装的。
- 然后服务器会返回下列东西,表示已经接受到请求, 成功建立WebSocket啦!
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
五. 撸一下代码,感受一下😜
- client
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title></title> <script> let ws = new WebSocket('ws://localhost:8080/'); ws.onopen = function (){ alert('连接已建立'); }; ws.onmessage=function (){}; ws.onclose=function (){}; ws.onerror=function (){}; </script> </head> <body> </body> </html> - server
const net=require('net'); const crypto=require('crypto'); function parseHeader(str){ // http协议所有的换行都是\r\n,希望把空行滤掉 let arr = str.split('\r\n').filter(line => line); arr.shift(); let headers = {}; arr.forEach(line => { let [name, value] = line.split(':'); name = name.replace(/^\s+|\s+$/g, '').toLowerCase(); value = value.replace(/^\s+|\s+$/g, ''); headers[name] = value; }); return headers; } let server = net.createServer(sock => { sock.once('data', buffer => { let str = buffer.toString(); let headers = parseHeader(str); if (headers['upgrade'] != 'websocket') { console.log('no upgrade'); sock.end(); } else if (headers['sec-websocket-version']!='13'){ console.log('no 13'); sock.end(); } else { let key = headers['sec-websocket-key']; let uuid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; let hash =c rypto.createHash('sha1'); hash.update(key+uuid); let key2 = hash.digest('base64'); sock.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection:upgrade\r\nSec-Websocket-Accept:${key2}\r\n\r\n`); } }); sock.on('end', ()=>{ }); }); server.listen(8080);
六. socket.io
- 通过io来实现server与client的连接,兼容多个浏览器。
- client
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title></title> <script src="http://localhost:8080/socket.io/socket.io.js" charset="utf-8"></script> <script> let sock = io.connect('ws://localhost:8080/'); sock.on('timer', time => { console.log(time); }); </script> </head> <body> </body> </html> - server
const http = require('http'); const io = require('socket.io'); // 1.建立普通http let server = http.createServer((req, res) => {}); server.listen(8080); // 2.建立ws let wsServer = io.listen(server); wsServer.on('connection', sock => { // sock.emit('name', 数据) // sock.on('name', function (数据) { }); /*sock.on('aaa', function (a, b) { console.log(a, b, a+b); });*/ setInterval(function (){ sock.emit('timer', new Date().getTime()); }, 1000); });
七. 总结
- 如果你的应用需要提供多个用户相互交流,并且服务器端的数据经常由变动,真的可以考虑一下用websocket来实现。