WebSocket的简单介绍及应用


Posted in Javascript onMay 23, 2019

定时刷新的不足与改进

web开发中可能遇到这样的场景:网页里的某一块区域里写了一些内容,但这些内容不是固定的,即使看网页的人没有做任何操作,它们也会随时间不断变化。股票行情、活动或游戏的榜单都是比较常见的例子。

对此,一般的做法是用setTimeout()或setInverval()定时执行任务,任务内容是Ajax访问一次服务器,并在成功拿到返回数据后去更新页面。

这种定时刷新的做法会有这样一些感觉不足的地方:

  • 频繁的定时网络请求对浏览器(客户端)和服务器来说都是一种负担,尤其是当网页里有多个定时刷新区域的时候。
  • 某几次的定时任务可能是不必要的,因为服务器可能并没有新数据,还是返回了和上一次一样的内容。
  • 页面内容可能不够新,因为服务器可能刚更新了数据,但下一轮定时任务还没有开始。

造成这些不足的原因归结起来,主要还是由于服务器的响应总是被动的。HTTP协议限制了一次通信总是由客户端发起请求,再由服务器端来返回响应。

因此,如果让服务器端也可以主动发送信息到客户端,就可以很大程度改进这些不足。WebSocket就是一个实现这种双向通信的新协议。

WebSocket是基于HTTP的功能追加协议

WebSocket最初由html5提出,但现在已经发展为一个独立的协议标准。WebSocket可以分为协议(Protocol)和API两部分,分别由IETF和W3C制定了标准。

先来看看WebSocket协议的建立过程。

为了实现WebSocket通信,首先需要客户端发起一次普通HTTP请求(也就是说,WebSocket的建立是依赖HTTP的)。请求报文可能像这样:

GET ws://websocket.example.com/ HTTP/1.1
Host: websocket.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com
Sec-WebSocket-Key:pAloKxsGSHtpIHrJdWLvzQ==
Sec-WebSocket-Version:13

其中HTTP头部字段Upgrade: websocket和Connection: Upgrade很重要,告诉服务器通信协议将发生改变,转为WebSocket协议。支持WebSocket的服务器端在确认以上请求后,应返回状态码为101 Switching Protocols的响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: nRu4KAPUPjjWYrnzxDVeqOxCvlM=

其中字段Sec-WebSocket-Accept是由服务器对前面客户端发送的Sec-WebSocket-Key进行确认和加密后的结果,相当于一次验证,以帮助客户端确信对方是真实可用的WebSocket服务器。

验证通过后,这个握手响应就确立了WebSocket连接,此后,服务器端就可以主动发信息给客户端了。此时的状态比较像服务器端和客户端接通了电话,无论是谁有什么信息想告诉对方,开口就好了。

一旦建立了WebSocket连接,此后的通信就不再使用HTTP了,改为使用WebSocket独立的数据帧(这个帧有办法看到,见后文)。

整个过程像这样:

WebSocket的简单介绍及应用

简单的应用示例

应用WebSocket有这样几件事要做:

  • 选用支持WebSocket的浏览器。
  • 网页内添加创建WebSocket的代码。
  • 服务器端添加使用WebSocket通信的代码。

服务器端

以Node的服务器为例,我们使用ws这个组件,这样搭建一个支持WebSocket的服务器端:

var request = require("request");
var dateFormat = require("dateformat");
var WebSocket = require("ws"),
 WebSocketServer = WebSocket.Server,
 wss = new WebSocketServer({
 port: 8080,
 path: "/guest"
 });

// 收到来自客户端的连接请求后,开始给客户端推消息
wss.on("connection", function(ws) {
 ws.on("message", function(message) {
 console.log("received: %s", message);
 });
 sendGuestInfo(ws);
});

function sendGuestInfo(ws) {
 request("http://uinames.com/api?region=china",
 function(error, response, body) {
  if (!error && response.statusCode === 200) {
  var jsonObject = JSON.parse(body),
   guest = jsonObject.name + jsonObject.surname,
   guestInfo = {
   guest: guest,
   time: dateFormat(new Date(), "HH:MM:ss")
   };

  if (ws.readyState === WebSocket.OPEN) {

   // 发,送
   ws.send(JSON.stringify(guestInfo));

   // 用随机来“装”得更像不定时推送一些
   setTimeout(function() {
   sendGuestInfo(ws);
   }, (Math.random() * 5 + 3) * 1000);
  }
  }
 });
}

这个例子使用了姓名生成站点uinames的API服务,来生成{guest: "人名", time: "15:26:01"}这样的数据。函数sendGuestInfo()会不定时执行,并把包含姓名和时间的信息通过send()方法发送给客户端。另外,注意send()方法需要以字符串形式来发送json数据。

这就像是服务器自己在做一些事,然后在需要的时候会通知客户端一些信息。

客户端

客户端我们使用原生javascript来完成(仅支持WebSocket的浏览器):

var socket = new WebSocket("ws://localhost:8080/guest");

socket.onopen = function(openEvent) {
 console.log("WebSocket conntected.");
};

socket.onmessage = function(messageEvent) {
 var data = messageEvent.data,
 dataObject = JSON.parse(data);
 console.log("Guest at " + dataObject.time + ": " + dataObject.guest);
};

socket.onerror = function(errorEvent) {
 console.log("WebSocket error: ", errorEvent);
};

socket.onclose = function(closeEvent) {
 console.log("WebSocket closed.");
};

WebSocket的URL格式是ws://与wss://。因此,需要注意下URL地址的写法,这也包括注意WebSocket服务器端的路径(如这里的/guest)等信息。因为是本地的示例所以这里是localhost。

客户端代码的流程很简单:创建WebSocket对象,然后指定onopen、onmessage等事件的回调即可。其中onmessage是客户端与服务器端通过WebSocket通信的关键事件,想要在收到服务器通知后做点什么,写在onmessage事件的回调函数里就好了。

效果及分析

通过node server(假定服务器端的文件名为server.js)启动WebSocket服务器后,用浏览器打开一个引入了前面客户端代码的html(直接文件路径file:///就可以),就可以得到像这样的结果:

WebSocket的简单介绍及应用

联系前面客户端的代码可以想到,实际从创建WebSocket对象的语句开始,连接请求就会发送,并很快建立起WebSocket连接(不出错误的话),此后就可以收到来自服务器端的通知。如果此时客户端还想再告诉服务器点什么,这样做:

socket.send("Hello, server!");

服务器就可以收到:

WebSocket的简单介绍及应用

当然,这也是因为前面服务器端的代码内同样设置了message事件的回调。在这个客户端和服务器都是javascript的例子中,无论是服务器端还是客户端,都用send()发送信息,都通过message事件设置回调,形式上可以说非常一致。

其他可用的数据类型

WebSocket的send()可以发送的消息,除了前面用的字符串类型之外,还有两种可用,它们是Blob和ArrayBuffer。

它们都代表二进制数据,可用于原始文件数据的发送。比如,这是一个发送Blob类型数据以完成向服务器上传图片的例子:

var fileEl = document.getElementById("image_upload");
var file = fileEl.files[0];
socket.send(file);

然后服务器端可以这样把文件保存下来:

var fs = require("fs");

wss.on("connection", function(ws) {
 ws.on("message", function(message) {
 fs.writeFile("upload.png", message, "binary", function(error) {
  if (!error) {
  console.log("File saved.");
  }
 });
 });
});

在客户端接收二进制数据时,需注意WebSocket对象有一个属性binaryType,初始值为"blob"。因此,如果接收的二进制数据是ArrayBuffer,应在接收之前这样做:

socket.binaryType = "arraybuffer";

其他WebSocket服务器端

其他语言来做WebSocket服务器是怎样的呢?下面是一个php的WebSocket服务器的例子(使用Ratchet):

<?php
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;

require __DIR__ . '/vendor/autoload.php';

class GuestServer implements MessageComponentInterface {

 public function onOpen(ConnectionInterface $conn) {
 $conn->send('The server is listening to you now.');
 }

 public function onMessage(ConnectionInterface $conn, $msg) {
 $conn->send($this->generateGuestInfo());
 }

 public function onClose(ConnectionInterface $conn) {
 }

 public function onError(ConnectionInterface $conn, \Exception $e) {
 $conn->close();
 }

 private function generateGuestInfo() {
 $jsonString = file_get_contents('http://uinames.com/api?region=china');
 $jsonObject = json_decode($jsonString, true);
 $guest = $jsonObject['name'] . $jsonObject['surname'];
 $guestInfo = array(
  'guest' => $guest,
  'time' => date('H:i:s', time()),
 );

 return json_encode($guestInfo);
 }
}

$app = new Ratchet\App('localhost', 8080);
$app->route('/guest', new GuestServer(), array('*'));
$app->run();
?>

这个例子也同样是由服务器返回{guest: "人名", time: "15:26:01"}的json数据,不过由于php不像Node那样可以用setTimeout()很容易地实现异步定时任务,这里改为在客户端发送一次任意信息后,再去uinames取得信息并返回。

也可以看到,php搭建的WebSocket服务器仍然是近似的,主要通过WebSocket的open、message等事件来实现功能。

在Chrome开发工具中查看WebSocket数据帧

Chrome开发工具中选择Network,然后找到WebSocket的那个请求,里面可以选择Frames。在Frames里看到的,就是WebSocket的数据帧了:

WebSocket的简单介绍及应用

可以看到很像聊天记录,其中用浅绿色标注的是由客户端发送给服务器的部分。

结语

总的来说,把服务器和客户端拉到了一个聊天窗口来办事,这确实是很棒的想法。

即使只从形式上说,WebSocket的事件回调感觉也比定时任务用起来要更亲切一些。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript 脚本将当地时间转换成其它时区
Mar 19 Javascript
JS获取几种URL地址的方法小结
Feb 26 Javascript
使用jQuery不判断浏览器高度解决iframe自适应高度问题
Dec 16 Javascript
jquery.validate提示错误信息位置方法
Jan 22 Javascript
JavaScript数据操作_浅谈原始值和引用值的操作本质
Aug 23 Javascript
JavaScript实现图片轮播组件代码示例
Nov 22 Javascript
JS实现table表格固定表头且表头随横向滚动而滚动
Oct 26 Javascript
解决使用vue.js路由后失效的问题
Mar 17 Javascript
JavaScript之数组扁平化详解
Jun 03 Javascript
详细分析React 表单与事件
Jul 08 Javascript
vue 中的动态传参和query传参操作
Nov 09 Javascript
详解JavaScript的计时器和按钮效果设置
Feb 18 Javascript
webpack4 SplitChunks实现代码分隔详解
May 23 #Javascript
微信小程序实现的picker多级联动功能示例
May 23 #Javascript
js console.log打印对象时属性缺失的解决方法
May 23 #Javascript
Node.js+ELK日志规范的实现
May 23 #Javascript
jquery+php后台实现省市区联动功能示例
May 23 #jQuery
js尾调用优化的实现
May 23 #Javascript
浅谈redux, koa, express 中间件实现对比解析
May 23 #Javascript
You might like
解决php中Cannot send session cache limiter 的问题的方法
2007/04/27 PHP
smarty模板引擎中自定义函数的方法
2015/01/22 PHP
php 生成加密公钥加密私钥实例详解
2017/06/16 PHP
PHP网页安全认证的实例详解
2017/09/28 PHP
javascript 兼容FF的onmouseenter和onmouseleave的代码
2008/07/19 Javascript
jQuery的运行机制和设计理念分析
2011/04/05 Javascript
Javascript 八进制转义字符(8进制)
2011/04/08 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
2013/12/16 Javascript
javascript实现滑动解锁功能
2014/12/31 Javascript
JQuery给网页更换皮肤的方法
2015/05/30 Javascript
Bootstrap按钮下拉菜单组件详解
2016/05/10 Javascript
ES6概念 ymbol.for()方法
2016/12/25 Javascript
浅谈JavaScript的函数及作用域
2016/12/30 Javascript
Vue.js实现分页查询功能
2020/11/15 Javascript
JS运动特效之链式运动分析
2018/01/24 Javascript
浅析vue.js数组的变异方法
2018/06/30 Javascript
vue.js 双层嵌套for遍历的方法详解, 类似php foreach()
2018/09/07 Javascript
基于JS实现web端录音与播放功能
2019/04/17 Javascript
在vue+element ui框架里实现lodash的debounce防抖
2019/11/13 Javascript
three.js利用gpu选取物体并计算交点位置的方法示例
2019/11/25 Javascript
Javascript数组及类数组相关原理详解
2020/10/29 Javascript
[04:52]DOTA2亚洲邀请赛附加赛 TOP10精彩集锦
2015/01/29 DOTA
[01:20:38]完美世界DOTA2联赛 GXR vs IO 第一场 11.07
2020/11/09 DOTA
Python中你应该知道的一些内置函数
2017/03/31 Python
Python基于正则表达式实现文件内容替换的方法
2017/08/30 Python
Python进程间通信之共享内存详解
2017/10/30 Python
python安装requests库的实例代码
2019/06/25 Python
HTML5 3D旋转相册的实现示例
2019/12/03 HTML / CSS
网络艺术零售业的先驱者:artrepublic
2017/09/26 全球购物
警察思想汇报
2014/01/04 职场文书
小学生综合素质评语
2014/04/23 职场文书
医生个人自我剖析材料
2014/10/08 职场文书
2015年建党94周年演讲稿
2015/03/19 职场文书
聘任合同书
2015/09/21 职场文书
合作意向书范本
2019/04/17 职场文书
利用python做表格数据处理
2021/04/13 Python