作者:汪娇娇
时间:2017年8月2日
先看一个有道释义:
其实释义的挺形象的,下面我来一一解释哈:
1、聊天室:webSocket有名的应用就是聊天室了;
2、服务:webSocket提供客户端请求的服务器和服务;
3、套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合叫套接字,webSocket就是服务端和客户端的结合;
4、协议:webSocket是基于TCP的一种新的网络协议。
一、webSocket与ajax
作为一个码了还算久代码的前端,说起webSocket,脑子里最先闪现的当然就是ajax ajax ajax......ajax是啥,ajax刚出来时,可谓轰动一时,让我们愉快地告别那种提交一个表单必须得填完所有信息,然后再把数据转给服务器验证,结果发现有一个小小的输入框里输错了信息,然后又改掉重新提交走着重复的路的痛苦时代,所以它最大的贡献就是局部刷新。当然,不是说有了webSocket,它就out了,ajax现在依旧好用。下面稍微比较了下ajax和webSocket:
1、ajax
(1)浏览器主动发送消息给服务器;
(2)非实时数据交互(异步,局部刷新)。
原生写法:
四部曲:ajax对象、建立连接、发送请求、获取相应。
更通俗的用打电话来比喻,那就是:电话、拨号、说话、听到对方回应。demo
//创建一个ajax对象(想打电话,首先得有电话这个对象) var XHR = null; if (window.XMLHttpRequest) { // 非IE内核 XHR = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE内核,早期IE的版本写法不同 XHR = new ActiveXObject("Microsoft.XMLHTTP"); } else { XHR = null; } if(XHR){ //建立连接(拨号) XHR.open("GET","ajaxServer.action"); //发送请求(说话) XHR.send(); //获取响应(听到对方回应) XHR.onreadystatechange = function () { // readyState值说明 // 0,初始化,XHR对象已经创建,还未执行open // 1,载入,已经调用open方法,但是还没发送请求 // 2,载入完成,请求已经发送完成 // 3,交互,可以接收到部分数据 // status值说明 // 200:成功 // 404:没有发现文件、查询或URl // 500:服务器产生内部错误 if (XHR.readyState == 4 && XHR.status == 200) { // 这里可以对返回的内容做处理 // 一般会返回JSON或XML数据格式 console.log(XHR.responseText); // 主动释放,JS本身也会回收的 XHR = null; } }; }
JQuery写法(so easy,妈妈再也不用担心我的学习啦):
$.ajax({ type:"post",url:url,async:true,data:params,dataType:"json",success:function(res){ console.log(res); },error:function(jqXHQ){ alert("发生错误:"+jqXHQ.status); } });
2、webSocket
(1)实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端;
(2)实时数据交互。
// Create WebSocket connection. var socket = new WebSocket('ws://localhost:8080'); //创建一个webSocket实例 // Connection opened socket.addEventListener('open',function (event) { //一旦服务端响应WebSocket连接请求,就会触发open事件 socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message',function (event) { //当消息被接受会触发消息事件 console.log('Message from server',event.data); });
二、webSocket API
既然上面写了一部分代码,那不如把API全都贴出来,哈哈哈。
首先,创建一个webSocket实例:
var socket = new WebSocket('ws://localhost:8080');
然后再看下面的的API。
1、事件
(1)open
一个用于连接打开事件的事件监听器。当readyState
的值变为 OPEN 的时候会触发该事件。该事件表明这个连接已经准备好接受和发送数据。这个监听器会接受一个名为"open"的事件对象。
socket.onopen = function(e) { console.log("Connection open..."); };
或者:
socket.addEventListener('open',function (event) { console.log("Connection open..."); });
(2)message
一个用于消息事件的事件监听器,这一事件当有消息到达的时候该事件会触发。这个Listener会被传入一个名为"message"的MessageEvent
对象。
socket.onmessage = function(e) { console.log("message received",e,e.data); };
(3)error
当错误发生时用于监听error事件的事件监听器。会接受一个名为“error”的event对象。
socket.onerror = function(e) { console.log("WebSocket Error: ",e); };
(4)close
用于监听连接关闭事件监听器。当 WebSocket 对象的readyState 状态变为 CLOSED 时会触发该事件。这个监听器会接收一个叫close的CloseEvent
对象。
socket.onclose = function(e) { console.log("Connection closed",e); };
2、方法
(1)send
通过WebSocket连接向服务器发送数据。
一旦在服务端和客户端建立了全双工的双向连接,可以使用send方法去发送消息,当连接是open的时候send()方法传送数据,当连接关闭或获取不到的时候回抛出异常。
一个通常的错误是人们喜欢在连接open之前发送消息。如下所示:
// 这将不会工作 var socket= new WebSocket("ws://localhost:8080") socket.send("Initial data");
应该等待open事件触发后再发送消息,正确的姿势如下:
var socket= new WebSocket("ws://localhost:8080") socket.onopen = function(e) { socket.send("Initial data"); }
(2)close
关闭WebSocket连接或停止正在进行的连接请求。如果连接的状态已经是closed
,这个方法不会有任何效果。
使用close方法来关闭连接,如果连接以及关闭,这方法将什么也不做。调用close方法只后,将不能发送数据。close方法可以传入两个可选的参数,code(numerical)和reason(string),以告诉服务端为什么终止连接。
socket.close(1000,"Closing normally"); //1000是状态码,代表正常结束。
3、属性
属性名 | 类型 | 描述 |
binaryType |
DOMString |
一个字符串表示被传输二进制的内容的类型。取值应当是"blob"或者"arraybuffer"。 "blob"表示使用DOM |
bufferedAmount |
unsigned long |
调用send() 方法将多字节数据加入到队列中等待传输,但是还未发出。该值会在所有队列数据被发送后重置为 0。而当连接关闭时不会设为0。如果持续调用send() ,这个值会持续增长。只读。 |
extensions |
DOMString |
服务器选定的扩展。目前这个属性只是一个空字符串,或者是一个包含所有扩展的列表。 |
protocol |
DOMString |
一个表明服务器选定的子协议名字的字符串。这个属性的取值会被取值为构造器传入的protocols参数。 |
readyState |
unsigned short |
连接的当前状态。取值是Ready state constants之一。只读。 |
url |
DOMString |
传入构造器的URL。它必须是一个绝对地址的URL。只读。 |
4、常量
Ready state 常量
常量 | 值 | 描述 |
CONNECTING |
0 |
连接还没开启。 |
OPEN |
1 |
连接已开启并准备好进行通信。 |
CLOSING |
2 |
连接正在关闭的过程中。 |
CLOSED |
3 |
连接已经关闭,或者连接无法建立。 |
三、webSocket与HTTP
webSocket和http同为协议,大家心里肯定会想它俩之间有什么联系,当然,我也好奇,所以就有了下面的研究结果,呵呵呵呵~~
大家都知道,webSocket是H5的一种新协议(这样看来和http是没什么关系),本质是通过http/https协议进行握手后创建一个用于交换数据的TCP连接,服务端与客户端通过此TCP连接进行实时通信。也就是说,webSocket是http协议上的一种补充。
相对于HTTP这种非持久的协议来说,Websocket是一个持久化的协议。
以PHP的生命周期为例:
在http1.0中,一个request,一个response,一个周期就结束了。
在http1.1中,有了keep-alive,可以发送多个Request,接收多个Response。但在http中永远是一个request对应一个response。而且这个response是被动的,不能主动发起。
这时候webSocket就派上用场了。
四、webSocket原理
首先,先来看一张http的Request Headers:
再看一张webSocket的:
以及webSocket的Response Headers:
I guess,无论熟不熟悉http,想必都看出了区别,哈哈哈。接下来就要对这些东西进行讲解啦:
(1)Upgrade和Connection
Upgrade: websocket Connection: Upgrade
这个就是webSocket的核心,告诉Apache、ngix等服务器:注意啦,我发起的是webSocket协议,快点帮我找到对应的助理处理~ 不是那个老土的http。
(2)Sec-WebSocket-Key、Sec-WebSocket-Extensions和Sec-WebSocket-Version
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Extensions: chat,superchat Sec-WebSocket-Version: 13
这个很好理解啦,首先,Sec-WebSocket-Key是一个Base64 encode的值,这个是浏览器随机生成的,告诉服务器:尼好,我是webSocket,这是我的ID卡,让我过去吧。
然后,Sec-WebSocket-Extensions:协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强
最后,Sec-WebSocket-Version是告诉服务器所使用的webSocket Draft(协议版本)。喏,我是小喵4.1版本哆啦A梦,哈哈哈哈哈哈哈哈。
然后只要服务器返回了上面我放的那一系列balabala的东西,就代表已经接受请求,webSocket建立成功啦!
(3)Sec-WebSocket-Accept和Sec-WebSocket-Extensions
请求时,webSocket会自带加密过的ID卡过来让服务端验证;
对应的,接受请求之后,服务端也得搞一个安全卡(Accept头域的值就是Key的值,是由浏览器发过来的Sec-WebSocket-Key生成的)来证明是我同意你通过的,而不是什么肯蒙拐骗的坏银->
就这样,原理部分就说完啦,握手成功!
五、webSocket的作用
说webSocket之前,先说一下ajax轮询和long poll。
1、ajax轮询:
ajax轮询很简单,就是让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
客户端:hello hello,有没有新信息(Request) 服务端:没有(Response) 客户端:hello hello,有没有新信息(Request) 服务端:没有。。(Response) 客户端:hello hello,有没有新信息(Request) 服务端:你好烦啊,没有啊。。(Response) 客户端:hello hello,有没有新消息(Request) 服务端:有啦有啦,here you are(Response) 客户端:hello hello,有没有新消息(Request) 服务端:。。没。。。。没。。。没。。。。(Response)
2、long poll
long poll和ajax轮询原理很像,不过long poll是阻塞模型,简单来说,就是一直给你打电话,直到你接听为止。
客户端:hello hello,有没有新信息,没有的话就等有了再返回给我吧(Request) 服务端:额。。。 (。。。。等待到有消息的时候。。。。) 有了,给你(Response)
很明显,ajax轮询和long poll弊大于利:
(1)被动性
上面这两种方式都是客户端先主动消息给服务端,然后等待服务端应答,要知道,等待总是难熬的,如果服务端能主动发消息多好,这也就是缺点之一:被动性。
(2)非常消耗资源
ajax轮询 需要服务器有很快的处理速度和资源(速度);
long poll 需要有很高的并发,也就是说同时接待客户的能力(场地大小)。
so,当ajax轮询和long poll碰上503(啊啊啊啊啊,game over)
这时候,神奇的webSocket又派上用场了。
3、webSocket
(1)被动性
首先,解决被动性:
客户端:hello hello,我要建立webSocket协议,扩展服务:chat,Websocket,协议版本:17(HTTP Request) 服务端:ok,确认,已升级为webSocket协议(HTTP Protocols Switched) 客户端:麻烦你有信息的时候推送给我噢。。 服务端:ok,有的时候会告诉你的。 服务端:balabalabalabala 服务端:balabalabalabala 服务端:哈哈哈哈哈啊哈哈哈哈 服务端:笑死我了哈哈哈哈哈哈哈
就这样,只需要一次http请求,就会有源源不断的信息传送了,是不是很方便。
(2)消耗资源问题
首先,了解一下,我们所用的程序是要经过两层代理的,即http协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。
本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢,导致客服不够。
webSocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员再统一转交给客户。
这样就可以解决客服处理速度过慢的问题了。
同时,在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输鉴别信息,来告诉服务端你是谁。
虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降的,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网路传输中消耗过多的流量/时间。
但是webSocket只需要一次http握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了http的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析http协议,还要查看identity info的信息。
六、Socket.io
既然说到了webSocket,就难免扯到socket.io。
有人说socket.io就是对webSocket的封装,并且实现了webSocket的服务端代码。可以这样说,但不完全正确。
在webSocket没有出现之前,实现与服务端的实时通讯可以通过轮询来完成任务。Socket.io将webSocket和轮询(Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。也就是说,webSocket仅仅是Socket.io实现实时通信的一个子集。
下面直接上一个用socket.io做的小小聊天室吧。
(1)首先你得有node,然后安装socket.io。
$ npm install socket.io
(2)服务器端(index.js)
'use strict'; module.exports = require('./lib/express'); var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); app.get('/',function(req,res){ res.sendFile(__dirname + '/index.html'); }); io.on('connection',function(socket){ socket.on('message',function(msg){ console.log(msg); socket.broadcast.emit('chat',msg); //广播消息 }) }); http.listen(3000);
(3)客户端
先引入js文件:
<script src="/socket.io/socket.io.js"></script>
交互代码(index.html):
<!DOCTYPE html><html><head> <Meta charset="UTF-8"> <title>聊天室</title> <style> body,div,ul,li{margin: 0;padding: 0;list-style: none;} .auto{margin: auto;} .l{text-align: left;} .r{text-align: right;} .flex{display: Box;display: -webkit-Box;display: -moz-Box;display: -ms-flexBox;display: -webkit-flex;display: flex;-webkit-Box-pack: center;-webkit-justify-content: center;-moz-justify-content: center;-ms-justify-content: center;-o-justify-content: center;justify-content: center;-webkit-Box-align: center;-webkit-align-items: center;-moz-align-items: center;-ms-align-items: center;-o-align-items: center;align-items: center;} .chat-Box{background: #f1f1f1;width: 56vw;padding:2vw;height:36vw;border:1px solid #ccc;margin-top: 2vw;} .chat-li{display:inline-block;margin-top: 5px;background: #5CB85C;border-radius: 5px;padding: 3px 10px;color: #fff;} .other-chat-li{background: #fff;color: #333;} .send-Box{width: 60vw;border:1px solid #ccc;justify-content: space-between;border-top: 0;} .send-text{width: 50vw; border: none; padding: 10px;outline:0;} .send{width: 10vw;background: #5cb85c; border: none; padding: 10px;color: #fff;cursor: pointer;} .chat-name{color: #f00;} .other-Box,.self-Box{width: 50%;height:100%;} </style> </head> <body> <div class="chat-Box auto flex"> <ul class="other-Box l"></ul> <ul class="self-Box r"></ul> </div> <div class="flex send-Box auto"> <input class="send-text" type="text"> <button class="send" type="button">发送</button> </div> </body> <script src="/socket.io/socket.io.js"></script> <script src="https://code.jquery.com/jquery-1.11.1.js"></script> <script> $(function(){ var socket = io(); $(".send").click(function(){ var msg = $(".send-text").val(); if(msg != ""){ socket.send(msg); $('.self-Box').append('<li class="chat-li">'+ msg +'<li>'); $(".send-text").val(""); }else{ return false; } }) $(".send-text").keydown(function(event){ if(event.keyCode == 13){ var msg = $(".send-text").val(); if(msg != ""){ socket.send(msg); $('.self-Box').append('<li class="chat-li">'+ msg +'<li>'); $(".send-text").val(""); }else{ return false; } } }) socket.on("chat",function(msg){ $('.other-Box').append('<li class="other-chat-li chat-li">'+ msg +'<li>'); }) }) </script> </html>
(4)运行代码:
$ node index.js
然后打开两个浏览器页面(http://localhost:3000/),就可以聊天啦,至于聊天名称呀、聊天头像呀什么的,可以自己去研究罗~~~
下面是效果图:
到底为止啦,感觉好像裹脚布,so long~~~~~~~