websocket网络层详解_WebSocket 详解

WebSocket 出现前

构建网络应用的过程中,我们经常需要与服务器进行持续的通讯以保持双方信息的同步。通常这种持久通讯在不刷新页面的情况下进行,消耗一定的内存资源常驻后台,并且对于用户不可见。在 WebSocket 出现之前,我们有以下解决方案:

传统轮询(Traditional Polling)

当前Web应用中较常见的一种持续通信方式,通常采取 setInterval 或者 setTimeout 实现。例如如果我们想要定时获取并刷新页面上的数据,可以结合Ajax写出如下实现:

setInterval(function() {

$.get("/path/to/server", function(data, status) {

console.log(data);

});

}, 10000);

上面的程序会每隔10秒向服务器请求一次数据,并在数据到达后存储。这个实现方法通常可以满足简单的需求,然而同时也存在着很大的缺陷:在网络情况不稳定的情况下,服务器从接收请求、发送请求到客户端接收请求的总时间有可能超过10秒,而请求是以10秒间隔发送的,这样会导致接收的数据到达先后顺序与发送顺序不一致。于是出现了采用 setTimeout 的轮询方式:

function poll() {

setTimeout(function() {

$.get("/path/to/server", function(data, status) {

console.log(data);

// 发起下一次请求

poll();

});

}, 10000);

}

程序首先设置10秒后发起请求,当数据返回后再隔10秒发起第二次请求,以此类推。这样的话虽然无法保证两次请求之间的时间间隔为固定值,但是可以保证到达数据的顺序。

长轮询(Long Polling)

上面两种传统的轮询方式都存在一个严重缺陷:程序在每次请求时都会新建一个HTTP请求,然而并不是每次都能返回所需的新数据。当同时发起的请求达到一定数目时,会对服务器造成较大负担。这时我们可以采用长轮询方式解决这个问题。

长轮询与以下将要提到的服务器发送事件和WebSocket不能仅仅依靠客户端JavaScript实现,我们同时需要服务器支持并实现相应的技术。

长轮询的基本思想是在每次客户端发出请求后,服务器检查上次返回的数据与此次请求时的数据之间是否有更新,如果有更新则返回新数据并结束此次连接,否则服务器 hold住此次连接,直到有新数据时再返回相应。而这种长时间的保持连接可以通过设置一个较大的HTTP timeout` 实现。下面是一个简单的长连接示例:

服务器(PHP):

// 示例数据为data.txt

$filename= dirname(__FILE__)."/data.txt";

// 从请求参数中获取上次请求到的数据的时间戳

$lastmodif = isset( $_GET["timestamp"])? $_GET["timestamp"]: 0 ;

// 将文件的最后一次修改时间作为当前数据的时间戳

$currentmodif = filemtime($filename);

// 当上次请求到的数据的时间戳*不旧于*当前文件的时间戳,使用循环"hold"住当前连接,并不断获取文件的修改时间

while ($currentmodif <= $lastmodif) {

// 每次刷新文件信息的时间间隔为10秒

usleep(10000);

// 清除文件信息缓存,保证每次获取的修改时间都是最新的修改时间

clearstatcache();

$currentmodif = filemtime($filename);

}

// 返回数据和最新的时间戳,结束此次连接

$response = array();

$response["msg"] =Date("h:i:s")." ".file_get_contents($filename);

$response["timestamp"]= $currentmodif;

echo json_encode($response);

?>

客户端:

function longPoll (timestamp) {

var _timestamp;

$.get("/path/to/server?timestamp=" + timestamp)

.done(function(res) {

try {

var data = JSON.parse(res);

console.log(data.msg);

_timestamp = data.timestamp;

} catch (e) {}

})

.always(function() {

setTimeout(function() {

longPoll(_timestamp || Date.now()/1000);

}, 10000);

});

}

长轮询可以有效地解决传统轮询带来的带宽浪费,但是每次连接的保持是以消耗服务器资源为代价的。尤其对于Apache+PHP 服务器,由于有默认的 worker threads 数目的限制,当长连接较多时,服务器便无法对新请求进行相应。

服务器发送事件(Server-Sent Event)

服务器发送事件(以下简称SSE)是HTML 5规范的一个组成部分,可以实现服务器到客户端的单向数据通信。通过 SSE ,客户端可以自动获取数据更新,而不用重复发送HTTP请求。一旦连接建立,“事件”便会自动被推送到客户端。服务器端SSE通过 事件流(Event Stream) 的格式产生并推送事件。事件流对应的 MIME类型 为 text/event-stream ,包含四个字段:event、data、id和retry。event表示事件类型,data表示消息内容,id用于设置客户端 EventSource 对象的 last event ID string 内部属性,retry指定了重新连接的时间。

服务器(PHP):

header("Content-Type: text/event-stream");

header("Cache-Control: no-cache");

// 每隔1秒发送一次服务器的当前时间

while (1) {

$time = date("r");

echo "event: ping\n";

echo "data: The server time is: {$time}\n\n";

ob_flush();

flush();

sleep(1);

}

?>

客户端中,SSE借由 EventSource 对象实现。EventSource 包含五个外部属性:onerror, onmessage, onopen, readyState、url,以及两个内部属性:reconnection time与 last event ID string。在onerror属性中我们可以对错误捕获和处理,而 onmessage 则对应着服务器事件的接收和处理。另外也可以使用 addEventListener 方法来监听服务器发送事件,根据event字段区分处理。

客户端:

var eventSource = new EventSource("/path/to/server");

eventSource.onmessage = function (e) {

console.log(e.event, e.data);

}

// 或者

eventSource.addEventListener("ping", function(e) {

console.log(e.event, e.data);

}, false);

SSE相较于轮询具有较好的实时性,使用方法也非常简便。然而SSE只支持服务器到客户端单向的事件推送,而且所有版本的IE(包括到目前为止的Microsoft Edge)都不支持SSE。如果需要强行支持IE和部分移动端浏览器,可以尝试 EventSource Polyfill(本质上仍然是轮询)。SS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值