分享

WebSocket的简单介绍及应用

 开启美好每一天 2016-03-21

定时刷新的不足与改进

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: websocketConnection: 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有这样几件事要做:

服务器端

以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:///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 对象,然后指定 onopenonmessage 等事件的回调即可。其中 onmessage 是客户端与服务器端通过WebSocket通信的关键事件,想要在收到服务器通知后做点什么,写在 onmessage 事件的回调函数里就好了。

效果及分析

通过 node server (假定服务器端的文件名为 server.js )启动WebSocket服务器后,用浏览器打开一个引入了前面客户端代码的html(直接文件路径 file:/// 就可以),就可以得到像这样的结果:

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

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

服务器就可以收到:

当然,这也是因为前面服务器端的代码内同样设置了 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:///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的 openmessage 等事件来实现功能。

在Chrome开发工具中查看WebSocket数据帧

Chrome开发工具中选择Network,然后找到WebSocket的那个请求,里面可以选择Frames。在Frames里看到的,就是WebSocket的数据帧了:

可以看到很像聊天记录,其中用浅绿色标注的是由客户端发送给服务器的部分。

结语

总的来说,把服务器和客户端拉到了一个聊天窗口来办事,这确实是很棒的想法。

即使只从形式上说,WebSocket的事件回调感觉也比定时任务用起来要更亲切一些。

============================================

WebSocket

编辑 锁定
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。
外文名
WebSocket
解    释
HTML5一种新的协议
优    点
服务器可以主动传送数据给客户端
功    能
实现了浏览器与服务器全双工通信

背景

编辑
在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模 拟双向通信,但效率较低,并需要服务器有较好的支持; flash中的socket和xmlsocket可以实现真正的双向通信,通过 flex ajax bridge,可以在javascript中使用这两项功能. 可以预见,如果websocket一旦在浏览器中得到实现,将会替代上面两项技术,得到广泛的使用.面对这种状况,HTML5定义了WebSocket协 议,能更好的节省服务器资源和带宽并达到实时通讯。
在JavaEE7中也实现了WebSocket协议。

原理

编辑
WebSocket protocol 。
现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮 询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。
而比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求。
在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
1. Header
互相沟通的Header是很小的-大概只有 2 Bytes
2. Server Push

握手协议

编辑
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” (handshaking)。
PS1:握手协议在后期的版本中,会标明版本编号,下面的例子属于早期的协定之一,对于新版的 chrome 和 Firefox 皆不适用。
PS2:后期的版本大多属于功能上的扩充,例如使用第7版的握手协议同样也适用于第8版的握手协议。
例子:
浏览器请求
GET /demo HTTP/1.1
Host: 你的网址.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Upgrade: WebSocket
Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
Origin: http://你的网址.com
^n:ds[4U
//2010年之后的新版本websocket协议的Sec- WebSocket-Key只有一个,新的浏览只支持一个的,google的phpwebsocket用的是之前的协议所以不能直接运行通过,现在有个新 版本是基于这个协议的是php-websocket-server-1
服务器回应
HTTP/1.1 101
WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://你的网址.com
Sec-WebSocket-Location: ws://你的网址.com/demo
Sec-WebSocket-Protocol: sample
8jKS’y:G*Co,Wxa-

浏览器

编辑
实现了websocket的浏览器:
Chrome
Supported in version 4+
Firefox
Supported in version 4+
Internet Explorer
Supported in version 10+
Opera
Supported in version 10+
Safari
Supported in version 5+

服务器

编辑
在服务器端,也出现了一些实现websocket协议的项目:
jetty 7.0.1 包含了一个初步的实现
resin 包含有websocket 实现
pywebsocket, apache http server 扩展
apache tomcat 7.0.27 版本
Nginx 1.3.13 版本
jWebSocket java实现版
websocket api在浏览器端的广泛实现似乎只是一个时间问题了, 值得注意的是服务器端没有标准的api, 各个实现都有自己的一套api, 并且jcp也没有类似的提案, 所以使用websocket开发服务器端有一定的风险.可能会被锁定在某个平台上或者将来被迫升级.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多