HTML5中的webSocket、ajax、http
本文原链接:https://cloud.tencent.com/developer/article/1115496
https://cloud.tencent.com/developer/article/1193011
webSocket与ajax、web
先看一个有道释义:
![](https://ask.qcloudimg.com/http-save/yehe-1729928/4e3rssrb94.png?imageView2/2/w/1620)
其实释义的挺形象的,下面我来一一解释哈:
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)非实时数据交互(异步,局部刷新)。
![](https://ask.qcloudimg.com/http-save/yehe-1729928/me757wg861.png?imageView2/2/w/1620)
原生写法:
四部曲: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",true);
//发送请求(说话)
XHR.send();
//获取响应(听到对方回应)
XHR.onreadystatechange = function () {
// readyState值说明
// 0,初始化,XHR对象已经创建,还未执行open
// 1,载入,已经调用open方法,但是还没发送请求
// 2,载入完成,请求已经发送完成
// 3,交互,可以接收到部分数据
// 4,交互完毕
// 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)实时数据交互。
WebSocket 握手过程
WebSocket 协议本质上是一个基于 TCP 的协议,WebSocket
连接与 TCP 连接的建立过程类似[4]。但与传统的基于 TCP 连 接的协议有所不同的是 WebSocket 协议需要从 HTTP 协议“过 渡”而来,而这个“过度”过程也被称为 WebSocket 协议的握手 过程。因此想要建立基于 WebSocket 的 Web 应用必须首先了解 WebSocket 协议的握手机制,如图 2 给出了 WebSocket 协议握手 过程的示意图。
首先由浏览器客户端向服务器发起 WebSocket 握手请求报 文,这个报文是基于 HTTP 协议的,它告诉服务器客户端想要升 级当前的 HTTP 协议为 WebSocket 协议。服务器收到客户端的 WebSocket 握手请求报文之后会对报头进行解析,如果服务器 理解客户端握手请求报头并且满足升级为 WebSocket 协议的条 件,便会向客户端发送握手应答报文,这个应答报文同样是基于 HTTP 协议的。客户端收到服务器的应答报文后会对该报文进 行一次验证,验证成功之后便会成功升级为 WebSocket 协议,如 果验证失败客户端将会主动断开连接。建立了 WebSocket 连接 之后,双方便可以进行全双工通信,
WebSocket 握手过程与 TCP 握手过程类似,但是 WebSocket 协议握手采用了更加简洁的方式。
相对于传统的 TCP 握手,WebSocket 握手协议有以下明显 的特点: 首先,WebSocket 整个握手过程只需要两次握手,相对 于 TCP 的三次握手,WebSocket 简化并且加入了自己的规则。 其次,WebSocket 握手协议是基于 HTTP 协议的,相对于字节流 的解析,ASCII 序列解析起来更加简便。最后,WebSocket 握手 协议引入了随机序列认证机制,易于实现。
两次握手成功预示着双方接下来将升级当前的 HTTP 协议 为 WebSocket 协议。WebSocket 握手请求报文的报头部分除了 必须包含必要的 HTTP 字段[5],还须遵循通用消息格式[RFC 822],同时又要包含和 WebSocket 紧密联系的字段[6],如 Web- Socket 协议的版本信息、客户端随即生成的 Key、必要的 GET 请 求方法等。
![](https://ask.qcloudimg.com/http-save/yehe-1729928/xx7u4h11yp.png?imageView2/2/w/1620)
// 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"表示使用DOMBlob 对象,而"arraybuffer"表示使用 ArrayBuffer 对象。 |
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:
![](https://ask.qcloudimg.com/http-save/yehe-1729928/3blb8417h5.png?imageView2/2/w/1620)
再看一张webSocket的:
![](https://ask.qcloudimg.com/http-save/yehe-1729928/xko1nvurqg.png?imageView2/2/w/1620)
以及webSocket的Response Headers:
![](https://ask.qcloudimg.com/http-save/yehe-1729928/0bxjgmwjuk.png?imageView2/2/w/1620)
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