欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WebSocket的简单介绍及应用

程序员文章站 2023-12-19 20:31:10
定时刷新的不足与改进 web开发中可能遇到这样的场景:网页里的某一块区域里写了一些内容,但这些内容不是固定的,即使看网页的人没有做任何操作,它们也会随时间不断变化。股...

定时刷新的不足与改进

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(直接文件路径就可以),就可以得到像这样的结果:

WebSocket的简单介绍及应用

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

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

服务器就可以收到:

WebSocket的简单介绍及应用

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

其他可用的数据类型

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

它们都代表二进制数据,可用于原始文件数据的发送。比如,这是一个发送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的事件回调感觉也比定时任务用起来要更亲切一些。

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

上一篇:

下一篇: