1、场景
IM通讯
2、WebSocket 与 HTTP
WebSocket 协议诞生于2008年,2011年成为国际标准,目前所有浏览器都已经支持。WebSocket 的最大特点就是实现服务器端和客户端双向平等对话:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。
HTTP有1.0和1.1的说法,也就是所谓的keep-alive,把多个HTTP整合成一个。WebSocket是一个新的协议,跟HTTP协议基本没什么关系,只是为了更好的浏览器兼容,在握手阶段采用了HTTP协议。
3、HTTP 与 WebSocket 的主要区别(如下图)
4、WebSocket 的其他特点
1、建立在 TCP 协议之上,服务器端的实现比较容易
2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,握手阶段采用 HTTP 协议,握手时不容易屏蔽,能通过各种 HTTP 代理服务器
3、数据格式比较轻量,性能开销小,通信高效
4、可以发送文本,也可以发送二进制数据
5、没有同源限制,客户端可以与任意服务器通信
6、协议标识符是ws(如果加密,则为wss),服务器网址就是 URL
5、WebSocket 是什么样的协议,具体有什么优点
相对于 HTTP 这种非持久的协议来说,WebSocket 是一个持久化的协议。
HTTP 是通过 Request 来界定生命周期的,也就是一个 Request 一个 Response ,在 HTTP1.0 中,这次 HTTP 请求就结束了。在 HTTP1.1 中有了改进,存在一个 keep-alive:在一个 HTTP 连接中,可以发送多个 Request,接收多个 Response,需要注意的是 Request => Response, 在 HTTP 中永远是这样,也就是说一个 Request 只能有一个 Response,并且这个 Response 也是被动的,不能主动发起。
WebSocket 是基于 HTTP 协议的来完成一部分握手,先看一个典型的 WebSocket 握手
1、GET /chat HTTP/1.1
2、Host: server.example.com
3、Upgrade: websocket
4、Connection: Upgrade
5、Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
6、Sec-WebSocket-Protocol: chat, superchat
7、Sec-WebSocket-Version: 13
8、Origin: http://example.com
上边那段类似 HTTP 协议的握手请求中,多了这么几个东西
1、Upgrade: websocket
2、Connection: Upgrade
这就是 WebSocket 的核心了,告诉服务器:我发起的请求要用 WebSocket 协议了,快点帮我找到对应的助理处理,而不是那个传统的 HTTP
3、Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
4、Sec-WebSocket-Protocol: chat, superchat
5、Sec-WebSocket-Version: 13
首先, Sec-WebSocket-Key 是一个 Base64 encode 的值,这个是浏览器随机生成的,告诉服务器:我要验证你是不是真的是 WebSocket 助理
然后, Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同 URL 下,不同的服务所需要的协议。
最后, Sec-WebSocket-Version 是告诉服务器所使用的 WebSocket Draft (协议版本)
然后服务器会返回下列东西,表示已经接受到请求, 成功建立 WebSocket 啦!
1、HTTP/1.1 101 Switching Protocols
2、Upgrade: websocket
3、Connection: Upgrade
4、Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
5、Sec-WebSocket-Protocol: chat
这里开始就是 HTTP 最后负责的区域了,告诉客户,我已经成功切换协议啦~
1、Upgrade: websocket
2、Connection: Upgrade
依然是固定的,告诉客户端即将升级的是 WebSocket 协议,而不是 mozillasocket,lurnarsocket 或者 shitsocket。
然后, Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key 。
后面的, Sec-WebSocket-Protocol 则是表示最终使用的协议。
至此,HTTP 已经完成它所有工作了,接下来就是完全按照 WebSocket 协议进行了。
6、WebSocket 的作用
在讲 WebSocket之前,我就顺带着讲下 ajax轮询 和 long poll 的原理
6-1、ajax轮询
ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息,如以下场景
1、客户端:小哥,有没有新信息(Request)
2、服务端:没有(Response)
3、客户端:大哥,有没有新信息(Request)
4、服务端:没有...(Response)
5、客户端:大叔,有没有新信息(Request)
6、服务端:你好烦啊,没有啊...(Response)
7、客户端:呜呜呜...有没有新消息(Request)
8、服务端:好啦好啦,有啦给你。(Response)
9、客户端:哈哈...有没有新消息呀(Request)
10、服务端:...没...没...没有(Response) —- loop
6-2、long poll
long poll 原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起请求后,如果没消息,就一直不返回 Response 给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始,如以下场景
1、客户端:小哥,有没有新信息,没有的话就等有了才返回给我吧(Request)
2、服务端:嗯嗯... 等有消息的时候再给你(Response)
3、客户端:小哥,有没有新信息,没有的话就等有了才返回给我吧(Request) -loop
从上面可以看出ajax和long poll这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性:服务端不能主动联系客户端,只能有客户端发起。
这两种都是非常消耗资源的,ajax轮询需要服务器有很快的处理速度和资源。long poll 需要有很高的并发性,也就是说同时接待客户端的能力。所以 ajax轮询 和 long poll 都有可能发生这种情况:
1、客户端:有新信息么?
2、服务端:正忙,请稍后再试(503 Server Unavailable)
3、客户端:...好吧,有新信息么?
4、服务端:正忙,请稍后再试(503 Server Unavailable)
注意:HTTP 是一个无状态协议。通俗的说就是,服务器因为每天要接待太多客户了,你一挂电话,他就把你的东西全忘光了,把你的东西全丢掉了,你第二次还得再告诉服务器一遍。
6-3、WebSocket
当服务器完成协议升级后(HTTP->Websocket),由被动变成主动,主动推送信息给客户端,模拟场景:
1、客户端:我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
2、服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
3、客户端:麻烦你有信息的时候推送给我呀
4、服务端:ok,有的时候会告诉你的
5、服务端:今天小明上学迟到了
6、服务端:老师罚小明背诵唐诗宋词三百首
7、服务端:哈哈哈哈哈啊哈哈哈哈
8、服务端:笑死我了哈哈哈哈哈哈哈
这样,只需要经过一次 HTTP 请求,就可以做到源源不断的信息传送了
7、项目实战
工程说明需求
1、js文件:service.js
2、聊天室文件(html):chatRoom.html
3、需要引入的模块:http、fs、ws(http与fs为系统模块,可直接引入,ws为webSocket模块,需要npm或者cnpm下载到工程主目录中)
7-1-1、service.js文件中代码
开启聊天室服务
const http = require('http');
读取文件
const fs = require('fs');
客户端服务
var server = http.createServer((req,res)=>{
res.writeHead(200,{'Content-Type':'text/html;charset=utf8'});
//将页面显示到浏览器中
res.end(fs.readFileSync('./chatRoom.html'));
});
引入socket服务,注意大小写,区分客户端服务
const Server = require('ws').Server;
建立好服务端,在这里监听客户端的服务(server)
var wss = new Server({server})
存放前端连接的socket
var clientMap = {};
记录进入聊天室的客户数量
var count = 0; //只做计数
var id = 0; //用作客户的id
当客户端连接上时就会触发,回调会接收一个scoket对象
wss.on('connection',function(socket){
count ++;
id ++;
//给每个客服添加一个id编号,
socket.id = id;
//以id编号存储客户数据
clientMap[socket.id] = socket;
//在服务端监听上线客户
console.log('用户' + id + '上线了');
//当客户端向服务端发送数据时触发
socket.on('message',function(msg){
broadcast(socket,msg);
})
//当客户端下线即断开时触发
socket.on('close',function(){
count --;
//将下线的客户移出当前的聊天室
console.log('用户' + socket.id+ '下线了');
delete clientMap[socket.id];
})
//当客户连接错误时触发
socket.on('error',function(err){
console.log('客户连接错误:'+err);
})
})
创建广播函数,向所有的客户端发送某一个客户端说的话,模拟聊天室
function broadcast (socket,msg){
for(var id in clientMap){
clientMap[id].send('用户'+socket.id+'说:'+msg);
}
}
设置端口
server.listen(1314);
7-1-2、chatRoom.html文件中代码
聊天室简单样式,写到header标签中
.room{
border: solid 1px #efefef;
width: 300px;
height: 300px;
overflow-y: scroll;
}
聊天窗口
<body>
<div class='room'></div>
<input type='text' id='mes'>
<button id='send' >发送</button>
</body>
7-1-3、script中的js代码
创建客户端的socket连接
注意:socket的IP地址要与服务端的一致,包括端口号
var wsc = new WebSocket('ws://172.16.92.71:1314');
检测聊天室的开关状态
var isOpen = true;
当客户端连接上的时候触发
wsc.onopen = function(e){
console.log(e,"我成功进入聊天室了")
}
后台返回消息的时候触发
wsc.onmessage = function(e){
console.log(e,"===后端消息==")
document.getElementsByClassName('room')[0].innerHTML += '<p>'+e.data+'</p>';
}
当服务端关闭的时候触发
wsc.onclose = function(e){
console.log(e,"聊天室已经关闭了")
isOpen = false;
alert('聊天室已经关闭了');
}
错误状况下触发
wsc.onerror = function(e){
console.log(e,'连接错误');
}
向服务端发送数据
send.onclick = function(){
if(!isOpen){
alert('聊天室已经关闭了,不能聊天了')
}else{
if(mes.value == ''){
alert('发送的内容不能为空')
}else{
wsc.send(mes.value);
}
}
}
7-1-4、结束
在终端执行下面的命令开启你的本地服务:
node service.js
将你设置的ip地址发给别人,尝试聊天吧,本项目中的ip为:
http://172.16.92.71:1314
项目实战部分为作者原生编辑,其他为后期编辑借用 nnngu的blog:https://www.cnblogs.com/nnngu/p/9347635.html