WebSocket的简单介绍及应用
作者:庭院茶 发布时间:2024-04-23 09:20:09
定时刷新的不足与改进
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通信的代码。
服务器端
以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连接(不出错误的话),此后就可以收到来自服务器端的通知。如果此时客户端还想再告诉服务器点什么,这样做:
socket.send("Hello, server!");
服务器就可以收到:
当然,这也是因为前面服务器端的代码内同样设置了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的事件回调感觉也比定时任务用起来要更亲切一些。
来源:http://acgtofe.com/posts/2016/03/websocket-how-to


猜你喜欢
- 1. 为什么要备份数据先说一下为什么需要备份MySQL数据?一句话总结就是:为了保证数据的安全性。如果我们把数据只存储在一个地方,如果物理机
- MySQL 一级防范检查列表以下是加固你的 Mysql 服务器安全所要做的工作的重要参考:Securing MySQL: step-by-s
- 下面基础的解释一下这错误: 1:本质上的错误: object a;//a是Null对象 protected void Page_Load(o
- CSS浮动一直是个比较让人郁闷的问题,很多的布局问题都出在浮动上,特别是当浮动的列数很多时,但其实只要理解了两列结构的浮动,面对多列数的浮动
- 什么是约束作用于表上的规则,限制存储在表中的数据约束分类:约束描述关键字非空约束该字段数据不能为nullNOT NULL唯一约束该字段数据唯
- 1. 整体思路首先我们来梳理下整体上的实现思路,首先一点:整体思路和 vhr 一模一样。考虑到有的小伙伴可能已经忘记 vhr 中前端动态菜单
- 如下所示:Numpy中reshape的使用方法为:numpy.reshape(a, newshape, order='C')
- 本文实例讲述了PHP设计模式:装饰器模式Decorator。分享给大家供大家参考,具体如下:1. 概述  
- 一、集合的基本信息集合:集合是无序的,集合中的元素是唯一的,集合一般用于元组或者列表中的元素去重。格式:set1 = set()或a={值1
- 微信好友全头像话不多说,直接上代码import itchatimport mathimport PIL.Image as Imageimpo
- 在做深度学习的目标检测中,我们在测试好数据集后,可以用批量检查两个数据文件下面的标签文件,看看他们的行数是否一致,这样可以判断是否有漏检的情
- 通常,由于类别不均衡,需要使用weighted cross entropy loss平衡。def inverse_freq(label):
- 计数器(Counter)是一个非常常用的功能组件,这篇blog以未读消息数为例,介绍了在 Django中实现一个高性能计数器的基本要点。故事
- 本文为大家分享了mysql8.0.11客户端无法登陆的解决方法,供大家参考,具体内容如下mysql8.0.11 默认加密方式【caching
- 年前接到QCon的邀请,颇感意外。在我的印象里,QCon大会是后端开发工程师和架构师的技术大会。后来去QCon大会的官网搜索了下,发现原来Q
- 附上官网地址:https://pytorch.org/docs/stable/index.html1.torch.squeezesqueez
- --分页存储过程示例 Alter PROCEDURE [dbo].[JH_PageDemo] @pageSize int = 9000000
- 计算表达式:1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/
- 发现问题python嵌套列表大家应该都不陌生,但最近遇到了一个问题,这是工作中遇到的一个坑,首先看一下问题raw_list = [[&quo
- mysql出现ERROR : (2006, 'MySQL server has gone away') 的问题意思就是指cl