一、概述
1.Web端即时通讯技术
即时通讯技术简单的说就是实现这样一种功能:服务器端可以即时地将数据的更新或变化反应到客户端,例如消息即时推送等功能都是通过这种技术实现的。但是在Web中,由于浏览器的限制,实现即时通讯需要借助一些方法。这种限制出现的主要原因是,一般的Web通信都是浏览器先发送请求到服务器,服务器再进行响应完成数据的现实更新。
2.实现Web端即时通讯的方法
实现即时通讯主要有四种方式,它们分别是短轮询、长轮询(comet)、长连接(SSE)、WebSocket。它们大体可以分为两类,一种是在HTTP基础上实现的,包括短轮询、comet和SSE;另一种不是在HTTP基础上实现是,即WebSocket。下面分别介绍一下这四种轮询方式,以及它们各自的优缺点。
(1)短轮询
短轮询的基本思路就是浏览器每隔一段时间向浏览器发送http请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
这种方式的优点是比较简单,易于理解,实现起来也没有什么技术难点。缺点是显而易见的,这种方式由于需要不断的建立http连接,严重浪费了服务器端和客户端的资源。尤其是在客户端,距离来说,如果有数量级想对比较大的人同时位于基于短轮询的应用中,那么每一个用户的客户端都会疯狂的向服务器端发送http请求,而且不会间断。人数越多,服务器端压力越大,这是很不合理的。
因此短轮询不适用于那些同时在线用户数量比较大,并且很注重性能的Web应用。
(2)comet
comet指的是,当服务器收到客户端发来的请求后,不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)后关闭连接。
长轮询和短轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源。长轮询的缺点在于,连接挂起也会导致资源的浪费。
(3)SSE
SSE是HTML5新增的功能,全称为Server-Sent Events。它可以允许服务推送数据到客户端。SSE在本质上就与之前的长轮询、短轮询不同,虽然都是基于http协议的,但是轮询需要客户端先发送请求。而SSE最大的特点就是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。
SSE的优势很明显,它不需要建立或保持大量的客户端发往服务器端的请求,节约了很多资源,提升应用性能。并且后面会介绍道,SSE的实现非常简单,并且不需要依赖其他插件。
(4)WebSocket
WebSocket是HTML5定义的一个新协议,与传统的http协议不同,该协议可以实现服务器与客户端之间全双工通信。简单来说,首先需要在客户端和服务器端建立起一个连接,这部分需要http。连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。
WebSocket的优点是实现了双向通信,缺点是服务器端的逻辑非常复杂。现在针对不同的后台语言有不同的插件可以使用。
3.四种Web即时通信技术比较
从兼容性角度考虑,短轮询>长轮询>长连接SSE>WebSocket;
从性能方面考虑,WebSocket>长连接SSE>长轮询>短轮询
二、短轮询
1.原理
短轮询利用http的请求/响应模式,通过每隔一定时间向服务器发送请求来获得服务器数据的更新。在开发过程中,可以利用ajax,然后通过setInterval()方法,实现每隔一段时间向服务器发送请求的功能。
2.实现
后端以php为例进行实现。
客户端代码:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>短轮询ajax实现</title> <script type="text/javascript" src="../jquery.min.js"></script> </head> <body> <form id="form1" runat="server"> <div id="news"></div> </form> </body> <script type="text/javascript"> function showUnreadNews(){ $(document).ready(function() { //使用jquery的ajax方法 $.ajax({ type: "GET", url: “do.php", //请求发送到的后台php文件 dataType: "json", //请求成功的回调方法,遍历获取到的数据进行客户度界面的更新 success: function(msg) { $.each(msg, function(id, title) { $("#news").append("<a>" + title + "</a><br>"); }); } }); }); } //设置每2s执行一次showUnreadNews()方法,向服务器发送请求 setInterval('showUnreadNews()',2000); </script> </html>
服务器端代码(php):
<?php $arr = array('title'=>'新闻','text'=>'新闻内容'); //服务器端更新的数据 echo json_encode($arr); //返回的öson数据 ?>
三、comet
1.原理
comet与传统的ajax区别在于,客户端会与服务器端保持一个长连接,只有当客户端需要的数据更新时,服务器才会主动将数据推送给客户端。comet的实现有两种方式,一种方式是使用基于ajax的长轮询,另一种方式是基于Iframe及htmlfile的流方式。基于ajax的长轮询方式中,服务器端在接受到客户端ajax发送的请求后,不立即返回响应,而是阻塞请求直到超时或有数据更新。当服务器端在上述情况下返回响应后,客户端通过js再次发送请求建立连接,重复上述步骤。
2.实现
客户端代码:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>长轮询ajax实现</title> <script type="text/javascript" src="../jquery.min.js"></script> </head> <body> <input type="button" id="btn" value="click"> <div id="msg">数据情况</div> </body> <script type="text/javascript"> $(function(){ $("#btn").bind('click',{btn:$('#btn')},function(e){ $.ajax({ type: 'POST', dataType: 'json', url: 'ajax.php', timeout: '20000',//设置请求超时时间 data: {time: '2000000'},// 每次请求等待时间,将其传到后台用来挂起请求 success: function(data,status){ //对返回的数据进行判断和读取 if(data.success == '1'){ $("#msg").append('<br>[有数据]'+data.text); e.data.btn.click(); //再此发送请求 } // 未从服务器中获的数据 if(data.success == '0'){ $("#msg").append('<br>[无数据]'); e.data.btn.click(); } }, // ajax超时,继续查询 error:function(XMLHttpRequest,textStatus,errorThrown){ if(textStatus == "timeout"){ $("#msg").append('超时'); e.data.btn.click(); } } }); }); }); </script> </html>
服务器端代码(php):
<?php set_time_limit(0);// 无限请求超时时间 usleep($_POST['time’]);//通过前台传过来的数据设置挂起时间 while(true){ //无限循环 $rand = rand(1,999); if($rand < 500){ $arr = array('success'=>'1','name'=>'有值','text'=>$rand); echo json_encode($arr); exit(); }else{ $arr = array('success'=>'0','name'=>'无值','text'=>$rand); echo json_encode($arr); exit(); } } ?>
四、SSE
1.原理
SSE不需要依赖客户端向服务器发送请求,而是可以直接在服务器端有数据更新时进行发送到客户端,相比于轮询的“拉数据”,这种“推数据” 有着低延迟、高性能的优势。
这种方法的服务器端非常简介,只要维护一个服务器和客户端之间的协议即可。前端使用EventSource对象。
服务器端需要提供的协议基本代码如下:
data: first event
data: second event
id: 100
event: myevent
data: third event
id: 101
: this is a comment
data: fourth event
data: fourth event continue
下面解释一下基本用法。要定义各个事件,每一个事件之间使用一个换行符隔开。每个事件内部可以有多行,每一行都是type:value的形式。type有以下集中选择:
(1)类型为空白,表示该行是注释,会在处理时被忽略。
(2)类型为 data,表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
(3)类型为 event,表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。
(4)类型为 id,表示该行用来声明事件的标识符。
(5)类型为 retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。
比如上面的第一个事件,只传输了一个数据,数据内容为first event。服务器端通过这个清单发送到客户端,就可以通过前端进行响应的处理,诸如读取新数据、更新界面等。
客户端需要在JavaScript中使用EventSource对象。
首先需要初始化一个EventSource对象,实例化的时候需要传入与其交互的服务器端的文件地址,如:
var es = new EventSource(“sse.php”);
接下来,可以对进行事件的监听。EventSource给出了三种标准事件,它们的名称和触发时机如下表:
open 当成功与服务器建立连接时执行
message 当收到服务器发送的事件时执行
error 当出现错误时执行
和普通的事件一样,可以通过以下两种方法使用这些事件:
es.onmessage = function(e){};
es.addEventListener(“message”,function(e){});
2.实现
服务器端代码(php):
<?php header('Content-Type: text/event-stream'); //这是专门为sse设置的数据格式 $time = date('Y-m-d H:i:s'); //下面这些echo出来的东西就是上面说的服务器端和客户端之间的协议 echo 'retry: 3000'.PHP_EOL; //retry类型的数据,规定了浏览器在连接断开之后进行再次连接之前的等待时间 echo 'data: The server time is: '.$time.PHP_EOL.PHP_EOL; ?>
注意必须要先设定content-type为text/event-stream,这是为SSE专门定义的数据传输格式。
接下来通过php的echo输出协议,上面的代码输出的结果如下:
retry:3000
data:The server time is...
输出了一个事件,这个事件中分别定义了retry类型和data类型的行。
客户端代码:
<html> <head> <meta charset="UTF-8"> <title>basic SSE test</title> </head> <body> <div id=”content”></div> </body> <script> var es = new EventSource("sse.php"); es.addEventListener("message",function(e){ document.getElementById("content").innerHTML += "\n"+e.data; }); </script> </html>
上面的代码首先实例化了一个EventSource对象,并传入与之通信的服务器端文件sse.php。利用addEventListener()方法,为对象绑定一个message事件(上面提到message在收到服务器发送的事件时执行)。当客户端收到服务器传来的协议时,为页面中添加数据,通过e.data获取,对应了服务器端文件中协议表的data类型定义的数据,即The server time is...。
五、WebSocket
1.原理
WebSocket的实现了一次连接,双方通信的功能。首先由客户端发出WebSocket请求,服务器端进行响应,实现类似TCP握手的动作。这个连接一旦建立起来,就保持在客户端和服务器之间,两者之间可以直接的进行数据的互相传送。服务器端的逻辑比较复杂,如果是java或者node开发,都有很多封装好的组件可以使用。
2.前端API
(1)创建WebSocket对象
var ws = new WebSocket(“ws//localhost:8080”);
WebSocket是一个不同于HTTP的协议,其参数传递中的ws://前缀类似于http://,用于进行协议的声明。
(2)事件操作
WebSocket提供了四个事件操作,如下表:
onmessage 收到服务器响应时执行
onerroe 出现异常时执行
onopen 建立起连接时执行
onclose 断开连接时执行