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 相关文章推荐
html数组字符串拼接的最快方法
Sep 16 Javascript
node.js中的buffer.fill方法使用说明
Dec 14 Javascript
js实现屏幕自适应局部代码分享
Jan 30 Javascript
用Move.js配合创建CSS3动画的入门指引
Jul 22 Javascript
JS如何设置iOS中微信浏览器的title
Nov 22 Javascript
AngularJS指令与指令之间的交互功能示例
Dec 14 Javascript
浅谈struts1 &amp; jquery form 文件异步上传
May 25 jQuery
Express进阶之log4js实用入门指南
Feb 10 Javascript
js实现鼠标滑动到某个div禁止滚动
Sep 17 Javascript
基于JavaScript实现简单扫雷游戏
Jan 02 Javascript
详解CocosCreator项目结构机制
Apr 14 Javascript
Vue监视数据的原理详解
Feb 24 Vue.js
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正确配置mysql(apache环境)
2011/08/28 PHP
php 记录进行累加并显示总时长为秒的结果
2011/11/04 PHP
php将textarea数据提交到mysql出现很多空格的解决方法
2014/12/19 PHP
隐性调用php程序的方法
2015/06/13 PHP
yii2.0整合阿里云oss删除单个文件的方法
2017/09/19 PHP
基于jQuery实现的双11天猫拆红包抽奖效果
2015/12/01 Javascript
JavaScript encodeURI 和encodeURIComponent
2015/12/04 Javascript
基于jQuery实现返回顶部实例代码
2016/01/01 Javascript
JavaScript实现输入框与清空按钮联动效果
2016/09/09 Javascript
深入理解Node.js的HTTP模块
2016/10/12 Javascript
基于Bootstrap漂亮简洁的CSS3价格表(附源码下载)
2017/02/28 Javascript
vue和react等项目中更简单的实现展开收起更多等效果示例
2018/02/22 Javascript
angularJs在多个控制器中共享服务数据的方法
2018/09/30 Javascript
Vue仿微信app页面跳转动画效果
2019/08/21 Javascript
Javascript ParentNode和ChildNode接口原理解析
2020/03/16 Javascript
Vuex的各个模块封装的实现
2020/06/05 Javascript
浅谈Python基础之I/O模型
2017/05/11 Python
Ubuntu下升级 python3.7.1流程备忘(推荐)
2018/12/10 Python
Python实现决策树并且使用Graphviz可视化的例子
2019/08/09 Python
Pytorch 实现sobel算子的卷积操作详解
2020/01/10 Python
python下对hsv颜色空间进行量化操作
2020/06/04 Python
详解Django自定义图片和文件上传路径(upload_to)的2种方式
2020/12/01 Python
利用CSS3的checked伪类实现OL的隐藏显示的方法
2010/12/18 HTML / CSS
详解window.open被浏览器拦截的解决方案
2019/07/18 HTML / CSS
美国家居装饰网上商店:Lulu & Georgia
2019/09/14 全球购物
体育专业学生自我评价范文
2014/01/17 职场文书
幼儿园数学教学反思
2014/02/02 职场文书
计算机科学系职业生涯规划书
2014/03/08 职场文书
小学毕业演讲稿
2014/04/25 职场文书
羊脂球读书笔记
2015/06/30 职场文书
教师节随笔
2015/08/15 职场文书
2016年小学圣诞节活动总结
2016/03/31 职场文书
python第三方网页解析器 lxml 扩展库与 xpath 的使用方法
2021/04/06 Python
Pytorch 如何实现LSTM时间序列预测
2021/05/17 Python
在vue中import()语法不能传入变量的问题及解决
2022/04/01 Vue.js
SpringBoot详解自定义Stater的应用
2022/07/15 Java/Android