├── .gitignore ├── Example ├── Chat_Robot │ ├── README.md │ ├── Web │ │ ├── css │ │ │ ├── common.css │ │ │ └── index.css │ │ ├── images │ │ │ └── 1.png │ │ ├── index.html │ │ └── lay │ │ │ ├── layer │ │ │ ├── extend │ │ │ │ └── layer.ext.js │ │ │ ├── index.js │ │ │ ├── layer.min.js │ │ │ └── skin │ │ │ │ ├── default │ │ │ │ ├── textbg.png │ │ │ │ ├── xubox_ico0.png │ │ │ │ ├── xubox_loading0.gif │ │ │ │ ├── xubox_loading1.gif │ │ │ │ ├── xubox_loading2.gif │ │ │ │ ├── xubox_loading3.gif │ │ │ │ └── xubox_title0.png │ │ │ │ ├── layer.css │ │ │ │ └── layer.ext.css │ │ │ ├── layim.js │ │ │ └── lib.js │ ├── chat_rebot.php │ ├── show1.png │ └── show2.png ├── Monitor_Log │ ├── README.md │ └── monitor_log_server.php ├── Monitor_Server_Status │ ├── README.md │ ├── monitor_server_status_client.php │ └── monitor_server_status_server.php ├── Real_Time_Monitor_Ssh │ ├── README.md │ ├── Web │ │ ├── exporting.js │ │ ├── highcharts.js │ │ ├── index.html │ │ ├── jquery.min.js │ │ └── jquery.min.map │ ├── demo.gif │ ├── real_time_monitor_ssh_server.php │ ├── web_server.php │ └── websocket_server.php └── Web_Server │ ├── README.md │ ├── Web │ ├── 404.html │ ├── blog.html │ ├── index.php │ ├── login.php │ └── upload.php │ └── webserver_server.php ├── LICENSE ├── MeepoPS ├── Api │ ├── Cbnsq.php │ ├── Http.php │ ├── Telnet.php │ ├── Trident.php │ └── Websocket.php ├── Core │ ├── ApplicationProtocol │ │ ├── ApplicationProtocolInterface.php │ │ ├── Cbnsq.php │ │ ├── Http.php │ │ ├── README.md │ │ ├── Telnet.php │ │ ├── Telnetjson.php │ │ └── Websocket.php │ ├── Autoload.php │ ├── CheckEnv.php │ ├── Config.php │ ├── Constant.php │ ├── Errorcode.php │ ├── Event │ │ ├── EventInterface.php │ │ ├── Libevent.php │ │ └── Select.php │ ├── Func.php │ ├── Init.php │ ├── Log.php │ ├── MeepoPS.php │ ├── README.md │ ├── Statistic.php │ ├── Timer.php │ ├── TransportProtocol │ │ ├── README.md │ │ ├── Tcp.php │ │ └── TransportProtocolInterface.php │ └── Trident │ │ ├── AppBusiness.php │ │ ├── Business.php │ │ ├── BusinessAndConfluenceService.php │ │ ├── BusinessAndTransferService.php │ │ ├── Confluence.php │ │ ├── MsgTypeConst.php │ │ ├── README.md │ │ ├── Tool.php │ │ ├── Transfer.php │ │ ├── TransferAndBusinessService.php │ │ └── TransferAndConfluenceService.php ├── Library │ ├── Db │ │ └── Mysql.php │ ├── Session.php │ └── TcpClient.php ├── README.md ├── config.ini └── index.php ├── README.md ├── Test ├── test_client.php ├── test_client_pressure.php ├── test_less_connect_quick_send1.php ├── test_less_connect_quick_send2.php ├── test_more_connect_quick_send.php └── test_server_capacity.php ├── demo-cbnsq.php ├── demo-http.php ├── demo-telnet.php ├── demo-trident.php └── demo-websocket.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store -------------------------------------------------------------------------------- /Example/Chat_Robot/README.md: -------------------------------------------------------------------------------- 1 | # 人机聊天 2 | 3 | 新增于V0.0.5 4 | 5 | 后端采用MeepoPS的WebSocket接口类。 6 | 7 | 前端采用[LayIM1.0](http://www.w3cdream.com/content-sort-21-article-521.html)为基础, 并由zhaodan-it@360.cn修改 8 | 9 | ### 展示: 10 | ![WebSocket人机聊天](show1.png?raw=true "WebSocket人机聊天") 11 | 12 | ### 启动 13 | 使用: 14 | ```bash 15 | cd Example/Chat_Robot 16 | sudo php chat_rebot.php start 17 | ``` 18 | 启动. 19 | 20 | 守护进程模式启动使用: 21 | ```bash 22 | cd Example/Chat_Robot 23 | sudo php webserver_server.php start -d 24 | ``` 25 | 26 | 启动后, MeepoPS会有两个实例, 一个实例作为WebSocket的服务端, 监听19910端口, 等待接受WebSocket链接并进行业务逻辑的处理。另一个实例是WebServer, 监听19911端口, 充当Nginx/Apache的功能。 27 | 28 | ![WebSocket人机聊天启动](show2.png?raw=true "WebSocket人机聊天启动") 29 | 30 | ### 使用 31 | 32 | 打开浏览器, 访问http://localhost:19911/index.html即可。 33 | 34 | 浏览器打开页面后, JS会使用WebSocket协议链接到服务端, 本例是链接到127.0.0.1:19910。 35 | 36 | 链接建立完成后, 会触发JS的onopen()事件, 像服务器发送一个消息。 37 | 38 | 我们可以在界面上输入任何字符并发送。 39 | 40 | 服务端在收到前端的消息后, 会返回"收到消息:*****"的字样给前端JS。 41 | 42 | JS会渲染到界面上。 43 | 44 | ### 友情提示: 45 | 前端断线重链的库: [ReconnectingWebSocket.js](https://github.com/joewalnes/reconnecting-websocket) 46 | 47 | 引入后, 只需要将JS中的WebSocket("ws://")替换位ReconnectingWebSocket("ws://")即可。 -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/css/common.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* CSS Document */ 3 | body,div,p,h1,h2,h3,h4,h5,h6,ul,li,ol,a,i,b,em,strong,span,img{ padding:0;margin:0; } 4 | a{ text-decoration:none; } 5 | li{ list-style:none; } 6 | .fl{ float:left; } 7 | .fr{ float:right; } 8 | .clearFix:after{ content:'';display:block;clear:both; } 9 | .clearFix{ zoom:1; } 10 | b{font-weight: normal; } 11 | i{ font-style: normal; cursor: pointer; -ms-user-select: none; -webkit-user-select: none; color: #f90; } 12 | .wrapper{ width:1100px;margin:0 auto; } 13 | body{ font-size:12px; color:#3c3c3c; font-family: "微软雅黑"; } 14 | body,a{ font-size:14px;color:#0c0c0c; } 15 | img{ display:block; border: none; } 16 | input{ border:none;outline:none; } 17 | body a:hover{ 18 | text-decoration: none; 19 | } -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/css/index.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* CSS Document */ 3 | .chat{ 4 | position: relative; 5 | width: 100%; 6 | height: 100%; 7 | min-width: 900px; 8 | min-height: 500px; 9 | } 10 | /*left*/ 11 | .left{ 12 | height: 100%; 13 | margin-right: 220px; 14 | background: #f5f5f5; 15 | position: relative; 16 | } 17 | .left .fixed{ 18 | width: 100%; 19 | height: 59px; 20 | border-bottom: 1px solid #dddddd; 21 | background: rgba(254,254,254,.8); 22 | left: 0; 23 | top: 0; 24 | position: absolute; 25 | -ms-user-select: none; 26 | -webkit-user-select: none; 27 | z-index: 100000; 28 | } 29 | .fixed img{ 30 | width: 72px; 31 | height: 72px; 32 | margin-left: 40px; 33 | display: inline-block; 34 | opacity: 1; 35 | border-radius: 50%; 36 | } 37 | .fixed h6{ 38 | margin:0 40px; 39 | font-size: 20px; 40 | line-height: 60px; 41 | } 42 | .fixed p{ 43 | line-height: 60px; 44 | } 45 | .content_box{ 46 | width: 100%; 47 | overflow: hidden; 48 | overflow-y: auto; 49 | padding-top: 80px; 50 | } 51 | .content{ 52 | width: 100%; 53 | line-height: 20px; 54 | word-break: break-all; 55 | } 56 | .content img{ 57 | display: inline-block; 58 | } 59 | .content ul{ 60 | padding: 0 20px; 61 | } 62 | .content ul li{ 63 | width: 100%; 64 | position: relative; 65 | margin-bottom: 10px; 66 | font-size: 14px; 67 | } 68 | .content ul li span{ 69 | position: absolute; 70 | width: 0; 71 | height: 0; 72 | border-top: 5px solid transparent; 73 | border-bottom: 5px solid transparent; 74 | border-left: 0; 75 | border-right: 7px solid #fff; 76 | top: 15px; 77 | } 78 | .content ul li span.arrow{ 79 | left: -7px; 80 | } 81 | .content ul li.layim_chateme span{ 82 | border-left: 7px solid #34BBF9; 83 | border-right: 0; 84 | right: -7px; 85 | } 86 | .content .layim_chatsay{ 87 | max-width: 80%; 88 | padding: 12px 18px 10px; 89 | border-radius: 5px; 90 | background: #fff; 91 | float: left; 92 | } 93 | .content .layim_chateme .layim_chatsay{ 94 | float: right; 95 | background: #34BBF9; 96 | color: #fff; 97 | } 98 | .content .clickSend{ 99 | text-indent: 12px; 100 | display: inline-block; 101 | } 102 | 103 | 104 | /*聊天时间*/ 105 | li.time{ 106 | text-align: center; 107 | } 108 | li.time strong{ 109 | font-size: 12px; 110 | } 111 | /*send*/ 112 | .send_box{ 113 | width: 100%; 114 | height: 90px; 115 | background: #ddd; 116 | position: absolute; 117 | left: 0; 118 | bottom: 0; 119 | } 120 | .send_box textarea{ 121 | width: 82%; 122 | height: 45px; 123 | margin-top: 10px; 124 | margin-left: 20px; 125 | padding: 5px 0; 126 | border:none; 127 | outline:none; 128 | resize: none; 129 | background: #ddd; 130 | } 131 | .send_box input[type=button]{ 132 | width: 90px; 133 | height: 90px; 134 | background: #ddd; 135 | cursor: pointer; 136 | border-left: 1px solid #f5f5f5; 137 | } 138 | /*right*/ 139 | .right{ 140 | width: 220px; 141 | height: 100%; 142 | position: absolute; 143 | right: 0; 144 | top: 0; 145 | overflow: hidden; 146 | } 147 | 148 | .right h5{ 149 | text-align: center; 150 | line-height: 90px; 151 | font-size: 20px; 152 | } 153 | .hotline{ 154 | width: 200px; 155 | height: 130px; 156 | position:absolute; 157 | bottom: 0px; 158 | right:-200px; 159 | cursor: pointer; 160 | } 161 | .hotline .hover{ 162 | width: 20px; 163 | height: 130px; 164 | position: absolute; 165 | left: -20px; 166 | top: 0; 167 | line-height: 26px; 168 | text-align: center; 169 | border: 1px solid #ccc; 170 | box-sizing: border-box; 171 | } 172 | .hotline ul{ 173 | padding: 0 10px; 174 | line-height: 43px; 175 | } 176 | .ad{ 177 | width: 200px; 178 | } 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/images/1.png -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | MeepoPS客服系统 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 |
智能机器人-首席客服小刚
20 |

亲,有什么问题尽管问昂,么么哒~

21 |
22 |
23 |
24 |
    25 |
26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 |
34 |
投放广告区
35 |
36 | 37 |
38 |
39 |
联系方式 40 | < 41 |
42 |
    43 |
  • 最快的方式就是打电话
  • 44 |
  • 电话:18500001234
  • 45 |
  • 邮箱:a@a.com
  • 46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/extend/layer.ext.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @Name: layer拓展库,依赖于layer 4 | @Date: 2013.12.14 5 | @Author: 贤心 6 | @Versions:1.0.0 7 | @Api:http://sentsin.com/jquery/layer 8 | @Desc: 本拓展会持续更新 9 | 10 | **/ 11 | 12 | layer.use('skin/layer.ext.css', function(){ 13 | 14 | //仿系统prompt 15 | layer.prompt = function(parme, yes, no){ 16 | var log = {}, parme = parme || {}, conf = { 17 | area: ['auto', 'auto'], 18 | offset: [parme.top || '200px', ''], 19 | title: parme.title || '信息', 20 | dialog: { 21 | btns: 2, 22 | type: -1, 23 | msg: '', 32 | yes: function(index){ 33 | var val = log.prompt.val(); 34 | yes && yes(val); 35 | if(val === ''){ 36 | log.prompt.focus(); 37 | } else { 38 | layer.close(index); 39 | } 40 | }, no: no 41 | }, success: function(){ 42 | log.prompt = $('#xubox_prompt'); 43 | } 44 | }; 45 | if(parme.type === 3){ 46 | conf.dialog.msg = '' 47 | } 48 | return $.layer(conf); 49 | }; 50 | 51 | //tab层 52 | layer.tab = function(parme){ 53 | var log = {}, parme = parme || {}, data = parme.data || [], conf = { 54 | type: 1, 55 | border: [0], 56 | area: ['auto', 'auto'], 57 | title: false, 58 | shade : parme.shade, 59 | move: ['.xubox_tabmove', true], 60 | closeBtn: false, 61 | page: {html: '
'; 64 | }() 65 | +'' 66 | +'
' 67 | +function(){ 68 | var len = data.length, ii = 1, str = ''; 69 | if(len > 0){ 70 | str = ''+ data[0].title +''; 71 | for(; ii < len; ii++){ 72 | str += ''+ data[ii].title +''; 73 | } 74 | } 75 | 76 | return str; 77 | }() +'
' 78 | +'' 88 | +'X' 89 | +'
' 90 | }, success: function(layerE){ 91 | //切换事件 92 | var btn = $('.xubox_tabtit').children(), main = $('.xubox_tab_main').children(), close = $('.xubox_tabclose'); 93 | btn.on('click', function(){ 94 | var othis = $(this), index = othis.index(); 95 | othis.addClass('xubox_tabnow').siblings().removeClass('xubox_tabnow'); 96 | main.eq(index).show().siblings().hide(); 97 | }); 98 | //关闭层 99 | close.on('click', function(){ 100 | layer.close(layerE.attr('times')); 101 | }); 102 | } 103 | }; 104 | return $.layer(conf); 105 | }; 106 | 107 | //相册层 108 | layer.photo = function(selector, options){ 109 | 110 | }; 111 | 112 | }); -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/index.js: -------------------------------------------------------------------------------- 1 | function getStyle(b,a){return(b.currentStyle||getComputedStyle(b,false))[a]}function startMove(f,b,d,e){var h=parseFloat(getStyle(f,b));var a=d-h;var c=Math.floor(e/30);var g=0;clearInterval(f.timer);f.timer=setInterval(function(){g++;var i=h+a*g/c;if(b=="opacity"){f.style[b]=i;f.style.filter="alpha(opacity:"+i*100+")"}else{f.style[b]=i+"px"}if(g==c){clearInterval(f.timer)}},30)}(function(){window.onresize=function(){document.body.style.height=document.documentElement.clientHeight+"px";document.querySelector(".content_box").style.height=document.documentElement.clientHeight-170+"px"};document.body.style.height=document.documentElement.clientHeight+"px";document.querySelector(".content_box").style.height=document.documentElement.clientHeight-170+"px";var b=document.getElementById("hotline");var a=document.getElementById("arrows");b.onmouseover=function(){startMove(b,"right",0,300);a.innerHTML=">"};b.onmouseout=function(){startMove(b,"right",-200,300);a.innerHTML="<"}})(); -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/textbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/textbg.png -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/xubox_ico0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/xubox_ico0.png -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading0.gif -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading1.gif -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading2.gif -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/xubox_loading3.gif -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/default/xubox_title0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/Web/lay/layer/skin/default/xubox_title0.png -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/layer.css: -------------------------------------------------------------------------------- 1 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,input,button,textarea,p,blockquote,th,td,form{margin:0;padding:0}*html{background-image:url(about:blank);background-attachment:fixed}.xubox_shade,.xubox_layer{position:fixed;_position:absolute}.xubox_shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.xubox_layer{top:150px;left:50%;height:auto;width:310px;margin-left:-155px}.xubox_border,.xubox_title,.xubox_title i,.xubox_page,.xubox_iframe,.xubox_title em,.xubox_close,.xubox_msgico,.xubox_moves{position:absolute}.xubox_border{border-radius:5px}.xubox_title{left:0;top:0}.xubox_main{position:relative;height:100%;_float:left}.xubox_page{top:0;left:0}.xubox_load{background:url(default/xubox_loading0.gif) #fff center center no-repeat}.xubox_loading{display:block;float:left;text-decoration:none;color:#FFF;_float:none}.xulayer_png32{background:url(default/xubox_ico0.png) no-repeat}.xubox_moves{border:3px solid #333;cursor:move;opacity:.7;filter:alpha(opacity=70)}.xubox_msgico{width:32px;height:32px;top:52px;left:15px;background:url(default/xubox_ico0.png) no-repeat}.xubox_text{padding-left:55px;float:left;line-height:25px;word-break:break-all;padding-right:20px;overflow:hidden;font-size:14px}.xubox_msgtype0{background-position:-91px -38px}.xubox_msgtype1{background-position:-128px -38px}.xubox_msgtype2{background-position:-163px -38px}.xubox_msgtype3{background-position:-91px -75px}.xubox_msgtype4{background-position:-163px -75px}.xubox_msgtype5{background-position:-163px -112px}.xubox_msgtype6{background-position:-163px -148px}.xubox_msgtype7{background-position:-128px -75px}.xubox_msgtype8{background-position:-91px -6px}.xubox_msgtype9{background-position:-129px -6px}.xubox_msgtype10{background-position:-163px -6px}.xubox_msgtype11{background-position:-206px -6px}.xubox_msgtype12{background-position:-206px -44px}.xubox_msgtype13{background-position:-206px -81px}.xubox_msgtype14{background-position:-206px -122px}.xubox_msgtype15{background-position:-206px -157px}.xubox_loading_0{width:60px;height:24px;background:url(default/xubox_loading0.gif) no-repeat}.xubox_loading_1{width:37px;height:37px;background:url(default/xubox_loading1.gif) no-repeat}.xubox_loading_2,.xubox_msgtype16{width:32px;height:32px;background:url(default/xubox_loading2.gif) no-repeat}.xubox_loading_3{width:126px;height:22px;background:url(default/xubox_loading3.gif) no-repeat}.xubox_title{width:100%;height:35px;line-height:35px;border-bottom:1px solid #d5d5d5;background:url(default/xubox_title0.png) #ebebeb repeat-x;cursor:move;font-size:14px;color:#333}.xubox_title em{display:block;height:20px;line-height:20px;width:80%;top:9px;left:10px;font-style:normal;overflow:hidden}.xubox_close0{right:10px;top:10px;width:14px;height:14px;background-position:-31px -7px;cursor:pointer;overflow:hidden}.xubox_close0:hover{background-position:-51px -7px}.xubox_close1{right:-20px;top:-21px;width:34px;height:30px;background-position:-5px -252px;cursor:pointer;overflow:hidden;_right:3px;_top:3px;_width:14px;_height:14px;_background-position:-31px -7px}.xubox_close1:hover{background-position:-44px -252px;_background-position:-51px -7px}.xubox_botton a{position:absolute;bottom:10px;left:50%;background:url(default/xubox_ico0.png) repeat;text-decoration:none;color:#FFF;font-size:14px;text-align:center;font-weight:bold;overflow:hidden}.xubox_botton a:hover{text-decoration:none;color:#FFF}.xubox_botton .xubox_botton1{width:79px;height:32px;line-height:32px;margin-left:-39px;background-position:-6px -34px}.xubox_botton1:hover{background-position:-6px -72px}.xubox_botton .xubox_botton2{margin-left:-76px;width:71px;height:29px;line-height:29px;background-position:-5px -114px}.xubox_botton2:hover{background-position:-5px -146px}.xubox_botton .xubox_botton3{width:71px;height:29px;line-height:29px;margin-left:10px;background-position:-81px -114px}.xubox_botton3:hover{background-position:-81px -146px}.xubox_tips{position:relative;line-height:20px;padding:3px 30px 3px 10px;font-size:12px;_float:left;border-radius:3px;box-shadow:1px 1px 3px rgba(0,0,0,.3)}.xubox_tips i.layerTipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.xubox_tips i.layerTipsT,.xubox_tips i.layerTipsB{left:5px;border-right-style:solid}.xubox_tips i.layerTipsT{bottom:-8px}.xubox_tips i.layerTipsB{top:-8px}.xubox_tips i.layerTipsR,.xubox_tips i.layerTipsL{top:1px;border-bottom-style:solid}.xubox_tips i.layerTipsR{left:-8px}.xubox_tips i.layerTipsL{right:-8px} 2 | -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layer/skin/layer.ext.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @Name: layer拓展样式 4 | @Date: 2012.12.13 5 | @Author: 贤心 6 | @blog: sentsin.com 7 | 8 | **/ 9 | 10 | /* prompt模式 */ 11 | .xubox_layer .xubox_form{width:240px; line-height:30px; padding: 0 5px; border: 1px solid #ccc; background: url(default/textbg.png) #fff repeat-x; color:#333;} 12 | .xubox_layer .xubox_formArea{width:300px; height:100px; line-height:20px;} 13 | 14 | /* tab模式 */ 15 | .xubox_layer .xubox_tab{position:relative; border:1px solid #ccc;} 16 | .xubox_layer .xubox_tabmove{position:absolute; width:600px; height:30px; top:0; left:0;} 17 | .xubox_layer .xubox_tabtit{ display:block; height:30px; border-bottom:1px solid #ccc; background-color:#eee;} 18 | .xubox_layer .xubox_tabtit span{position:relative; float:left; width:120px; height:30px; line-height:30px; text-align:center; cursor:pointer;} 19 | .xubox_layer .xubox_tabtit span.xubox_tabnow{left:-1px; _top:1px; height:31px; border-left:1px solid #ccc; border-right:1px solid #ccc; background-color:#fff; z-index:10;} 20 | .xubox_layer .xubox_tab_main{line-height:24px; clear:both;} 21 | .xubox_layer .xubox_tab_main .xubox_tabli{display:none;} 22 | .xubox_layer .xubox_tab_main .xubox_tabli.xubox_tab_layer{display:block;} 23 | .xubox_layer .xubox_tabclose{position:absolute; right:10px; top:5px; cursor:pointer;} 24 | -------------------------------------------------------------------------------- /Example/Chat_Robot/Web/lay/layim.js: -------------------------------------------------------------------------------- 1 | /* 2 | @Author:赵丹 3 | @Date: 2016-07-25 4 | @email: zhaodan-it@360.cn 5 | 修改自LayIM1.0 6 | */ 7 | ;!function(win, undefined){ 8 | 9 | var xxim = {}; 10 | //节点 11 | xxim.renode = function(){ 12 | var node = xxim.node = { 13 | tabs: $('#xxim_tabs>span') 14 | }; 15 | }; 16 | 17 | var ws = new WebSocket('ws://127.0.0.1:19910'); 18 | ws.onopen = function() { 19 | ws.send('前端JS触发onopen事件, 链接完成'); 20 | }; 21 | //聊天窗口 22 | xxim.popchat = function(){ 23 | 24 | var node = xxim.node, log = {}; 25 | 26 | log.success = function(layero){ 27 | xxim.transmit(); 28 | }; 29 | 30 | ws.onmessage = function(e){ 31 | var log = {}; 32 | //聊天模版 33 | log.html = function(param){ 34 | return '
  • ' 35 | +'' 36 | +'
    '+param.content+'
    ' 37 | +'
  • '; 38 | }; 39 | log.contentBox = $('.content_box'); 40 | log.imarea = $('#content ul'); 41 | //解析json 42 | var response = JSON.parse(e.data); 43 | //判断错误码 44 | if(response.errno !== 0) { 45 | log.imarea.append(log.html({ 46 | content:response.errmsg 47 | })); 48 | log.contentBox.scrollTop(log.imarea[0].scrollHeight); 49 | return; 50 | } 51 | //判断服务端返回值 52 | //如果是response.data.content是空字符串 53 | if(response.data.content === '') { 54 | response.data.content = '没有查询到相关问题, 请咨询人工客服'; 55 | } 56 | log.imarea.append(log.html({ 57 | content:response.data.content 58 | })); 59 | log.contentBox.scrollTop(log.imarea[0].scrollHeight); 60 | }; 61 | $.layer({ 62 | type: 1, 63 | shade: [0], 64 | success: function(layero){ 65 | log.success(layero); 66 | } 67 | }) 68 | }; 69 | //消息传输 70 | xxim.transmit = function(drag){ 71 | var node = xxim.node, log = {}; 72 | node.sendbtn = $('#sendbtn'); 73 | node.imwrite = $('#write'); 74 | //发送 75 | log.send = function(){ 76 | var data = { 77 | content: node.imwrite.val(), 78 | _: +new Date 79 | }; 80 | if(data.content.replace(/\s/g, '') === ''){ 81 | layer.tips('说点啥呗!', '#write', 2); 82 | node.imwrite.focus(); 83 | } else { 84 | //聊天模版 85 | log.html = function(param, type){ 86 | return '
  • ' 87 | + function(){ 88 | if(type === 'me'){ 89 | return ''; 90 | } else { 91 | return '' 92 | } 93 | }() 94 | +'
    '+ param.content +'
    ' 95 | +'
  • '; 96 | }; 97 | log.contentBox = $('.content_box'); 98 | log.imarea = $('#content ul'); 99 | log.imarea.append(log.html({ 100 | content: data.content 101 | }, 'me')); 102 | var val = node.imwrite.val().replace(/\s/g,''); 103 | ws.send(val); 104 | node.imwrite.val('').focus(); 105 | log.contentBox.scrollTop(log.imarea[0].scrollHeight); 106 | } 107 | }; 108 | node.sendbtn.on('click', function(){ 109 | log.send(); 110 | }); 111 | 112 | node.imwrite.keyup(function(e){ 113 | if(e.keyCode === 13){ 114 | log.send(); 115 | } 116 | }); 117 | }; 118 | xxim.renode(); 119 | xxim.popchat(); 120 | }(window); -------------------------------------------------------------------------------- /Example/Chat_Robot/chat_rebot.php: -------------------------------------------------------------------------------- 1 | callbackNewData = function ($connect, $data){ 19 | $msg = '收到消息: ' . $data; 20 | $message = array( 21 | 'errno' => 0, 'errmsg' => 'OK', 'data' => array( 22 | 'content' => $msg, 'create_time' => date('Y-m-d H:i:s'), 23 | ), 24 | ); 25 | $connect->send(json_encode($message)); 26 | }; 27 | 28 | //使用HTTP协议传输的Api类 29 | $http = new \MeepoPS\Api\Http('0.0.0.0', '19911'); 30 | $http->setDocument('localhost:19911', './Web'); 31 | 32 | //启动MeepoPS 33 | \MeepoPS\runMeepoPS(); -------------------------------------------------------------------------------- /Example/Chat_Robot/show1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/show1.png -------------------------------------------------------------------------------- /Example/Chat_Robot/show2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Chat_Robot/show2.png -------------------------------------------------------------------------------- /Example/Monitor_Log/README.md: -------------------------------------------------------------------------------- 1 | # 监控日志 2 | 监控日志示例程序是指, 一台服务器作为服务端, 启动monitor_log_server.php. 不需要在待监控的服务器上部署客户端程序. 而是使用ssh模拟登陆到服务器使用tail命令监控 3 | 4 | 监控日志示例程序可以做很多事情, 比如实时监控就用tail -f, 如果是查询就用cat, 后面都可以跟着管道 | grep来匹配. 5 | 6 | 想要在我们自己电脑上看日志, 就直接`telnet 服务端IP 端口`. 7 | 8 | 意义: 这样做, 一个服务端监控日志, 开发人员和运维人员直接使用Telnet就可以看到所有的机器上的日志, 不用分别链接到每台机器上手动操作. 9 | 10 | 适用于集群中多台服务器, 我们不能手动ssh登陆上去看, 而这个示例程序可以帮我们批量监控. 11 | 12 | 示例中的集群IP列表, 命令, ssh的账号密码都是写死在代码里的, 在真实场景中, 这些都做成可配置的, 允许多业务线的同事来看. 也就是做成监控平台的一个子项. 13 | 14 | ### monitor_log_server.php 是服务端. 15 | 使用: 16 | ```bash 17 | sudo php monitor_log_server.php start 18 | ``` 19 | 启动. 20 | 21 | 守护进程模式启动使用: 22 | ```bash 23 | sudo php monitor_log_server.php start -d 24 | ``` -------------------------------------------------------------------------------- /Example/Monitor_Log/monitor_log_server.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 22 | 23 | //设置MeepoPS实例名称 24 | $telnet->instanceName = 'MonitorLog-Telnet'; 25 | 26 | //设置回调函数 - 这是所有应用的业务代码入口 27 | $telnet->callbackConnect = 'callbackConnect'; 28 | 29 | //启动MeepoPS 30 | \MeepoPS\runMeepoPS(); 31 | 32 | //以下为回调函数, 业务相关. 33 | function callbackConnect($connect){ 34 | $ipList = array( 35 | '10.10.10.1', 36 | '10.10.10.2', 37 | '10.10.10.3', 38 | '10.10.10.4', 39 | ); 40 | $username = 'lane'; 41 | $password = '123456'; 42 | $cmd = 'tail -f /data/logs/error.log'; 43 | $streamList = array(); 44 | foreach($ipList as $ip){ 45 | $ssh = ssh2_connect($ip, 22); 46 | if (!$ssh){ 47 | echo 'Connection failed: ' . $ip; 48 | return; 49 | } 50 | ssh2_auth_password($ssh, $username, $password); 51 | $stream = ssh2_exec($ssh, $cmd); 52 | stream_set_blocking($stream, true); 53 | $streamList[] = $stream; 54 | } 55 | $connect->send("链接完成\n"); 56 | while(true){ 57 | foreach($streamList as $key=>$stream){ 58 | $ip = $ipList[$key]; 59 | $content = @fgets($stream); 60 | if(!$content){ 61 | continue; 62 | } 63 | $connect->send($ip . ': ' . $content); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Example/Monitor_Server_Status/README.md: -------------------------------------------------------------------------------- 1 | # 监控系统数据采集(Agent) 2 | 监控系统数据采集是指, 一台服务器作为服务端, 启动monitor_server_status_server.php. 然后, 在待采集的机器上, 全部启动monitor_server_status_client.php. 3 | 4 | ### monitor_server.php 是服务端. 5 | 使用: 6 | ```bash 7 | sudo php monitor_server_status_server.php start 8 | ``` 9 | 启动. 10 | 11 | 守护进程模式启动使用: 12 | ```bash 13 | sudo php monitor_server_status_server.php start -d 14 | ``` 15 | 启动后监听19910端口, 接收数据. 在启动时, 每启动一个子进程, 这个子进程都会初始化一个Mysql链接.这个进程内的所有链接都可以使用. 16 | 17 | Mysql的链接信息请自行修改为自己的IP, 账号, 密码, 数据库名, 端口. 18 | 19 | 接收客户端发来的数据, 将数据写入Mysql中. 客户端发来的数据是内存和CPU的使用情况. 大家可以根据实际需要, 从Mysql读取出来绘制折线图等. 20 | 21 | ### monitor_server_status_client.php 是客户端. 22 | 在命令行 23 | ```bash 24 | nohup php monitor_server_status_client.php & 25 | ``` 26 | 启动即可. 27 | 28 | 客户端每秒向服务端发送内存和CPU的使用情况. 29 | 30 | 31 | ### Mysql 32 | - 默认库名: meep_ops 33 | - 默认表名: moni_2015-05-25 34 | 35 | 表结构: 36 | ``` 37 | CREATE TABLE `moni_2016-05-25` ( 38 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 39 | `ip` varchar(255) DEFAULT NULL, 40 | `cpu_sys` float DEFAULT NULL, 41 | `cpu_user` float DEFAULT NULL, 42 | `memory` float DEFAULT NULL, 43 | `create_time` datetime DEFAULT NULL, 44 | PRIMARY KEY (`id`) 45 | ) ENGINE=InnoDB AUTO_INCREMENT=1; 46 | ``` -------------------------------------------------------------------------------- /Example/Monitor_Server_Status/monitor_server_status_client.php: -------------------------------------------------------------------------------- 1 | $key){ 40 | if(!$key){ 41 | unset($keyList[$k]); 42 | } 43 | } 44 | $keyList = array_values($keyList); 45 | $valueList = explode(' ', $vmstat[2]); 46 | foreach($valueList as $k=>$value){ 47 | if($value === ''){ 48 | unset($valueList[$k]); 49 | } 50 | } 51 | $valueList = array_values($valueList); 52 | $data = array(); 53 | foreach($keyList as $k=>$key){ 54 | switch($key){ 55 | case 'buff': 56 | case 'us': 57 | case 'sy': 58 | case 'id': 59 | $data[$key] = $valueList[$k]; 60 | break; 61 | } 62 | } 63 | return $data; 64 | } -------------------------------------------------------------------------------- /Example/Monitor_Server_Status/monitor_server_status_server.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 22 | 23 | //设置MeepoPS实例名称 24 | $telnet->instanceName = 'MonitorServerStatus-Telnet'; 25 | 26 | //设置回调函数 - 这是所有应用的业务代码入口 27 | $telnet->callbackStartInstance = 'callbackStartInstance'; 28 | $telnet->callbackNewData = 'callbackNewData'; 29 | 30 | 31 | //启动MeepoPS 32 | \MeepoPS\runMeepoPS(); 33 | 34 | //global $mysql; 35 | $mysql = null; 36 | 37 | 38 | //以下为回调函数, 业务相关. 39 | function callbackStartInstance($instance) 40 | { 41 | global $mysql; 42 | $mysql = new \MeepoPS\Library\Db\Mysql('127.0.0.1', 'root', '123456', 'meepops'); 43 | } 44 | 45 | function callbackNewData($connect, $data) 46 | { 47 | global $mysql; 48 | $data = json_decode($data, true); 49 | $ip = $connect->getClientAddress()[0]; 50 | $cpuSys = $data['sy']; 51 | $cpuUser = $data['us']; 52 | $memory = $data['buff']; 53 | $createTime = date('Y-m-d H:i:s'); 54 | $sql = 'INSERT INTO `moni_2016-05-25` (ip, cpu_sys, cpu_user, memory, create_time) VALUES ("'.$ip.'", "'.$cpuSys.'", "'.$cpuUser.'", "'.$memory.'", "'.$createTime.'")'; 55 | $result = $mysql->query($sql); 56 | $connect->send($result); 57 | } -------------------------------------------------------------------------------- /Example/Real_Time_Monitor_Ssh/README.md: -------------------------------------------------------------------------------- 1 | # 采集数据并实时制图(SSH) 2 | 采集数据并实时制图是指, 一台服务器作为服务端, 启动real_time_monitor_ssh_server.php. 此时, 同时启动了WebSocket实例和WebServer实例. 3 | 4 | WebSocket实例监听19910端口, 使用SSH的方式, 用ssh用户名和密码自动登陆到服务器上, 获取想要监控的指标。本例是仅获取空闲内存(MemFree)。因为没有Agent, 所以要SSH登陆服务器来获取数据。 5 | 6 | WebServer实例监听19911端口, 充当Web服务器(替代Apache/Nginx), 访问Web页面时, JS脚本使用WebSocket协议链接MeepoPS的WebSocket实例, 实时获取数据, 并绘制成折线图。 7 | 8 | ### real_time_monitor_ssh_server.php 是服务端. 9 | 使用: 10 | ```bash 11 | sudo php real_time_monitor_ssh_server.php start 12 | ``` 13 | 14 | 守护进程模式启动使用: 15 | ```bash 16 | sudo php real_time_monitor_ssh_server.php start -d 17 | ``` 18 | 19 | ### WebServer配置 20 | 请在config.ini中配置域名和根目录, 示例如下: 21 | ``` 22 | http_domain_document_list = 'localhost:19911 & /var/www/MeepoPS/Example/Real_Time_Monitor_Ssh/Web/' 23 | ``` 24 | 25 | 打开浏览器, 访问http://localhost:19911/ 26 | 27 | ### 展示: 28 | ![WebSocket实时监控](demo.gif?raw=true "WebSocket实时监控") 29 | 30 | ### 友情提示: 31 | 前端断线重链的库: [ReconnectingWebSocket.js](https://github.com/joewalnes/reconnecting-websocket) 32 | 33 | 引入后, 只需要将JS中的WebSocket("ws://")替换位ReconnectingWebSocket("ws://")即可。 -------------------------------------------------------------------------------- /Example/Real_Time_Monitor_Ssh/Web/exporting.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v4.2.5 (2016-05-06) 3 | Exporting module 4 | 5 | (c) 2010-2016 Torstein Honsi 6 | 7 | License: www.highcharts.com/license 8 | */ 9 | (function(f){typeof module==="object"&&module.exports?module.exports=f:f(Highcharts)})(function(f){var t=f.win,k=t.document,C=f.Chart,v=f.addEvent,D=f.removeEvent,E=f.fireEvent,s=f.createElement,u=f.discardElement,x=f.css,l=f.merge,q=f.each,r=f.extend,F=f.splat,G=Math.max,H=f.isTouchDevice,I=f.Renderer.prototype.symbols,A=f.getOptions(),B;r(A.lang,{printChart:"Print chart",downloadPNG:"Download PNG image",downloadJPEG:"Download JPEG image",downloadPDF:"Download PDF document",downloadSVG:"Download SVG vector image", 10 | contextButtonTitle:"Chart context menu"});A.navigation={menuStyle:{border:"1px solid #A0A0A0",background:"#FFFFFF",padding:"5px 0"},menuItemStyle:{padding:"0 10px",background:"none",color:"#303030",fontSize:H?"14px":"11px"},menuItemHoverStyle:{background:"#4572A5",color:"#FFFFFF"},buttonOptions:{symbolFill:"#E0E0E0",symbolSize:14,symbolStroke:"#666",symbolStrokeWidth:3,symbolX:12.5,symbolY:10.5,align:"right",buttonSpacing:3,height:22,theme:{fill:"white",stroke:"none"},verticalAlign:"top",width:24}}; 11 | A.exporting={type:"image/png",url:"http://export.highcharts.com/",printMaxWidth:780,buttons:{contextButton:{menuClassName:"highcharts-contextmenu",symbol:"menu",_titleKey:"contextButtonTitle",menuItems:[{textKey:"printChart",onclick:function(){this.print()}},{separator:!0},{textKey:"downloadPNG",onclick:function(){this.exportChart()}},{textKey:"downloadJPEG",onclick:function(){this.exportChart({type:"image/jpeg"})}},{textKey:"downloadPDF",onclick:function(){this.exportChart({type:"application/pdf"})}}, 12 | {textKey:"downloadSVG",onclick:function(){this.exportChart({type:"image/svg+xml"})}}]}}};f.post=function(a,b,e){var c,a=s("form",l({method:"post",action:a,enctype:"multipart/form-data"},e),{display:"none"},k.body);for(c in b)s("input",{type:"hidden",name:c,value:b[c]},null,a);a.submit();u(a)};r(C.prototype,{sanitizeSVG:function(a){return a.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/.*?$/,"").replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g,'$1="rgb($2)" $1-opacity="$3"').replace(/ /g,"\u00a0").replace(/­/g,"\u00ad").replace(//g,"<$1title>").replace(/height=([^" ]+)/g,'height="$1"').replace(/width=([^" ]+)/g,'width="$1"').replace(/hc-svg-href="([^"]+)">/g,'xlink:href="$1"/>').replace(/ id=([^" >]+)/g, 14 | ' id="$1"').replace(/class=([^" >]+)/g,'class="$1"').replace(/ transform /g," ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g,function(a){return a.toLowerCase()})},getChartHTML:function(){return this.container.innerHTML},getSVG:function(a){var b=this,e,c,g,j,h,d=l(b.options,a),m=d.exporting.allowHTML;if(!k.createElementNS)k.createElementNS=function(a,b){return k.createElement(b)};c=s("div",null,{position:"absolute",top:"-9999em",width:b.chartWidth+"px",height:b.chartHeight+"px"},k.body); 15 | g=b.renderTo.style.width;h=b.renderTo.style.height;g=d.exporting.sourceWidth||d.chart.width||/px$/.test(g)&&parseInt(g,10)||600;h=d.exporting.sourceHeight||d.chart.height||/px$/.test(h)&&parseInt(h,10)||400;r(d.chart,{animation:!1,renderTo:c,forExport:!0,renderer:"SVGRenderer",width:g,height:h});d.exporting.enabled=!1;delete d.data;d.series=[];q(b.series,function(a){j=l(a.userOptions,{animation:!1,enableMouseTracking:!1,showCheckbox:!1,visible:a.visible});j.isInternal||d.series.push(j)});a&&q(["xAxis", 16 | "yAxis"],function(b){q(F(a[b]),function(a,c){d[b][c]=l(d[b][c],a)})});e=new f.Chart(d,b.callback);q(["xAxis","yAxis"],function(a){q(b[a],function(b,c){var d=e[a][c],f=b.getExtremes(),g=f.userMin,f=f.userMax;d&&(g!==void 0||f!==void 0)&&d.setExtremes(g,f,!0,!1)})});g=e.getChartHTML();d=null;e.destroy();u(c);if(m&&(c=g.match(/<\/svg>(.*?$)/)))c=''+c[1]+"",g=g.replace("",c+""); 17 | g=this.sanitizeSVG(g);return g=g.replace(/(url\(#highcharts-[0-9]+)"/g,"$1").replace(/"/g,"'")},getSVGForExport:function(a,b){var e=this.options.exporting;return this.getSVG(l({chart:{borderRadius:0}},e.chartOptions,b,{exporting:{sourceWidth:a&&a.sourceWidth||e.sourceWidth,sourceHeight:a&&a.sourceHeight||e.sourceHeight}}))},exportChart:function(a,b){var e=this.getSVGForExport(a,b),a=l(this.options.exporting,a);f.post(a.url,{filename:a.filename||"chart",type:a.type,width:a.width||0,scale:a.scale|| 18 | 2,svg:e},a.formAttributes)},print:function(){var a=this,b=a.container,e=[],c=b.parentNode,f=k.body,j=f.childNodes,h=a.options.exporting.printMaxWidth,d,m,n;if(!a.isPrinting){a.isPrinting=!0;a.pointer.reset(null,0);E(a,"beforePrint");if(n=h&&a.chartWidth>h)d=a.hasUserSize,m=[a.chartWidth,a.chartHeight,!1],a.setSize(h,a.chartHeight,!1);q(j,function(a,b){if(a.nodeType===1)e[b]=a.style.display,a.style.display="none"});f.appendChild(b);t.focus();t.print();setTimeout(function(){c.appendChild(b);q(j,function(a, 19 | b){if(a.nodeType===1)a.style.display=e[b]});a.isPrinting=!1;if(n)a.setSize.apply(a,m),a.hasUserSize=d;E(a,"afterPrint")},1E3)}},contextMenu:function(a,b,e,c,f,j,h){var d=this,m=d.options.navigation,n=m.menuItemStyle,o=d.chartWidth,p=d.chartHeight,l="cache-"+a,i=d[l],w=G(f,j),y,z,t,u=function(b){d.pointer.inClass(b.target,a)||z()};if(!i)d[l]=i=s("div",{className:a},{position:"absolute",zIndex:1E3,padding:w+"px"},d.container),y=s("div",null,r({MozBoxShadow:"3px 3px 10px #888",WebkitBoxShadow:"3px 3px 10px #888", 20 | boxShadow:"3px 3px 10px #888"},m.menuStyle),i),z=function(){x(i,{display:"none"});h&&h.setState(0);d.openMenu=!1},v(i,"mouseleave",function(){t=setTimeout(z,500)}),v(i,"mouseenter",function(){clearTimeout(t)}),v(k,"mouseup",u),v(d,"destroy",function(){D(k,"mouseup",u)}),q(b,function(a){if(a){var b=a.separator?s("hr",null,null,y):s("div",{onmouseover:function(){x(this,m.menuItemHoverStyle)},onmouseout:function(){x(this,n)},onclick:function(b){b&&b.stopPropagation();z();a.onclick&&a.onclick.apply(d, 21 | arguments)},innerHTML:a.text||d.options.lang[a.textKey]},r({cursor:"pointer"},n),y);d.exportDivElements.push(b)}}),d.exportDivElements.push(y,i),d.exportMenuWidth=i.offsetWidth,d.exportMenuHeight=i.offsetHeight;b={display:"block"};e+d.exportMenuWidth>o?b.right=o-e-f-w+"px":b.left=e-w+"px";c+j+d.exportMenuHeight>p&&h.alignOptions.verticalAlign!=="top"?b.bottom=p-c-w+"px":b.top=c+j-w+"px";x(i,b);d.openMenu=!0},addButton:function(a){var b=this,e=b.renderer,c=l(b.options.navigation.buttonOptions,a),g= 22 | c.onclick,j=c.menuItems,h,d,m={stroke:c.symbolStroke,fill:c.symbolFill},n=c.symbolSize||12;if(!b.btnCount)b.btnCount=0;if(!b.exportDivElements)b.exportDivElements=[],b.exportSVGElements=[];if(c.enabled!==!1){var o=c.theme,p=o.states,k=p&&p.hover,p=p&&p.select,i;delete o.states;g?i=function(a){a.stopPropagation();g.call(b,a)}:j&&(i=function(){b.contextMenu(d.menuClassName,j,d.translateX,d.translateY,d.width,d.height,d);d.setState(2)});c.text&&c.symbol?o.paddingLeft=f.pick(o.paddingLeft,25):c.text|| 23 | r(o,{width:c.width,height:c.height,padding:0});d=e.button(c.text,0,0,i,o,k,p).attr({title:b.options.lang[c._titleKey],"stroke-linecap":"round",zIndex:3});d.menuClassName=a.menuClassName||"highcharts-menu-"+b.btnCount++;c.symbol&&(h=e.symbol(c.symbol,c.symbolX-n/2,c.symbolY-n/2,n,n).attr(r(m,{"stroke-width":c.symbolStrokeWidth||1,zIndex:1})).add(d));d.add().align(r(c,{width:d.width,x:f.pick(c.x,B)}),!0,"spacingBox");B+=(d.width+c.buttonSpacing)*(c.align==="right"?-1:1);b.exportSVGElements.push(d,h)}}, 24 | destroyExport:function(a){var a=a.target,b,e;for(b=0;b 2 | 3 | 4 | 5 | 空闲内存实时监控 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 15 | 143 | 144 | -------------------------------------------------------------------------------- /Example/Real_Time_Monitor_Ssh/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixuancn/MeepoPS/15e664b9bbe22fea25b7bf7f55b53dadc9b82c55/Example/Real_Time_Monitor_Ssh/demo.gif -------------------------------------------------------------------------------- /Example/Real_Time_Monitor_Ssh/real_time_monitor_ssh_server.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 17 | 18 | //设置MeepoPS实例名称 19 | $http->instanceName = 'MeepoPS-Http'; 20 | 21 | //启动放在上层 -------------------------------------------------------------------------------- /Example/Real_Time_Monitor_Ssh/websocket_server.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 17 | 18 | //设置MeepoPS实例名称 19 | $webServer->instanceName = 'MeepoPS-WebSocket'; 20 | $webServer->callbackNewData = 'callbackNewData'; 21 | $webServer->callbackWSDisconnect = function ($connect){ 22 | \MeepoPS\Core\Timer::delOne($connect->business['timer_id']); 23 | }; 24 | 25 | //以下为回调函数, 业务相关. 26 | function callbackNewData($connect, $data){ 27 | $data = json_decode($data, true); 28 | if($data['action'] === 'real-time-monitor/memfree'){ 29 | memfree($connect, $data['param']); 30 | } 31 | } 32 | 33 | /** 34 | * 获取空闲内存 35 | * @param $connect 36 | * @param $param 37 | */ 38 | function memfree($connect, $param){ 39 | if(empty($connect->business['ssh']) || !is_resource($connect->business['ssh'])){ 40 | if(true !== _connectServer($connect, $param['ip'], $param['ssh_username'], $param['ssh_password'])){ 41 | return; 42 | } 43 | } 44 | $cmd = 'cat /proc/meminfo | grep "MemFree:"'; 45 | $connect->business['timer_id'] = \MeepoPS\Core\Timer::add('_execCmd', array($connect, $cmd), $param['interval']); 46 | } 47 | 48 | /** 49 | * ssh的方式登陆到服务器上 50 | * @param $connect 51 | * @param $ip 52 | * @param $username 53 | * @param $password 54 | * @return bool 55 | */ 56 | function _connectServer($connect, $ip, $username, $password){ 57 | try{ 58 | $ssh = @ssh2_connect($ip, 22); 59 | if (!$ssh){ 60 | $connect->send(returnJson('', 1, 'Connection failed.')); 61 | return false; 62 | } 63 | if(!@ssh2_auth_password($ssh, $username, $password)){ 64 | $connect->send(returnJson('', 2, 'Auth failed.')); 65 | return false; 66 | } 67 | $connect->business['ssh'] = $ssh; 68 | return true; 69 | }catch (\Exception $e){ 70 | $connect->send(returnJson('', 3, 'Connect Exception: ' . json_encode($e))); 71 | return false; 72 | } 73 | } 74 | 75 | /** 76 | * 执行命令并发送消息 77 | * @param $connect 78 | * @param $cmd 79 | */ 80 | function _execCmd($connect, $cmd){ 81 | try{ 82 | $stream = @ssh2_exec($connect->business['ssh'], $cmd); 83 | if(!$stream){ 84 | $connect->send(returnJson('', 4, 'ssh2_exec failed.', $connect->business['timer_id'])); 85 | } 86 | if(!@stream_set_blocking($stream, true)){ 87 | $connect->send(returnJson('', 5, 'stream_set_blocking failed.', $connect->business['timer_id'])); 88 | } 89 | $string = @fgets($stream, 1000); 90 | if(!@preg_match('/memfree:(.*)kb/', strtolower($string), $data)){ 91 | $connect->send(returnJson('', 6, 'No field MemFree, or units of measurement is not KB', $connect->business['timer_id'])); 92 | return; 93 | } 94 | $connect->send(returnJson(trim($data[1]))); 95 | }catch (\Exception $e){ 96 | $connect->send(returnJson('', 7, 'Exec cmd failed: ' . json_encode($e), $connect->business['timer_id'])); 97 | } 98 | } 99 | 100 | /** 101 | * 将数据整理为JSON 102 | * 成功时errCode=0 103 | * 失败时不需要data 104 | * @param $data string|array 105 | * @param int $errCode 106 | * @param string $errMsg 107 | * @param string $delTimerId 108 | * @return string 109 | */ 110 | function returnJson($data, $errCode=0, $errMsg='', $delTimerId=0){ 111 | if(intval($delTimerId)){ 112 | \MeepoPS\Core\Timer::delOne($delTimerId); 113 | } 114 | return json_encode( 115 | array('data' => $data, 'errcode' => $errCode, 'errmsg' => $errMsg) 116 | ); 117 | } -------------------------------------------------------------------------------- /Example/Web_Server/README.md: -------------------------------------------------------------------------------- 1 | # 一个WebServer 2 | 一个WebServer是指, 一台服务器作为服务端, 启动webserver_server.php. 此时, 启动了WebServer实例. 3 | 4 | WebServer实例监听19910端口, 充当Web服务器(替代Apache/Nginx). 5 | 6 | ### real_time_monitor_ssh_server.php 是服务端. 7 | 使用: 8 | ```bash 9 | sudo php webserver_server.php start 10 | ``` 11 | 启动. 12 | 13 | 守护进程模式启动使用: 14 | ```bash 15 | sudo php webserver_server.php start -d 16 | ``` 17 | 18 | ### WebServer配置 19 | 请在config.ini中配置域名和根目录, 示例如下: 20 | ``` 21 | http_domain_document_list = 'localhost:19910 & /var/www/MeepoPS/Example/Web_Server/Web/' 22 | ``` 23 | 24 | 打开浏览器, 访问http://localhost:19910/index.php -------------------------------------------------------------------------------- /Example/Web_Server/Web/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 页面已迷路... 6 | 7 | 8 | 28 | 29 | 30 |
    31 |
    32 |

    404!

    33 |

    页面迷路了!

    34 |
    35 |
    36 |
    copyright © 2016 MeepoPS: 纯PHP的Socket服务 all rights reserved.
    37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/Web_Server/Web/blog.html: -------------------------------------------------------------------------------- 1 | 博客 -------------------------------------------------------------------------------- /Example/Web_Server/Web/index.php: -------------------------------------------------------------------------------- 1 | 7 | alert("请登陆"); 8 | location.href="login.php"; 9 | document.onmousedown=click 10 | '; 11 | } else { 12 | echo '登陆成功!
    '; 13 | echo '用户名: ' . $_SESSION['user_info']['username'] . '. 密码: ' . $_SESSION['user_info']['password'] . '
    '; 14 | echo '点击测试上传文件'; 15 | } -------------------------------------------------------------------------------- /Example/Web_Server/Web/login.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 账号:
    4 | 密码:
    5 | 6 |
    7 | 8 | $username, 'password' => $password); 15 | $_SESSION['user_info'] = $userInfo; 16 | if(!empty($_SESSION['user_info']['username']) && !empty($_SESSION['user_info']['password'])){ 17 | \MeepoPS\Api\Http::setHeader('Location: index.php'); 18 | } 19 | } -------------------------------------------------------------------------------- /Example/Web_Server/Web/upload.php: -------------------------------------------------------------------------------- 1 |
    2 | 文件名:
    3 | 文件名:
    4 | 文件名:
    5 | 文件名:
    6 | 选择文件:
    7 | 8 |
    9 | childProcessCount = 1; 21 | 22 | //设置MeepoPS实例名称 23 | $http->instanceName = 'MeepoPS-Http'; 24 | 25 | //设置错误页 26 | //404, 设置一个专门的页面来展示 27 | $http->setErrorPage('404', __DIR__ . '/Example/Web/404.html'); 28 | //403, 使用默认样式(其实就是居中了一句话), 自定义错误描述 29 | $http->setErrorPage('403', '您没有被授权访问!'); 30 | 31 | //启动MeepoPS 32 | \MeepoPS\runMeepoPS(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2014 Lane 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /MeepoPS/Api/Cbnsq.php: -------------------------------------------------------------------------------- 1 | '/var/www/') 23 | private $_documentRoot = array(); 24 | //错误页 array('404' => '页面不在', '503' => 'tpl/err_503.html') 25 | private $_errorPage = array(); 26 | //用户自定义的callbackNewData 27 | private $_userCallbackNewData; 28 | //Session 29 | private static $_sessionInstance; 30 | 31 | /** 32 | * WebServer constructor. 33 | * @param string $host string 需要监听的地址 34 | * @param string $port string 需要监听的端口 35 | * @param array $contextOptionList 36 | */ 37 | public function __construct($host, $port, $contextOptionList = array()) 38 | { 39 | if (!$host || !$port) { 40 | return; 41 | } 42 | parent::__construct('http', $host, $port, $contextOptionList); 43 | //域名和目录 44 | $domainDocumentList = explode('|', MEEPO_PS_HTTP_DOMAIN_DOCUMENT_LIST); 45 | foreach($domainDocumentList as $domainDocument){ 46 | if(!$domainDocument){ 47 | continue; 48 | } 49 | $domainDocument = explode('&', $domainDocument); 50 | if(empty($domainDocument[0]) || empty($domainDocument[1])){ 51 | continue; 52 | } 53 | $this->_documentRoot[strtolower(trim($domainDocument[0]))] = trim($domainDocument[1]); 54 | } 55 | //默认页 56 | $this->_defaultIndexList = explode(',', MEEPO_PS_HTTP_DEFAULT_PAGE); 57 | } 58 | 59 | /** 60 | * 运行一个WebService实例 61 | */ 62 | public function run() 63 | { 64 | if (empty($this->_documentRoot)) { 65 | Log::write('not set document root.', 'ERROR'); 66 | } 67 | //设置MeepoPS的回调. 68 | $this->_userCallbackNewData = $this->callbackNewData; 69 | $this->callbackNewData = array($this, 'callbackNewData'); 70 | //运行MeepoPS 71 | parent::run(); 72 | } 73 | 74 | /** 75 | * 设置域名和路径 76 | * @param $domain 77 | * @param $path 78 | * @return bool 79 | */ 80 | public function setDocument($domain, $path){ 81 | if(!$domain || !$path){ 82 | return false; 83 | } 84 | if(!file_exists($path) || !is_dir($path)){ 85 | return false; 86 | } 87 | $this->_documentRoot[$domain] = $path; 88 | return true; 89 | } 90 | 91 | /** 92 | * 设置http头 93 | * @param string $string 头字符串 94 | * @param bool $replace 是否用后面的头替换前面相同类型的头.即相同的多个头存在时,后来的会覆盖先来的. 95 | * @param int $httpResponseCode 头字符串 96 | * @return bool 97 | */ 98 | public static function setHeader($string, $replace = true, $httpResponseCode = 0) 99 | { 100 | return \MeepoPS\Core\ApplicationProtocol\Http::setHeader($string, $replace, $httpResponseCode); 101 | } 102 | 103 | /** 104 | * 删除header()设置的HTTP头信息 105 | * @param string $name 删除指定的头信息 106 | */ 107 | public static function delHttpHeader($name) 108 | { 109 | \MeepoPS\Core\ApplicationProtocol\Http::delHttpHeader($name); 110 | } 111 | 112 | /** 113 | * 设置Cookie 114 | * 参数意义请参考setcookie() 115 | * @param string $name 116 | * @param string $value 117 | * @param integer $maxage 118 | * @param string $path 119 | * @param string $domain 120 | * @param bool $secure 121 | * @param bool $httpOnly 122 | * @return bool 123 | */ 124 | public static function setCookie($name, $value = '', $maxage = 0, $path = '', $domain = '', $secure = false, $httpOnly = false) 125 | { 126 | return \MeepoPS\Core\ApplicationProtocol\Http::setCookie($name, $value, $maxage, $path, $domain, $secure, $httpOnly); 127 | } 128 | 129 | /** 130 | * 开启SESSION 131 | * 功能类似session_start(); 132 | * @return bool 133 | */ 134 | public static function sessionStart() 135 | { 136 | self::$_sessionInstance->start(); 137 | } 138 | 139 | /** 140 | * 写入SESSION 141 | * 默认情况下自动执行 142 | * 功能类似session_write_close(); 143 | * @return bool 144 | */ 145 | public static function sessionWrite() 146 | { 147 | self::$_sessionInstance->write(); 148 | } 149 | 150 | /** 151 | * 获取SESSION ID 152 | * 功能类似session_id(); 153 | * @return bool 154 | */ 155 | public static function sessionId() 156 | { 157 | self::$_sessionInstance->id(); 158 | } 159 | 160 | /** 161 | * SESSION 162 | * 功能类似session_destroy(); 163 | * @return bool 164 | */ 165 | public static function sessionDestroy() 166 | { 167 | self::$_sessionInstance->destroy(); 168 | } 169 | 170 | /** 171 | * 结束,程序不在向下执行。退出业务逻辑返回到MeepoPS 172 | * 功能和普通Web开发的Exit一样 173 | * 就像平时exit()后退出PHP,交接给Nginx继续执行。 174 | * 可是在MeepoPS中, 不能使用exit() 175 | * @return bool 176 | */ 177 | public static function end($msg='') 178 | { 179 | echo $msg; 180 | throw new \Exception('meepops_http_end'); 181 | } 182 | 183 | /** 184 | * 收到新消息的回调 185 | * @param $connect 186 | * @param $data array 187 | */ 188 | public function callbackNewData($connect, $data) 189 | { 190 | if (!empty($this->_userCallbackNewData)) { 191 | try { 192 | call_user_func_array($this->_userCallbackNewData, array($connect, $data)); 193 | } catch (\Exception $e) { 194 | Tcp::$statistics['exception_count']++; 195 | Log::write('MeepoPS: execution callback function callbackNewData-' . json_encode($this->_userCallbackNewData) . ' throw exception' . json_encode($e), 'ERROR'); 196 | } 197 | } 198 | self::$_sessionInstance = new Session(); 199 | //解析来访的URL 200 | $requestUri = parse_url($_SERVER['REQUEST_URI']); 201 | if (!$requestUri) { 202 | $this->setHeader('HTTP/1.1 400 Bad Request'); 203 | $this->_close($connect, $this->_getErrorPage(400, 'Bad Request')); 204 | return; 205 | } 206 | $urlPath = !empty($requestUri['path']) ? $requestUri['path'] : ''; 207 | $_SERVER['HTTP_HOST'] = !empty($_SERVER['HTTP_HOST']) ? strtolower($_SERVER['HTTP_HOST']) : ''; 208 | $urlPath = $urlPath[strlen($urlPath) - 1] === '/' ? substr($urlPath, 0, -1) : $urlPath; 209 | $documentRoot = isset($this->_documentRoot[$_SERVER['HTTP_HOST']]) ? $this->_documentRoot[$_SERVER['HTTP_HOST']] : current($this->_documentRoot); 210 | $filename = $documentRoot . $urlPath; 211 | //清除文件状态缓存 212 | clearstatcache(); 213 | //如果是目录 214 | if (is_dir($filename)) { 215 | //如果缺省首页存在 216 | if ($this->_defaultIndexList) { 217 | foreach ($this->_defaultIndexList as $index) { 218 | $file = $filename . '/' . trim($index); 219 | if (is_file($file)) { 220 | $filename = $file; 221 | break; 222 | } 223 | } 224 | } else { 225 | $this->setHeader("HTTP/1.1 403 Forbidden"); 226 | $this->_close($connect, $this->_getErrorPage(403, 'Forbidden')); 227 | return; 228 | } 229 | } 230 | //文件是否有效 231 | if (!is_file($filename)) { 232 | // 博客特殊处理 start 233 | $_SERVER['REQUEST_URI'] = $urlPath; 234 | $filename = $documentRoot . '/index.php'; 235 | if (!is_file($filename)) { 236 | // 博客特殊处理 end 237 | $this->setHeader("HTTP/1.1 404 Not Found"); 238 | $this->_close($connect, $this->_getErrorPage(404, 'File not found')); 239 | return; 240 | } 241 | } 242 | //文件是否可读 243 | if (!is_readable($filename)) { 244 | $this->setHeader("HTTP/1.1 403 Forbidden"); 245 | $this->_close($connect, $this->_getErrorPage(403, 'Forbidden')); 246 | return; 247 | } 248 | //获取文件后缀 249 | $urlPathInfo = pathinfo($filename); 250 | $fileExt = isset($urlPathInfo['extension']) ? $urlPathInfo['extension'] : ''; 251 | //访问的路径是否是指定根目录的子目录 252 | $realFilename = realpath($filename); 253 | $documentRootRealPath = realpath($documentRoot) . '/'; 254 | if (!$realFilename || !$documentRootRealPath || strpos($realFilename, $documentRootRealPath) !== 0) { 255 | $this->setHeader("HTTP/1.1 403 Forbidden"); 256 | $this->_close($connect, $this->_getErrorPage(403, 'Forbidden')); 257 | return; 258 | } 259 | //如果请求的是PHP文件 260 | if ($fileExt === 'php') { 261 | ob_start(); 262 | try{ 263 | include $realFilename; 264 | }catch (\Exception $e){ 265 | if ($e->getMessage() != 'meepops_http_end') { 266 | Log::write('Exception was introduced to the PHP file.', 'WARNING'); 267 | } 268 | } 269 | $content = ob_get_clean(); 270 | $this->_close($connect, $content); 271 | return; 272 | } 273 | //静态文件 274 | $mimeType = $this->_getMimeTypeByExt($fileExt); 275 | $fileExt && isset($mimeType) ? $this->setHeader('Content-Type: ' . $mimeType) : $this->setHeader('Content-Type: text/html; charset=utf-8'); 276 | //获取文件更新时间 277 | $fileMtime = filemtime($filename); 278 | if($fileMtime){ 279 | $fileMtime = date('D, d M Y H:i:s', $fileMtime) . ' GMT'; 280 | $this->setHeader('Last-Modified: ' . $fileMtime); 281 | //静态文件未改变.则返回304 282 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $fileMtime === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { 283 | $this->setHeader('HTTP/1.1 304 Not Modified'); 284 | $this->_close($connect, ''); 285 | return; 286 | } 287 | } 288 | //给客户端发送消息,并且断开连接. 289 | $this->_close($connect, file_get_contents($realFilename)); 290 | return; 291 | } 292 | 293 | private function _close($connect, $data){ 294 | $connect->close($data); 295 | self::$_sessionInstance->write($_SESSION); 296 | self::$_sessionInstance = null; 297 | } 298 | 299 | /** 300 | * 根据文件后缀获取MIME TYPE 301 | * @param 文件后缀 302 | * @return string 303 | */ 304 | private function _getMimeTypeByExt($ext) 305 | { 306 | //从nginx1.10.0的mime.types中复制的, 然后转换成数组 307 | $mimeTypeList = array('html' => 'text/html', 'htm' => 'text/html', 'shtml' => 'text/html', 'css' => 'text/css', 'xml' => 'text/xml', 'gif' => 'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'js' => 'application/javascript', 'atom' => 'application/atom+xml', 'rss' => 'application/rss+xml', 'mml' => 'text/mathml', 'txt' => 'text/plain', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'wml' => 'text/vnd.wap.wml', 'htc' => 'text/x-component', 'png' => 'image/png', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'wbmp' => 'image/vnd.wap.wbmp', 'ico' => 'image/x-icon', 'jng' => 'image/x-jng', 'bmp' => 'image/x-ms-bmp', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'webp' => 'image/webp', 'woff' => 'application/font-woff', 'jar' => 'application/java-archive', 'war' => 'application/java-archive', 'ear' => 'application/java-archive', 'json' => 'application/json', 'hqx' => 'application/mac-binhex40', 'doc' => 'application/msword', 'pdf' => 'application/pdf', 'ps' => 'application/postscript', 'eps' => 'application/postscript', 'ai' => 'application/postscript', 'rtf' => 'application/rtf', 'm3u8' => 'application/vnd.apple.mpegurl', 'xls' => 'application/vnd.ms-excel', 'eot' => 'application/vnd.ms-fontobject', 'ppt' => 'application/vnd.ms-powerpoint', 'wmlc' => 'application/vnd.wap.wmlc', 'kml' => 'application/vnd.google-earth.kml+xml', 'kmz' => 'application/vnd.google-earth.kmz', '7z' => 'application/x-7z-compressed', 'cco' => 'application/x-cocoa', 'jardiff' => 'application/x-java-archive-diff', 'jnlp' => 'application/x-java-jnlp-file', 'run' => 'application/x-makeself', 'pl' => 'application/x-perl', 'pm' => 'application/x-perl', 'prc' => 'application/x-pilot', 'pdb' => 'application/x-pilot', 'rar' => 'application/x-rar-compressed', 'rpm' => 'application/x-redhat-package-manager', 'sea' => 'application/x-sea', 'swf' => 'application/x-shockwave-flash', 'sit' => 'application/x-stuffit', 'tcl' => 'application/x-tcl', 'tk' => 'application/x-tcl', 'der' => 'application/x-x509-ca-cert', 'pem' => 'application/x-x509-ca-cert', 'crt' => 'application/x-x509-ca-cert', 'xpi' => 'application/x-xpinstall', 'xhtml' => 'application/xhtml+xml', 'xspf' => 'application/xspf+xml', 'zip' => 'application/zip', 'bin' => 'application/octet-stream', 'exe' => 'application/octet-stream', 'dll' => 'application/octet-stream', 'deb' => 'application/octet-stream', 'dmg' => 'application/octet-stream', 'iso' => 'application/octet-stream', 'img' => 'application/octet-stream', 'msi' => 'application/octet-stream', 'msp' => 'application/octet-stream', 'msm' => 'application/octet-stream', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'kar' => 'audio/midi', 'mp3' => 'audio/mpeg', 'ogg' => 'audio/ogg', 'm4a' => 'audio/x-m4a', 'ra' => 'audio/x-realaudio', '3gpp' => 'video/3gpp', '3gp' => 'video/3gpp', 'ts' => 'video/mp2t', 'mp4' => 'video/mp4', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mov' => 'video/quicktime', 'webm' => 'video/webm', 'flv' => 'video/x-flv', 'm4v' => 'video/x-m4v', 'mng' => 'video/x-mng', 'asx' => 'video/x-ms-asf', 'asf' => 'video/x-ms-asf', 'wmv' => 'video/x-ms-wmv', 'avi' => 'video/x-msvideo'); 308 | return isset($mimeTypeList[$ext]) ? $mimeTypeList[$ext] : ''; 309 | } 310 | 311 | /** 312 | * 设置HTTP错误页 313 | * @param $httpCode int HTTP状态码 314 | * @param $description string 该状态码时, 错误页的描述或自定义的错误页面路径 315 | */ 316 | public function setErrorPage($httpCode, $description) 317 | { 318 | $this->_errorPage[$httpCode] = $description; 319 | } 320 | 321 | /** 322 | * 获取错误页面 323 | * @param $httpCode 324 | * @param string $message 325 | * @param string $description 326 | * @return bool|string 327 | */ 328 | private function _getErrorPage($httpCode, $message = '', $description = '') 329 | { 330 | if (!$httpCode) { 331 | return false; 332 | } 333 | if (!isset($this->_errorPage[$httpCode])) { 334 | $httpCodeArray = \MeepoPS\Core\ApplicationProtocol\Http::getHttpCode(); 335 | $message = $message ? $message : ''; 336 | $description = $description ? $description : (isset($httpCodeArray[$httpCode]) ? $httpCodeArray[$httpCode] : ''); 337 | $display = '%s %s

    %s %s


    %s
    '; 338 | $display = sprintf($display, $httpCode, $message, $httpCode, $message, $description); 339 | } else { 340 | //如果是文件 341 | if (file_exists($this->_errorPage[$httpCode])) { 342 | ob_start(); 343 | include $this->_errorPage[$httpCode]; 344 | $display = ob_get_clean(); 345 | } else { 346 | $display = '%s

    %s


    %s
    '; 347 | $display = sprintf($display, $httpCode, $httpCode, $this->_errorPage[$httpCode]); 348 | } 349 | } 350 | return $display; 351 | } 352 | } -------------------------------------------------------------------------------- /MeepoPS/Api/Telnet.php: -------------------------------------------------------------------------------- 1 | _transferApiName = $apiName; 77 | $this->_transferHost = $host; 78 | $this->_transferPort = $port; 79 | $this->_container = strtolower($container); 80 | $this->_contextOptionList = $contextOptionList; 81 | } 82 | 83 | /** 84 | * 启动三层模型 85 | */ 86 | public function run(){ 87 | //根据容器选项启动, 如果为空, 则全部启动 88 | switch($this->_container){ 89 | case 'confluence': 90 | $this->_initConfluence(); 91 | break; 92 | case 'transfer': 93 | $this->_initTransfer(); 94 | break; 95 | case 'business': 96 | $this->_initBusiness(); 97 | break; 98 | default: 99 | $this->_initConfluence(); 100 | echo "MeepoPS Confluence Start: \033[40G[\033[49;32;5mOK\033[0m]\n"; 101 | $this->_initTransfer(); 102 | echo "MeepoPS Transfer Start: \033[40G[\033[49;32;5mOK\033[0m]\n"; 103 | $this->_initBusiness(); 104 | echo "MeepoPS Business Start: \033[40G[\033[49;32;5mOK\033[0m]\n"; 105 | break; 106 | } 107 | } 108 | 109 | /** 110 | * 魔术方法。所有不可访问的、不存在的属性, 统统赋值给Transfer所使用的API类 111 | * __set 112 | * @param $name 113 | * @param $value 114 | */ 115 | public function __set($name, $value){ 116 | //四个回调函数需要单独收集, 其他的和普通属性一样, 直接赋值给API类 117 | if(in_array($name, array('callbackStartInstance', 'callbackConnect', 'callbackNewData', 'callbackConnectClose'))){ 118 | self::$callbackList[$name] = $value; 119 | }else{ 120 | $this->_transferApiPropertyAndMethod['property'][$name] = $value; 121 | } 122 | } 123 | 124 | public function __call($name, $arguments) 125 | { 126 | $this->_transferApiPropertyAndMethod['method'][$name] = $arguments; 127 | } 128 | 129 | 130 | private function _initConfluence(){ 131 | $confluence = new Confluence(self::$innerProtocol, $this->confluenceIp, $this->confluencePort); 132 | $confluence->childProcessCount = $this->_confluenceChildProcessCount; 133 | $confluence->instanceName = $this->confluenceName; 134 | } 135 | 136 | private function _initTransfer(){ 137 | $transfer = new Transfer($this->_transferApiName, $this->_transferHost, $this->_transferPort, $this->_contextOptionList); 138 | $transfer->innerIp = $this->transferInnerIp; 139 | $transfer->innerPort = $this->transferInnerPort; 140 | 141 | $transfer->encodeFunction = $this->transferEncodeFunction; 142 | 143 | $transfer->confluenceIp = $this->confluenceInnerIp; 144 | $transfer->confluencePort = $this->confluencePort; 145 | //设置API接口的属性 146 | if($this->_transferApiPropertyAndMethod['property']){ 147 | foreach($this->_transferApiPropertyAndMethod['property'] as $methodName => $arguments){ 148 | $transfer->setApiClassProperty($methodName, $arguments); 149 | } 150 | } 151 | //调用API接口的方法 152 | if(!empty($this->_transferApiPropertyAndMethod['method'])){ 153 | foreach($this->_transferApiPropertyAndMethod['method'] as $methodName => $arguments){ 154 | $transfer->callApiClassMethod($methodName, $arguments); 155 | } 156 | } 157 | } 158 | 159 | private function _initBusiness(){ 160 | $business = new Business(); 161 | $business->childProcessCount = $this->businessChildProcessCount; 162 | $business->instanceName = $this->businessName; 163 | 164 | $business->confluenceIp = $this->confluenceInnerIp; 165 | $business->confluencePort = $this->confluencePort; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /MeepoPS/Api/Websocket.php: -------------------------------------------------------------------------------- 1 | = '5.3') { 17 | $fatalErrorList[] = "Fatal error: MeepoPS requires PHP version must be greater than 5.3(contain 5.3). Because MeepoPS used php-namespace"; 18 | } 19 | 20 | //MeepoPS不支持在Windows下运行 21 | if (strpos(strtolower(PHP_OS), 'win') === 0) { 22 | $fatalErrorList[] = "Fatal error: MeepoPS not support Windows. Because the required extension is supported only by Linux, such as php-pcntl, php-posix"; 23 | } 24 | 25 | //MeepoPS必须运行在命令行下 26 | if (php_sapi_name() != 'cli') { 27 | $fatalErrorList[] = "Fatal error: MeepoPS must run in command line!"; 28 | } 29 | 30 | //是否已经安装PHP-pcntl 扩展 31 | if (!extension_loaded('pcntl')) { 32 | $fatalErrorList[] = "Fatal error: MeepoPS must require php-pcntl extension. Because the signal monitor, multi process needs php-pcntl\nPHP manual: http://php.net/manual/zh/intro.pcntl.php"; 33 | } 34 | 35 | //是否已经安装PHP-posix 扩展 36 | if (!extension_loaded('posix')) { 37 | $fatalErrorList[] = "Fatal error: MeepoPS must require php-posix extension. Because send a signal to a process, get the real user ID of the current process needs php-posix\nPHP manual: http://php.net/manual/zh/intro.posix.php"; 38 | } 39 | 40 | //启动参数是否正确 41 | global $argv; 42 | if (!isset($argv[1]) || !in_array($argv[1], array('start', 'stop', 'restart', 'status', 'kill'))) { 43 | $fatalErrorList[] = "Fatal error: MeepoPS needs to receive the execution of the operation.\nUsage: php index.php start|stop|restart|status|kill\n\""; 44 | } 45 | //日志路径是否已经配置 46 | if (!defined('MEEPO_PS_LOG_PATH_PREFIX')) { 47 | $fatalErrorList[] = "Fatal error: Log file path prefix is not defined. Please define MEEPO_PS_LOG_PATH_PREFIX in Config.php"; 48 | } else { 49 | //日志目录是否存在 50 | if (!file_exists(dirname(MEEPO_PS_LOG_PATH_PREFIX))) { 51 | if (@!mkdir(dirname(MEEPO_PS_LOG_PATH_PREFIX), 0777, true)) { 52 | $fatalErrorList[] = "Fatal error: Log file directory creation failed: " . dirname(MEEPO_PS_LOG_PATH_PREFIX); 53 | } 54 | } 55 | //日志目录是否可写 56 | if (!is_writable(dirname(MEEPO_PS_LOG_PATH_PREFIX))) { 57 | $fatalErrorList[] = "Fatal error: Log file path not to be written: " . dirname(MEEPO_PS_LOG_PATH_PREFIX); 58 | } 59 | } 60 | 61 | //MeepoPS主进程Pid文件路径是否已经配置 62 | if (!defined('MEEPO_PS_MASTER_PID_PATH')) { 63 | $fatalErrorList[] = "Fatal error: master pid file path is not defined. Please define MEEPO_PS_MASTER_PID_PATH in Config.php"; 64 | } else { 65 | //MeepoPS主进程Pid文件目录是否存在 66 | if (!file_exists(dirname(MEEPO_PS_MASTER_PID_PATH))) { 67 | if (@!mkdir(dirname(MEEPO_PS_MASTER_PID_PATH), 0777, true)) { 68 | $fatalErrorList[] = "Fatal error: master pid file directory creation failed: " . dirname(MEEPO_PS_MASTER_PID_PATH); 69 | } 70 | } 71 | //MeepoPS主进程Pid文件目录是否可写 72 | if (!is_writable(dirname(MEEPO_PS_MASTER_PID_PATH))) { 73 | $fatalErrorList[] = "Fatal error: master pid file path not to be written: " . dirname(MEEPO_PS_MASTER_PID_PATH); 74 | } 75 | } 76 | 77 | //标准输出路径是否已经配置 78 | if (!defined('MEEPO_PS_STDOUT_PATH')) { 79 | $warningErrorList[] = "Warning error: standard output file path is not defined. Please define MEEPO_PS_STDOUT_PATH in Config.php"; 80 | } else if (MEEPO_PS_STDOUT_PATH !== '/dev/null') { 81 | //标准输出目录是否存在 82 | if (!file_exists(dirname(MEEPO_PS_STDOUT_PATH))) { 83 | if (@!mkdir(dirname(MEEPO_PS_STDOUT_PATH), 0777, true)) { 84 | $warningErrorList[] = "Warning error: standard output file directory creation failed: " . dirname(MEEPO_PS_STDOUT_PATH); 85 | } 86 | } 87 | //标准输出目录是否可写 88 | if (!is_writable(dirname(MEEPO_PS_STDOUT_PATH))) { 89 | $warningErrorList[] = "Warning error: standard output file path not to be written: " . dirname(MEEPO_PS_STDOUT_PATH); 90 | } 91 | } 92 | 93 | //统计信息存储文件路径是否已经配置 94 | if (!defined('MEEPO_PS_STATISTICS_PATH')) { 95 | $warningErrorList[] = "Warning error: statistics file path is not defined. Please define MEEPO_PS_STATISTICS_PATH in Config.php"; 96 | } else { 97 | //统计信息存储文件目录是否存在 98 | if (!file_exists(dirname(MEEPO_PS_STATISTICS_PATH))) { 99 | if (@!mkdir(dirname(MEEPO_PS_STATISTICS_PATH), 0777, true)) { 100 | $warningErrorList[] = "Warning error: statistics file directory creation failed: " . dirname(MEEPO_PS_STATISTICS_PATH); 101 | } 102 | } 103 | //统计信息存储文件目录是否可写 104 | if (!is_writable(dirname(MEEPO_PS_STATISTICS_PATH))) { 105 | $warningErrorList[] = "Warning error: statistics file path not to be written: " . dirname(MEEPO_PS_STATISTICS_PATH); 106 | } 107 | } 108 | 109 | if ($fatalErrorList) { 110 | $fatalErrorList = implode("\n\n", $fatalErrorList); 111 | exit($fatalErrorList); 112 | } 113 | 114 | if ($warningErrorList) { 115 | $warningErrorList = implode("\n\n", $warningErrorList); 116 | echo $warningErrorList . "\n\n"; 117 | } 118 | 119 | unset($fatalErrorList); 120 | unset($warningErrorList); -------------------------------------------------------------------------------- /MeepoPS/Core/Config.php: -------------------------------------------------------------------------------- 1 | _eventBase = event_base_new(); 32 | } 33 | 34 | /** 35 | * 添加事件 36 | * @param $callback string|array 回调函数 37 | * @param $args array 回调函数的参数 38 | * @param $resource resource|int 读写事件中表示socket资源,定时器任务中表示时间(int,秒),信号回调中表示信号(int) 39 | * @param $type int 类型 40 | * @return int|false 41 | */ 42 | public function add($callback, array $args, $resource, $type) 43 | { 44 | $type = intval($type); 45 | //1.创建一个新的事件 46 | $event = event_new(); 47 | switch ($type) { 48 | //读/写/信号事件 49 | //必须指定EV_PERSIST.不指定这个属性的话,回调函数被触发后事件会被删除 50 | case self::EVENT_TYPE_READ: 51 | case self::EVENT_TYPE_WRITE: 52 | case self::EVENT_TYPE_SIGNAL: 53 | $libeventType = $type === self::EVENT_TYPE_READ ? (EV_READ | EV_PERSIST) : 54 | ($type === self::EVENT_TYPE_WRITE ? (EV_WRITE | EV_PERSIST) : (EV_SIGNAL | EV_PERSIST)); 55 | $uniqueId = (int)($resource); 56 | //2.准备想要在event_add中添加事件 57 | if (event_set($event, $resource, $libeventType, $callback, $args) 58 | //3.关联事件到事件base 59 | && event_base_set($event, $this->_eventBase) 60 | //4.向指定的设置中添加一个执行事件 61 | && event_add($event) 62 | ) { 63 | $this->_eventList[$type][$uniqueId] = $event; 64 | return $uniqueId; 65 | } 66 | return false; 67 | //永久性定时任务/一次性定时任务 68 | case self::EVENT_TYPE_TIMER: 69 | case self::EVENT_TYPE_TIMER_ONCE: 70 | $timerId = (int)$event; 71 | $intervalMicrosecond = $resource * 1000000; 72 | if (event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timerId) 73 | && event_base_set($event, $this->_eventBase) 74 | && event_add($event, $intervalMicrosecond) 75 | ) { 76 | $this->_eventList[$type][$timerId] = array($callback, (array)$args, $event, $type, $intervalMicrosecond); 77 | return $timerId; 78 | } 79 | return false; 80 | default : 81 | Log::write('Libevent: add failed. ' . $type . ' is unrecognized type', 'WARNING'); 82 | return false; 83 | } 84 | } 85 | 86 | /** 87 | * 删除指定的事件 88 | * @param $resource resource|int 读写事件中表示socket资源,定时器任务中表示时间(int,秒),信号回调中表示信号(int) 89 | * @param $type int 类型 90 | */ 91 | public function delOne($resource, $type) 92 | { 93 | $type = intval($type); 94 | $uniqueId = (int)($resource); 95 | switch ($type) { 96 | case self::EVENT_TYPE_READ: 97 | case self::EVENT_TYPE_WRITE: 98 | case self::EVENT_TYPE_SIGNAL: 99 | $event = !empty($this->_eventList[$type][$uniqueId]) ? $this->_eventList[$type][$uniqueId] : ''; 100 | break; 101 | case self::EVENT_TYPE_TIMER: 102 | case self::EVENT_TYPE_TIMER_ONCE: 103 | $event = ''; 104 | if(!empty($this->_eventList[self::EVENT_TYPE_TIMER][$uniqueId][2])){ 105 | $event = $this->_eventList[self::EVENT_TYPE_TIMER][$uniqueId][2]; 106 | }else if(!empty($this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$uniqueId][2])){ 107 | $event = $this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$uniqueId][2]; 108 | } 109 | break; 110 | default: 111 | Log::write('Libevent: del one failed. ' . $type . ' is unrecognized type', 'WARNING'); 112 | return; 113 | } 114 | if (!empty($event)) { 115 | event_del($event); 116 | unset($this->_eventList[$type][$uniqueId]); 117 | } 118 | } 119 | 120 | /** 121 | * 清除所有的计时器事件 122 | * @return mixed 123 | */ 124 | public function delAllTimer() 125 | { 126 | //从永久性定时任务中移除 127 | foreach ($this->_eventList[self::EVENT_TYPE_TIMER] as $timerEvent) { 128 | //从设置的事件中移除事件 129 | event_del($timerEvent[2]); 130 | } 131 | //从一次性定时任务中移除 132 | foreach ($this->_eventList[self::EVENT_TYPE_TIMER_ONCE] as $timerEvent) { 133 | //从设置的事件中移除事件 134 | event_del($timerEvent[2]); 135 | } 136 | } 137 | 138 | /** 139 | * 循环事件 140 | */ 141 | public function loop() 142 | { 143 | //处理事件,根据指定的base来处理事件循环 144 | event_base_loop($this->_eventBase); 145 | } 146 | 147 | /** 148 | * 定时器的回调函数 149 | * Libevent会先回调本方法.本方法再回调使用方设置的回调函数 150 | * @param int $timerId 定时器ID 151 | */ 152 | public function timerCallback($timerId) 153 | { 154 | if (isset($this->_eventList[self::EVENT_TYPE_TIMER][$timerId])) { 155 | $timer = $this->_eventList[self::EVENT_TYPE_TIMER][$timerId]; 156 | } else if (isset($this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$timerId])) { 157 | $timer = $this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$timerId]; 158 | } else { 159 | Log::write('Libevent: timer event handle failed. timer id is ' . $timerId, 'WARNING'); 160 | return; 161 | } 162 | //如果是永久定时器任务.则再次加入事件 163 | if ($timer[3] === self::EVENT_TYPE_TIMER) { 164 | event_add($timer[2], $timer[4]); 165 | } 166 | //如果是一次性定时器任务.则从任务列表中删除 167 | if ($timer[3] === self::EVENT_TYPE_TIMER_ONCE) { 168 | $this->delOne($timerId, self::EVENT_TYPE_TIMER_ONCE); 169 | } 170 | //触发回调函数 171 | try { 172 | call_user_func_array($timer[0], $timer[1]); 173 | } catch (\Exception $e) { 174 | Log::write('MeepoPS: execution callback function timer callback-' . $timer[0] . ' throw exception' . json_encode($e), 'ERROR'); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Event/Select.php: -------------------------------------------------------------------------------- 1 | _initEventList(); 40 | //初始化一个队列 41 | $this->_splPriorityQueue = new \SplPriorityQueue(); 42 | //设置队列为提取数组包含值和优先级 43 | $this->_splPriorityQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 44 | } 45 | 46 | /** 47 | * 初始化事件列表 48 | */ 49 | private function _initEventList() 50 | { 51 | $this->_eventList = array( 52 | self::EVENT_TYPE_READ => array(), 53 | self::EVENT_TYPE_WRITE => array(), 54 | self::EVENT_TYPE_SIGNAL => array(), 55 | self::EVENT_TYPE_TIMER => array(), 56 | self::EVENT_TYPE_TIMER_ONCE => array(), 57 | ); 58 | } 59 | 60 | /** 61 | * 添加事件 62 | * @param $callback string|array 回调函数 63 | * @param $args array 回调函数的参数 64 | * @param $resource resource|int 读写事件中表示socket资源,定时器任务中表示时间(int,秒),信号回调中表示信号(int) 65 | * @param $type int 类型 66 | * @return int|false 67 | */ 68 | public function add($callback, array $args, $resource, $type) 69 | { 70 | //如果是读写事件,则两者和不能超过MEEPO_PS_EVENT_SELECT_MAX_SIZE限制. 71 | if (($type == EventInterface::EVENT_TYPE_READ || $type == EventInterface::EVENT_TYPE_WRITE)) { 72 | $total = (count($this->_eventList[self::EVENT_TYPE_READ]) + count($this->_eventList[self::EVENT_TYPE_WRITE])); 73 | if (MEEPO_PS_EVENT_SELECT_MAX_SIZE < $total) { 74 | Log::write('Select maximum number of listening resources is set to ' . MEEPO_PS_EVENT_SELECT_MAX_SIZE . ', but you have descriptors numbered at least as high as ' . $total . '. If you want to change this value, you must recompile PHP (--enable-fd-setsize=2048)', 'WARNING'); 75 | return false; 76 | } 77 | } 78 | $type = intval($type); 79 | switch ($type) { 80 | //读/写/信号事件 81 | case self::EVENT_TYPE_READ: 82 | case self::EVENT_TYPE_WRITE: 83 | case self::EVENT_TYPE_SIGNAL: 84 | $uniqueId = (int)($resource); 85 | $this->_eventList[$type][$uniqueId] = array($callback, $resource); 86 | if ($type === self::EVENT_TYPE_READ) { 87 | $this->_readEventResourceList[$uniqueId] = $resource; 88 | } else if ($type === self::EVENT_TYPE_WRITE) { 89 | $this->_writeEventResourceList[$uniqueId] = $resource; 90 | } else { 91 | pcntl_signal($resource, array($this, 'signalCallback'), false); 92 | } 93 | return $uniqueId; 94 | //永久性定时任务/一次性定时任务 95 | case self::EVENT_TYPE_TIMER: 96 | case self::EVENT_TYPE_TIMER_ONCE: 97 | //下次运行时间 = 当前时间 + 时间间隔 98 | $runTime = microtime(true) + $resource; 99 | //添加到定时器任务 100 | $timerId = $this->_timerId++; 101 | $this->_eventList[$type][$timerId] = array($callback, $args, $resource, $type); 102 | //入队,优先级大的排在队列的前面, 即,下次运行时间越大,优先级越低. 所以传入下次运行时间的负数 103 | $this->_splPriorityQueue->insert($timerId, -$runTime); 104 | //执行 105 | $this->_runTimerEvent(); 106 | return $timerId; 107 | default: 108 | Log::write('MeepoPS: Event library Select adds an unknown type: ' . $type, 'ERROR'); 109 | return false; 110 | } 111 | } 112 | 113 | /** 114 | * 信号回调函数 115 | * @param $signal int 信号 116 | */ 117 | public function signalCallback($signal) 118 | { 119 | $uniqueId = (int)($signal); 120 | try { 121 | call_user_func($this->_eventList[self::EVENT_TYPE_SIGNAL][$uniqueId][0], $uniqueId); 122 | } catch (\Exception $e) { 123 | Log::write('MeepoPS: execution callback function run timer signal callback-' . $this->_eventList[self::EVENT_TYPE_SIGNAL][$uniqueId][0] . ' throw exception' . json_encode($e), 'ERROR'); 124 | } 125 | } 126 | 127 | /** 128 | * 执行定时器任务 129 | */ 130 | private function _runTimerEvent() 131 | { 132 | //如果队列不为空 133 | while (!$this->_splPriorityQueue->isEmpty()) { 134 | //查看队列顶部的最高优先级的数据(只看,不出队) 135 | $data = $this->_splPriorityQueue->top(); 136 | //优先级 = 下次运行时间的负数 137 | $runTime = -$data['priority']; 138 | $nowTime = microtime(true); 139 | //当前时间还没有到下次运行时间 140 | if ($nowTime < $runTime) { 141 | $this->_selectTimeout = ($runTime - $nowTime) * 1000000; 142 | return; 143 | } 144 | //定时器任务的Id 145 | $timerId = $data['data']; 146 | //将数据出队 147 | $this->_splPriorityQueue->extract(); 148 | //从定时器任务列表中获取本次的任务 149 | if (isset($this->_eventList[self::EVENT_TYPE_TIMER][$timerId])) { 150 | $type = self::EVENT_TYPE_TIMER; 151 | } else if (isset($this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$timerId])) { 152 | $type = self::EVENT_TYPE_TIMER_ONCE; 153 | } else { 154 | continue; 155 | } 156 | $task = $this->_eventList[$type][$timerId]; 157 | //如果是长期的定时器任务.则计算下次执行时间,并重新根据优先级入队 158 | if ($type === EventInterface::EVENT_TYPE_TIMER) { 159 | $nextRunTime = $nowTime + $task[2]; 160 | $this->_splPriorityQueue->insert($timerId, -$nextRunTime); 161 | //如果是一次性定时任务,则从队列列表中删除 162 | } else if ($type === EventInterface::EVENT_TYPE_TIMER_ONCE) { 163 | $this->delOne($timerId, EventInterface::EVENT_TYPE_TIMER_ONCE); 164 | } 165 | try { 166 | call_user_func_array($task[0], $task[1]); 167 | } catch (\Exception $e) { 168 | Log::write('MeepoPS: execution callback function run timer event-' . $task[0] . ' throw exception' . json_encode($e), 'ERROR'); 169 | } 170 | continue; 171 | } 172 | $this->_selectTimeout = MEEPO_PS_EVENT_SELECT_POLL_TIMEOUT; 173 | } 174 | 175 | /** 176 | * 删除指定的事件 177 | * @param $resource resource|int 读写事件中表示socket资源,定时器任务中表示时间(int,秒),信号回调中表示信号(int) 178 | * @param $type int 类型 179 | */ 180 | public function delOne($resource, $type) 181 | { 182 | $uniqueId = (int)($resource); 183 | if ($type === self::EVENT_TYPE_READ) { 184 | unset($this->_readEventResourceList[$uniqueId]); 185 | unset($this->_eventList[$type][$uniqueId]); 186 | } else if ($type === self::EVENT_TYPE_WRITE) { 187 | unset($this->_writeEventResourceList[$uniqueId]); 188 | unset($this->_eventList[$type][$uniqueId]); 189 | } else if($type === self::EVENT_TYPE_TIMER){ 190 | if(isset($this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$uniqueId])){ 191 | unset($this->_eventList[self::EVENT_TYPE_TIMER_ONCE][$uniqueId]); 192 | }else{ 193 | unset($this->_eventList[self::EVENT_TYPE_TIMER][$uniqueId]); 194 | } 195 | }else{ 196 | //将信号设置为忽略信号 197 | pcntl_signal($resource, SIG_IGN); 198 | } 199 | } 200 | 201 | /** 202 | * 清除所有的计时器事件 203 | */ 204 | public function delAllTimer() 205 | { 206 | //清空计时器任务列表 207 | $this->_eventList[self::EVENT_TYPE_TIMER] = array(); 208 | $this->_eventList[self::EVENT_TYPE_TIMER_ONCE] = array(); 209 | //清空队列 210 | $this->_splPriorityQueue = new \SplPriorityQueue(); 211 | $this->_splPriorityQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); 212 | } 213 | 214 | /** 215 | * 循环事件 216 | */ 217 | public function loop() 218 | { 219 | //检测空轮询 220 | $this->_checkEmptyLoop(); 221 | $e = null; 222 | while (true) { 223 | //调用等待信号的处理器.即收到信号后执行通过pcntl_signal安装的信号处理函数.此处不会阻塞一直等待 224 | pcntl_signal_dispatch(); 225 | //已添加的读事件 - 每个元素都是socket资源 226 | $readList = $this->_readEventResourceList; 227 | //已添加的写事件 - 每个元素都是socket资源 228 | $writeList = $this->_writeEventResourceList; 229 | //监听读写事件列表,如果哪个有变化则发回变化数量.同时引用传入的两个列表将会变化 230 | //请注意:stream_select()最多只能接收1024个监听 231 | $selectNum = @stream_select($readList, $writeList, $e, 0, $this->_selectTimeout); 232 | //执行定时器队列 233 | if (!$this->_splPriorityQueue->isEmpty()) { 234 | $this->_runTimerEvent(); 235 | } 236 | //如果没有变化的读写事件则开始执行下次等待 237 | if (!$selectNum) { 238 | continue; 239 | } 240 | //处理接收到的读和写请求 241 | $selectList = array( 242 | array('type' => EventInterface::EVENT_TYPE_READ, 'data' => $readList), 243 | array('type' => EventInterface::EVENT_TYPE_WRITE, 'data' => $writeList), 244 | ); 245 | foreach ($selectList as $select) { 246 | foreach ($select['data'] as $item) { 247 | $uniqueId = (int)($item); 248 | if (isset($this->_eventList[$select['type']][$uniqueId])) { 249 | try { 250 | call_user_func($this->_eventList[$select['type']][$uniqueId][0], $this->_eventList[$select['type']][$uniqueId][1]); 251 | } catch (\Exception $e) { 252 | Log::write('MeepoPS: execution callback function select loop-' . $this->_eventList[$select['type']][$uniqueId][0] . ' throw exception' . json_encode($e), 'ERROR'); 253 | } 254 | } 255 | } 256 | } 257 | } 258 | } 259 | 260 | /** 261 | * 如果读事件列表为空,则创建空socket链接,以避免造成空轮询. 262 | * 空轮询会使得CPU瞬间飙升至100% 263 | */ 264 | private function _checkEmptyLoop() 265 | { 266 | if (empty($this->_eventList[self::EVENT_TYPE_READ])) { 267 | $channel = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); 268 | if ($channel) { 269 | stream_set_blocking($channel[0], 0); 270 | $this->_eventList[self::EVENT_TYPE_READ][0] = array('', $channel[0]); 271 | fclose($channel[1]); 272 | } 273 | } 274 | } 275 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Func.php: -------------------------------------------------------------------------------- 1 | $count){ 38 | echo $instanceExitInfo['info']['instanceName'] . ' | Status: ' . $status . ' | count: ' . $count . "\n"; 39 | } 40 | } 41 | echo empty($masterStatistic['instance_exit_info']) ? " No Exit Process \n" : ''; 42 | } 43 | 44 | /** 45 | * 输出子进程 46 | */ 47 | private static function _displayStatisticChildProcess(){ 48 | $childStatisticList = @array_map('file_get_contents', glob(MEEPO_PS_STATISTICS_PATH . '_child_*')); 49 | echo "---------------------Child Process Statistic----------------------\n"; 50 | foreach($childStatisticList as $childStatistic){ 51 | echo "##################################################################\n"; 52 | $childStatistic = json_decode($childStatistic, true); 53 | echo 'Instance Name: ' . $childStatistic['instance_name'] . ' ( ' . $childStatistic['bind'] . " )\n"; 54 | echo 'Pid: ' . $childStatistic['pid'] . ' | Memory: ' . $childStatistic['memory'] . ' | Exception count: ' . $childStatistic['transport_protocol_statistics']['exception_count'] . "\n"; 55 | echo 'Total Connect: ' . $childStatistic['transport_protocol_statistics']['total_connect_count'] . ' | Current Connect: ' . $childStatistic['transport_protocol_statistics']['current_connect_count'] . "\n"; 56 | echo 'Total Read: ' . $childStatistic['transport_protocol_statistics']['total_read_count'] . ' | Total Read Failed: ' . $childStatistic['transport_protocol_statistics']['total_read_failed_count'] . "\n"; 57 | echo 'Total Read Package: ' . $childStatistic['transport_protocol_statistics']['total_read_package_count'] . ' | Total Read Package Failed: ' . $childStatistic['transport_protocol_statistics']['total_read_package_failed_count'] . "\n"; 58 | echo 'Total Send: ' . $childStatistic['transport_protocol_statistics']['total_send_count'] . ' | Total Send Failed: ' . $childStatistic['transport_protocol_statistics']['total_send_failed_count'] . "\n"; 59 | } 60 | echo "##################################################################\n"; 61 | } 62 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Timer.php: -------------------------------------------------------------------------------- 1 | add($callback, $args, $intervalSecond, $isAlways ? EventInterface::EVENT_TYPE_TIMER : EventInterface::EVENT_TYPE_TIMER_ONCE); 54 | } else { 55 | pcntl_alarm(1); 56 | $startTime = time() + $intervalSecond; 57 | if(is_null($timerId)) { 58 | $timerId = self::$_id++; 59 | } 60 | self::$_taskList[$timerId] = array($callback, $args, $startTime, $intervalSecond, $isAlways); 61 | return $timerId; 62 | } 63 | } 64 | 65 | /** 66 | * 删除一个定时器 67 | * @param $timerId 68 | */ 69 | public static function delOne($timerId) 70 | { 71 | if (!is_null(self::$_event)) { 72 | self::$_event->delOne($timerId, EventInterface::EVENT_TYPE_TIMER); 73 | } else { 74 | self::$_waitDelList[$timerId] = null; 75 | unset(self::$_taskList[$timerId]); 76 | } 77 | } 78 | 79 | /** 80 | * 删除所有的定时器任务 81 | */ 82 | public static function delAll() 83 | { 84 | self::$_taskList = array(); 85 | self::$_waitDelList = array(); 86 | pcntl_alarm(0); 87 | if (!is_null(self::$_event)) { 88 | self::$_event->delAllTimer(); 89 | } 90 | } 91 | 92 | /** 93 | * 信号处理函数 94 | */ 95 | public static function signalCallback() 96 | { 97 | //没有事件机制,并且队列不为空,则使用alarm信号 98 | if (is_null(self::$_event) && !empty(self::$_taskList)) { 99 | //创建一个计时器,每秒向进程发送一个alarm信号。 100 | pcntl_alarm(1); 101 | self::_execute(); 102 | } 103 | } 104 | 105 | /** 106 | * 执行任务 107 | */ 108 | private static function _execute() 109 | { 110 | $nowTime = time(); 111 | foreach (self::$_taskList as $timerId => $task) { 112 | //检测是否即将删除 113 | if(isset(self::$_waitDelList[$timerId])){ 114 | unset(self::$_taskList[$timerId]); 115 | unset(self::$_waitDelList[$timerId]); 116 | continue; 117 | } 118 | //当前时间小于启动时间,则不启动该时间段任务 119 | if ($nowTime < $task[2]) { 120 | continue; 121 | } 122 | //如果是持续性定时器任务,则覆盖任务队列中本次任务xinxi 123 | if ($task[4]) { 124 | self::add($task[0], $task[1], $task[3], $task[4], $timerId); 125 | } 126 | //执行回调函数 127 | try { 128 | call_user_func_array($task[0], $task[1]); 129 | } catch (\Exception $e) { 130 | Log::write('MeepoPS: execution callback function timer execute-' . $task[0] . ' throw exception' . json_encode($e), 'ERROR'); 131 | } 132 | //一次性定时器任务,删除本次已经执行的任务 133 | if (!$task[4]) { 134 | unset(self::$_taskList[$timerId]); 135 | } 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /MeepoPS/Core/TransportProtocol/README.md: -------------------------------------------------------------------------------- 1 | # TransportProtocol 2 | 3 | 本目录是传输层协议目录, 一个协议为一个文件。 如TCP协议, 是Tcp.php -------------------------------------------------------------------------------- /MeepoPS/Core/TransportProtocol/TransportProtocolInterface.php: -------------------------------------------------------------------------------- 1 | 0, 21 | //总读取数 22 | 'total_read_count' => 0, 23 | //总读取失败数 24 | 'total_read_failed_count' => 0, 25 | //总读取包数 26 | 'total_read_package_count' => 0, 27 | //总读取包失败数 28 | 'total_read_package_failed_count' => 0, 29 | //总发送数 30 | 'total_send_count' => 0, 31 | //总发送失败数 32 | 'total_send_failed_count' => 0, 33 | //异常数 34 | 'exception_count' => 0, 35 | //当前链接数 36 | 'current_connect_count' => 0, 37 | ); 38 | 39 | /** 40 | * 构造函数 41 | * @param $socket resource 由stream_socket_accept()返回 42 | * @param $clientAddress string 由stream_socket_accept()的第三个参数$peerName 43 | * @param $applicationProtocol string 应用层协议, 默认为空 44 | */ 45 | // abstract public function __construct($socket, $clientAddress, $applicationProtocol = ''); 46 | 47 | /** 48 | * 读取数据 49 | * @param $connect resource 由stream_socket_accept()返回 50 | * @param $isDestroy bool 如果fread读取到的是空数据或者false的话,是否销毁链接.默认为true 51 | */ 52 | abstract public function read($connect, $isDestroy = true); 53 | 54 | /** 55 | * 发送数据 56 | * @param $data mixed 待发送的数据 57 | * @param $isEncode bool 发送前是否根据应用层协议转码 58 | */ 59 | abstract public function send($data, $isEncode = true); 60 | 61 | /** 62 | * 关闭客户端链接 63 | * @param $data string 关闭链接前发送的消息 64 | */ 65 | abstract public function close($data = ''); 66 | 67 | /** 68 | * 获取客户端地址 69 | * @return array|int 成功返回array[0]是ip,array[1]是端口. 失败返回false 70 | */ 71 | abstract public function getClientAddress(); 72 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/AppBusiness.php: -------------------------------------------------------------------------------- 1 | send($data); 74 | } 75 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/Business.php: -------------------------------------------------------------------------------- 1 | callbackStartInstance = array($this, 'callbackBusinessStartInstance'); 25 | parent::__construct(); 26 | } 27 | 28 | /** 29 | * 进程启动时, 链接到Confluence 30 | * 作为客户端, 连接到中心机(Confluence层), 获取Transfer列表 31 | */ 32 | public function callbackBusinessStartInstance(){ 33 | //作为客户端, 连接到中心机(Confluence层), 获取Transfer列表 34 | $businessAndConfluenceService = new BusinessAndConfluenceService(); 35 | $businessAndConfluenceService->confluenceIp = $this->confluenceIp; 36 | $businessAndConfluenceService->confluencePort = $this->confluencePort; 37 | $businessAndConfluenceService->connectConfluence(); 38 | } 39 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/BusinessAndConfluenceService.php: -------------------------------------------------------------------------------- 1 | _businessAndTranferService = new BusinessAndTransferService(); 27 | } 28 | 29 | /** 30 | * 向中心机(Confluence层)通知自己, 表示新的Business进程已经上线, 并定时获得Confluence推送的消息。 31 | */ 32 | public function connectConfluence(){ 33 | $this->_confluence = new TcpClient(Trident::$innerProtocol, $this->confluenceIp, $this->confluencePort, true); 34 | //实例化一个空类 35 | $this->_confluence->instance = new \stdClass(); 36 | $this->_confluence->instance->callbackNewData = array($this, 'callbackConfluenceNewData'); 37 | $this->_confluence->instance->callbackConnectClose = array($this, 'callbackConfluenceConnectClose'); 38 | $this->_confluence->confluence = array(); 39 | $this->_confluence->connect(); 40 | $result = $this->_confluence->send(array('token'=>'', 'msg_type'=>MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_CONFLUENCE)); 41 | if($result === false){ 42 | Log::write('Business: add confluence failed.' . 'WARNING'); 43 | $this->_closeConfluence(); 44 | } 45 | } 46 | 47 | /** 48 | * 收到Confluence发来的消息时 49 | * @param $connect 50 | * @param $data 51 | */ 52 | public function callbackConfluenceNewData($connect, $data){ 53 | switch($data['msg_type']){ 54 | case MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_CONFLUENCE: 55 | $this->_addConfluenceResponse($connect, $data); 56 | break; 57 | case MsgTypeConst::MSG_TYPE_PING: 58 | $this->_receivePingFromConfluence($connect, $data); 59 | break; 60 | case MsgTypeConst::MSG_TYPE_RESET_TRANSFER_LIST: 61 | $this->_businessAndTranferService->resetTransferList($data); 62 | break; 63 | default: 64 | Log::write('Business: Confluence message type is not supported, data=' . json_encode($data) . ', client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 65 | $this->_closeConfluence(); 66 | return; 67 | } 68 | } 69 | 70 | /** 71 | * 收到Confluence发来的加入Confluence确认信息 72 | * 如果Confluence确认, 则增加定时器, 检测是否正常收到PING。如果不正常, 则尝试重连。检测频率和Confluence发送PING的频率一样 73 | * @param $connect 74 | * @param $data 75 | */ 76 | private function _addConfluenceResponse($connect, $data){ 77 | //链接失败 78 | if($data['msg_content'] !== 'OK'){ 79 | $this->_closeConfluence(); 80 | return; 81 | } 82 | //链接成功 83 | $this->_confluence->confluence['confluence_no_ping_limit'] = 0; 84 | //添加计时器, 如果一定时间内没有收到中心机发来的PING, 则断开本次链接并重新向中心机发起注册 85 | $this->_confluence->confluence['waiter_confluence_ping_timer_id'] = Timer::add(function(){ 86 | if((++$this->_confluence->confluence['confluence_no_ping_limit']) >= MEEPO_PS_TRIDENT_SYS_PING_NO_RESPONSE_LIMIT){ 87 | //断开链接 88 | $this->_closeConfluence(); 89 | } 90 | }, array(), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 91 | Log::write('Business: add Confluence success. ' . $this->confluenceIp . ':' . $this->confluencePort); 92 | } 93 | 94 | /** 95 | * 收到Confluence发来的PING消息 96 | * 收到PING后, 将没有收到PING的次数-1 97 | * @param $connect 98 | * @param $data 99 | */ 100 | private function _receivePingFromConfluence($connect, $data){ 101 | if($data['msg_content'] !== 'PING'){ 102 | return; 103 | } 104 | if($this->_confluence->confluence['confluence_no_ping_limit'] >= 1){ 105 | $this->_confluence->confluence['confluence_no_ping_limit']--; 106 | } 107 | $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_PONG, 'msg_content'=>'PONG')); 108 | } 109 | 110 | /** 111 | * 如果Business和Confluence的链接断开, 则尝试重连 112 | */ 113 | public function callbackConfluenceConnectClose(){ 114 | Timer::add(array($this, 'connectConfluence'), array(), 1, false); 115 | } 116 | 117 | /** 118 | * 重新连入Confluence 119 | * 包含断开链接,并且重新链接。 120 | * 调用此方法时自动调用_closeConfluence()方法 121 | */ 122 | private function _reConnectConfluence(){ 123 | $this->_closeConfluence(); 124 | $this->connectConfluence(); 125 | } 126 | 127 | /** 128 | * 断开和Confluence的链接 129 | */ 130 | private function _closeConfluence(){ 131 | if(isset($this->_confluence->confluence['waiter_confluence_ping_timer_id'])){ 132 | Timer::delOne($this->_confluence->confluence['waiter_confluence_ping_timer_id']); 133 | } 134 | if (method_exists($this->_confluence, 'close')) { 135 | $this->_confluence->close(); 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/BusinessAndTransferService.php: -------------------------------------------------------------------------------- 1 | _connectTransfer($transfer['ip'], $transfer['port']); 44 | } 45 | } 46 | 47 | /** 48 | * 作为客户端, 链接到Transfer 49 | * 50 | */ 51 | private function _connectTransfer($ip, $port){ 52 | $transfer = new TcpClient(Trident::$innerProtocol, $ip, $port, false); 53 | //实例化一个空类 54 | $transfer->instance = new \stdClass(); 55 | $transfer->instance->callbackNewData = array($this, 'callbackTransferNewData'); 56 | $transfer->instance->callbackConnectClose = array($this, 'callbackTransferConnectClose'); 57 | $transfer->transfer = array(); 58 | $transfer->connect(); 59 | $result = $transfer->send(array('token'=>'', 'msg_type'=>MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_TRANSFER)); 60 | if($result === false){ 61 | Log::write('Business: Link transfer failed.' . $ip . ':' . $port , 'WARNING'); 62 | $this->_close($transfer); 63 | return false; 64 | } 65 | $transferKey = Tool::encodeTransferAddress($ip, $port); 66 | $this->_connectingTransferList[$transferKey] = array('ip' => $ip, 'port' => $port); 67 | return $result; 68 | } 69 | 70 | public function callbackTransferConnectClose($connect){ 71 | Timer::add(array($this, 'reConnectTransfer'), array($connect), 1, false); 72 | } 73 | 74 | /** 75 | * 回调函数 - 收到Transfer发来新消息时 76 | * 只接受新增Business、PING两种消息 77 | * @param $connect 78 | * @param $data 79 | */ 80 | public function callbackTransferNewData($connect, $data){ 81 | switch($data['msg_type']){ 82 | case MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_TRANSFER: 83 | $this->_addTransferResponse($connect, $data); 84 | break; 85 | case MsgTypeConst::MSG_TYPE_PING: 86 | $this->_receiveTransferPing($connect, $data); 87 | break; 88 | case MsgTypeConst::MSG_TYPE_APP_MSG: 89 | $this->_appMessage($connect, $data); 90 | break; 91 | default: 92 | Log::write('Business: Transfer message type is not supported, data=' . json_encode($data). ', client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 93 | return; 94 | } 95 | } 96 | 97 | private function _addTransferResponse($connect, $data){ 98 | //链接失败 99 | if($data['msg_content'] !== 'OK' || empty($data['msg_attachment']['ip']) || empty($data['msg_attachment']['port'])){ 100 | $this->_close($connect); 101 | return; 102 | } 103 | $transferKey = Tool::encodeTransferAddress($data['msg_attachment']['ip'], $data['msg_attachment']['port']); 104 | if(!isset($this->_connectingTransferList[$transferKey])){ 105 | Log::write('Business: Rejected an unknown Transfer that would like to join.', 'WARNING'); 106 | $this->_close($connect); 107 | return; 108 | } 109 | //链接成功 110 | $connect->business['transfer_no_ping_limit'] = 0; 111 | //添加计时器, 如果一定时间内没有收到Transfer发来的PING, 则断开本次链接并重新链接到Transfer 112 | $connect->business['waiter_transfer_ping_timer_id'] = Timer::add(function()use($connect){ 113 | $connect->business['transfer_no_ping_limit']++; 114 | if( $connect->business['transfer_no_ping_limit'] >= MEEPO_PS_TRIDENT_SYS_PING_NO_RESPONSE_LIMIT){ 115 | //断开连接 116 | $this->_close($connect); 117 | } 118 | }, array(), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 119 | self::$transferList[$transferKey] = $connect; 120 | Log::write('Business: link Transfer success. ' . $connect->host . ':' . $connect->port); 121 | } 122 | 123 | private function _receiveTransferPing($connect, $data){ 124 | if($data['msg_content'] !== 'PING'){ 125 | return; 126 | } 127 | if($connect->business['transfer_no_ping_limit'] >= 1){ 128 | $connect->business['transfer_no_ping_limit']--; 129 | } 130 | $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_PONG, 'msg_content'=>'PONG')); 131 | } 132 | 133 | public function reConnectTransfer($connect){ 134 | $this->_close($connect); 135 | $this->_connectTransfer($connect->host, $connect->port); 136 | } 137 | 138 | private function _close($connect){ 139 | if(isset($connect->business['waiter_transfer_ping_timer_id'])){ 140 | Timer::delOne($connect->business['waiter_transfer_ping_timer_id']); 141 | } 142 | if(method_exists($connect, 'close')){ 143 | $connect->close(); 144 | } 145 | } 146 | 147 | /** 148 | * 业务逻辑 149 | * @param $connect 150 | * @param $data 151 | */ 152 | private function _appMessage($connect, $data){ 153 | if(empty(Trident::$callbackList['callbackNewData']) || !is_callable(Trident::$callbackList['callbackNewData'])){ 154 | return; 155 | } 156 | //填充$_SERVER 157 | $_SERVER['MEEPO_PS_MSG_TYPE'] = $data['msg_type']; 158 | $_SERVER['MEEPO_PS_TRANSFER_IP'] = $data['transfer_ip']; 159 | $_SERVER['MEEPO_PS_TRANSFER_PORT'] = $data['transfer_port']; 160 | $_SERVER['MEEPO_PS_CLIENT_IP'] = $data['client_ip']; 161 | $_SERVER['MEEPO_PS_CLIENT_PORT'] = $data['client_port']; 162 | $_SERVER['MEEPO_PS_CLIENT_CONNECT_ID'] = $data['client_connect_id']; 163 | $_SERVER['MEEPO_PS_CLIENT_UNIQUE_ID'] = $data['client_unique_id']; 164 | //填充$_SESSION 165 | $_SESSION = $data['app_business']['session']; 166 | try{ 167 | call_user_func_array(Trident::$callbackList['callbackNewData'], array($connect, $data['msg_content'])); 168 | }catch (\Exception $e){ 169 | Log::write('MeepoPS: Trident execution callback function callbackNewData-' . json_encode(Trident::$callbackList['callbackNewData']) . ' throw exception' . json_encode($e), 'ERROR'); 170 | } 171 | } 172 | 173 | /** 174 | * 发送给Business的消息格式 175 | * @param $data mixed 176 | * @param $msgType string|null 177 | * @return array 178 | */ 179 | public static function formatMessageToTransfer($data, $msgType=null){ 180 | if(is_null($msgType)){ 181 | $msgType = $_SERVER['MEEPO_PS_MSG_TYPE']; 182 | } 183 | $format = array( 184 | 'msg_type' => $msgType, 185 | 'msg_content' => $data, 186 | 'app_business' => array( 187 | 'session' => $_SESSION, 188 | ), 189 | 'transfer_ip' => $_SERVER['MEEPO_PS_TRANSFER_IP'], 190 | 'transfer_port' => $_SERVER['MEEPO_PS_TRANSFER_PORT'], 191 | 'client_ip' => $_SERVER['MEEPO_PS_CLIENT_IP'], 192 | 'client_port' => $_SERVER['MEEPO_PS_CLIENT_PORT'], 193 | 'client_connect_id' => $_SERVER['MEEPO_PS_CLIENT_CONNECT_ID'], 194 | 'client_unique_id' => $_SERVER['MEEPO_PS_CLIENT_UNIQUE_ID'], 195 | ); 196 | return $format; 197 | } 198 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/Confluence.php: -------------------------------------------------------------------------------- 1 | callbackStartInstance = array($this, 'callbackConfluenceStartInstance'); 28 | $this->callbackConnect = array($this, 'callbackConfluenceConnect'); 29 | $this->callbackNewData = array($this, 'callbackConfluenceNewData'); 30 | $this->callbackConnectClose = array($this, 'callbackConfluenceConnectClose'); 31 | parent::__construct($protocol, $host, $port, $contextOptionList); 32 | } 33 | 34 | /** 35 | * 回调函数 - 进程启动时 36 | * 设置定时器, 每段时间强制发送所有的Transfer给所有的Business 37 | */ 38 | public function callbackConfluenceStartInstance(){ 39 | //设置定时器, 每段时间强制发送所有的Transfer给所有的Business 40 | Timer::add(function (){ 41 | $this->broadcastToBusiness(); 42 | }, array(), MEEPO_PS_TRIDENT_SYS_CONFLUENCE_BROADCAST_INTERVAL); 43 | } 44 | 45 | /** 46 | * 定时器广播给Business。 47 | * 定时向所有Business更新一次全量的Transfer 48 | */ 49 | public function timerBroadcastToBusiness(){ 50 | $message = array(); 51 | $message['msg_type'] = MsgTypeConst::MSG_TYPE_RESET_TRANSFER_LIST; 52 | $message['msg_content']['transfer_list'] = $this->_transferList; 53 | foreach($this->_businessList as $business){ 54 | $business->send($message); 55 | } 56 | } 57 | 58 | /** 59 | * 回调函数 - 收到新链接时 60 | * 新链接加入时, 先不做处理, 等待token验证通过后再处理 61 | * token的验证是收到token后校验, 因此会进入callbackConfluenceNewData方法中 62 | * 再此处加入一次性的定时器, 如果N秒后仍然未通过验证, 则断开链接。 63 | * @param $connect 64 | */ 65 | public function callbackConfluenceConnect($connect){ 66 | $connect->confluence['waiter_verify_timer_id'] = Timer::add(function ($connect){ 67 | Log::write('Confluence: Wait for token authentication timeout. client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 68 | $this->_close($connect); 69 | }, array($connect), MEEPO_PS_TRIDENT_SYS_WAIT_VERIFY_TIMEOUT, false); 70 | } 71 | 72 | /** 73 | * 回调函数 - 收到新消息时 74 | * 只接受新增Transfer、新增Business、PONG三种消息 75 | * @param $connect 76 | * @param $data 77 | */ 78 | public function callbackConfluenceNewData($connect, $data){ 79 | switch($data['msg_type']){ 80 | //新的Transfer加入 81 | case MsgTypeConst::MSG_TYPE_ADD_TRANSFER_TO_CONFLUENCE: 82 | //token校验 83 | if(!isset($data['token']) || Tool::verifyAuth($data['token']) !== true){ 84 | Log::write('Confluence: New link token validation failed, client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 85 | $this->_close($connect); 86 | return; 87 | } 88 | $result = $this->_addTransfer($connect, $data); 89 | if($result){ 90 | //删除等待校验超时的定时器 91 | Timer::delOne($connect->confluence['waiter_verify_timer_id']); 92 | }else{ 93 | Log::write('Confluence: _addTransfer return result: ' . $result . ', client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 94 | } 95 | break; 96 | //新的Business加入 97 | case MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_CONFLUENCE: 98 | //token校验 99 | if(!isset($data['token']) || Tool::verifyAuth($data['token']) !== true){ 100 | Log::write('Confluence: New link token validation failed, client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 101 | $this->_close($connect); 102 | return; 103 | } 104 | $result = $this->_addBusiness($connect, $data); 105 | if($result){ 106 | //删除等待校验超时的定时器 107 | Timer::delOne($connect->confluence['waiter_verify_timer_id']); 108 | }else{ 109 | Log::write('Confluence: _addBusiness return result: ' . $result . ', client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 110 | } 111 | break; 112 | //PONG 113 | case MsgTypeConst::MSG_TYPE_PONG: 114 | $this->_pong($connect, $data); 115 | break; 116 | default: 117 | Log::write('Confluence: New link message type is not supported, client address: ' . json_encode($connect->getClientAddress()) .', data=' . json_encode($data), 'ERROR'); 118 | $this->_close($connect); 119 | return; 120 | } 121 | } 122 | 123 | /** 124 | * 回调函数 - 断开链接时 125 | * @param $connect 126 | */ 127 | public function callbackConfluenceConnectClose($connect){ 128 | if(isset($this->_transferList[$connect->id])){ 129 | unset($this->_transferList[$connect->id]); 130 | $this->broadcastToBusiness(); 131 | }else{ 132 | unset($this->_businessList[$connect->id]); 133 | } 134 | } 135 | 136 | /** 137 | * 新增一个Transfer 138 | * @param $connect 139 | * @param $data 140 | * @return bool 141 | */ 142 | private function _addTransfer($connect, $data){ 143 | if(empty($data['msg_content']['ip']) || empty($data['msg_content']['port'])) { 144 | return false; 145 | } 146 | $this->_transferList[$connect->id] = array( 147 | 'ip' => $data['msg_content']['ip'], 148 | 'port' => $data['msg_content']['port'], 149 | ); 150 | //初始化发送PING未收到PONG的次数 151 | $connect->confluence['ping_no_response_count'] = 0; 152 | //设定PING的定时器 153 | $connect->confluence['ping_timer_id'] = Timer::add(function ($connect){ 154 | $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_PING, 'msg_content'=>'PING')); 155 | }, array($connect), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 156 | //检测PING回复情况 157 | $connect->confluence['check_ping_timer_id'] = Timer::add(array($this, 'checkPingLimit'), array($connect), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 158 | $this->broadcastToBusiness(); 159 | //告知对方, 已经收到消息, 并且已经添加成功了 160 | return $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_ADD_TRANSFER_TO_CONFLUENCE, 'msg_content'=>'OK')); 161 | } 162 | 163 | /** 164 | * 新增一个Business 165 | * @param $connect 166 | * @param $data 167 | * @return bool 168 | */ 169 | private function _addBusiness($connect, $data){ 170 | $connect->confluence['ping_no_response_count'] = 0; 171 | $this->_businessList[$connect->id] = $connect; 172 | //设定PING的定时器 173 | $connect->confluence['ping_timer_id'] = Timer::add(function ($connect){ 174 | $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_PING, 'msg_content'=>'PING')); 175 | }, array($connect), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 176 | //检测PING回复情况 177 | $connect->confluence['check_ping_timer_id'] = Timer::add(array($this, 'checkPingLimit'), array($connect), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 178 | $this->broadcastToBusiness($connect); 179 | //告知对方, 已经收到消息, 并且已经添加成功了 180 | return $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_CONFLUENCE, 'msg_content'=>'OK')); 181 | } 182 | 183 | /** 184 | * 接收到消息PONG 185 | * @param $connect 186 | * @param $data string 187 | */ 188 | private function _pong($connect, $data){ 189 | if($data['msg_content'] === 'PONG'){ 190 | $connect->confluence['ping_no_response_count']--; 191 | } 192 | } 193 | 194 | 195 | /** 196 | * 检测PING的回复情况 197 | * @param $connect 198 | */ 199 | public function checkPingLimit($connect){ 200 | $connect->confluence['ping_no_response_count']++; 201 | //超出无响应次数限制时断开连接 202 | if( ($connect->confluence['ping_no_response_count'] - 1) >= MEEPO_PS_TRIDENT_SYS_PING_NO_RESPONSE_LIMIT){ 203 | $conn = ''; 204 | if(isset($this->_businessList[$connect->id])){ 205 | $conn = $this->_businessList[$connect->id]; 206 | }else if(isset($this->_transferList[$connect->id])){ 207 | $conn = $this->_transferList[$connect->id]; 208 | } 209 | Log::write('Confluence: PING no response beyond the limit, has been disconnected, client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 210 | $this->_close($connect); 211 | } 212 | } 213 | 214 | /** 215 | * 关闭连接 216 | * @param $connect 217 | */ 218 | private function _close($connect){ 219 | if(isset($connect->confluence['ping_timer_id'])){ 220 | Timer::delOne($connect->confluence['ping_timer_id']); 221 | } 222 | if(isset($connect->confluence['check_ping_timer_id'])){ 223 | Timer::delOne($connect->confluence['check_ping_timer_id']); 224 | } 225 | if(isset($connect->confluence['waiter_verify_timer_id'])){ 226 | Timer::delOne($connect->confluence['waiter_verify_timer_id']); 227 | } 228 | if (method_exists($connect, 'close')) { 229 | $connect->close(); 230 | } 231 | } 232 | 233 | /** 234 | * 给Business发送消息 235 | * @param null $connect resource 236 | */ 237 | public function broadcastToBusiness($connect=null){ 238 | $message = array(); 239 | $message['msg_type'] = MsgTypeConst::MSG_TYPE_RESET_TRANSFER_LIST; 240 | $message['msg_content']['transfer_list'] = $this->_transferList; 241 | //新增Business时, 只给指定的Business发送 242 | if(!is_null($connect)){ 243 | $connect->send($message); 244 | return; 245 | } 246 | //新增Transfer时, 给所有的Business发送 247 | foreach($this->_businessList as $business){ 248 | $business->send($message); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/MsgTypeConst.php: -------------------------------------------------------------------------------- 1 | $result[0], 'transfer_port' => $result[1]); 24 | } 25 | 26 | public static function encodeClientId($transferIp, $transferPort, $connectId){ 27 | return base64_encode($transferIp . '_' . $transferPort . '_' . $connectId); 28 | } 29 | 30 | public static function decodeClientId($clientId){ 31 | $result = explode('_', base64_decode($clientId)); 32 | return array('transfer_ip' => $result[0], 'transfer_port' => $result[1], 'connect_id' => $result[2]); 33 | } 34 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/Transfer.php: -------------------------------------------------------------------------------- 1 | _transferAndBusinessService = new TransferAndBusinessService(); 43 | $this->_transferAndConfluenceService = new TransferAndConfluenceService(); 44 | $this->_apiClass = new $apiName($host, $port, $contextOptionList); 45 | $this->_apiClass->callbackStartInstance = array($this, 'callbackTransferStartInstance'); 46 | $this->_apiClass->callbackConnect = array($this, 'callbackTransferConnect'); 47 | $this->_apiClass->callbackNewData = array($this, 'callbackTransferNewData'); 48 | $this->_apiClass->callbackConnectClose = array($this, 'callbackTransferConnectClose'); 49 | } 50 | 51 | public function setApiClassProperty($name, $value){ 52 | $this->_apiClass->$name = $value; 53 | } 54 | 55 | public function callApiClassMethod($methodName, array $arguments){ 56 | return call_user_func_array(array($this->_apiClass, $methodName), $arguments); 57 | } 58 | 59 | 60 | 61 | /** 62 | * 进程启动时, 监听端口, 提供给Business, 同时, 链接到Confluence 63 | */ 64 | public function callbackTransferStartInstance($instance){ 65 | $this->innerPort = $this->innerPort + $instance->id; 66 | //监听一个端口, 用来做内部通讯(Business会链接这个端口)。 67 | $this->_transferAndBusinessService->transferIp = $this->innerIp; 68 | $this->_transferAndBusinessService->transferPort = $this->innerPort; 69 | $this->_transferAndBusinessService->encodeFunction = $this->encodeFunction; 70 | $this->_transferAndBusinessService->listenBusiness(); 71 | //向中心机(Confluence层)发送自己的地址和端口, 以便Business感知。 72 | $this->_transferAndConfluenceService->transferIp = $this->innerIp; 73 | $this->_transferAndConfluenceService->transferPort = $this->innerPort; 74 | $this->_transferAndConfluenceService->confluenceIp = $this->confluenceIp; 75 | $this->_transferAndConfluenceService->confluencePort = $this->confluencePort; 76 | $this->_transferAndConfluenceService->connectConfluence(); 77 | try{ 78 | call_user_func_array(Trident::$callbackList['callbackStartInstance'], array($instance)); 79 | }catch (\Exception $e){ 80 | Log::write('MeepoPS: Trident execution callback function callbackStartInstance-' . json_encode(Trident::$callbackList['callbackConnect']) . ' throw exception' . json_encode($e), 'ERROR'); 81 | } 82 | } 83 | 84 | /** 85 | * 回调函数 - 客户端的新链接 86 | * @param $connect 87 | */ 88 | public function callbackTransferConnect($connect){ 89 | $connect->unique_id = Tool::encodeClientId($this->innerIp, $this->innerPort, $connect->id); 90 | self::$clientList[$connect->id] = $connect; 91 | if(empty(Trident::$callbackList['callbackNewData']) || !is_callable(Trident::$callbackList['callbackNewData'])){ 92 | return; 93 | } 94 | try{ 95 | call_user_func_array(Trident::$callbackList['callbackConnect'], array($connect)); 96 | }catch (\Exception $e){ 97 | Log::write('MeepoPS: Trident execution callback function callbackConnect-' . json_encode(Trident::$callbackList['callbackConnect']) . ' throw exception' . json_encode($e), 'ERROR'); 98 | } 99 | } 100 | 101 | /** 102 | * 回调函数 - 客户端发来的新消息 103 | * @param $connect 104 | * @param $data 105 | */ 106 | public function callbackTransferNewData($connect, $data){ 107 | //把消息转发给Business层处理 108 | $this->_transferAndBusinessService->sendToBusiness($connect, $data); 109 | } 110 | 111 | /** 112 | * 回调函数 - 客户端断开链接 113 | * @param $connect 114 | */ 115 | public function callbackTransferConnectClose($connect){ 116 | try{ 117 | call_user_func_array(Trident::$callbackList['callbackConnectClose'], array($connect)); 118 | }catch (\Exception $e){ 119 | Log::write('MeepoPS: Trident execution callback function callbackConnectClose-' . json_encode(Trident::$callbackList['callbackConnectClose']) . ' throw exception' . json_encode($e), 'ERROR'); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/TransferAndBusinessService.php: -------------------------------------------------------------------------------- 1 | _transfer = new MeepoPS(Trident::$innerProtocol, $this->transferIp, $this->transferPort); 36 | $this->_transfer->callbackConnect = array($this, 'callbackBusinessConnect'); 37 | $this->_transfer->callbackNewData = array($this, 'callbackBusinessNewData'); 38 | $this->_transfer->callbackConnectClose = array($this, 'callbackBusinessConnectClose'); 39 | $this->_transfer->listen(); 40 | } 41 | 42 | /** 43 | * 回调函数 - 收到新链接时 44 | * 新链接加入时, 先不做处理, 等待token验证通过后再处理 45 | * token的验证是收到token后校验, 因此会进入callbackBusinessNewData方法中 46 | * 再此处加入一次性的定时器, 如果N秒后仍然未通过验证, 则断开链接。 47 | * @param $connect 48 | */ 49 | public function callbackBusinessConnect($connect){ 50 | $connect->business['waiter_verify_timer_id'] = Timer::add(function ($connect){ 51 | Log::write('Transfer: Wait Business for token authentication timeout. client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 52 | $this->_close($connect); 53 | }, array($connect), MEEPO_PS_TRIDENT_SYS_WAIT_VERIFY_TIMEOUT, false); 54 | } 55 | 56 | /** 57 | * 回调函数 - 收到新消息时 58 | * 只接受新增Business、PONG两种消息 59 | * @param $connect 60 | * @param $data 61 | */ 62 | public function callbackBusinessNewData($connect, $data){ 63 | //根据类型选择不同的处理方式 64 | switch($data['msg_type']){ 65 | case MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_TRANSFER: 66 | //token校验 67 | if(!isset($data['token']) || Tool::verifyAuth($data['token']) !== true){ 68 | Log::write('Transfer: New link token validation failed', 'ERROR'); 69 | $this->_close($connect); 70 | return; 71 | } 72 | if($this->_addBusiness($connect, $data)){ 73 | //删除等待校验超时的定时器 74 | Timer::delOne($connect->business['waiter_verify_timer_id']); 75 | } 76 | break; 77 | case MsgTypeConst::MSG_TYPE_PONG: 78 | $this->_receivePongFromBusiness($connect, $data); 79 | break; 80 | default: 81 | $this->_appMessage($connect, $data); 82 | return; 83 | } 84 | } 85 | 86 | /** 87 | * 回调函数 - 断开链接时 88 | * @param $connect 89 | */ 90 | public function callbackBusinessConnectClose($connect){ 91 | if(isset($this->_businessList[$connect->id])){ 92 | unset($this->_businessList[$connect->id]); 93 | }else{ 94 | unset($this->_businessList[$connect->id]); 95 | } 96 | } 97 | 98 | /** 99 | * 新增一个Business 100 | * @param $connect 101 | * @param $data 102 | * @return bool 103 | */ 104 | private function _addBusiness($connect, $data){ 105 | $this->_businessList[$connect->id] = $connect; 106 | //初始化发送PING未收到PONG的次数 107 | $connect->business['ping_no_response_count'] = 0; 108 | //设定PING的定时器 109 | $connect->business['ping_timer_id'] = Timer::add(function ($connect){ 110 | $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_PING, 'msg_content'=>'PING')); 111 | }, array($connect), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 112 | //检测PING回复情况 113 | $connect->business['check_ping_timer_id'] = Timer::add(array($this, 'checkPingLimit'), array($connect), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 114 | //告知对方, 已经收到消息, 并且已经添加成功了 115 | return $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_ADD_BUSINESS_TO_TRANSFER, 'msg_content'=>'OK', 'msg_attachment'=>array('ip' => $this->transferIp, 'port'=>$this->transferPort))); 116 | } 117 | 118 | /** 119 | * 接收到消息PONG 120 | * @param $connect 121 | * @param $data string 122 | */ 123 | private function _receivePongFromBusiness($connect, $data){ 124 | if($data['msg_content'] === 'PONG'){ 125 | $connect->business['ping_no_response_count']--; 126 | } 127 | } 128 | 129 | /** 130 | * 接收到业务相关的消息 131 | * @param $connect 132 | * @param $data string 133 | */ 134 | private function _appMessage($connect, $data){ 135 | switch($data['msg_type']){ 136 | case MsgTypeConst::MSG_TYPE_SEND_ALL: 137 | $this->_sendAll($data); 138 | break; 139 | case MsgTypeConst::MSG_TYPE_SEND_ONE: 140 | $this->_sendOne($data); 141 | break; 142 | default: 143 | Log::write('Transfer: Business message type is not supported, client address: ' . json_encode($connect->getClientAddress()) .', data=' . json_encode($data), 'ERROR'); 144 | } 145 | } 146 | 147 | /** 148 | * 检测PING的回复情况 149 | * @param $connect 150 | */ 151 | public function checkPingLimit($connect){ 152 | $connect->business['ping_no_response_count']++; 153 | //超出无响应次数限制时断开连接 154 | if( ($connect->business['ping_no_response_count'] - 1) >= MEEPO_PS_TRIDENT_SYS_PING_NO_RESPONSE_LIMIT){ 155 | $conn = ''; 156 | if(isset($this->_businessList[$connect->id])){ 157 | $conn = $this->_businessList[$connect->id]; 158 | } 159 | Log::write('Transfer: PING Business no response beyond the limit, has been disconnected, client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 160 | $this->_close($connect); 161 | } 162 | } 163 | 164 | private function _close($connect){ 165 | if(isset($connect->business['waiter_verify_timer_id'])){ 166 | Timer::delOne($connect->business['waiter_verify_timer_id']); 167 | } 168 | if(isset($connect->business['ping_timer_id'])){ 169 | Timer::delOne($connect->business['ping_timer_id']); 170 | } 171 | if(isset($connect->business['check_ping_timer_id'])){ 172 | Timer::delOne($connect->business['check_ping_timer_id']); 173 | } 174 | if(method_exists($connect, 'close')){ 175 | $connect->close(); 176 | } 177 | } 178 | 179 | 180 | //----------------Transfer To Business---------- 181 | 182 | public function sendToBusiness($connect, $data){ 183 | $business = $this->_selectBusiness(); 184 | if($business === false){ 185 | return false; 186 | } 187 | $message = $this->_formatMessageToBusiness($connect, $data); 188 | $result = $business->send($message); 189 | return $result; 190 | } 191 | 192 | /** 193 | * 选择一个Business 194 | * @return bool|mixed 195 | */ 196 | private function _selectBusiness(){ 197 | if(empty($this->_businessList)){ 198 | return false; 199 | } 200 | $businessKey = array_rand($this->_businessList); 201 | if($businessKey === false || !isset($this->_businessList[$businessKey])){ 202 | return false; 203 | } 204 | return $this->_businessList[$businessKey]; 205 | } 206 | 207 | /** 208 | * 发送给Business的消息格式 209 | */ 210 | private function _formatMessageToBusiness(&$connect, &$data){ 211 | $clientAddress = $connect->getClientAddress(); 212 | return array( 213 | 'msg_type' => MsgTypeConst::MSG_TYPE_APP_MSG, 214 | 'msg_content' => $data, 215 | 'app_business' => array( 216 | 'session' => !empty($connect->app_business['session']) ? $connect->app_business['session'] : array(), 217 | ), 218 | 'transfer_ip' => $this->transferIp, 219 | 'transfer_port' => $this->transferPort, 220 | 'client_ip' => $clientAddress[0], 221 | 'client_port' => $clientAddress[1], 222 | 'client_connect_id' => $connect->id, 223 | 'client_unique_id' => $connect->unique_id, 224 | ); 225 | } 226 | 227 | private function _sendAll($data){ 228 | foreach(Transfer::$clientList as $client){ 229 | $clientId = Tool::encodeClientId($this->transferIp, $this->transferPort, $client->id); 230 | if($clientId !== $data['client_unique_id']){ 231 | $this->_send($client, $data); 232 | } 233 | } 234 | } 235 | 236 | private function _sendOne($data){ 237 | if(empty($data['to_client_connect_id']) || !isset(Transfer::$clientList[$data['to_client_connect_id']])){ 238 | Log::write('sendOne: choice connect object from Transfer::$clientList failed', 'warning'); 239 | return; 240 | } 241 | $clientConnect = Transfer::$clientList[$data['to_client_connect_id']]; 242 | $this->_send($clientConnect, $data); 243 | } 244 | 245 | private function _send($connect, $data){ 246 | //处理链接需要保留的数据, 如SESSION等 247 | $this->_connectProperty($connect, $data['app_business']); 248 | $data = $data['msg_content']; 249 | //数据转码 250 | if(!empty($this->encodeFunction)){ 251 | try{ 252 | $data = call_user_func($this->encodeFunction, $data); 253 | }catch (\Exception $e){ 254 | Log::write('Trident: execution callback function encodeFunction-' . json_encode($this->encodeFunction) . ' throw exception' . json_encode($e), 'ERROR'); 255 | } 256 | } 257 | //发送给客户端 258 | $connect->send($data); 259 | } 260 | 261 | /** 262 | * 处理链接的属性, 如SESSION等 263 | * @param $connect resource 264 | * @param $connectProperty array 265 | */ 266 | private function _connectProperty($connect, $connectProperty){ 267 | $connect->app_business['session'] = !empty($connectProperty['session']) ? $connectProperty['session'] : array(); 268 | } 269 | } -------------------------------------------------------------------------------- /MeepoPS/Core/Trident/TransferAndConfluenceService.php: -------------------------------------------------------------------------------- 1 | _confluence = new TcpClient(Trident::$innerProtocol, $this->confluenceIp, $this->confluencePort, true); 31 | //实例化一个空类 32 | $this->_confluence->instance = new \stdClass(); 33 | $this->_confluence->instance->callbackNewData = array($this, 'callbackConfluenceNewData'); 34 | $this->_confluence->instance->callbackConnectClose = array($this, 'callbackConfluenceConnectClose'); 35 | $this->_confluence->confluence = array(); 36 | $this->_confluence->connect(); 37 | $result = $this->_confluence->send(array('token'=>'', 'msg_type'=>MsgTypeConst::MSG_TYPE_ADD_TRANSFER_TO_CONFLUENCE, 'msg_content'=>array('ip'=>$this->transferIp, 'port'=>$this->transferPort))); 38 | if($result === false){ 39 | Log::write('Transfer: add confluence failed.' . $this->transferIp . ':' . $this->transferPort . 'WARNING'); 40 | $this->_closeConfluence(); 41 | } 42 | } 43 | 44 | /** 45 | * 回调函数 - 收到Confluence发来新消息时 46 | * 只接受新增Business、PING两种消息 47 | * @param $connect 48 | * @param $data 49 | */ 50 | public function callbackConfluenceNewData($connect, $data){ 51 | switch($data['msg_type']){ 52 | case MsgTypeConst::MSG_TYPE_ADD_TRANSFER_TO_CONFLUENCE: 53 | $this->_addConfluenceResponse($connect, $data); 54 | break; 55 | case MsgTypeConst::MSG_TYPE_PING: 56 | $this->_receivePingFromConfluence($connect, $data); 57 | break; 58 | default: 59 | Log::write('Transfer: Confluence message type is not supported, data' . json_encode($data) . ', client address: ' . json_encode($connect->getClientAddress()), 'ERROR'); 60 | $this->_closeConfluence(); 61 | return; 62 | } 63 | } 64 | 65 | /** 66 | * 收到Confluence发来的加入Confluence确认信息 67 | * 如果Confluence确认, 则增加定时器, 检测是否正常收到PING。如果不正常, 则尝试重连。检测频率和Confluence发送PING的频率一样 68 | * @param $connect 69 | * @param $data 70 | */ 71 | private function _addConfluenceResponse($connect, $data){ 72 | //链接失败 73 | if($data['msg_content'] !== 'OK'){ 74 | $this->_closeConfluence(); 75 | return; 76 | } 77 | //链接成功 78 | $this->_confluence->confluence['confluence_no_ping_limit'] = 0; 79 | //添加计时器, 如果一定时间内没有收到中心机发来的PING, 则断开本次链接并重新向中心机发起注册 80 | $this->_confluence->confluence['waiter_confluence_ping_timer_id'] = Timer::add(function(){ 81 | if((++$this->_confluence->confluence['confluence_no_ping_limit']) >= MEEPO_PS_TRIDENT_SYS_PING_NO_RESPONSE_LIMIT){ 82 | //断开连接 83 | $this->_closeConfluence(); 84 | } 85 | }, array(), MEEPO_PS_TRIDENT_SYS_PING_INTERVAL); 86 | Log::write('Transfer: add Confluence success. ' . $this->confluenceIp . ':' . $this->confluencePort); 87 | } 88 | 89 | /** 90 | * 收到Confluence发来的PING消息 91 | * 收到PING后, 将没有收到PING的次数-1 92 | * @param $connect 93 | * @param $data 94 | */ 95 | private function _receivePingFromConfluence($connect, $data){ 96 | if($data['msg_content'] !== 'PING'){ 97 | return; 98 | } 99 | if($this->_confluence->confluence['confluence_no_ping_limit'] >= 1){ 100 | $this->_confluence->confluence['confluence_no_ping_limit']--; 101 | } 102 | $connect->send(array('msg_type'=>MsgTypeConst::MSG_TYPE_PONG, 'msg_content'=>'PONG')); 103 | } 104 | 105 | /** 106 | * 如果Transfer和Confluence的链接断开, 则尝试重连 107 | */ 108 | public function callbackConfluenceConnectClose(){ 109 | $this->_reConnectConfluence(); 110 | } 111 | 112 | /** 113 | * 重新连入Confluence 114 | * 包含断开链接,并且重新链接。 115 | * 调用此方法时自动调用_closeConfluence()方法 116 | */ 117 | private function _reConnectConfluence(){ 118 | $this->_closeConfluence(); 119 | $this->connectConfluence(); 120 | } 121 | 122 | /** 123 | * 断开和Confluence的链接 124 | */ 125 | private function _closeConfluence() 126 | { 127 | if (isset($this->_confluence->confluence['waiter_confluence_ping_timer_id'])) { 128 | Timer::delOne($this->_confluence->confluence['waiter_confluence_ping_timer_id']); 129 | } 130 | if (method_exists($this->_confluence, 'close')) { 131 | $this->_confluence->close(); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /MeepoPS/Library/Db/Mysql.php: -------------------------------------------------------------------------------- 1 | _host = $host; 35 | $this->_username = $username; 36 | $this->_password = $password; 37 | $this->_port = $port; 38 | $this->_dbName = $dbName; 39 | $this->_connect(); 40 | } 41 | } 42 | 43 | /** 44 | * 执行Sql语句 45 | * @param $sql 46 | */ 47 | public function query($sql){ 48 | $result = mysqli_query(self::$conn, $sql); 49 | if($result === false){ 50 | self::$conn = null; 51 | $this->_connect(); 52 | } 53 | return $result; 54 | } 55 | 56 | /** 57 | * 链接Mysql 58 | */ 59 | private function _connect(){ 60 | while(is_null(self::$conn = null)){ 61 | self::$conn = mysqli_connect($this->_host, $this->_username, $this->_password, $this->_dbName, $this->_port); 62 | if(self::$conn && is_object(self::$conn)){ 63 | break; 64 | } 65 | self::$conn = null; 66 | Log::write(__METHOD__.' Mysql connect failed', 'ERROR'); 67 | sleep(10); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /MeepoPS/Library/Session.php: -------------------------------------------------------------------------------- 1 | _savePath = !empty($sessionSavePath) ? $sessionSavePath : sys_get_temp_dir(); 34 | if(strlen($this->_savePath) > 1 && $this->_savePath[strlen($this->_savePath)-1] === '/'){ 35 | $this->_savePath = substr($this->_savePath, 0, -1); 36 | } 37 | if(!$this->_savePath){ 38 | //日志 39 | return false; 40 | } 41 | if(!is_dir($this->_savePath)){ 42 | $result = @mkdir($this->_savePath, 0777); 43 | if($result !== true){ 44 | //日志 45 | return false; 46 | } 47 | } 48 | //获取SessionId 49 | $this->_sessionId = isset($_COOKIE[MEEPO_PS_HTTP_SESSION_NAME]) ? $_COOKIE[MEEPO_PS_HTTP_SESSION_NAME] : ''; 50 | if(empty($this->_sessionId) || !is_file($this->_savePath . '/' . $_COOKIE[MEEPO_PS_HTTP_SESSION_NAME])){ 51 | $this->_sessionId = uniqid('sess_', true); 52 | return Http::setCookie( 53 | MEEPO_PS_HTTP_SESSION_NAME 54 | , $this->_sessionId 55 | , ini_get('session.cookie_lifetime') 56 | , ini_get('session.cookie_path') 57 | , ini_get('session.cookie_domain') 58 | , ini_get('session.cookie_secure') 59 | , ini_get('session.cookie_httponly') 60 | ); 61 | } 62 | //填充$_SESSION 63 | $_SESSION = $this->_read(); 64 | $_SESSION = $this->_decode($_SESSION); 65 | //Session状态 66 | $this->_isStart = true; 67 | //回收过期SESSION 68 | $this->_gc(); 69 | return true; 70 | } 71 | 72 | public function id(){ 73 | return $this->_sessionId; 74 | } 75 | 76 | /** 77 | * 读取Session时调用 78 | * @return string 79 | */ 80 | private function _read(){ 81 | return file_get_contents($this->_savePath . '/' . $this->_sessionId); 82 | } 83 | 84 | /** 85 | * Session保存到文件时先encode 86 | */ 87 | private function _encode($data){ 88 | return serialize($data); 89 | } 90 | 91 | /** 92 | * Session从文件读取后先Decode 93 | */ 94 | private function _decode($data){ 95 | return unserialize($data); 96 | } 97 | 98 | /** 99 | * 保存Session 100 | * @param $data 101 | * @return bool or int 102 | */ 103 | public function write($data){ 104 | return @file_put_contents($this->_savePath . '/' . $this->_sessionId, $this->_encode($data)); 105 | } 106 | 107 | /** 108 | * 关闭Session 109 | * @return bool 110 | */ 111 | public function close(){ 112 | return true; 113 | } 114 | 115 | /** 116 | * 销毁Session 117 | * @return bool 118 | */ 119 | public function destroy(){ 120 | $file = $this->_savePath . '/' . $this->_sessionId; 121 | if (file_exists($file)) { 122 | unlink($file); 123 | } 124 | return true; 125 | } 126 | 127 | /** 128 | * 资源回收。 129 | * 本方法涉及到三个外部参数, 来自PHP.ini 130 | * 调用周期由 session.gc_probability 和 session.gc_divisor 参数控制 131 | * SESSION有效期由session.gc_maxlifetime 设置 132 | * @return bool 133 | */ 134 | private function _gc(){ 135 | $probability = intval(ini_get('session.gc_probability')); 136 | $divisor = intval(ini_get('session.gc_divisor')); 137 | $maxLifeTime = intval(ini_get('session.gc_maxlifetime')); 138 | if(!$probability || !$divisor || !$maxLifeTime){ 139 | return false; 140 | } 141 | //概率计算 142 | if($probability < $divisor){ 143 | //概率 144 | $rand = mt_rand(0, $divisor); 145 | if($rand > $probability){ 146 | return false; 147 | } 148 | } 149 | //开始清除 150 | foreach (glob($this->_savePath . '/sess_*') as $file) { 151 | if (filemtime($file) + $maxLifeTime < time()) { 152 | @unlink($file); 153 | } 154 | } 155 | return true; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /MeepoPS/Library/TcpClient.php: -------------------------------------------------------------------------------- 1 | _protocol = '\MeepoPS\Core\ApplicationProtocol\\' . $protocol; 41 | $this->_applicationProtocolClassName = $protocol; 42 | } else { 43 | Log::write('Application layer protocol class not found. portocol:' . $protocol, 'FATAL'); 44 | } 45 | } 46 | 47 | //属性赋值 48 | $this->host = $host; 49 | $this->port = $port; 50 | $this->id = self::$_recorderId++; 51 | $this->_isAsync = $isAsync ? STREAM_CLIENT_ASYNC_CONNECT : STREAM_CLIENT_CONNECT; 52 | $this->_currentStatus = self::CONNECT_STATUS_CONNECTING; 53 | //更改统计信息 54 | self::$statistics['total_connect_count']++; 55 | self::$statistics['current_connect_count']++; 56 | } 57 | 58 | public function connect(){ 59 | $remoteSocket = 'tcp://' . $this->host . ':' . $this->port; 60 | $this->_connect = stream_socket_client($remoteSocket, $errno, $errmsg, 5, $this->_isAsync); 61 | if(!$this->_connect){ 62 | Log::write('TcpClient link to '.$remoteSocket.' failed.', 'ERROR'); 63 | $this->_currentStatus = self::CONNECT_STATUS_CLOSED; 64 | return; 65 | } 66 | //监听此链接 67 | MeepoPS::$globalEvent->add(array($this, 'checkConnection'), array(), $this->_connect, EventInterface::EVENT_TYPE_WRITE); 68 | } 69 | 70 | /** 71 | * @param $tcpConnect resource TCP链接 72 | */ 73 | public function checkConnection($tcpConnect){ 74 | if(!stream_socket_get_name($tcpConnect, true)){ 75 | $this->destroy(); 76 | Log::write('Get Socket name found socket resource is invalid.', 'ERROR'); 77 | return; 78 | } 79 | MeepoPS::$globalEvent->delOne($tcpConnect, EventInterface::EVENT_TYPE_WRITE); 80 | stream_set_blocking($tcpConnect, 0); 81 | if (function_exists('socket_import_stream')) { 82 | $socket = socket_import_stream($tcpConnect); 83 | @socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); 84 | @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); 85 | } 86 | MeepoPS::$globalEvent->add(array($this, 'read'), array(), $tcpConnect, EventInterface::EVENT_TYPE_READ); 87 | if($this->_sendBuffer){ 88 | MeepoPS::$globalEvent->add(array($this, 'sendEvent'), array(), $tcpConnect, EventInterface::EVENT_TYPE_WRITE); 89 | } 90 | $this->_currentStatus = self::CONNECT_STATUS_ESTABLISH; 91 | $this->_clientAddress = stream_socket_get_name($tcpConnect, true); 92 | } 93 | } -------------------------------------------------------------------------------- /MeepoPS/README.md: -------------------------------------------------------------------------------- 1 | # MeepoPS 2 | 3 | 本目录是MeepoPS的文件目录. 4 | 5 | 在这里有入口文件, 配置文件, 接口文件, MeepoPS核心代码目录文件 6 | 7 | 使用时,请使用Api目录下文件. Api目录是MeepoPS暴露给应用来继承或实例化的类 -------------------------------------------------------------------------------- /MeepoPS/config.ini: -------------------------------------------------------------------------------- 1 | ; MeepoPS configuration file 2 | ; MeepoPS configuration file as in php.ini 3 | ; MeepoPS configuration file comments start with ';' 4 | 5 | [system] 6 | ; Whether to open Debug. true or false 7 | debug = true 8 | 9 | ; Default timezone 10 | date_default_timezone_set = 'PRC' 11 | 12 | ; The end of a number of instance running, the end of each interval time(seconds) 13 | stop_multi_instance_time_interval = 5 14 | 15 | ; Whether brush immediately sent to the output. If the caller does not ob_ family of functions you do not need to modify this value. true or false 16 | implicit_flush = true 17 | 18 | [file] 19 | ; Log file path prefix 20 | log_filename_prefix = '/var/log/meepops/meepops_' 21 | 22 | ; Standard output file path 23 | stdout_path_prefix = '/var/log/meepops/meepops_' 24 | 25 | ; Pid file path 26 | master_pid_path = '/var/run/meepops/meepops_master.pid' 27 | 28 | ; Statistics file path 29 | statistics_path = '/var/run/meepops/meepops_statistics' 30 | 31 | [connection] 32 | ; TCP protocol, a connection to be the default maximum send buffer 33 | tcp_send_max_buffer_size = 1048576 34 | 35 | ; TCP protocol, a connection can accept the maximum packet size 36 | tcp_read_max_packet_size = 10485760 37 | 38 | 39 | [event] 40 | ; Event(select) polling timeout (microsecond) 41 | event_select_poll_timeout = 100000000 42 | 43 | ; SELECT polling event the maximum number of listening resources. 44 | ; This is the PHP source limit. The default is 1024. MeepoPS up to 1020. 45 | ; MeepoPS must be less than FD_SETSIZE 4. 46 | ; If you want to change this value, you must recompile PHP FD_SETSIZE (eg: --enable-fd-setsize=2048). 47 | ; PHP FD_SETSIZE to equal the maximum number of open files supported by your system. 48 | event_select_max_size = 1020 49 | 50 | [http] 51 | ; Domain and document. support multiple groups 52 | ; format: domain1&path1 | domain2&path2 .The domain name must contain the port, 80 can be omitted. 53 | ; Between the domain name and directory use & segmentation, multi group with * segmentation 54 | ; Automatically ignore spaces, \r, \t, \n 55 | http_domain_document_list = 'www.meepops-jiankong.com:19910 & /var/www/MeepoPS/Example/Real_Time_Monitor_Ssh/Web/ | 56 | meepops.lanecn.com&/var/www/MeepoPS/Example/Web_Server/Web/ | 57 | www.lanecn.com:19910&/var/www/blog | 58 | lanewechat.lanecn.com:19910&/var/www/LaneWeChat' 59 | 60 | ; Default page, multiple use","split 61 | ; Automatically ignore spaces, \r, \t, \n 62 | http_default_page = index.php, index.html 63 | 64 | ; Session name 65 | http_session_name = MeepoPS-Session-Id 66 | 67 | ; Upload files to generate temporary files, or access to the contents of the document. 68 | ; true is the same as the Nginx/Apache, the production of temporary files. However, when MeepoPS save the file, MeepoPS will return the contents of the file. 69 | ; false is the only access to data, do not generate temporary files 70 | http_upload_file_generate_temp_file = true 71 | 72 | [trident] 73 | trident_sys_ping_interval = 10 74 | trident_sys_ping_no_response_limit = 3 75 | trident_sys_wait_verify_timeout = 3 76 | trident_sys_confluence_broadcast_interval = 60 -------------------------------------------------------------------------------- /MeepoPS/index.php: -------------------------------------------------------------------------------- 1 | $client) { 23 | fwrite($client, "hello world\n"); 24 | $data = ''; 25 | while (feof($client) === false && $d = fgetc($client)) { 26 | if ($d === "\n") { 27 | break; 28 | } 29 | $data .= $d; 30 | } 31 | var_dump('客户端用户' . $id . '号收到消息: "' . $data . '"'); 32 | } 33 | } -------------------------------------------------------------------------------- /Test/test_client_pressure.php: -------------------------------------------------------------------------------- 1 | = $clientCount) { 25 | break; 26 | } 27 | $errno = $errmsg = ''; 28 | $client = stream_socket_client('10.10.10.10:19910', $errno, $errmsg); 29 | if (!$client) { 30 | var_dump($errno); 31 | var_dump($errmsg); 32 | continue; 33 | } 34 | $clientList[] = $client; 35 | } 36 | echo "创建成功\n"; 37 | while (1) { 38 | foreach($clientList as $key => $client){ 39 | $result = fwrite($client, "PING\n"); 40 | if(!$result){ 41 | $statistic['sendFailedCount']++; 42 | var_dump("一个链接断开了\n"); 43 | fclose($client); 44 | unset($clientList[$key]); 45 | $clientList[] = stream_socket_client('127.0.0.1:19910', $errno, $errmsg); 46 | continue; 47 | } 48 | $statistic['sendCount']++; 49 | $data = ''; 50 | while (feof($client) !== true) { 51 | $data .= fread($client, 2000); 52 | if ($data[strlen($data) - 1] === "\n") { 53 | break; 54 | } 55 | } 56 | if($data === "PONG\n"){ 57 | $statistic['readCount']++; 58 | }else{ 59 | $statistic['readFailedCount']++; 60 | } 61 | } 62 | file_put_contents('/home/lanec/meepops-statistic', json_encode($statistic)); 63 | sleep(1); 64 | } -------------------------------------------------------------------------------- /Test/test_less_connect_quick_send1.php: -------------------------------------------------------------------------------- 1 | $totalCount, 'err_connect' => $errConnect, 'err_write' => $errWrite, 'err_read' => $errRead))); 45 | } 46 | fclose($f); 47 | -------------------------------------------------------------------------------- /Test/test_less_connect_quick_send2.php: -------------------------------------------------------------------------------- 1 | $totalCount, 'err_connect' => $errConnect, 'err_write' => $errWrite, 'err_read' => $errRead))); 45 | } 46 | fclose($f); 47 | -------------------------------------------------------------------------------- /Test/test_more_connect_quick_send.php: -------------------------------------------------------------------------------- 1 | = $clientCount) { 23 | break; 24 | } 25 | $errno = $errmsg = ''; 26 | $client = stream_socket_client('127.0.0.1:19910', $errno, $errmsg); 27 | if (!$client) { 28 | var_dump($errno); 29 | var_dump($errmsg); 30 | continue; 31 | } 32 | $clientList[] = $client; 33 | } 34 | echo "创建成功\n"; 35 | while (1) { 36 | foreach ($clientList as $id => $client) { 37 | $result = fwrite($client, "hello world\n"); 38 | $result ? $sendCount++ : $sendErrorCount++; 39 | $data = ''; 40 | while (feof($client) !== true) { 41 | $data .= fread($client, 2000); 42 | if ($data[strlen($data) - 1] === "\n") { 43 | break; 44 | } 45 | } 46 | $data ? $receiveCount++ : $receiveErrorCount++; 47 | } 48 | fwrite($f, json_encode(array('send_count' => $sendCount, 'receive_count' => $sendErrorCount, 'err_send' => $sendErrorCount, 'err_receive' => $receiveErrorCount)) . "\n"); 49 | } -------------------------------------------------------------------------------- /Test/test_server_capacity.php: -------------------------------------------------------------------------------- 1 | = $clientCount) { 18 | break; 19 | } 20 | $errno = $errmsg = ''; 21 | $client = stream_socket_client('127.0.0.1:19910', $errno, $errmsg); 22 | if (!$client) { 23 | var_dump($errno); 24 | var_dump($errmsg); 25 | continue; 26 | } 27 | $clientList[] = $client; 28 | } 29 | echo "创建成功\n"; 30 | while (1) { 31 | var_dump(count($clientList)); 32 | sleep(10); 33 | } 34 | -------------------------------------------------------------------------------- /demo-cbnsq.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 10; 20 | 21 | //设置MeepoPS实例名称 22 | $cbNsq->instanceName = 'MeepoPS-CBSNQ'; 23 | 24 | //设置回调函数 - 这是所有应用的业务代码入口 25 | $cbNsq->callbackStartInstance = 'callbackStartInstance'; 26 | $cbNsq->callbackConnect = 'callbackConnect'; 27 | $cbNsq->callbackNewData = 'callbackNewData'; 28 | $cbNsq->callbackSendBufferEmpty = 'callbackSendBufferEmpty'; 29 | $cbNsq->callbackInstanceStop = 'callbackInstanceStop'; 30 | $cbNsq->callbackConnectClose = 'callbackConnectClose'; 31 | 32 | //启动MeepoPS 33 | \MeepoPS\runMeepoPS(); 34 | 35 | 36 | //以下为回调函数, 业务相关. 37 | //回调 - 示例启动时 38 | function callbackStartInstance($instance) 39 | { 40 | echo "实例{$instance->instanceName}成功启动\n"; 41 | } 42 | 43 | //回调 - 收到新链接 44 | function callbackConnect($connect) 45 | { 46 | echo "收到新链接. 链接ID={$connect->id}\n"; 47 | } 48 | 49 | //回调 - 收到新消息 50 | function callbackNewData($connect, $data) 51 | { 52 | echo "收到新消息, ID:{$_SERVER['MESSAGE_ID']} 内容: {$data}\n"; 53 | $connect->send("200 ok"); 54 | } 55 | 56 | function callbackSendBufferEmpty($connect) 57 | { 58 | echo "用户{$connect->id}的待发送队列已经为空\n"; 59 | } 60 | 61 | function callbackInstanceStop($instance) 62 | { 63 | foreach ($instance->clientList as $client) { 64 | $client->send('服务即将停止.'); 65 | } 66 | } 67 | 68 | function callbackConnectClose($connect) 69 | { 70 | echo "链接断开了. 链接ID={$connect->id}\n"; 71 | } 72 | -------------------------------------------------------------------------------- /demo-http.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 21 | 22 | //设置MeepoPS实例名称 23 | $http->instanceName = 'MeepoPS-Http'; 24 | 25 | //设置错误页 26 | //404, 设置一个专门的页面来展示 27 | $http->setErrorPage('404', __DIR__ . '/Example/Web/404.html'); 28 | //403, 使用默认样式(其实就是居中了一句话), 自定义错误描述 29 | $http->setErrorPage('403', '您没有被授权访问!'); 30 | 31 | //启动MeepoPS 32 | \MeepoPS\runMeepoPS(); -------------------------------------------------------------------------------- /demo-telnet.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 20 | 21 | //设置MeepoPS实例名称 22 | $telnet->instanceName = 'MeepoPS-Telnet'; 23 | 24 | //设置回调函数 - 这是所有应用的业务代码入口 25 | $telnet->callbackStartInstance = 'callbackStartInstance'; 26 | $telnet->callbackConnect = 'callbackConnect'; 27 | $telnet->callbackNewData = 'callbackNewData'; 28 | $telnet->callbackSendBufferEmpty = 'callbackSendBufferEmpty'; 29 | $telnet->callbackInstanceStop = 'callbackInstanceStop'; 30 | $telnet->callbackConnectClose = 'callbackConnectClose'; 31 | 32 | //启动MeepoPS 33 | \MeepoPS\runMeepoPS(); 34 | 35 | 36 | //以下为回调函数, 业务相关. 37 | function callbackStartInstance($instance) 38 | { 39 | echo '实例' . $instance->instanceName . '成功启动' . "\n"; 40 | } 41 | 42 | function callbackConnect($connect) 43 | { 44 | foreach($connect->instance->clientList as $client){ 45 | //上线提示就不用告诉自己了, 对吧! 46 | if($connect->id != $client->id){ 47 | $client->send('新用户'.$connect->id.'已经上线了.'); 48 | } 49 | } 50 | 51 | //定时器 52 | // \MeepoPS\Core\Timer::add(function($connect){ 53 | // $connect->send('PIN广播'); 54 | // }, array($connect), 5, true); 55 | 56 | var_dump('收到新链接. UniqueId=' . $connect->id . "\n"); 57 | } 58 | 59 | function callbackNewData($connect, $data) 60 | { 61 | $connect->send('用户' . $connect->id . '说: ' . $data . "\n"); 62 | var_dump('UniqueId=' . $connect->id . '说:' . $data . "\n"); 63 | foreach ($connect->instance->clientList as $client) { 64 | if ($connect->id != $client->id) { 65 | $client->send('群发: 用户' . $connect->id . '说: ' . $data . "\n"); 66 | } 67 | } 68 | } 69 | 70 | function callbackSendBufferEmpty($connect) 71 | { 72 | var_dump('用户' . $connect->id . "的待发送队列已经为空\n"); 73 | } 74 | 75 | function callbackInstanceStop($instance) 76 | { 77 | foreach ($instance->clientList as $client) { 78 | $client->send('服务即将停止.'); 79 | } 80 | } 81 | 82 | function clientListClose($connect) 83 | { 84 | var_dump('UniqueId=' . $connect->id . '断开了' . "\n"); 85 | } 86 | 87 | function callbackConnectClose($connect) 88 | { 89 | $connect->send('88'); 90 | } 91 | -------------------------------------------------------------------------------- /demo-trident.php: -------------------------------------------------------------------------------- 1 | confluenceIp = '0.0.0.0'; 20 | $trident->confluencePort = '19911'; 21 | $trident->confluenceInnerIp = '127.0.0.1'; 22 | $trident->instanceName = 'MeepoPS-Trident-Transfer'; 23 | 24 | $trident->transferInnerIp = '0.0.0.0'; 25 | $trident->transferInnerPort = '19912'; 26 | $trident->childProcessCount = 3; 27 | 28 | $trident->businessChildProcessCount = 3; 29 | 30 | $trident->callbackStartInstance = function(){ 31 | var_dump('实例启动'); 32 | }; 33 | $trident->callbackConnect = function(){ 34 | var_dump('新链接'); 35 | }; 36 | $trident->callbackConnectClose = function(){ 37 | var_dump('连接断开'); 38 | }; 39 | $trident->callbackInstanceStop = function(){ 40 | var_dump('实例停止'); 41 | }; 42 | 43 | //例如客户端消息格式: {"type":"SEND_ALL", "content":"hello world"} 44 | //例如客户端消息格式: {"type":"SEND_ONE", "content":"zai ma ?", "send_to_one":"MC4wLjAuMF8xOTkxM183"} 45 | $trident->callbackNewData = function($connect, $data){ 46 | var_dump('用户' . $_SERVER['MEEPO_PS_CLIENT_UNIQUE_ID'] . '发消息啦'); 47 | $data = json_decode($data, true); 48 | if(empty($data['type'])){ 49 | return; 50 | } 51 | $data['type'] = strtoupper($data['type']); 52 | switch($data['type']){ 53 | case 'SEND_ALL': 54 | if(empty($data['content'])){ 55 | return; 56 | } 57 | $message = '收到群发消息: ' . $data['content']; 58 | \MeepoPS\Core\Trident\AppBusiness::sendToAll($message); 59 | break; 60 | case 'SEND_ONE': 61 | $message = '收到私聊消息: ' . $data['content'] . '(From: ' . $_SERVER['MEEPO_PS_CLIENT_UNIQUE_ID'] . ')'; 62 | $clientId = $data['send_to_one']; 63 | \MeepoPS\Core\Trident\AppBusiness::sendToOne($message, $clientId); 64 | break; 65 | default: 66 | return; 67 | } 68 | }; 69 | 70 | //启动三层模型 71 | $trident->run(); 72 | 73 | //启动MeepoPS 74 | \MeepoPS\runMeepoPS(); -------------------------------------------------------------------------------- /demo-websocket.php: -------------------------------------------------------------------------------- 1 | childProcessCount = 1; 20 | 21 | //设置MeepoPS实例名称 22 | $webSocket->instanceName = 'MeepoPS-WebSocket'; 23 | 24 | $webSocket->callbackNewData = 'callbackNewData'; 25 | 26 | //启动MeepoPS 27 | \MeepoPS\runMeepoPS(); 28 | 29 | function callbackNewData($connect, $data){ 30 | $msg = $connect->id . ': ' . $data; 31 | foreach($connect->instance->clientList as $client){ 32 | $client->send($msg); 33 | } 34 | } --------------------------------------------------------------------------------