PHPSocket.IO开发一个实时消息推送系统

PHPSocket.IO开发一个实时消息推送系统

PHPSocket.IO开发一个实时消息推送系统

现代浏览器对websocket的支持越来越方便,我们开发一个及时的消息推送系统也很方便,那么如何通过http的方式向已连接websocket的用户进行消息实时推动,不必让用户主动去查找消息呢,今天我们来实现一个基于workman及phpsocketio开发的实时消息推送功能,通过http的方式向已经连接的用户实时推送消息,具体代码如下:

一、html5代码

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<style>
    @charset "utf-8";
body {
	margin:0px; padding:0px;
	font-family: Arial, Helvetica, sans-serif;
	background:url(/repeat.jpg);
	font-size:15px;
	color:#000;
}
ul{list-style:none; margin:0px; padding:0px; margin-top:20px;}
li{padding-bottom:20px;}
.sticky p, .floated p, .fixed p, .ondemand p{ float:left; padding:0px; margin:0px; margin-left:10px; line-height:45px; color:#fff; font-size:12px;}
.sticky a, .floated a, .fixed a, .ondemand a{ float:right; margin:13px 10px 0px 0px; }
img{border:0px;}
.wrapper{padding:20px;}

.sticky {

position:fixed;
top:0;
left:0;
z-index:1000;
	width:100%;
	border-bottom:3px solid #fff !important;

background: #91BD09; /* Old browsers */
background: -moz-linear-gradient(top, #91BD09 0%, #91BD09 100%); /* FF3.6+ */
	
	/* FireFox 3.6 */
	/* Safari4+, Chrome */
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#91BD09', endColorstr='#91BD09')";
	-pie-background: linear-gradient(#91BD09, #91BD09 100%);
	behavior: url(PIE.htc);
	-moz-box-shadow: 1px 1px 7px #676767;
	-webkit-box-shadow: 1px 1px 7px #676767;
	box-shadow: 1px 1px 7px #676767;
	height: 45px;
	background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #91BD09),color-stop(1, #91BD09));/* IE6,IE7 */
	/* IE8 */
	/* Firefox F3.5+ */
	/* Safari3.0+, Chrome */
}


.floated {

position:absolute;
top:0;
left:0;
z-index:1000;
	width:100%;
	border-bottom:3px solid #fff !important;
	background: #0e59ae; /* Old browsers */
background: -moz-linear-gradient(top, #0e59ae 0%, #0e59ae 100%); /* FF3.6+ */
	
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#0E59AE', endColorstr='#0E59AE')";
	
	-moz-box-shadow: 1px 1px 7px #676767;
	-webkit-box-shadow: 1px 1px 7px #676767;
	box-shadow: 1px 1px 7px #676767;
	height: 45px;
	background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #0E59AE),color-stop(1, #0E59AE));/* IE6,IE7 */
	-pie-background: linear-gradient(#0E59AE, #0E59AE 100%);
	behavior: url(PIE.htc);
}


.fixed {
    position:absolute;
	top:0;
	left:0;
	width:100%;
	border-bottom:3px solid #fff !important;

	background: #660099; /* Old browsers */
	background: -moz-linear-gradient(top, #660099 0%, #660099 100%); /* FF3.6+ */
	
	/* FireFox 3.6 */
	/* Safari4+, Chrome */
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#660099', endColorstr='#660099')";
	-pie-background: linear-gradient(#660099, #660099 100%);
	behavior: url(PIE.htc);
	-moz-box-shadow: 1px 1px 7px #676767;
	-webkit-box-shadow: 1px 1px 7px #676767;
	box-shadow: 1px 1px 7px #676767;
	height: 45px;
	background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #660099),color-stop(1, #660099));/* IE6,IE7 */
	/* IE8 */
	/* Firefox F3.5+ */
	/* Safari3.0+, Chrome */
}

.ondemand {

	width:100%;
	border-bottom:3px solid #fff !important;
	position:absolute;	
	top:0;
	left:0;
	z-index:1000;

background: #CC0000; /* Old browsers */
	background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */

	/* FireFox 3.6 */
	/* Safari4+, Chrome */
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')";
	-pie-background: linear-gradient(#CC0000, #CC0000 100%);
	behavior: url(PIE.htc);
	-moz-box-shadow: 1px 1px 7px #676767;
	-webkit-box-shadow: 1px 1px 7px #676767;
	box-shadow: 1px 1px 7px #676767;
	height: 45px;
	background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */
	/* IE8 */
	/* Firefox F3.5+ */
	/* Safari3.0+, Chrome */
}
.ondemand-button
{
width:40px !important;
height:40px;
float:right !important;
z-index:999;
position:absolute;
margin-right:100px!important;
}





#footer{width:100%; margin:0 auto; font-size:12px; color:#0E59AE; height:30px;  margin-top:200px;border-top:1px solid #CCC;padding:18px;}
.hide{display:none;}


/* Buttons */

.round.button {
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
background-image: url(button-images/round-button-overlay.png);
border: 1px solid rgba(0, 0, 0, 0.25);
font-size: 13px;
padding: 0;
}

.button {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50);
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50);
background: #222;
border: 1px solid rgba(0, 0, 0, 0.25);
color: white !important;
cursor: pointer;
display: inline-block;
font-size: 13px;
font-weight: bold;
line-height: 1;
overflow: visible;
padding: 5px 15px 6px;
position: relative;
text-decoration: none;
text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25);
width: auto;
text-align: center;
}

.round.button span {
-moz-border-radius: 14px;
-webkit-border-radius: 14px;
border-radius: 14px;
display: block;
line-height: 1;
padding: 4px 15px 6px;
}


.green.button {
background-color:#91BD09;
}
.green.button:hover {
background-color:#749A02;
}
.green.button:active {
background-color:#a4d50b;
}
.blue.button {
background-color:#0E59AE;
}
.blue.button:hover {
background-color:#063468;
}
.blue.button:active {
background-color:#1169cc;
}
.purple.button {
background-color:#660099;
}
.purple.button:hover {
background-color:#330066;
}
.purple.button:active {
background-color:#7f02bd;
}

.red.button {
background-color:#CC0000;
}
.red.button:hover {
background-color:#990000;
}
.red.button:active {
background-color:#ea0202;
}
.close
{}

.show{

background: #CC0000; /* Old browsers */
background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */


/* FireFox 3.6 */
	/* Safari4+, Chrome */
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')";
	-pie-background: linear-gradient(#CC0000, #CC0000 100%);
	behavior: url(PIE.htc);
	-moz-box-shadow: 1px 1px 7px #676767;
	-webkit-box-shadow: 1px 1px 7px #676767;
	box-shadow: 1px 1px 7px #676767;
	height: 35px;
	float: right;
	width: 30px;
	overflow:hidden;
	/*margin-top: 0px !important;*/
	margin-right: 10px !important;
	text-align: center;
	background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */
	/* IE8 */
	/* Firefox F3.5+ */
	/* Safari3.0+, Chrome */
	 /* Opera 10.5, IE 9.0 */
	
	
	}

.show img{margin-top:10px;}

</style>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/socket.io.min.js"></script>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/jquery.17.js"></script>
<script >
    (function ($) {
    $.fn.extend({
        notify: function (options) {
            var settings = $.extend({ type: 'sticky', speed: 500, onDemandButtonHeight: 35 }, options);
            return this.each(function () {
                var wrapper = $(this);
                varBfwOndemandBtn= $('.ondemand-button');
                var dh = -35;
                var w = wrapper.outerWidth() - ondemandBtn.outerWidth();
                ondemandBtn.css('left', w).css('margin-top',  dh + "px" );
                var h = -wrapper.outerHeight();
                wrapper.addClass(settings.type).css('margin-top', h).addClass('visible').removeClass('hide');
                if (settings.type != 'ondemand') {
                    wrapper.stop(true, false).animate({ marginTop: 0 }, settings.speed);
                }
                else {
                    ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed);
                }

                var closeBtn = $('.close', wrapper);
                closeBtn.click(function () {
                    if (settings.type == 'ondemand') {
                        wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () {
                            wrapper.removeClass('visible').addClass('hide');
                            ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed);
                        });
                    }
                    else {
                        wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () {
                            wrapper.removeClass('visible').addClass('hide');
                        });
                    }
                });
                if (settings.type == 'floated') {
                    $(document).scroll(function (e) {
                        wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed);
                    }).resize(function (e) {
                        wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed);
                    });
                }
                else if (settings.type == 'ondemand') {
                    ondemandBtn.click(function () {
                        $(this).animate({ marginTop: dh }, settings.speed, function () {
                            wrapper.removeClass('hide').addClass('visible').animate({ marginTop: 0 }, settings.speed, function () {

                            });
                        })
                    });
                }

            });

        }
    });
})(jQuery);

    
</script>
</head>
<body>

<div class="notification sticky hide">
    <p id="content"> </p>
    <a class="close" href="BfwJavascript"> <img src="/icon-close.png" /></a>
</div>
<div class="wrapper">
    <div style="width:850px;">
    <h3>介绍:</h3>
    <b>Web-msg-sender</b> 是一个web消息推送系统,基于<a rel="nofollow" href="https://github.com/walkor/phpsocket.io">PHPSocket.IO</a>开发。<br><br><br>
    <h3>支持以下特性:</h3>
    <ul>
      <li>多浏览器支持</li>
      <li>支持针对单个用户推送消息</li>
      <li>支持向所有用户推送消息</li>
      <li>长连接推送(websocket或者comet),消息即时到达</li>
      <li>支持在线用户数实时统计推送(见页脚统计)</li>
      <li>支持在线页面数实时统计推送(见页脚统计)</li>
    </ul>
    <h3>测试:</h3>
    当前用户uid:<b class="uid"></b><br>
    可以通过url:<a id="send_to_one" href="http://www.workerman.net:9121/?type=publish&to=1445590039000&content=%E6%B6%88%E6%81%AF%E5%86%85%E5%AE%B9" target="_blank"><font style="color:#91BD09">http://<font class="domain"></font>:9121?type=publish&to=<b class="uid"></b>&content=消息内容</font></a>  向当前用户发送消息<br>
    可以通过url:<a href="http://www.workerman.net:9121/?type=publish&to=&content=%E6%B6%88%E6%81%AF%E5%86%85%E5%AE%B9" target="_blank"  id="send_to_all" ><font style="color:#91BD09">http://<font class="domain"></font>:9121?type=publish&to=&content=消息内容</font></a> 向所有在线用户推送消息<br>
    <script>
        // 使用时替换成真实的uid,这里方便演示使用时间戳
        var uid = Date.parse(new Date());
        $('#send_to_one').attr('href', 'http://'+document.domain+':9121/?type=publish&content=%E6%B6%88%E6%81%AF%E5%86%85%E5%AE%B9&to='+uid);
        $('.uid').html(uid);
		$('#send_to_all').attr('href', 'http://'+document.domain+':9121/?type=publish&content=%E6%B6%88%E6%81%AF%E5%86%85%E5%AE%B9');
        $('.uid').html(uid);
        $('.domain').html(document.domain);
    </script>
</div>

<script>
$(document).ready(function () {
    // 连接服务端
    var socket = io('http://'+document.domain+':9120');
    // 连接后登录
    socket.on('connect', function(){
    	socket.emit('login', uid);
    });
    // 后端推送来消息时
    socket.on('new_msg', function(msg){
         $('#content').html('收到消息:'+msg);
         $('.notification.sticky').notify();
    });
    // 后端推送来在线数据时
    socket.on('update_online_count', function(online_stat){
        $('#online_box').html(online_stat);
    });
});
</script>
<div id="footer">
<center id="online_box"></center>
<center><p style="font-size:11px;color:#555;"> Powered by <a href="http://www.workerman.net/web-sender" target="_blank"><strong>web-msg-sender!</strong></a></p></center>
</div>
</body>
</html>

二、php后端代码

<?php
use Workerman\Worker;
use Workerman\Lib\Timer;
use PHPSocketIO\SocketIO;
use Workerman\Protocols\Http\Request;
use Workerman\Connection\TcpConnection;





require_once '/data/server/phpsocket/vendor/autoload.php';

// 全局数组保存uid在线数据
$uidConnectionMap = array();
// 记录最后一次广播的在线用户数
$last_online_count = 0;
// 记录最后一次广播的在线页面数
$last_online_page_count = 0;

// PHPSocketIO服务
$sender_io = new SocketIO(9120);
// 客户端发起连接事件时,设置连接socket的各种事件回调
$sender_io->on('connection', function($socket){
    // 当客户端发来登录事件时触发
    $socket->on('login', function ($uid)use($socket){
        global $uidConnectionMap, $last_online_count, $last_online_page_count;
        // 已经登录过了
        if(isset($socket->uid)){
            return;
        }
        // 更新对应uid的在线数据
        $uid = (string)$uid;
        if(!isset($uidConnectionMap[$uid]))
        {
            $uidConnectionMap[$uid] = 0;
        }
        // 这个uid有++$uidConnectionMap[$uid]个socket连接
        ++$uidConnectionMap[$uid];
        // 将这个连接加入到uid分组,方便针对uid推送数据
        $socket->join($uid);
        $socket->uid = $uid;
        // 更新这个socket对应页面的在线数据
        $socket->emit('update_online_count', "当前<b>{$last_online_count}</b>人在线,共打开<b>{$last_online_page_count}</b>个页面");
    });
    
    // 当客户端断开连接是触发(一般是关闭网页或者跳转刷新导致)
    $socket->on('disconnect', function () use($socket) {
        if(!isset($socket->uid))
        {
             return;
        }
        global $uidConnectionMap, $sender_io;
        // 将uid的在线socket数减一
        if(--$uidConnectionMap[$socket->uid] <= 0)
        {
            unset($uidConnectionMap[$socket->uid]);
        }
    });
});

// 当$sender_io启动后监听一个http端口,通过这个端口可以给任意uid或者所有uid推送数据
$sender_io->on('workerStart', function(){
    // 监听一个http端口
    $inner_http_worker = new Worker('http://0.0.0.0:9121');
    // 当http客户端发来数据时触发
    $inner_http_worker->onMessage = function(TcpConnection $http_connection,  $request){
        $post=$request['get'];
       // $data = json_decode($buffer, true);
      // var_dump($request);
       // return;
        global $uidConnectionMap;
       // $post = $request->post();
       // $post = $post ? $post : $request->get();
        // 推送数据的url格式 type=publish&to=uid&content=xxxx
        switch($post['type']){
            case 'publish':
                global $sender_io;
                $to = $post['to'];
                $post['content'] = htmlspecialchars($post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if($to){
                    $sender_io->to($to)->emit('new_msg', $post['content']);
                // 否则向所有uid推送数据
                }else{
                    $sender_io->emit('new_msg', $post['content']);
                }
                // http接口返回,如果用户离线socket返回fail
                if($to && !isset($uidConnectionMap[$to])){
                    return $http_connection->send('offline');
                }else{
                    return $http_connection->send('ok');
                }
        }
        return $http_connection->send('fail');
    };
    // 执行监听
    $inner_http_worker->listen();

    // 一个定时器,定时向所有uid推送当前uid在线数及在线页面数
    Timer::add(1, function(){
        global $uidConnectionMap, $sender_io, $last_online_count, $last_online_page_count;
        $online_count_now = count($uidConnectionMap);
        $online_page_count_now = array_sum($uidConnectionMap);
        // 只有在客户端在线数变化了才广播,减少不必要的客户端通讯
        if($last_online_count != $online_count_now || $last_online_page_count != $online_page_count_now)
        {
            $sender_io->emit('update_online_count', "当前<b>{$online_count_now}</b>人在线,共打开<b>{$online_page_count_now}</b>个页面");
            $last_online_count = $online_count_now;
            $last_online_page_count = $online_page_count_now;
        }
    });
});

if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

把这段代码放到bfwstudio的phpcli中进行调试运行

{{collectdata}}

网友评论0