视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
WebSocket的简单介绍及应用
2020-11-27 21:56:18 责编:小采
文档


定时刷新的不足与改进

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的事件回调感觉也比定时任务用起来要更亲切一些。

    下载本文
    显示全文
    专题