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中进行调试运行
网友评论0