├── .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 | 
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 | 
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 |
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 | +'
'+ function(){
79 | var len = data.length, ii = 1, str = '';
80 | if(len > 0){
81 | str = '- '+ (data[0].content || '请配置content') +'
';
82 | for(; ii < len; ii++){
83 | str += '- '+ (data[ii].content || '请配置content') +'
';
84 | }
85 | }
86 | return str;
87 | }() +'
'
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 | 
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 |
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 |
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 |
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 | }
--------------------------------------------------------------------------------