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 相关文章推荐
js技巧--转义符&quot;\&quot;的妙用
Jan 09 Javascript
零基础学JavaScript最新动画教程+iso光盘下载
Jan 22 Javascript
JavaScript中valueOf函数与toString方法深入理解
Dec 02 Javascript
Extjs Gird 支持中文拼音排序实现代码
Apr 15 Javascript
js使用DOM设置单选按钮、复选框及下拉菜单的方法
Jan 20 Javascript
轻松实现javascript图片轮播特效
Jan 13 Javascript
js贪吃蛇游戏实现思路和源码
Apr 14 Javascript
js原生之焦点图转换加定时器实例
Dec 12 Javascript
利用JS实现scroll自定义滚动效果详解
Oct 17 Javascript
vue 使用外部JS与调用原生API操作示例
Dec 02 Javascript
Javascript组合继承方法代码实例解析
Apr 02 Javascript
适用于 Vue 的播放器组件Vue-Video-Player操作
Nov 16 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 遍历显示文件夹下所有目录、所有文件的函数,没有分页的代码
2008/11/14 PHP
PHP 数组遍历方法大全(foreach,list,each)
2010/06/30 PHP
php+mysql实现简单的增删改查功能
2015/07/13 PHP
php session 写入数据库
2016/02/13 PHP
javascript开发中因空格引发的错误
2010/11/08 Javascript
返回页面顶部top按钮通过锚点实现(自写)
2013/08/30 Javascript
document.getElementById获取控件对象为空的解决方法
2013/11/20 Javascript
jQuery调用RESTful WCF示例代码(GET方法/POST方法)
2014/01/26 Javascript
jquery日历控件实现方法分享
2014/03/07 Javascript
javascript实现tab响应式切换特效
2016/01/29 Javascript
javascript cookie基础应用之记录用户名的方法
2016/09/20 Javascript
jQuery通过ajax方法获取json数据不执行success的原因及解决方法
2016/10/15 Javascript
JavaScript中校验银行卡号的实现代码
2016/12/19 Javascript
es6系列教程_ Map详解以及常用api介绍
2017/09/25 Javascript
AngularJS实现的简单拖拽功能示例
2018/01/02 Javascript
红黑树的插入详解及Javascript实现方法示例
2018/03/26 Javascript
深入浅析angular和vue还有jquery的区别
2018/08/13 jQuery
node版本管理工具n包使用教程详解
2018/11/09 Javascript
开发Node CLI构建微信小程序脚手架的示例
2020/03/27 Javascript
js实现特别简单的钟表效果
2020/09/14 Javascript
Python中用pycurl监控http响应时间脚本分享
2015/02/02 Python
Python网络爬虫与信息提取(实例讲解)
2017/08/29 Python
Python使用pip安装pySerial串口通讯模块
2018/04/20 Python
python日期与时间戳的各种转换示例
2020/02/12 Python
浅谈Python里面None True False之间的区别
2020/07/09 Python
Python+OpenCV图像处理——实现直线检测
2020/10/23 Python
BASIC HOUSE官方旗舰店:韩国著名的服装品牌
2018/09/27 全球购物
EJB需直接实现它的业务接口或Home接口吗,请简述理由
2016/11/23 面试题
护士试用期自我鉴定
2014/02/08 职场文书
医院合作协议书
2014/08/19 职场文书
感恩老师演讲稿600字
2014/08/28 职场文书
2014年教师节演讲稿范文
2014/09/10 职场文书
2015年上半年信访工作总结
2015/03/30 职场文书
2016年幼儿园庆六一开幕词
2016/03/04 职场文书
CSS浮动引起的高度塌陷问题
2022/08/05 HTML / CSS
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers