HTML5服务器端推送事件 解决PHP微信墙推送问题
问题描述
以前的文章中《 PHP微信墙制作,开源 》已经用PHP搭建了一个微信墙获取信息的服务器,然后我就在想推送技术应该怎么解决,上一篇已经用了.NET 的signalr做了一个微信墙,PHP一直没什么好思路,本来想用websocket,但自己写socket要解析websocket协议,PHP有一个比较好的实现是workerman,github地址请戳这里,但是都不是很中意,昨天给自己做官网的时候,看了一下MDN,浏览发现了这个东西“使用服务器发送事件”,突然就有了思路,今天可以试一下这个。
HTML5 服务器推送事件 简介
服务器推送事件(Server-sent Events)是 HTML 5 规范中的一个组成部分,可以用来从服务端实时推送数据到浏览器端。相对于与之类似的 COMET 和 WebSocket 技术来说,服务器推送事件的使用更简单,对服务器端的改动也比较小。对于某些类型的应用来说(比如我们现在做的这个微信墙),服务器推送事件是最佳的选择。
在介绍 HTML 5 服务器推送事件之前,首先介绍一些上面提到的几种服务器端数据推送技术。第一种是 WebSocket。WebSocket 规范是 HTML 5 中的一个重要组成部分,已经被很多主流浏览器所支持,也有不少基于 WebSocket 开发的应用。正如名称所表示的一样,WebSocket 使用的是套接字连接,基于 TCP 协议。使用 WebSocket 之后,实际上在服务器端和浏览器之间建立一个套接字连接,可以进行双向的数据传输。WebSocket 的功能是很强大的,使用起来也灵活,可以适用于不同的场景。不过 WebSocket 技术也比较复杂,包括服务器端和浏览器端的实现都不同于一般的 Web 应用。
除了 WebSocket 之外,其他的实现方式是基于 HTTP 协议来达到实时推送的效果。第一种做法是简易轮询,即浏览器端定时向服务器端发出请求,来查询是否有数据更新.另一种是 COMET 技术,他改进了简易轮询的缺点,使用的是长轮询。不过 COMET 技术的实现在服务器端和浏览器端都需要第三方库的支持。
综合比较上面提到的 4 种不同的技术,简易轮询由于其本身的缺陷,并不推荐使用。COMET 技术并不是 HTML 5 标准的一部分,从兼容标准的角度出发,也不推荐使用。WebSocket 规范和服务器推送技术都是 HTML 5 标准的组成部分,在主流浏览器上都提供了原生的支持,是推荐使用的。不过 WebSocket 规范更加复杂一些,适用于需要进行复杂双向数据通讯的场景。对于简单的服务器数据推送的场景,使用服务器推送事件就足够了。
服务器推送事件的例子请看这里传送门,我们下面就直接使用这个技术制作微信墙了
服务器端
在这里我们接着使用上几篇文章中写好的PHP服务器,github地址在这里传送门。首先我们要在原来项目基础上新建一个文件夹,用来放置推送服务的脚本以及微信墙的html页面,如下图的push文件夹
接着我们将上篇文章中signalR制作微信墙开源写好的微信墙的页面拿过来,去掉其中signalr部分,加入服务器推送事件的代码,如下
<script type="text/javascript">
var serverData;
window.onload = function () {
init();
}
function init() {
serverData = document.getElementById('serverData');
var es = new EventSource('PushtoClient.php');
es.onopen = openConnect;
es.onmessage = getMessage;
}
function openConnect(e) {
}
function getMessage(e) {
var data = $.parseJSON(e.data);
$("#UL").append('<li>\
<div class="single">\
<div class="pic">\
<img src="http://ouredaimage.qiniudn.com/touxiang.jpg">\
</div>\
<div class="message">\
<div class="s-name">\
<span>' + data.nick_name + '</span> :\
</div>\
<div class="s-word">' + data.content + '\
</div>\
</div>\
</div>\
</li>');
Slide();
$("#total-num").text(++total);
}
</script>
接着我们编写PHP脚本用来向页面推送需要上墙的信息,需要上墙的信息来自mongodb中的一个pushqueue的collection,这个队列表的信息由我们一会将创建的客户端提供,他从队列里面拿到一个数据推送出去后就会从队列里面删除这条数据,队列里面有数据就会一直发送,直到为空一直监听。代码如下
while(true){
header("Content-Type:text/event-stream");
$connection = new MongoClient( "mongodb://ip" );
$db = $connection->testwechat;
$coll = $db->pushqueue;
$cursor = $coll->findOne();
if($cursor!=null){
echo 'data:' .json_encode($cursor);
echo "\n\n";
ob_flush();
flush();
print_r($cursor);
$coll->remove(array("messageid",$curDate['messageid']));
sleep(1);
}
}
客户端
上面我们创建好了服务端的时候提到一个问题,就是上墙信息队列里面的信息是需要我们使用客户端审核后存放到pushqueue这个collection里面的,这里我们创建一个Laravel项目,由于业务比较简单,我们直接在routes.php里面写业务代码如下,里面的mongodb帮助类是我们之前在服务器端代码里面是用过的,直接copy过来
include __DIR__ . '/Mongo/MongoUtil.php';
Route::get('/', function () {
$mongoClient = new MongoUtil('testwechat');
$list = $mongoClient->getTop100('message');
return View::make('hello')->with('list', $list);
});
Route::get('add/{id}', function ($id) {
$mongoClient = new MongoUtil('testwechat');
$querydoc = array('_id' => new MongoId($id));
$queryres = $mongoClient->finone('message', $querydoc);
if($queryres!=null){
$doc = array(
'messageid' => $queryres['messageid'],
'fakeid' => $queryres['fakeid'],
'nick_name' => $queryres['nick_name'],
'content' => $queryres['content']
);
$mongoClient->insert('pushqueue', $doc);
}
return Redirect::to('/');
});
接下来我们在view下创建一个hello.blade.php页面,里面的内容就是我们曾经写好的服务器从微信官网爬取的数据,简单用bootstrap搭建的,代码如下
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel PHP Framework</title>
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.1/css/bootstrap-theme.css">
<script src="http://cdn.bootcss.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<nav class="navbar navbar-inverse" role="navigation">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">微上墙</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">微上墙</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">blog</a></li>
<li><a href="#">github</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="bs-callout bs-callout-info">
<h4>使用方法</h4>
<p>定期刷新可以获得新消息</p>
</div>
<div class="row">
<div class="col-md-12">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>名字</th>
<th>内容</th>
<th>上墙</th>
</tr>
</thead>
<tbody>
@foreach($list as $id => $value)
<tr>
<td>{{{ $value['nick_name']}}}</td>
<td>{{{ $value['content']}}}</td>
<td>{{{ $value['date_time']}}}</td>
<td><a href="./add/{{$value['_id']}}">上墙</a> </td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
到这里我们的工作就都做好了,开启写好的爬取服务器,推送服务器,和客户端,点击上墙 good!效果如下
这里明显有一个PushtoClient.PHP 的连接,其实这里面可以不用写一个死循环也可以达到同样的效果
总结
这个系列就到这里,演示了PHP.NET的推送,模拟http等方面,并做了一系列的实例,尤其是最后一个服务器推送事件,以前我也是不知道的,最近在翻看w3c和MDN的一些文档,也是在无意中发现这个让我还比较满意的方法,以前项目中只是使用过websocket,长轮询。另外这个服务器推送事件 可以添加一些事件,客户端做监听,是自己需要的事件才会接受消息,可以用来做类似组推的功能,不过压力测等还没有做,不过作微信墙是足够了。总算满足了我那个“用最合适的技术解决合适的问题”的小心思。最后放上github地址:微信上墙PHP ,微信上墙.NET版本