Jelajahi Sumber

UPDATE TO VERSION 3.0

walkor 10 tahun lalu
induk
melakukan
8ca98e0319
100 mengubah file dengan 6016 tambahan dan 5627 penghapusan
  1. 2 1
      Applications/Todpole/Config/Store.php
  2. 71 0
      Applications/Todpole/Event.php
  3. 25 0
      Applications/Todpole/Web/auth.html
  4. TEMPAT SAMPAH
      Applications/Todpole/Web/css/images/github.png
  5. TEMPAT SAMPAH
      Applications/Todpole/Web/css/images/logo.png
  6. 239 0
      Applications/Todpole/Web/css/main.css
  7. TEMPAT SAMPAH
      Applications/Todpole/Web/favicon.ico
  8. TEMPAT SAMPAH
      Applications/Todpole/Web/images/apple-touch-icon.png
  9. TEMPAT SAMPAH
      Applications/Todpole/Web/images/fb-image.jpg
  10. TEMPAT SAMPAH
      Applications/Todpole/Web/images/workerman-todpole-browser.png
  11. 117 0
      Applications/Todpole/Web/index.php
  12. 261 0
      Applications/Todpole/Web/js/App.js
  13. 68 0
      Applications/Todpole/Web/js/Arrow.js
  14. 102 0
      Applications/Todpole/Web/js/Camera.js
  15. 38 0
      Applications/Todpole/Web/js/Cookie.js
  16. 9 0
      Applications/Todpole/Web/js/Keys.js
  17. 54 0
      Applications/Todpole/Web/js/Message.js
  18. 6 0
      Applications/Todpole/Web/js/Model.js
  19. 14 0
      Applications/Todpole/Web/js/Settings.js
  20. 171 0
      Applications/Todpole/Web/js/Tadpole.js
  21. 75 0
      Applications/Todpole/Web/js/TadpoleTail.js
  22. 32 0
      Applications/Todpole/Web/js/WaterParticle.js
  23. 133 0
      Applications/Todpole/Web/js/WebSocketService.js
  24. 106 0
      Applications/Todpole/Web/js/formControls.js
  25. 3 0
      Applications/Todpole/Web/js/frogMode.js
  26. 0 0
      Applications/Todpole/Web/js/jquery.min.js
  27. 193 0
      Applications/Todpole/Web/js/lib/Stats.js
  28. 154 0
      Applications/Todpole/Web/js/lib/jquery-1.4.2.min.js
  29. 19 0
      Applications/Todpole/Web/js/lib/modernizr-1.5.min.js
  30. 32 0
      Applications/Todpole/Web/js/lib/parseUri.js
  31. 81 0
      Applications/Todpole/Web/js/main.js
  32. 37 0
      Applications/Todpole/start.php
  33. 151 0
      GatewayWorker/BusinessWorker.php
  34. 360 0
      GatewayWorker/Gateway.php
  35. 28 0
      GatewayWorker/Lib/Autoloader.php
  36. 4 9
      GatewayWorker/Lib/Context.php
  37. 2 2
      GatewayWorker/Lib/Db.php
  38. 2 7
      GatewayWorker/Lib/DbConnection.php
  39. 58 81
      GatewayWorker/Lib/Gateway.php
  40. 8 7
      GatewayWorker/Lib/Lock.php
  41. 2 2
      GatewayWorker/Lib/Store.php
  42. 1 1
      GatewayWorker/Lib/StoreDriver/File.php
  43. 38 188
      README.md
  44. 23 0
      Workerman/Autoloader.php
  45. 165 0
      Workerman/Connection/AsyncTcpConnection.php
  46. 66 0
      Workerman/Connection/ConnectionInterface.php
  47. 424 0
      Workerman/Connection/TcpConnection.php
  48. 104 0
      Workerman/Connection/UdpConnection.php
  49. 16 21
      Workerman/Events/EventInterface.php
  50. 123 0
      Workerman/Events/Libevent.php
  51. 145 0
      Workerman/Events/Select.php
  52. 10 0
      Workerman/Lib/Constants.php
  53. 28 35
      Workerman/Lib/Timer.php
  54. 127 0
      Workerman/Protocols/GatewayProtocol.php
  55. 450 0
      Workerman/Protocols/Http.php
  56. 0 0
      Workerman/Protocols/Http/mime.types
  57. 44 0
      Workerman/Protocols/ProtocolInterface.php
  58. 51 0
      Workerman/Protocols/Telnet.php
  59. 140 0
      Workerman/Protocols/Websocket.php
  60. 224 0
      Workerman/WebServer.php
  61. 1180 0
      Workerman/Worker.php
  62. 0 227
      applications/Demo/Bootstrap/BusinessWorker.php
  63. 0 877
      applications/Demo/Bootstrap/Gateway.php
  64. 0 25
      applications/Demo/Config/Db.php
  65. 0 83
      applications/Demo/Event.php
  66. 0 20
      applications/Demo/Lib/Autoloader.php
  67. 0 198
      applications/Demo/Lib/StatisticClient.php
  68. 0 167
      applications/Demo/Protocols/GatewayProtocol.php
  69. 0 53
      applications/Demo/Protocols/JsonProtocol.php
  70. 0 48
      applications/Demo/Protocols/TextProtocol.php
  71. 0 148
      applications/Demo/Protocols/WebSocket.php
  72. 0 176
      applications/Demo/README.md
  73. 0 10
      applications/Demo/conf.d/BusinessWorker.conf
  74. 0 45
      applications/Demo/conf.d/Gateway.conf
  75. 0 8
      applications/README.md
  76. 0 537
      applications/Statistics/Bootstrap/StatisticProvider.php
  77. 0 368
      applications/Statistics/Bootstrap/StatisticWorker.php
  78. 0 192
      applications/Statistics/Clients/StatisticClient.php
  79. 0 0
      applications/Statistics/Config/Cache/empty
  80. 0 13
      applications/Statistics/Config/Config.php
  81. 0 10
      applications/Statistics/Lib/Cache.php
  82. 0 142
      applications/Statistics/Lib/functions.php
  83. 0 118
      applications/Statistics/Modules/admin.php
  84. 0 64
      applications/Statistics/Modules/logger.php
  85. 0 273
      applications/Statistics/Modules/main.php
  86. 0 43
      applications/Statistics/Modules/setting.php
  87. 0 146
      applications/Statistics/Modules/statistic.php
  88. 0 7
      applications/Statistics/README.md
  89. 0 84
      applications/Statistics/Views/admin.tpl.php
  90. 0 3
      applications/Statistics/Views/footer.tpl.php
  91. 0 36
      applications/Statistics/Views/header.tpl.php
  92. 0 39
      applications/Statistics/Views/log.tpl.php
  93. 0 67
      applications/Statistics/Views/login.tpl.php
  94. 0 283
      applications/Statistics/Views/main.tpl.php
  95. 0 83
      applications/Statistics/Views/setting.tpl.php
  96. 0 196
      applications/Statistics/Views/statistic.tpl.php
  97. 0 10
      applications/Statistics/Web/_init.php
  98. 0 7
      applications/Statistics/Web/config.php
  99. 0 459
      applications/Statistics/Web/css/bootstrap-theme.css
  100. 0 8
      applications/Statistics/Web/css/bootstrap-theme.min.css

+ 2 - 1
applications/Demo/Config/Store.php → Applications/Todpole/Config/Store.php

@@ -30,4 +30,5 @@ class Store
 }
 
 // 默认系统临时目录下
-Store::$storePath = sys_get_temp_dir().'/workerman-demo/';
+$path_array = explode(DIRECTORY_SEPARATOR, __DIR__);
+Store::$storePath = sys_get_temp_dir().'/workerman-'.$path_array[count($path_array)-2].'/';

+ 71 - 0
Applications/Todpole/Event.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * 
+ * 主逻辑
+ * 主要是处理 onMessage onClose 三个方法
+ * @author walkor <walkor@workerman.net>
+ * 
+ */
+
+use \GatewayWorker\Lib\Gateway;
+
+class Event
+{
+   /**
+    * 有消息时
+    * @param int $client_id
+    * @param string $message
+    */
+   public static function onMessage($client_id, $message)
+   {
+        // 获取客户端请求
+        $message_data = json_decode($message, true);
+        if(!$message_data)
+        {
+            return ;
+        }
+        
+        switch($message_data['type'])
+        {
+            case 'login':
+                Gateway::sendToCurrentClient('{"type":"welcome","id":'.$client_id.'}');
+                break;
+            // 更新用户
+            case 'update':
+                // 转播给所有用户
+                Gateway::sendToAll(json_encode(
+                        array(
+                                'type'     => 'update',
+                                'id'         => $client_id,
+                                'angle'   => $message_data["angle"]+0,
+                                'momentum' => $message_data["momentum"]+0,
+                                'x'                   => $message_data["x"]+0,
+                                'y'                   => $message_data["y"]+0,
+                                'life'                => 1,
+                                'name'           => isset($message_data['name']) ? $message_data['name'] : 'Guest.'.$client_id,
+                                'authorized'  => false,
+                                )
+                        ));
+                return;
+            // 聊天
+            case 'message':
+                // 向大家说
+                $new_message = array(
+                    'type'=>'message', 
+                    'id'=>$client_id,
+                    'message'=>$message_data['message'],
+                );
+                return Gateway::sendToAll(json_encode($new_message));
+        }
+   }
+   
+   /**
+    * 当用户断开连接时
+    * @param integer $client_id 用户id
+    */
+   public static function onClose($client_id)
+   {
+       // 广播 xxx 退出了
+       GateWay::sendToAll(json_encode(array('type'=>'closed', 'id'=>$client_id)));
+   }
+}

+ 25 - 0
Applications/Todpole/Web/auth.html

@@ -0,0 +1,25 @@
+<!doctype html>
+
+<html>
+	<head>
+		<meta charset="utf-8">
+		<title>Hold on...</title>
+		<link rel="stylesheet" type="text/css" href="css/main.css" />
+	</head>
+	<body>
+	
+		<script src="js/lib/parseUri.js"></script>                
+		<script>
+			
+			
+			uri = parseUri(document.location)
+			if ( uri.queryKey.oauth_token ) {
+					window.opener.app.authorize(uri.queryKey.oauth_token, uri.queryKey.oauth_verifier);
+					window.close()
+			}
+			
+		
+		</script>
+	</body>
+</html>
+

TEMPAT SAMPAH
Applications/Todpole/Web/css/images/github.png


TEMPAT SAMPAH
Applications/Todpole/Web/css/images/logo.png


+ 239 - 0
Applications/Todpole/Web/css/main.css

@@ -0,0 +1,239 @@
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 3.2.0
+build: 2676
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
+
+html {
+	-webkit-user-select: none;
+	cursor: caret;
+	background: #000;
+}
+
+body {
+	font-family: "Microsoft YaHei","proxima-nova-1","proxima-nova-2", arial, sans-serif;
+	margin: 0;
+	padding: 0;
+	overflow: hidden;
+	font-size: 62.5%;
+}
+
+h1 {
+	display: block;
+	position: absolute;
+	top: 30px;
+	left: 30px;
+	width: 268px;
+	height: 30px;
+	background: url(images/logo.png);
+	overflow: hidden;
+	text-indent: 300px;
+}
+
+h2 {
+	color: #fff;
+	font-size: 1.3em;
+	margin-bottom: 0.2em;
+	font-weight: bold;
+}
+
+a {
+	color: #c0fdf7;
+	text-decoration: none;
+}
+
+
+
+	a:hover {
+		color: #fff;
+		text-decoration: underline;
+	}
+
+p {
+	color: #999999;
+	font-size: 1.3em;
+	margin-bottom:0.5em;
+}
+
+strong {
+	font-weight:bold;
+}
+
+/*#ui {
+	display: none;
+}*/
+
+#fps {
+	position: absolute;
+	top: 0;
+	left: 0;
+	z-index:2;
+}
+
+#chatText
+{
+	display:inline-block;	
+}
+#chatText,#chat
+{
+	font-size:3em;
+	font-weight: bold;
+	max-width: 70%;
+}
+#chat {
+	/*font-family: "Arial Rounded MT Bold";*/
+	display: block;
+	position: absolute;
+	top: 60%;
+	left: 50%;
+	padding: 10px 20px;
+	background: rgba(255,255,255,.1);
+	border-radius: 20px;
+	color: #fff;
+	text-align: center;
+	border-style:none;
+	outline-style:none;
+	height:30px;
+	opacity:0;
+	transition: opacity .2s linear;
+	-o-transition: opacity .2s linear;
+	-moz-transition: opacity .2s linear;
+	-webkit-transition: opacity .2s linear;
+	-ms-transition: opacity .2s linear;
+}
+
+
+#cant-connect {
+	/*font-family: "Arial Rounded MT Bold";*/
+	display: none;
+	position: absolute;
+	top: 200px;
+	left: 10px;
+	padding: 10px 20px;
+	color: rgba(249,136,119,0.9);
+	width: 400px;
+	font-size: 2em;
+	font-weight: bold;
+}
+
+#cant-connect a:link {
+	color: #c0fdf7;
+	text-decoration: underline;
+}
+
+#unsupported-browser {
+  display: none;  
+	position: absolute;
+	z-index:2;	
+	background-color: #fff;
+	background-image: linear-gradient(top, #fff, #e2e2e2);
+	background-image: -o-linear-gradient(top, #fff, #e2e2e2);
+	background-image: -moz-linear-gradient(top, #fff, #e2e2e2);
+	background-image: -ms-linear-gradient(top, #fff, #e2e2e2);
+	background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #fff),color-stop(1, #e2e2e2));
+
+	border-radius: 10px;
+	box-shadow: 1px 1px 5px #000;
+	
+	top: 55%;
+	left: 50%;
+	width:420px;
+	margin:-200px;
+	padding:35px;
+	font-size: 1.5em;
+	border: 1px solid #fff;
+}
+
+#unsupported-browser p {
+	color: #000;
+	text-shadow: white 1px 1px 0;
+	line-height: 1.4em;
+}
+
+a#force-init-button {
+	font-size: .6em;
+	color: #e47c6c;
+	text-align: center;
+	font-weight: bold;
+	text-decoration: underline;
+}
+a#force-init-button:hover {
+  color: #999;  
+}
+
+
+#unsupported-browser ul li  {
+	display: inline;
+	list-style-type: none;
+	padding-right: 10px;
+	font-weight: bold;
+	padding: 4px 10px 4px 10px;
+	background-color: #606061;
+	border-radius: 100px;
+	box-shadow:inset -1px -1px -5px #000;
+	text-shadow: none;
+	font-size: .8em;
+}
+
+#unsupported-browser ul   {
+	margin: 20px 0px 20px 0px;
+}
+
+
+
+#info {
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+	background: rgba(0,0,0,0.2);
+}
+	#info section {
+		float: left;
+		margin: 20px 40px 20px 20px;
+		margin-right: 40px;
+	}
+		#info #wtf {
+			float: left;
+			margin-left: 30px;
+			margin-right: 40px;
+		}
+		
+		#info #share {
+			float: right;
+			margin-right: 40px;
+		}
+		
+		#info section ul {
+		
+		}
+			#info section ul li {
+				float: left;
+				margin-right: 10px;
+			}
+				#info section ul li a {
+					font-size: 1.3em;
+				}
+	
+	#info #settings .edit {
+		display: none;
+	}
+	
+
+#instructions {
+	position: absolute;
+	top: 30px;
+	right: 60px;
+}
+
+#fwa {
+	display: none; /*remove on 27th November 2010*/
+	position: absolute;
+	top: 0px;
+	right: 0px;
+}
+
+

TEMPAT SAMPAH
Applications/Todpole/Web/favicon.ico


TEMPAT SAMPAH
Applications/Todpole/Web/images/apple-touch-icon.png


TEMPAT SAMPAH
Applications/Todpole/Web/images/fb-image.jpg


TEMPAT SAMPAH
Applications/Todpole/Web/images/workerman-todpole-browser.png


+ 117 - 0
Applications/Todpole/Web/index.php

@@ -0,0 +1,117 @@
+<?php 
+if(!function_exists('is_mobile'))
+{
+    function is_mobile()
+    {
+        //php判断客户端是否为手机
+        $agent = $_SERVER['HTTP_USER_AGENT'];
+        return (strpos($agent,"NetFront") || strpos($agent,"iPhone") || strpos($agent,"MIDP-2.0") || strpos($agent,"Opera Mini") || strpos($agent,"UCWEB") || strpos($agent,"Android") || strpos($agent,"Windows CE") || strpos($agent,"SymbianOS"));
+    }
+}
+?>
+<!doctype html>
+
+<html>
+	<head>
+		<meta charset="utf-8">
+		<title>Workerman小蝌蚪互动聊天室 HTML5+Websocket+PHP多进程socket实时推送技术</title>
+		<link rel="stylesheet" type="text/css" href="css/main.css" />
+		<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=0;" />		
+    <meta name="apple-mobile-web-app-capable" content="YES">
+    <meta name="apple-mobile-web-app-status-bar-style" content="black">
+	   <link rel="apple-touch-icon" href="/images/apple-touch-icon.png"/>
+		<meta property="fb:app_id" content="149260988448984" />
+		<meta name="title" content="Workerman-todpole!" />
+		<meta name="description" content="workerman + HTML5+WebSocket +PHP socket 广播 小蝌蚪交互游戏程序 ,坐标实时推送、实时聊天等" />
+		<link rel="image_src" href="/images/fb-image.jpg" / >
+	</head>
+	<body>
+		<canvas id="canvas"></canvas>
+		
+		<div id="ui">
+			<div id="fps"></div>
+		
+			<input id="chat" type="text" />
+			<div id="chatText"></div>
+			<h1>workerman</h1>
+		<?php if(!is_mobile()){?>
+			<div id="instructions">
+				<h2>介绍</h2>
+				<p>直接打字聊天!<br />输入 name: XX 则会设置你的昵称为XX</p>
+			</div>
+			<aside id="info">
+			<section id="share">
+				       <a rel="external" href="http://github.com/walkor/workerman-todpole" title="workerman-todpole at GitHub">源代码:<img src="css/images/github.png" alt="fork on github"></a>
+				       &nbsp;&nbsp;
+			</section>
+			<section id="wtf">
+			
+			    <!-- 尊重他人劳动成果,请保留原作者相关链接 -->
+				<h2>powered&nbsp;by&nbsp;<a rel="external" href="http://workerman.net/workerman-todpole" target="_blank">workerman</a> &nbsp;&nbsp;&nbsp;&nbsp;感谢 <a href="http://rumpetroll.com/" target="_blank">rumpetroll.com</a></h2>
+				<!-- 尊重他人劳动成果,请保留原作者相关链接 -->
+				
+			</section>
+			</aside>
+			<?php }?>
+            <aside id="frogMode">
+                <h3>Frog Mode</h3>
+                <section id="tadpoles">
+                    <h4>Tadpoles</h4>
+                    <ul id="tadpoleList">
+                    </ul>
+                </section>
+                <section id="console">
+                    <h4>Console</h4>
+                </section>
+            </aside>
+		
+			<div id="cant-connect">
+				与服务器断开连接了。您可以重新刷新页面。
+			</div>
+			<div id="unsupported-browser">
+				<p>
+					您的浏览器不支持 <a rel="external" href="http://en.wikipedia.org/wiki/WebSocket">WebSockets</a>.
+					推荐您使用以下浏览器
+				</p>
+				<ul>										
+					<li><a rel="external" href="http://www.google.com/chrome">Google Chrome</a></li>
+					<li><a rel="external" href="http://apple.com/safari">Safari 4</a></li>
+					<li><a rel="external" href="http://www.mozilla.com/firefox/">Firefox 4</a></li>
+					<li><a rel="external" href="http://www.opera.com/">Opera 11</a></li>
+				</ul>
+				<p>
+					<a href="#" id="force-init-button">仍然浏览!</a>
+				</p>
+			</div>
+			
+		</div>
+
+		<script src="/js/lib/parseUri.js"></script> 
+		<script src="/js/lib/modernizr-1.5.min.js"></script>
+		<script src="/js/jquery.min.js"></script>
+		<script src="/js/lib/Stats.js"></script>
+		
+		<script src="/js/App.js"></script>
+		<script src="/js/Model.js"></script>
+		<script src="/js/Settings.js"></script>
+		<script src="/js/Keys.js"></script>
+		<script src="/js/WebSocketService.js"></script>
+		<script src="/js/Camera.js"></script>
+		
+		<script src="/js/Tadpole.js"></script>
+		<script src="/js/TadpoleTail.js"></script>
+		
+		<script src="/js/Message.js"></script>
+		<script src="/js/WaterParticle.js"></script>
+		<script src="/js/Arrow.js"></script>
+		<script src="/js/formControls.js"></script>
+		
+		<script src="/js/Cookie.js"></script>
+		<script src="/js/main.js"></script>
+<script type="text/javascript">
+var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");
+document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F5fedb3bdce89499492c079ab4a8a0323' type='text/javascript'%3E%3C/script%3E"));
+</script>
+		
+	</body>
+</html>

+ 261 - 0
Applications/Todpole/Web/js/App.js

@@ -0,0 +1,261 @@
+
+
+var App = function(aSettings, aCanvas) {
+	var app = this;
+	
+	var 	model,
+			canvas,
+			context,
+			webSocket,
+			webSocketService,
+			mouse = {x: 0, y: 0, worldx: 0, worldy: 0, tadpole:null},
+			keyNav = {x:0,y:0},
+			messageQuota = 5
+	;
+	
+	app.update = function() {
+	  if (messageQuota < 5 && model.userTadpole.age % 50 == 0) { messageQuota++; }
+	  
+		// Update usertadpole
+		if(keyNav.x != 0 || keyNav.y != 0) {
+			model.userTadpole.userUpdate(model.tadpoles, model.userTadpole.x + keyNav.x,model.userTadpole.y + keyNav.y);
+		}
+		else {
+			var mvp = getMouseWorldPosition();
+			mouse.worldx = mvp.x;
+			mouse.worldy = mvp.y;
+			model.userTadpole.userUpdate(model.tadpoles, mouse.worldx, mouse.worldy);
+		}
+		
+		if(model.userTadpole.age % 6 == 0 && model.userTadpole.changed > 1 && webSocketService.hasConnection) {
+			model.userTadpole.changed = 0;
+			webSocketService.sendUpdate(model.userTadpole);
+		}
+		
+		model.camera.update(model);
+		
+		// Update tadpoles
+		for(id in model.tadpoles) {
+			model.tadpoles[id].update(mouse);
+		}
+		
+		// Update waterParticles
+		for(i in model.waterParticles) {
+			model.waterParticles[i].update(model.camera.getOuterBounds(), model.camera.zoom);
+		}
+		
+		// Update arrows
+		for(i in model.arrows) {
+			var cameraBounds = model.camera.getBounds();
+			var arrow = model.arrows[i];
+			arrow.update();
+		}
+	};
+	
+	
+	
+	app.draw = function() {
+		model.camera.setupContext();
+		
+		// Draw waterParticles
+		for(i in model.waterParticles) {
+			model.waterParticles[i].draw(context);
+		}
+		
+		// Draw tadpoles
+		for(id in model.tadpoles) {
+			model.tadpoles[id].draw(context);
+		}
+		
+		// Start UI layer (reset transform matrix)
+		model.camera.startUILayer();
+		
+		// Draw arrows
+		for(i in model.arrows) {
+			model.arrows[i].draw(context, canvas);
+		}
+	};
+		
+	
+	
+	app.onSocketOpen = function(e) {
+		var sendObj = {
+				type: 'login'
+			};
+			
+		webSocket.send(JSON.stringify(sendObj));
+			
+		//console.log('Socket opened!', e);
+		
+		//FIXIT: Proof of concept. refactor!
+		uri = parseUri(document.location)
+		if ( uri.queryKey.oauth_token ) {
+			app.authorize(uri.queryKey.oauth_token, uri.queryKey.oauth_verifier)						
+		}
+		// end of proof of concept code.
+	};
+	
+	app.onSocketClose = function(e) {
+		//console.log('Socket closed!', e);
+		webSocketService.connectionClosed();
+	};
+	
+	app.onSocketMessage = function(e) {
+		try {
+			var data = JSON.parse(e.data);
+			webSocketService.processMessage(data);
+		} catch(e) {}
+	};
+	
+	app.sendMessage = function(msg) {
+	  
+	  if (messageQuota>0) {
+	    messageQuota--;
+	    webSocketService.sendMessage(msg);
+	  }
+	  
+	}
+	
+	app.authorize = function(token,verifier) {
+		webSocketService.authorize(token,verifier);
+	}
+	
+	app.mousedown = function(e) {
+		mouse.clicking = true;
+
+		if(mouse.tadpole && mouse.tadpole.hover && mouse.tadpole.onclick(e)) {
+            return;
+		}
+		if(model.userTadpole && e.which == 1) {
+			model.userTadpole.momentum = model.userTadpole.targetMomentum = model.userTadpole.maxMomentum;
+		}
+
+
+	};
+	
+	app.mouseup = function(e) {
+		if(model.userTadpole && e.which == 1) {
+			model.userTadpole.targetMomentum = 0;
+		}
+	};
+	
+	app.mousemove = function(e) {
+		mouse.x = e.clientX;
+		mouse.y = e.clientY;
+	};
+
+	app.keydown = function(e) {
+		if(e.keyCode == keys.up) {
+			keyNav.y = -1;
+			model.userTadpole.momentum = model.userTadpole.targetMomentum = model.userTadpole.maxMomentum;
+			e.preventDefault();
+		}
+		else if(e.keyCode == keys.down) {
+			keyNav.y = 1;
+			model.userTadpole.momentum = model.userTadpole.targetMomentum = model.userTadpole.maxMomentum;
+			e.preventDefault();
+		}
+		else if(e.keyCode == keys.left) {
+			keyNav.x = -1;
+			model.userTadpole.momentum = model.userTadpole.targetMomentum = model.userTadpole.maxMomentum;
+			e.preventDefault();
+		}
+		else if(e.keyCode == keys.right) {
+			keyNav.x = 1;
+			model.userTadpole.momentum = model.userTadpole.targetMomentum = model.userTadpole.maxMomentum;
+			e.preventDefault();
+		}
+	};
+	app.keyup = function(e) {
+		if(e.keyCode == keys.up || e.keyCode == keys.down) {
+			keyNav.y = 0;
+			if(keyNav.x == 0 && keyNav.y == 0) {
+				model.userTadpole.targetMomentum = 0;
+			}
+			e.preventDefault();
+		}
+		else if(e.keyCode == keys.left || e.keyCode == keys.right) {
+			keyNav.x = 0;
+			if(keyNav.x == 0 && keyNav.y == 0) {
+				model.userTadpole.targetMomentum = 0;
+			}
+			e.preventDefault();
+		}
+	};
+	
+	app.touchstart = function(e) {
+	  e.preventDefault();
+	  mouse.clicking = true;		
+		
+		if(model.userTadpole) {
+			model.userTadpole.momentum = model.userTadpole.targetMomentum = model.userTadpole.maxMomentum;
+		}
+		
+		var touch = e.changedTouches.item(0);
+    if (touch) {
+      mouse.x = touch.clientX;
+  		mouse.y = touch.clientY;      
+    }    
+	}
+	app.touchend = function(e) {
+	  if(model.userTadpole) {
+			model.userTadpole.targetMomentum = 0;
+		}
+	}
+	app.touchmove = function(e) {
+	  e.preventDefault();
+    
+    var touch = e.changedTouches.item(0);
+    if (touch) {
+      mouse.x = touch.clientX;
+  		mouse.y = touch.clientY;      
+    }		
+	}
+	
+	
+	app.resize = function(e) {
+		resizeCanvas();
+	};
+	
+	var getMouseWorldPosition = function() {
+		return {
+			x: (mouse.x + (model.camera.x * model.camera.zoom - canvas.width / 2)) / model.camera.zoom,
+			y: (mouse.y + (model.camera.y * model.camera.zoom  - canvas.height / 2)) / model.camera.zoom
+		}
+	}
+	
+	var resizeCanvas = function() {
+		canvas.width = window.innerWidth;
+		canvas.height = window.innerHeight;
+	};
+	
+	// Constructor
+	(function(){
+		canvas = aCanvas;
+		context = canvas.getContext('2d');
+		resizeCanvas();
+		
+		model = new Model();
+		model.settings = aSettings;
+		
+		model.userTadpole = new Tadpole();
+		model.userTadpole.id = -1;
+		model.tadpoles[model.userTadpole.id] = model.userTadpole;
+		
+		model.waterParticles = [];
+		for(var i = 0; i < 150; i++) {
+			model.waterParticles.push(new WaterParticle());
+		}
+		
+		model.camera = new Camera(canvas, context, model.userTadpole.x, model.userTadpole.y);
+		
+		model.arrows = {};
+		
+		webSocket 				= new WebSocket( model.settings.socketServer );
+		webSocket.onopen 		= app.onSocketOpen;
+		webSocket.onclose		= app.onSocketClose;
+		webSocket.onmessage 	= app.onSocketMessage;
+		
+		webSocketService		= new WebSocketService(model, webSocket);
+	})();
+}

+ 68 - 0
Applications/Todpole/Web/js/Arrow.js

@@ -0,0 +1,68 @@
+var Arrow = function(tadpole, camera) {
+	var arrow = this;
+	
+	this.x = 0;
+	this.y = 0;
+	
+	this.tadpole = tadpole;
+	this.camera = camera;
+	
+	this.angle = 0;
+	this.distance = 10;
+	
+	this.opacity = 1;
+	
+	this.update = function() {
+		arrow.angle = Math.atan2(tadpole.y - arrow.camera.y, tadpole.x - arrow.camera.x);
+	};
+	
+	this.draw = function(context, canvas) {
+		var cameraBounds = arrow.camera.getBounds();
+		
+		if( arrow.tadpole.x < cameraBounds[0].x ||
+			arrow.tadpole.y < cameraBounds[0].y ||
+			arrow.tadpole.x > cameraBounds[1].x ||
+			arrow.tadpole.y > cameraBounds[1].y ) {
+			
+			var size = 4;
+			
+			var arrowDistance = 100;
+
+			var angle = arrow.angle;
+			var w = (canvas.width/2) - 10;
+			var h = (canvas.height/2) - 10;
+			var aa = Math.atan(h / w);
+			var ss = Math.cos(angle);
+			var cc = Math.sin(angle);
+
+			if((Math.abs(angle) + aa) % Math.PI / 2 < aa) {
+				arrowDistance = w / Math.abs(ss);
+			} else {
+				arrowDistance = h / Math.abs(cc);
+			}
+
+			var x = (canvas.width/2) + Math.cos(arrow.angle) * arrowDistance;
+			var y = (canvas.height/2) + Math.sin(arrow.angle) * arrowDistance;
+
+			var point = calcPoint(x, y, this.angle, 2, size);
+			var side1 = calcPoint(x, y, this.angle, 1.5, size);
+			var side2 = calcPoint(x, y, this.angle, 0.5, size);
+
+			// Draw arrow
+			context.fillStyle = 'rgba(255,255,255,'+arrow.opacity+')';
+			context.beginPath();
+			context.moveTo(point.x, point.y);
+			context.lineTo(side1.x, side1.y);
+			context.lineTo(side2.x, side2.y)
+			context.closePath();
+			context.fill();
+		}
+	};
+	
+	var calcPoint = function(x, y, angle, angleMultiplier, length) {
+		return {
+			x: x + Math.cos(angle + Math.PI * angleMultiplier) * length,
+			y: y + Math.sin(angle + Math.PI * angleMultiplier) * length
+		}
+	};
+}

+ 102 - 0
Applications/Todpole/Web/js/Camera.js

@@ -0,0 +1,102 @@
+var Camera = function(aCanvas, aContext, x, y) {
+	var camera = this;
+	
+	var canvas = aCanvas;
+	var context = aContext;
+	
+	this.x = x;
+	this.y = y;
+	
+	this.minZoom = 1.3;
+	this.maxZoom = 1.8;
+	this.zoom = this.minZoom;
+	
+	var backgroundColor = Math.random()*360;
+	
+	this.setupContext = function() {
+		var translateX = canvas.width / 2 - camera.x * camera.zoom;
+		var translateY = canvas.height / 2 - camera.y * camera.zoom;
+		
+		// Reset transform matrix
+		context.setTransform(1,0,0,1,0,0);
+		context.fillStyle = 'hsl('+backgroundColor+',50%,10%)';
+		context.fillRect(0,0,canvas.width, canvas.height);
+		
+		context.translate(translateX, translateY);
+		context.scale(camera.zoom, camera.zoom);
+		
+		if(debug) {
+			drawDebug();
+		}
+	};
+	
+	this.update = function(model) {
+		backgroundColor += 0.08;
+		backgroundColor = backgroundColor > 360 ? 0 : backgroundColor;
+		
+		var targetZoom = (model.camera.maxZoom + (model.camera.minZoom - model.camera.maxZoom) * Math.min(model.userTadpole.momentum, model.userTadpole.maxMomentum) / model.userTadpole.maxMomentum);
+		model.camera.zoom += (targetZoom - model.camera.zoom) / 60;
+		
+		var delta = {
+			x: (model.userTadpole.x - model.camera.x) / 30,
+			y: (model.userTadpole.y - model.camera.y) / 30
+		}
+		
+		if(Math.abs(delta.x) + Math.abs(delta.y) > 0.1) {
+			model.camera.x += delta.x;
+			model.camera.y += delta.y;
+			
+			for(var i = 0, len = model.waterParticles.length; i < len; i++) {
+				var wp = model.waterParticles[i];
+				wp.x -= (wp.z - 1) * delta.x;
+				wp.y -= (wp.z - 1) * delta.y;
+			}
+		}
+	};
+	
+	// Gets bounds of current zoom level of current position
+	this.getBounds = function() {
+		return [
+			{x: camera.x - canvas.width / 2 / camera.zoom, y: camera.y - canvas.height / 2 / camera.zoom},
+			{x: camera.x + canvas.width / 2 / camera.zoom, y: camera.y + canvas.height / 2 / camera.zoom}
+		];
+	};
+	
+	// Gets bounds of minimum zoom level of current position
+	this.getOuterBounds = function() {
+		return [
+			{x: camera.x - canvas.width / 2 / camera.minZoom, y: camera.y - canvas.height / 2 / camera.minZoom},
+			{x: camera.x + canvas.width / 2 / camera.minZoom, y: camera.y + canvas.height / 2 / camera.minZoom}
+		];
+	};
+	
+	// Gets bounds of maximum zoom level of current position
+	this.getInnerBounds = function() {
+		return [
+			{x: camera.x - canvas.width / 2 / camera.maxZoom, y: camera.y - canvas.height / 2 / camera.maxZoom},
+			{x: camera.x + canvas.width / 2 / camera.maxZoom, y: camera.y + canvas.height / 2 / camera.maxZoom}
+		];
+	};
+	
+	this.startUILayer = function() {
+		context.setTransform(1,0,0,1,0,0);
+	}
+	
+	var debugBounds = function(bounds, text) {
+		context.strokeStyle   = '#fff';
+		context.beginPath();
+		context.moveTo(bounds[0].x, bounds[0].y);
+		context.lineTo(bounds[0].x, bounds[1].y);
+		context.lineTo(bounds[1].x, bounds[1].y);
+		context.lineTo(bounds[1].x, bounds[0].y);
+		context.closePath();
+		context.stroke();
+		context.fillText(text, bounds[0].x + 10, bounds[0].y + 10);
+	};
+	
+	var drawDebug = function() {
+		debugBounds(camera.getInnerBounds(), 'Maximum zoom camera bounds');
+		debugBounds(camera.getOuterBounds(), 'Minimum zoom camera bounds');
+		debugBounds(camera.getBounds(), 'Current zoom camera bounds');
+	};
+};

+ 38 - 0
Applications/Todpole/Web/js/Cookie.js

@@ -0,0 +1,38 @@
+jQuery.cookie = function(name, value, options) {
+          if (typeof value != 'undefined') {
+                    options = options || {};
+                    if (value === null) {
+                              value = '';
+                              options = $.extend({}, options);
+                              options.expires = -1;
+                    }
+                    var expires = '';
+                    if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
+                              var date;
+                              if (typeof options.expires == 'number') {
+                                        date = new Date();
+                                        date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
+                              } else {
+                                        date = options.expires;
+                              }
+                              expires = '; expires=' + date.toUTCString();
+                    }
+                    var path = options.path ? '; path=' + (options.path) : '';
+                    var domain = options.domain ? '; domain=' + (options.domain) : '';
+                    var secure = options.secure ? '; secure' : '';
+                    document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
+          } else {
+                    var cookieValue = null;
+                    if (document.cookie && document.cookie != '') {
+                              var cookies = document.cookie.split(';');
+                              for (var i = 0; i < cookies.length; i++) {
+                                        var cookie = jQuery.trim(cookies[i]);
+                                        if (cookie.substring(0, name.length + 1) == (name + '=')) {
+                                                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                                                  break;
+                                        }
+                              }
+                    }
+                    return cookieValue;
+          }
+};

+ 9 - 0
Applications/Todpole/Web/js/Keys.js

@@ -0,0 +1,9 @@
+var keys = {
+	esc: 27,
+        enter: 13,
+        space: 32,
+        up: 38,
+        down: 40,
+	left:37,
+	right:39
+};

+ 54 - 0
Applications/Todpole/Web/js/Message.js

@@ -0,0 +1,54 @@
+var Message = function(msg) {
+	var message = this;
+	
+	this.age = 1;
+	this.maxAge = 300;
+	
+	this.message = msg;
+	
+	this.update = function() {
+		this.age++;
+	}
+	
+	this.draw = function(context,x,y,i) {
+		var fontsize = 8;
+		context.font = fontsize + "px 'proxima-nova-1','proxima-nova-2', arial, sans-serif";
+		context.textBaseline = 'hanging';
+		
+		var paddingH = 3;
+		var paddingW = 6;
+		
+		var messageBox = {
+			width: context.measureText(message.message).width + paddingW * 2,
+			height: fontsize + paddingH * 2,
+			x: x,
+			y: (y - i * (fontsize + paddingH * 2 +1))-20
+		}
+		
+		var fadeDuration = 20;
+		
+		var opacity = (message.maxAge - message.age) / fadeDuration;
+		opacity = opacity < 1 ? opacity : 1;
+		
+		context.fillStyle = 'rgba(255,255,255,'+opacity/20+')';
+		drawRoundedRectangle(context, messageBox.x, messageBox.y, messageBox.width, messageBox.height, 10);
+		context.fillStyle = 'rgba(255,255,255,'+opacity+')';
+		context.fillText(message.message, messageBox.x + paddingW, messageBox.y + paddingH, 100);
+	}
+	
+	var drawRoundedRectangle = function(ctx,x,y,w,h,r) {
+		var r = r / 2;
+		ctx.beginPath();
+		ctx.moveTo(x, y+r);
+		ctx.lineTo(x, y+h-r);
+		ctx.quadraticCurveTo(x, y+h, x+r, y+h);
+		ctx.lineTo(x+w-r, y+h);
+		ctx.quadraticCurveTo(x+w, y+h, x+w, y+h-r);
+		ctx.lineTo(x+w, y+r);
+		ctx.quadraticCurveTo(x+w, y, x+w-r, y);
+		ctx.lineTo(x+r, y);
+		ctx.quadraticCurveTo(x, y, x, y+r);
+		ctx.closePath();
+		ctx.fill();
+	}
+}

+ 6 - 0
Applications/Todpole/Web/js/Model.js

@@ -0,0 +1,6 @@
+var Model = function() {
+	this.tadpoles = {};
+	this.userTadpole;
+	this.camera;
+	this.settings;
+}

+ 14 - 0
Applications/Todpole/Web/js/Settings.js

@@ -0,0 +1,14 @@
+// 此文件下载者不用更改,兼容其他域名使用
+var Settings = function() {
+	// 如果是workerman.net phpgame.cn域名 则采用多个接入端随机负载均衡
+	var domain_arr = ['workerman.net', 'phpgame.cn','www.workerman.net', 'www.phpgame.cn'];
+	if(0 <= $.inArray(document.domain, domain_arr))
+	{
+		this.socketServer = 'ws://'+domain_arr[Math.floor(Math.random() * domain_arr.length + 1)-1]+':8585';
+	}
+	else
+	{
+		// 运行在其它域名上
+		this.socketServer = 'ws://'+document.domain+':8585';
+	}
+}

+ 171 - 0
Applications/Todpole/Web/js/Tadpole.js

@@ -0,0 +1,171 @@
+var Tadpole = function() {
+	var tadpole = this;
+	
+	this.x = Math.random() * 300 - 150;
+	this.y = Math.random() * 300 - 150;
+	this.size = 4;
+	
+	this.name = '';
+	this.age = 0;
+	
+	this.hover = false;
+
+	this.momentum = 0;
+	this.maxMomentum = 3;
+	this.angle = Math.PI * 2;
+	
+	this.targetX = 0;
+	this.targetY = 0;
+	this.targetMomentum = 0;
+	
+	this.messages = [];
+	this.timeSinceLastActivity = 0;
+	
+	this.changed = 0;
+	this.timeSinceLastServerUpdate = 0;
+	
+	this.update = function(mouse) {
+		tadpole.timeSinceLastServerUpdate++;
+		
+		tadpole.x += Math.cos(tadpole.angle) * tadpole.momentum;
+		tadpole.y += Math.sin(tadpole.angle) * tadpole.momentum;
+		
+		if(tadpole.targetX != 0 || tadpole.targetY != 0) {
+			tadpole.x += (tadpole.targetX - tadpole.x) / 20;
+			tadpole.y += (tadpole.targetY - tadpole.y) / 20;
+		}
+		
+		// Update messages
+		for (var i = tadpole.messages.length - 1; i >= 0; i--) {
+			var msg = tadpole.messages[i];
+			msg.update();
+			
+			if(msg.age == msg.maxAge) {
+				tadpole.messages.splice(i,1);
+			}
+		}
+
+		// Update tadpole hover/mouse state
+		if(Math.sqrt(Math.pow(tadpole.x - mouse.worldx,2) + Math.pow(tadpole.y - mouse.worldy,2)) < tadpole.size+2) {
+			tadpole.hover = true;
+			mouse.tadpole = tadpole;
+		}
+		else {
+			if(mouse.tadpole && mouse.tadpole.id == tadpole.id) {
+				//mouse.tadpole = null;
+			}
+			tadpole.hover = false;
+		}
+
+		tadpole.tail.update();
+	};
+	
+	this.onclick = function(e) {
+		if(e.ctrlKey && e.which == 1) {
+			if(isAuthorized() && tadpole.hover) {
+				window.open("http://twitter.com/" + tadpole.name.substring(1));
+                return true;
+			}
+		}
+		else if(e.which == 2) {
+			//todo:open menu
+			e.preventDefault();
+            return true;
+		}
+        return false;
+	};
+	
+	this.userUpdate = function(tadpoles, angleTargetX, angleTargetY) {
+		this.age++;
+		
+		var prevState = {
+			angle: tadpole.angle,
+			momentum: tadpole.momentum,
+		}
+		
+		// Angle to targetx and targety (mouse position)
+		var anglediff = ((Math.atan2(angleTargetY - tadpole.y, angleTargetX - tadpole.x)) - tadpole.angle);
+		while(anglediff < -Math.PI) {
+			anglediff += Math.PI * 2;
+		}
+		while(anglediff > Math.PI) {
+			anglediff -= Math.PI * 2;
+		}
+		
+		tadpole.angle += anglediff / 5;
+		
+		// Momentum to targetmomentum
+		if(tadpole.targetMomentum != tadpole.momentum) {
+			tadpole.momentum += (tadpole.targetMomentum - tadpole.momentum) / 20;
+		}
+				
+		if(tadpole.momentum < 0) {
+			tadpole.momentum = 0;
+		}
+		
+		tadpole.changed += Math.abs((prevState.angle - tadpole.angle)*3) + tadpole.momentum;
+		
+		if(tadpole.changed > 1) {
+			this.timeSinceLastServerUpdate = 0;
+		}
+	};
+	
+	this.draw = function(context) {
+		var opacity = Math.max(Math.min(20 / Math.max(tadpole.timeSinceLastServerUpdate-300,1),1),.2).toFixed(3);
+
+		if(tadpole.hover && isAuthorized()) {
+			context.fillStyle = 'rgba(192, 253, 247,'+opacity+')';
+			// context.shadowColor   = 'rgba(249, 136, 119, '+opacity*0.7+')';
+		}
+		else {
+			context.fillStyle = 'rgba(226,219,226,'+opacity+')';
+		}
+		
+		context.shadowOffsetX = 0;
+		context.shadowOffsetY = 0;
+		context.shadowBlur    = 6;
+		context.shadowColor   = 'rgba(255, 255, 255, '+opacity*0.7+')';
+		
+		// Draw circle
+		context.beginPath();
+		context.arc(tadpole.x, tadpole.y, tadpole.size, tadpole.angle + Math.PI * 2.7, tadpole.angle + Math.PI * 1.3, true); 
+		
+		tadpole.tail.draw(context);
+		
+		context.closePath();
+		context.fill();
+		
+		context.shadowBlur = 0;
+		context.shadowColor   = '';
+		
+		drawName(context);
+		drawMessages(context);
+	};
+	
+	var isAuthorized = function() {
+		return tadpole.name.charAt('0') == "@";
+	};
+	
+	var drawName = function(context) {
+		var opacity = Math.max(Math.min(20 / Math.max(tadpole.timeSinceLastServerUpdate-300,1),1),.2).toFixed(3);
+		context.fillStyle = 'rgba(226,219,226,'+opacity+')';
+		context.font = 7 + "px 'proxima-nova-1','proxima-nova-2', arial, sans-serif";
+		context.textBaseline = 'hanging';
+		var width = context.measureText(tadpole.name).width;
+		context.fillText(tadpole.name, tadpole.x - width/2, tadpole.y + 8);
+	}
+	
+	var drawMessages = function(context) {
+		tadpole.messages.reverse();
+		for(var i = 0, len = tadpole.messages.length; i<len; i++) {
+			tadpole.messages[i].draw(context, tadpole.x+10, tadpole.y+5, i);
+		}
+		tadpole.messages.reverse();
+	};
+	
+	
+	// Constructor
+	(function() {
+		tadpole.tail = new TadpoleTail(tadpole);
+	})();
+}

+ 75 - 0
Applications/Todpole/Web/js/TadpoleTail.js

@@ -0,0 +1,75 @@
+var TadpoleTail = function(tadpole) {
+	var tail = this;
+	tail.joints = [];
+	
+	var tadpole = tadpole;
+	var jointSpacing = 1.4;
+	var animationRate = 0;
+	
+	
+	tail.update = function() {
+		animationRate += (.2 + tadpole.momentum / 10);
+		
+		for(var i = 0, len = tail.joints.length; i < len; i++) {
+			var tailJoint = tail.joints[i];
+			var parentJoint = tail.joints[i-1] || tadpole;
+			var anglediff = (parentJoint.angle - tailJoint.angle);
+			
+			while(anglediff < -Math.PI) {
+				anglediff += Math.PI * 2;
+			}
+			while(anglediff > Math.PI) {
+				anglediff -= Math.PI * 2;
+			}
+			
+			tailJoint.angle += anglediff * (jointSpacing * 3 + (Math.min(tadpole.momentum / 2, Math.PI * 1.8))) / 8;
+			tailJoint.angle += Math.cos(animationRate - (i / 3)) * ((tadpole.momentum + .3) / 40);
+			
+			if(i == 0) {
+				tailJoint.x = parentJoint.x + Math.cos(tailJoint.angle + Math.PI) * 5;
+				tailJoint.y = parentJoint.y + Math.sin(tailJoint.angle + Math.PI) * 5;
+			} else {
+				tailJoint.x = parentJoint.x + Math.cos(tailJoint.angle + Math.PI) * jointSpacing;
+				tailJoint.y = parentJoint.y + Math.sin(tailJoint.angle + Math.PI) * jointSpacing;
+			}
+		}
+	};
+	
+	tail.draw = function(context) {
+		var path = [[],[]];
+		
+		for(var i = 0, len = tail.joints.length; i < len; i++) {
+			var tailJoint = tail.joints[i];
+			
+			var falloff = (tail.joints.length - i) / tail.joints.length;
+			var jointSize =  (tadpole.size - 1.8) * falloff;
+			
+			var x1 = tailJoint.x + Math.cos(tailJoint.angle + Math.PI * 1.5) * jointSize;
+			var y1 = tailJoint.y + Math.sin(tailJoint.angle + Math.PI * 1.5) * jointSize;
+			
+			var x2 = tailJoint.x + Math.cos(tailJoint.angle + Math.PI / 2) * jointSize;
+			var y2 = tailJoint.y + Math.sin(tailJoint.angle + Math.PI / 2) * jointSize;
+			
+			path[0].push({x: x1, y: y1});
+			path[1].push({x: x2, y: y2});
+		}
+		
+		for(var i = 0; i < path[0].length; i++) {
+			context.lineTo(path[0][i].x, path[0][i].y);
+		}
+		path[1].reverse();
+		for(var i = 0; i < path[1].length; i++) {
+			context.lineTo(path[1][i].x, path[1][i].y);
+		}
+	};
+	
+	(function() {
+		for(var i = 0; i < 15; i++) {
+			tail.joints.push({
+				x: 0,
+				y: 0,
+				angle: Math.PI*2,
+			})
+		}
+	})();
+}

+ 32 - 0
Applications/Todpole/Web/js/WaterParticle.js

@@ -0,0 +1,32 @@
+var WaterParticle = function() {
+	var wp = this;
+	
+	wp.x = 0;
+	wp.y = 0;
+	wp.z = Math.random() * 1 + 0.3;
+	wp.size = 1.2;
+	wp.opacity = Math.random() * 0.8 + 0.1;
+	
+	wp.update = function(bounds) {
+		if(wp.x == 0 || wp.y == 0) {
+			wp.x = Math.random() * (bounds[1].x - bounds[0].x) + bounds[0].x;
+			wp.y = Math.random() * (bounds[1].y - bounds[0].y) + bounds[0].y;
+		}
+		
+		// Wrap around screen
+		wp.x = wp.x < bounds[0].x ? bounds[1].x : wp.x;
+		wp.y = wp.y < bounds[0].y ? bounds[1].y : wp.y;
+		wp.x = wp.x > bounds[1].x ? bounds[0].x : wp.x;
+		wp.y = wp.y > bounds[1].y ? bounds[0].y : wp.y;
+	};
+	
+	wp.draw = function(context) {
+		// Draw circle
+		context.fillStyle = 'rgba(226,219,226,'+wp.opacity+')';
+		//context.fillStyle = '#fff';
+		context.beginPath();
+		context.arc(wp.x, wp.y, this.z * this.size, 0, Math.PI*2, true);
+		context.closePath();
+		context.fill();
+	};
+}

+ 133 - 0
Applications/Todpole/Web/js/WebSocketService.js

@@ -0,0 +1,133 @@
+var WebSocketService = function(model, webSocket) {
+	var webSocketService = this;
+	
+	var webSocket = webSocket;
+	var model = model;
+	
+	this.hasConnection = false;
+	
+	this.welcomeHandler = function(data) {
+		webSocketService.hasConnection = true;
+		
+		model.userTadpole.id = data.id;
+		model.tadpoles[data.id] = model.tadpoles[-1];
+		delete model.tadpoles[-1];
+		
+		$('#chat').initChat();
+		if($.cookie('todpole_name'))	{
+			webSocketService.sendMessage('name:'+$.cookie('todpole_name'));
+		}
+	};
+	
+	this.updateHandler = function(data) {
+		var newtp = false;
+		
+		if(!model.tadpoles[data.id]) {
+			newtp = true;
+			model.tadpoles[data.id] = new Tadpole();
+			model.arrows[data.id] = new Arrow(model.tadpoles[data.id], model.camera);
+		}
+		
+		var tadpole = model.tadpoles[data.id];
+		
+		if(tadpole.id == model.userTadpole.id) {			
+			tadpole.name = data.name;
+			return;
+		} else {
+			tadpole.name = data.name;
+		}
+		
+		if(newtp) {
+			tadpole.x = data.x;
+			tadpole.y = data.y;
+		} else {
+			tadpole.targetX = data.x;
+			tadpole.targetY = data.y;
+		}
+		
+		tadpole.angle = data.angle;
+		tadpole.momentum = data.momentum;
+		
+		tadpole.timeSinceLastServerUpdate = 0;
+	}
+	
+	this.messageHandler = function(data) {
+		var tadpole = model.tadpoles[data.id];
+		if(!tadpole) {
+			return;
+		}
+		tadpole.timeSinceLastServerUpdate = 0;
+		tadpole.messages.push(new Message(data.message));
+	}
+	
+	this.closedHandler = function(data) {
+		if(model.tadpoles[data.id]) {
+			delete model.tadpoles[data.id];
+			delete model.arrows[data.id];
+		}
+	}
+	
+	this.redirectHandler = function(data) {
+		if (data.url) {
+			if (authWindow) {
+				authWindow.document.location = data.url;
+			} else {
+				document.location = data.url;
+			}			
+		}
+	}
+	
+	this.processMessage = function(data) {
+		var fn = webSocketService[data.type + 'Handler'];
+		if (fn) {
+			fn(data);
+		}
+	}
+	
+	this.connectionClosed = function() {
+		webSocketService.hasConnection = false;
+		$('#cant-connect').fadeIn(300);
+	};
+	
+	this.sendUpdate = function(tadpole) {
+		var sendObj = {
+			type: 'update',
+			x: tadpole.x.toFixed(1),
+			y: tadpole.y.toFixed(1),
+			angle: tadpole.angle.toFixed(3),
+			momentum: tadpole.momentum.toFixed(3)
+		};
+		
+		if(tadpole.name) {
+			sendObj['name'] = tadpole.name;
+		}
+		
+		webSocket.send(JSON.stringify(sendObj));
+	}
+	
+	this.sendMessage = function(msg) {
+		var regexp = /name: ?(.+)/i;
+		if(regexp.test(msg)) {
+			model.userTadpole.name = msg.match(regexp)[1];
+			$.cookie('todpole_name', model.userTadpole.name, {expires:14});
+			return;
+		}
+		
+		var sendObj = {
+			type: 'message',
+			message: msg
+		};
+		
+		webSocket.send(JSON.stringify(sendObj));
+	}
+	
+	this.authorize = function(token,verifier) {
+		var sendObj = {
+			type: 'authorize',
+			token: token,
+			verifier: verifier
+		};
+		
+		webSocket.send(JSON.stringify(sendObj));
+	}
+}

+ 106 - 0
Applications/Todpole/Web/js/formControls.js

@@ -0,0 +1,106 @@
+// Settings controls
+
+(function($){
+	
+	$.fn.initChat = function() {
+		var input = $(this);
+		var chatText = $("#chatText");
+		var hidden = true;
+		var messageHistory = [];
+		var messagePointer = -1;
+
+		var closechat = function() {
+			hidden = true;
+			input.css("opacity","0");
+			messagePointer = messageHistory.length;
+			input.val('');
+			chatText.text('')
+		}
+
+		var updateDimensions = function(){
+			chatText.text(input.val());
+			var width = chatText.width() + 30;
+			input.css({
+				width: width,
+				marginLeft: (width/2)*-1
+			});
+		};
+
+		input.blur(function(e) {
+			setTimeout(function(){input.focus()}, 0.1);
+		});
+		input.keydown(function(e){
+			if(input.val().length > 0) {
+				//set timeout because event occurs before text is entered
+				setTimeout(updateDimensions,0.1);
+				input.css("opacity","1");		
+			} else {
+				closechat();
+			}
+			
+			if(!hidden) {
+		
+				e.stopPropagation();
+				if(messageHistory.length > 0) {
+					if(e.keyCode == keys.up)
+					{
+						if(messagePointer > 0)
+						{
+							messagePointer--;
+							input.val(messageHistory[messagePointer]);
+						}
+					}
+					else if(e.keyCode == keys.down)
+					{
+						if(messagePointer < messageHistory.length-1)
+						{
+							messagePointer++;
+							input.val(messageHistory[messagePointer]);
+						}
+						else 
+						{
+							closechat();
+							return;
+						}
+					}
+				}
+			}
+		});
+		input.keyup(function(e) {
+
+			var k = e.keyCode;
+			if(input.val().length >= 45)
+			{
+				input.val(input.val().substr(0,45));
+			}
+
+			if(input.val().length > 0) {
+				updateDimensions();
+				input.css("opacity","1");
+				hidden = false;
+			} else {
+				closechat();
+			}
+			if(!hidden) {
+				if(k == keys.esc || k == keys.enter || (k == keys.space && input.val().length > 35)) {
+					if(k != keys.esc && input.val().length > 0) {
+					    	messageHistory.push(input.val());
+			    			messagePointer = messageHistory.length;
+						app.sendMessage(input.val());
+					}
+					closechat();
+				}
+				
+				e.stopPropagation();
+
+			}
+			
+		});
+		
+		input.focus();
+	}
+	
+	$(function() {
+		//$('#chat').initChat();
+	});
+})(jQuery);

+ 3 - 0
Applications/Todpole/Web/js/frogMode.js

@@ -0,0 +1,3 @@
+var frogMode = function() {	
+	
+}

+ 0 - 0
applications/Statistics/Web/js/jquery.min.js → Applications/Todpole/Web/js/jquery.min.js


+ 193 - 0
Applications/Todpole/Web/js/lib/Stats.js

@@ -0,0 +1,193 @@
+/*
+ * stats.js r4
+ * http://github.com/mrdoob/stats.js
+ *
+ * Released under MIT license:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * How to use:
+ *
+ *  var stats = new Stats();
+ *  parentElement.appendChild(stats.domElement);
+ *
+ *  setInterval(function () {
+ *
+ *  	stats.update();
+ *
+ *  }, 1000/60);
+ *
+ */
+
+var Stats = function () {
+
+	var _container, _mode = 'fps',
+	_frames = 0, _time = new Date().getTime(), _timeLastFrame = _time, _timeLastSecond = _time,
+	_fps = 0, _fpsMin = 1000, _fpsMax = 0, _fpsText, _fpsCanvas, _fpsContext, _fpsImageData,
+	_ms = 0, _msMin = 1000, _msMax = 0, _msText, _msCanvas, _msContext, _msImageData;
+
+	_container = document.createElement( 'div' );
+	_container.style.fontFamily = 'Helvetica, Arial, sans-serif';
+	_container.style.fontSize = '9px';
+	_container.style.backgroundColor = '#000020';
+	_container.style.opacity = '0.9';
+	_container.style.width = '80px';
+	_container.style.paddingTop = '2px';
+	_container.style.cursor = 'pointer';
+	_container.addEventListener( 'click', swapMode, false );
+
+	_fpsText = document.createElement( 'div' );
+	_fpsText.innerHTML = '<strong>FPS</strong>';
+	_fpsText.style.color = '#00ffff';
+	_fpsText.style.marginLeft = '3px';
+	_fpsText.style.marginBottom = '3px';
+	_container.appendChild(_fpsText);
+
+	_fpsCanvas = document.createElement( 'canvas' );
+	_fpsCanvas.width = 74;
+	_fpsCanvas.height = 30;
+	_fpsCanvas.style.display = 'block';
+	_fpsCanvas.style.marginLeft = '3px';
+	_fpsCanvas.style.marginBottom = '3px';
+	_container.appendChild(_fpsCanvas);
+
+	_fpsContext = _fpsCanvas.getContext( '2d' );
+	_fpsContext.fillStyle = '#101030';
+	_fpsContext.fillRect( 0, 0, _fpsCanvas.width, _fpsCanvas.height );
+
+	_fpsImageData = _fpsContext.getImageData( 0, 0, _fpsCanvas.width, _fpsCanvas.height );
+
+	_msText = document.createElement( 'div' );
+	_msText.innerHTML = '<strong>MS</strong>';
+	_msText.style.color = '#00ffff';
+	_msText.style.marginLeft = '3px';
+	_msText.style.marginBottom = '3px';
+	_msText.style.display = 'none';
+	_container.appendChild(_msText);
+
+	_msCanvas = document.createElement( 'canvas' );
+	_msCanvas.width = 74;
+	_msCanvas.height = 30;
+	_msCanvas.style.display = 'block';
+	_msCanvas.style.marginLeft = '3px';
+	_msCanvas.style.marginBottom = '3px';
+	_msCanvas.style.display = 'none';
+	_container.appendChild(_msCanvas);
+
+	_msContext = _msCanvas.getContext( '2d' );
+	_msContext.fillStyle = '#101030';
+	_msContext.fillRect( 0, 0, _msCanvas.width, _msCanvas.height );
+
+	_msImageData = _msContext.getImageData( 0, 0, _msCanvas.width, _msCanvas.height );
+
+	function updateGraph( data, value ) {
+
+		var x, y, index;
+
+		for ( y = 0; y < 30; y++ ) {
+
+			for ( x = 0; x < 73; x++ ) {
+
+				index = (x + y * 74) * 4;
+
+				data[ index ] = data[ index + 4 ];
+				data[ index + 1 ] = data[ index + 5 ];
+				data[ index + 2 ] = data[ index + 6 ];
+
+			}
+
+		}
+
+		for ( y = 0; y < 30; y++ ) {
+
+			index = (73 + y * 74) * 4;
+
+			if ( y < value ) {
+
+				data[ index ] = 16;
+				data[ index + 1 ] = 16;
+				data[ index + 2 ] = 48;
+
+			} else {
+
+				data[ index ] = 0;
+				data[ index + 1 ] = 255;
+				data[ index + 2 ] = 255;
+
+			}
+
+		}
+
+	}
+
+	function swapMode() {
+
+		switch( _mode ) {
+
+			case 'fps':
+
+				_mode = 'ms';
+
+				_fpsText.style.display = 'none';
+				_fpsCanvas.style.display = 'none';
+				_msText.style.display = 'block';
+				_msCanvas.style.display = 'block';
+
+				break;
+
+			case 'ms':
+
+				_mode = 'fps';
+
+				_fpsText.style.display = 'block';
+				_fpsCanvas.style.display = 'block';
+				_msText.style.display = 'none';
+				_msCanvas.style.display = 'none';
+
+				break;
+
+		}
+
+	}
+
+	return {
+
+		domElement: _container,
+
+		update: function () {
+
+			_frames ++;
+
+			_time = new Date().getTime();
+
+			_ms = _time - _timeLastFrame;
+			_msMin = Math.min( _msMin, _ms );
+			_msMax = Math.max( _msMax, _ms );
+
+			updateGraph( _msImageData.data, Math.min( 30, 30 - ( _ms / 200 ) * 30 ) );
+
+			_msText.innerHTML = '<strong>' + _ms + ' MS</strong> (' + _msMin + '-' + _msMax + ')';
+			_msContext.putImageData( _msImageData, 0, 0 );
+
+			_timeLastFrame = _time;
+
+			if ( _time > _timeLastSecond + 1000 ) {
+
+				_fps = Math.round( ( _frames * 1000) / ( _time - _timeLastSecond ) );
+				_fpsMin = Math.min( _fpsMin, _fps );
+				_fpsMax = Math.max( _fpsMax, _fps );
+
+				updateGraph( _fpsImageData.data, Math.min( 30, 30 - ( _fps / 100 ) * 30 ) );
+
+				_fpsText.innerHTML = '<strong>' + _fps + ' FPS</strong> (' + _fpsMin + '-' + _fpsMax + ')';
+				_fpsContext.putImageData( _fpsImageData, 0, 0 );
+
+				_timeLastSecond = _time;
+				_frames = 0;
+
+			}
+
+		}
+
+	};
+
+};

+ 154 - 0
Applications/Todpole/Web/js/lib/jquery-1.4.2.min.js

@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);

File diff ditekan karena terlalu besar
+ 19 - 0
Applications/Todpole/Web/js/lib/modernizr-1.5.min.js


+ 32 - 0
Applications/Todpole/Web/js/lib/parseUri.js

@@ -0,0 +1,32 @@
+// parseUri 1.2.2
+// (c) Steven Levithan <stevenlevithan.com>
+// MIT License
+
+function parseUri (str) {
+	var	o   = parseUri.options,
+		m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
+		uri = {},
+		i   = 14;
+
+	while (i--) uri[o.key[i]] = m[i] || "";
+
+	uri[o.q.name] = {};
+	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
+		if ($1) uri[o.q.name][$1] = $2;
+	});
+
+	return uri;
+};
+
+parseUri.options = {
+	strictMode: false,
+	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+	q:   {
+		name:   "queryKey",
+		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+	},
+	parser: {
+		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
+		loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+	}
+};

+ 81 - 0
Applications/Todpole/Web/js/main.js

@@ -0,0 +1,81 @@
+var settings = new Settings();
+
+var debug = false;
+var isStatsOn = false;
+
+var authWindow;
+
+var app;
+var runLoop = function() {
+	app.update();
+	app.draw();
+}
+var initApp = function() {
+	if (app!=null) { return; }
+	app = new App(settings, document.getElementById('canvas'));
+
+	window.addEventListener('resize', app.resize, false);
+
+	document.addEventListener('mousemove', 		app.mousemove, false);
+	document.addEventListener('mousedown', 		app.mousedown, false);
+	document.addEventListener('mouseup',			app.mouseup, false);
+	
+	document.addEventListener('touchstart',   app.touchstart, false);
+	document.addEventListener('touchend',     app.touchend, false);
+	document.addEventListener('touchcancel',  app.touchend, false);
+	document.addEventListener('touchmove',    app.touchmove, false);	
+
+	document.addEventListener('keydown',    app.keydown, false);
+	document.addEventListener('keyup',    app.keyup, false);
+	
+	setInterval(runLoop,30);
+}
+
+var forceInit = function() {
+	initApp()
+	document.getElementById('unsupported-browser').style.display = "none";
+	return false;
+}
+
+if(Modernizr.canvas && Modernizr.websockets) {
+	initApp();
+} else {
+	document.getElementById('unsupported-browser').style.display = "block";	
+	document.getElementById('force-init-button').addEventListener('click', forceInit, false);
+}
+
+var addStats = function() {
+	if (isStatsOn) { return; }
+	// Draw fps
+	var stats = new Stats();
+	document.getElementById('fps').appendChild(stats.domElement);
+
+	setInterval(function () {
+	    stats.update();
+	}, 1000/60);
+
+	// Array Remove - By John Resig (MIT Licensed)
+	Array.remove = function(array, from, to) {
+	  var rest = array.slice((to || from) + 1 || array.length);
+	  array.length = from < 0 ? array.length + from : from;
+	  return array.push.apply(array, rest);
+	};
+	isStatsOn = true;
+}
+
+//document.addEventListener('keydown',function(e) {
+//	if(e.which == 27) {
+//		addStats();
+//	}
+//})
+
+if(debug) { addStats(); }
+
+$(function() {
+	$('a[rel=external]').click(function(e) {
+		e.preventDefault();
+		window.open($(this).attr('href'));
+	});
+});
+
+document.body.onselectstart = function() { return false; }

+ 37 - 0
Applications/Todpole/start.php

@@ -0,0 +1,37 @@
+<?php 
+use \Workerman\WebServer;
+use \GatewayWorker\Gateway;
+use \GatewayWorker\BusinessWorker;
+
+// gateway
+$gateway = new Gateway("Websocket://0.0.0.0:8585");
+
+$gateway->name = 'TodpoleGateway';
+
+$gateway->count = 4;
+
+$gateway->lanIp = '127.0.0.1';
+
+$gateway->startPort = 4000;
+
+$gateway->pingInterval = 10;
+
+$gateway->pingData = '{"type":"ping"}';
+
+
+// bussinessWorker
+$worker = new BusinessWorker();
+
+$worker->name = 'TodpoleBusinessWorker';
+
+$worker->count = 4;
+
+
+// WebServer
+$web = new WebServer("http://0.0.0.0:8686");
+
+$web->user = 'www-data';
+
+$web->count = 2;
+
+$web->addRoot('www.your_domain.com', __DIR__.'/Web');

+ 151 - 0
GatewayWorker/BusinessWorker.php

@@ -0,0 +1,151 @@
+<?php
+namespace GatewayWorker;
+
+use \Workerman\Worker;
+use \Workerman\Connection\AsyncTcpConnection;
+use \Workerman\Protocols\GatewayProtocol;
+use \Workerman\Lib\Timer;
+use \GatewayWorker\Lib\Lock;
+use \GatewayWorker\Lib\Store;
+use \GatewayWorker\Lib\Context;
+use \GatewayWorker\Lib\Autoloader;
+use \Event;
+
+class BusinessWorker extends Worker
+{
+    const MAX_RETRY_COUNT = 5;
+    
+    public $gatewayConnections = array();
+    
+    public $badGatewayAddress = array();
+    
+    protected $_rootPath = '';
+    
+    public function __construct($socket_name = '', $context_option = array())
+    {
+        $this->onWorkerStart = array($this, 'onWorkerStart');
+        $backrace = debug_backtrace();
+        $this->_rootPath = dirname($backrace[0]['file']);
+        parent::__construct($socket_name, $context_option);
+    }
+    
+    protected function onWorkerStart()
+    {
+        Autoloader::setRootPath($this->_rootPath);
+        Timer::add(1, array($this, 'checkGatewayConnections'));
+        $this->checkGatewayConnections();
+        \GatewayWorker\Lib\Gateway::setBusinessWorker($this);
+    }
+    
+    public function onGatewayMessage($connection, $data)
+    {
+        Context::$client_ip = $data['client_ip'];
+        Context::$client_port = $data['client_port'];
+        Context::$local_ip = $data['local_ip'];
+        Context::$local_port = $data['local_port'];
+        Context::$client_id = $data['client_id'];
+        $_SERVER = array(
+                'REMOTE_ADDR' => Context::$client_ip,
+                'REMOTE_PORT' => Context::$client_port,
+                'GATEWAY_ADDR' => Context::$local_ip,
+                'GATEWAY_PORT'  => Context::$local_port,
+                'GATEWAY_CLIENT_ID' => Context::$client_id,
+        );
+        if($data['ext_data'] != '')
+        {
+            $_SESSION = Context::sessionDecode($data['ext_data']);
+        }
+        else
+        {
+            $_SESSION = null;
+        }
+        // 备份一次$data['ext_data'],请求处理完毕后判断session是否和备份相等,不相等就更新session
+        $session_str_copy = $data['ext_data'];
+        $cmd = $data['cmd'];
+    
+        try{
+            switch($cmd)
+            {
+                case GatewayProtocol::CMD_ON_CONNECTION:
+                    Event::onConnect(Context::$client_id);
+                    break;
+                case GatewayProtocol::CMD_ON_MESSAGE:
+                    Event::onMessage(Context::$client_id, $data['body']);
+                    break;
+                case GatewayProtocol::CMD_ON_CLOSE:
+                    Event::onClose(Context::$client_id);
+                    break;
+            }
+        }
+        catch(\Exception $e)
+        {
+            $msg = 'client_id:'.Context::$client_id."\tclient_ip:".Context::$client_ip."\n".$e->__toString();
+            $this->log($msg);
+        }
+    
+        $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : '';
+        if($session_str_copy != $session_str_now)
+        {
+            \GatewayWorker\Lib\Gateway::updateSocketSession(Context::$client_id, $session_str_now);
+        }
+    
+        Context::clear();
+    }
+    
+    public function onClose($connection)
+    {
+        unset($this->gatewayConnections[$connection->remoteAddress]);
+    }
+    
+    public function checkGatewayConnections()
+    {
+        $key = 'GLOBAL_GATEWAY_ADDRESS';
+        $addresses_list = Store::instance('gateway')->get($key);
+        if(empty($addresses_list))
+        {
+            return;
+        }
+        foreach($addresses_list as $addr)
+        {
+            if(!isset($this->gatewayConnections[$addr]))
+            {
+                $gateway_connection = new AsyncTcpConnection("GatewayProtocol://$addr", self::$_globalEvent);
+                $gateway_connection->remoteAddress = $addr;
+                $gateway_connection->onConnect = array($this, 'onConnectGateway');
+                $gateway_connection->onMessage = array($this, 'onGatewayMessage');
+                $gateway_connection->onClose = array($this, 'onClose');
+                $gateway_connection->onError = array($this, 'onError');
+            }
+        }
+    }
+    
+    public function onConnectGateway($connection)
+    {
+        $this->gatewayConnections[$connection->remoteAddress] = $connection;
+        unset($this->badGatewayAddress[$connection->remoteAddress]);
+    }
+    
+    public function onError($connection, $error_no, $error_msg)
+    {
+         $this->tryToDeleteGatewayAddress($connection->remoteAddress, $error_msg);
+    }
+    
+    public function tryToDeleteGatewayAddress($addr, $errstr)
+    {
+        $key = 'GLOBAL_GATEWAY_ADDRESS';
+        if(!isset($this->badGatewayAddress[$addr]))
+        {
+            $this->badGatewayAddress[$addr] = 0;
+        }
+        // 删除连不上的端口
+        if($this->badGatewayAddress[$addr]++ > self::MAX_RETRY_COUNT)
+        {
+            Lock::get();
+            $addresses_list = Store::instance('gateway')->get($key);
+            unset($addresses_list[$addr]);
+            Store::instance('gateway')->set($key, $addresses_list);
+            Lock::release();
+            $this->log("tcp://$addr ".$errstr." del $addr from store", false);
+        }
+    }
+}

+ 360 - 0
GatewayWorker/Gateway.php

@@ -0,0 +1,360 @@
+<?php 
+namespace GatewayWorker;
+
+use \Workerman\Worker;
+use \Workerman\Lib\Timer;
+use \Workerman\Protocols\GatewayProtocol;
+use \GatewayWorker\Lib\Lock;
+use \GatewayWorker\Lib\Store;
+use \GatewayWorker\Lib\Autoloader;
+
+class Gateway extends Worker
+{
+    public $lanIp = '127.0.0.1';
+    
+    public $startPort = 2000;
+    
+    public $reloadable = false;
+    
+    public $pingInterval = 0;
+
+    public $pingNotResponseLimit = 0;
+    
+    public $pingData = '';
+    
+    protected $_clientConnections = array();
+    
+    protected $_workerConnections = array();
+    
+    protected $_innerTcpWorker = null;
+    
+    protected $_innerUdpWorker = null;
+    
+    protected $_rootPath = '';
+    
+    public function __construct($socket_name, $context_option = array())
+    {
+        $this->onWorkerStart = array($this, 'onWorkerStart');
+        $this->onConnect = array($this, 'onClientConnect');
+        $this->onMessage = array($this, 'onClientMessage');
+        $this->onClose = array($this, 'onClientClose');
+        $this->onWorkerStop = array($this, 'onWorkerStop');
+        $backrace = debug_backtrace();
+        $this->_rootPath = dirname($backrace[0]['file']);
+        parent::__construct($socket_name, $context_option);
+    }
+    
+    public function onClientMessage($connection, $data)
+    {
+        $connection->pingNotResponseCount = 0;
+        $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data);
+    }
+    
+    public function onClientConnect($connection)
+    {
+        $connection->globalClientId = $this->createGlobalClientId();
+        $connection->gatewayHeader = array(
+            'local_ip' => $this->lanIp,
+            'local_port' => $this->lanPort,
+            'client_ip'=>$connection->getRemoteIp(),
+            'client_port'=>$connection->getRemotePort(),
+            'client_id'=>$connection->globalClientId,
+        );
+        $connection->session = '';
+        $connection->pingNotResponseCount = 0;
+        $this->_clientConnections[$connection->globalClientId] = $connection;
+        $address = $this->lanIp.':'.$this->lanPort;
+        $this->storeClientAddress($connection->globalClientId, $address);
+        if(method_exists('Event','onConnect'))
+        {
+            $this->sendToWorker(GatewayProtocol::CMD_ON_CONNECTION, $connection);
+        }
+    }
+    
+    protected function sendToWorker($cmd, $connection, $body = '')
+    {
+        $gateway_data = $connection->gatewayHeader;
+        $gateway_data['cmd'] = $cmd;
+        $gateway_data['body'] = $body;
+        $gateway_data['ext_data'] = $connection->session;
+        $key = array_rand($this->_workerConnections);
+        if($key)
+        {
+            if(false === $this->_workerConnections[$key]->send($gateway_data))
+            {
+                $msg = "sendBufferToWorker fail. May be the send buffer are overflow";
+                $this->log($msg);
+                return false;
+            }
+        }
+        else
+        {
+            $msg = "endBufferToWorker fail. the connections between Gateway and BusinessWorker are not ready";
+            $this->log($msg);
+            return false;
+        }
+        return true;
+    }
+    
+    /**
+     * @param int $global_client_id
+     * @param string $address
+     */
+    protected function storeClientAddress($global_client_id, $address)
+    {
+        if(!Store::instance('gateway')->set('gateway-'.$global_client_id, $address))
+        {
+            $msg = 'storeClientAddress fail.';
+            if(get_class(Store::instance('gateway')) == 'Memcached')
+            {
+                $msg .= " reason :".Store::instance('gateway')->getResultMessage();
+            }
+            $this->log($msg);
+            return false;
+        }
+        return true;
+    }
+    
+    protected function delClientAddress($global_client_id)
+    {
+        Store::instance('gateway')->delete('gateway-'.$global_client_id);
+    }
+    
+    public function onClientClose($connection)
+    {
+        $this->sendToWorker(GatewayProtocol::CMD_ON_CLOSE, $connection);
+        $this->delClientAddress($connection->globalClientId);
+        unset($this->_clientConnections[$connection->globalClientId]);
+    }
+    
+    protected function createGlobalClientId()
+    {
+        $global_socket_key = 'GLOBAL_SOCKET_ID_KEY';
+        $store = Store::instance('gateway');
+        $global_client_id = $store->increment($global_socket_key);
+        if(!$global_client_id || $global_client_id > 2147483646)
+        {
+            $store->set($global_socket_key, 0);
+            $global_client_id = $store->increment($global_socket_key);
+        }
+    
+        if(!$global_client_id)
+        {
+            $msg .= "createGlobalClientId fail :";
+            if(get_class($store) == 'Memcached')
+            {
+                $msg .= $store->getResultMessage();
+            }
+            $this->log($msg);
+        }
+        
+        return $global_client_id;
+    }
+    
+    public function onWorkerStart()
+    {
+        Autoloader::setRootPath($this->_rootPath);
+        
+        $this->lanPort = $this->startPort - posix_getppid() + posix_getpid();
+    
+        if($this->lanPort<0 || $this->lanPort >=65535)
+        {
+            $this->lanPort = rand($this->startPort, 65535);
+        }
+        
+        if($this->pingInterval > 0)
+        {
+            Timer::add($this->pingInterval, array($this, 'ping'));
+        }
+    
+        $this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}");
+        $this->_innerTcpWorker->listen();
+        $this->_innerUdpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}");
+        $this->_innerUdpWorker->transport = 'udp';
+        $this->_innerUdpWorker->listen();
+    
+        $this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage');
+        $this->_innerUdpWorker->onMessage = array($this, 'onWorkerMessage');
+        
+        $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect');
+        $this->_innerTcpWorker->onClose = array($this, 'onWorkerClose');
+        
+        if(!$this->registerAddress())
+        {
+            $this->log('registerAddress fail and exit');
+            Worker::stopAll();
+        }
+    }
+    
+    public function onWorkerConnect($connection)
+    {
+        $connection->remoteAddress = $connection->getRemoteIp().':'.$connection->getRemotePort();
+        $this->_workerConnections[$connection->remoteAddress] = $connection;
+    }
+    
+    public function onWorkerMessage($connection, $data)
+    {
+        $cmd = $data['cmd'];
+        switch($cmd)
+        {
+            // 向某客户端发送数据
+            case GatewayProtocol::CMD_SEND_TO_ONE:
+                if(isset($this->_clientConnections[$data['client_id']]))
+                {
+                    $this->_clientConnections[$data['client_id']]->send($data['body']);
+                }
+                break;
+            case GatewayProtocol::CMD_KICK:
+                if(isset($this->_clientConnections[$data['client_id']]))
+                {
+                    $this->_clientConnections[$data['client_id']]->close();
+                }
+                break;
+            case GatewayProtocol::CMD_SEND_TO_ALL:
+                if($data['ext_data'])
+                {
+                    $client_id_array = unpack('N*', $data['ext_data']);
+                    foreach($client_id_array as $client_id)
+                    {
+                        if(isset($this->_clientConnections[$client_id]))
+                        {
+                            $this->_clientConnections[$client_id]->send($data['body']);
+                        }
+                    }
+                }
+                else
+                {
+                    foreach($this->_clientConnections as $client_connection)
+                    {
+                        $client_connection->send($data['body']);
+                    }
+                }
+                break;
+            case GatewayProtocol::CMD_UPDATE_SESSION:
+                if(isset($this->_clientConnections[$data['client_id']]))
+                {
+                    $this->_clientConnections[$data['client_id']]->session = $data['ext_data'];
+                }
+                break;
+            case GatewayProtocol::CMD_GET_ONLINE_STATUS:
+                $online_status = json_encode(array_keys($this->_clientConnections));
+                $connection->send($online_status);
+                break;
+            case GatewayProtocol::CMD_IS_ONLINE:
+                $connection->send((int)isset($this->_clientConnections[$data['client_id']]));
+                break;
+            default :
+                $err_msg = "gateway inner pack err cmd=$cmd";
+                throw new \Exception($err_msg);
+        }
+    }
+    
+    public function onWorkerClose($connection)
+    {
+        $this->log("{$connection->remoteAddress} CLOSE INNER_CONNECTION\n");
+        unset($this->_workerConnections[$connection->remoteAddress]);
+    }
+    
+    /**
+     * 存储全局的通信地址
+     * @param string $address
+     */
+    protected function registerAddress()
+    {
+        $address = $this->lanIp.':'.$this->lanPort;
+        // key
+        $key = 'GLOBAL_GATEWAY_ADDRESS';
+        try
+        {
+            $store = Store::instance('gateway');
+        }
+        catch(\Exception $msg)
+        {
+            $this->log($msg);
+            return false;
+        }
+        Lock::get();
+        $addresses_list = $store->get($key);
+        if(empty($addresses_list))
+        {
+            $addresses_list = array();
+        }
+        $addresses_list[$address] = $address;
+        if(!$store->set($key, $addresses_list))
+        {
+            Lock::release();
+            if(get_class($store) == 'Memcached')
+            {
+                $msg = " registerAddress fail : " . $store->getResultMessage();
+            }
+            $this->log($msg);
+            return false;
+        }
+        Lock::release();
+        return true;
+    }
+    
+    /**
+     * 删除全局的通信地址
+     * @param string $address
+     */
+    protected function unregisterAddress()
+    {
+        $address = $this->lanIp.':'.$this->lanPort;
+        $key = 'GLOBAL_GATEWAY_ADDRESS';
+        try
+        {
+            $store = Store::instance('gateway');
+        }
+        catch (\Exception $msg)
+        {
+            $this->log($msg);
+            return false;
+        }
+        Lock::get();
+        $addresses_list = $store->get($key);
+        if(empty($addresses_list))
+        {
+            $addresses_list = array();
+        }
+        unset($addresses_list[$address]);
+        if(!$store->set($key, $addresses_list))
+        {
+            Lock::release();
+            $msg = "unregisterAddress fail";
+            if(get_class($store) == 'Memcached')
+            {
+                $msg .= " reason:".$store->getResultMessage();
+            }
+            $this->log($msg);
+            return;
+        }
+        Lock::release();
+        return true;
+    }
+    
+    public function ping()
+    {
+        // 关闭未回复心跳的连接
+        foreach($this->_clientConnections as $connection)
+        {
+            // 上次发送的心跳还没有回复次数大于限定值就断开
+            if($this->pingNotResponseLimit > 0 && $connection->pingNotResponseCount >= $this->pingNotResponseLimit)
+            {
+                $connection->close();
+                continue;
+            }
+            $connection->pingNotResponseCount++;
+            $connection->send($this->pingData);
+        }
+    }
+    
+    public function onWorkerStop()
+    {
+        $this->unregisterAddress();
+        foreach($this->_clientConnections as $connection)
+        {
+            $this->delClientAddress($connection->globalClientId);
+        }
+    }
+}

+ 28 - 0
GatewayWorker/Lib/Autoloader.php

@@ -0,0 +1,28 @@
+<?php
+namespace GatewayWorker\Lib;
+
+class Autoloader
+{
+    protected static $_rootPath = '';
+    
+    public static function setRootPath($root_path)
+    {
+        self::$_rootPath = $root_path;
+        spl_autoload_register('\GatewayWorker\Lib\Autoloader::loadByNamespace');
+    }
+    
+    public static function loadByNamespace($name)
+    {
+        $class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name);
+        $class_file = self::$_rootPath . '/' . $class_path.'.php';
+        if(is_file($class_file))
+        {
+            require_once($class_file);
+            if(class_exists($name, false))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 4 - 9
applications/Demo/Lib/Context.php → GatewayWorker/Lib/Context.php

@@ -1,5 +1,5 @@
 <?php
-namespace Lib;
+namespace GatewayWorker\Lib;
 /**
  * 上下文 包含当前用户uid, 内部通信local_ip local_port socket_id ,以及客户端client_ip client_port
  * @author walkor
@@ -17,11 +17,6 @@ class Context
      */
     public static $local_port;
     /**
-     * 内部通讯socket_id
-     * @var int
-     */
-    public static $socket_id;
-    /**
      * 客户端ip
      * @var string
      */
@@ -46,7 +41,7 @@ class Context
     {
         if($session_data !== '')
         {
-            return json_encode($session_data);
+            return serialize($session_data);
         }
         return '';
     }
@@ -58,7 +53,7 @@ class Context
      */
     public static function sessionDecode($session_buffer)
     {
-        return json_decode($session_buffer, true);
+        return unserialize($session_buffer);
     }
     
     /**
@@ -67,6 +62,6 @@ class Context
      */
     public static function clear()
     {
-        self::$local_ip = self::$local_port = self::$socket_id = self::$client_ip = self::$client_port = self::$client_id  = null;
+        self::$local_ip = self::$local_port  = self::$client_ip = self::$client_port = self::$client_id  = null;
     }
 }

+ 2 - 2
applications/Demo/Lib/Db.php → GatewayWorker/Lib/Db.php

@@ -1,5 +1,5 @@
 <?php
-namespace Lib;
+namespace GatewayWorker\Lib;
 /**
  * 数据库类
  * @author walkor <walkor@workerman.net>
@@ -28,7 +28,7 @@ class Db
         if(empty(self::$instance[$config_name]))
         {
             $config = \Config\Db::$$config_name;
-            self::$instance[$config_name] = new \Lib\DbConnection($config['host'], $config['port'], $config['user'], $config['password'], $config['dbname']);
+            self::$instance[$config_name] = new \GatewayWorker\Lib\DbConnection($config['host'], $config['port'], $config['user'], $config['password'], $config['dbname']);
         }
         return self::$instance[$config_name];
     }

+ 2 - 7
applications/Demo/Lib/DbConnection.php → GatewayWorker/Lib/DbConnection.php

@@ -1,5 +1,5 @@
 <?php
-namespace Lib;
+namespace GatewayWorker\Lib;
 
 /**
  * 数据库连接类,依赖mysql_pdo扩展
@@ -1672,14 +1672,9 @@ class DbConnection
         if ($statement === 'select' || $statement === 'show') {
             return $this->sQuery->fetchAll($fetchmode);
         }
-        elseif ( $statement === 'update' || $statement === 'delete' ) {
+        elseif ( $statement === 'insert' ||  $statement === 'update' || $statement === 'delete' ) {
             return $this->sQuery->rowCount();
         }
-        elseif( $statement === 'insert' ){
-            if( $this->sQuery->rowCount() > 0 ){
-                return $this->lastInsertId();
-            }
-        }
         else {
             return NULL;
         }

+ 58 - 81
applications/Demo/Lib/Gateway.php → GatewayWorker/Lib/Gateway.php

@@ -1,5 +1,5 @@
 <?php
-namespace Lib;
+namespace GatewayWorker\Lib;
 /**
  * 
  * 数据发送相关
@@ -7,9 +7,9 @@ namespace Lib;
  * 
  */
 require_once __DIR__ . '/Autoloader.php';
-use \Protocols\GatewayProtocol;
-use \Lib\Store;
-use \Lib\Context;
+use \Workerman\Protocols\GatewayProtocol;
+use \GatewayWorker\Lib\Store;
+use \GatewayWorker\Lib\Context;
 
 class Gateway
 {
@@ -26,33 +26,26 @@ class Gateway
     */
    public static function sendToAll($message, $client_id_array = null)
    {
-       $pack = new GatewayProtocol();
-       $pack->header['cmd'] = GatewayProtocol::CMD_SEND_TO_ALL;
-       $pack->header['local_ip'] = Context::$local_ip;
-       $pack->header['local_port'] = Context::$local_port;
-       $pack->header['socket_id'] = Context::$socket_id;
-       $pack->header['client_ip'] = Context::$client_ip;
-       $pack->header['client_port'] = Context::$client_port;
-       $pack->header['client_id'] = Context::$client_id;
-       $pack->body = (string)$message;
+       $gateway_data = GatewayProtocol::$empty;
+       $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_ALL;
+       $gateway_data['body'] = $message;
        
        if($client_id_array)
        {
            $params = array_merge(array('N*'), $client_id_array);
-           $pack->ext_data = call_user_func_array('pack', $params);
+           $gateway_data['ext_data'] = call_user_func_array('pack', $params);
        }
        elseif(empty($client_id_array) && is_array($client_id_array))
        {
            return;
        }
        
-       $buffer = $pack->getBuffer();
        // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据
        if(self::$businessWorker)
        {
-           foreach(self::$businessWorker->getGatewayConnections() as $con)
+           foreach(self::$businessWorker->gatewayConnections as $gateway_connection)
            {
-               self::$businessWorker->sendToClient($buffer, $con);
+               $gateway_connection->send($gateway_data);
            }
        }
        // 运行在其它环境中,使用udp向worker发送数据
@@ -61,7 +54,7 @@ class Gateway
            $all_addresses = Store::instance('gateway')->get('GLOBAL_GATEWAY_ADDRESS');
            foreach($all_addresses as $address)
            {
-               self::sendToGateway($address, $buffer);
+               self::sendToGateway($address, $gateway_data);
            }
        }
    }
@@ -71,9 +64,9 @@ class Gateway
     * @param int $client_id 
     * @param string $message
     */
-   public static function sendToClient($clinet_id, $message)
+   public static function sendToClient($client_id, $message)
    {
-       return self::sendCmdAndMessageToClient($clinet_id, GatewayProtocol::CMD_SEND_TO_ONE, $message);
+       return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SEND_TO_ONE, $message);
    } 
    
    /**
@@ -92,15 +85,15 @@ class Gateway
     */
    public static function isOnline($client_id)
    {
-       $pack = new GatewayProtocol();
-       $pack->header['cmd'] = \Protocols\GatewayProtocol::CMD_IS_ONLINE;;
-       $pack->header['client_id'] = $client_id;
-       $address = Store::instance('gateway')->get($client_id);
+       $address = Store::instance('gateway')->get('gateway-'.$client_id);
        if(!$address)
        {
            return 0;
        }
-       return self::sendUdpAndRecv($address['local_ip']. ':' .$address['local_port'], $pack->getBuffer());
+       $gateway_data = GatewayProtocol::$empty;
+       $gateway_data['cmd'] = GatewayProtocol::CMD_IS_ONLINE;
+       $gateway_data['client_id'] = $client_id;
+       return self::sendUdpAndRecv($address, $gateway_data);
    }
    
    /**
@@ -109,16 +102,17 @@ class Gateway
     */
    public static function getOnlineStatus()
    {
-       $pack = new GatewayProtocol();
-       $pack->header['cmd'] = \Protocols\GatewayProtocol::CMD_GET_ONLINE_STATUS;
-       $buffer = $pack->getBuffer();
+       $gateway_data = GatewayProtocol::$empty;
+       $gateway_data['cmd'] = GatewayProtocol::CMD_GET_ONLINE_STATUS;
+       $gateway_buffer = GatewayProtocol::encode($gateway_data);
+       
        $all_addresses = Store::instance('gateway')->get('GLOBAL_GATEWAY_ADDRESS');
        $client_array = $status_data = array();
        // 批量向所有gateway进程发送CMD_GET_ONLINE_STATUS命令
        foreach($all_addresses as $address)
        {
            $client = stream_socket_client("udp://$address", $errno, $errmsg);
-           if(strlen($buffer) == stream_socket_sendto($client, $buffer))
+           if(strlen($gateway_buffer) === stream_socket_sendto($client, $gateway_buffer))
            {
                $client_id = (int) $client;
                $client_array[$client_id] = $client;
@@ -137,7 +131,8 @@ class Gateway
                foreach($read as $client)
                {
                    // udp
-                   if($data = json_decode(fread($client, 655350), true))
+                   $data = json_decode(fread($client, 65535), true);
+                   if($data)
                    {
                        $status_data = array_merge($status_data, $data);
                    }
@@ -153,11 +148,11 @@ class Gateway
    }
    
    /**
-    * 将某个客户端踢出
+    * 关闭某个客户端
     * @param int $client_id
     * @param string $message
     */
-   public static function kickClient($client_id)
+   public static function closeClient($client_id)
    {
        if($client_id === Context::$client_id)
        {
@@ -166,12 +161,12 @@ class Gateway
        // 不是发给当前用户则使用存储中的地址
        else
        {
-           $address = Store::instance('gateway')->get($client_id);
+           $address = Store::instance('gateway')->get('gateway-'.$client_id);
            if(!$address)
            {
                return false;
            }
-           return self::kickAddress($address['local_ip'], $address['local_port'], $address['socket_id']);
+           return self::kickAddress($address, $client_id);
        }
    }
    
@@ -179,9 +174,9 @@ class Gateway
     * 踢掉当前客户端
     * @param string $message
     */
-   public static function kickCurrentClient()
+   public static function closeCurrentClient()
    {
-       return self::kickAddress(Context::$local_ip, Context::$local_port, Context::$socket_id);
+       return self::kickAddress(Context::$local_ip.':'.Context::$local_port, Context::$client_id);
    }
    
    /**
@@ -189,13 +184,13 @@ class Gateway
     * @param int $client_id
     * @param string $session_str
     */
-   public static function updateSocketSession($socket_id, $session_str)
+   public static function updateSocketSession($client_id, $session_str)
    {
-       $pack = new GatewayProtocol();
-       $pack->header['cmd'] = GatewayProtocol::CMD_UPDATE_SESSION;
-       $pack->header['socket_id'] = Context::$socket_id;
-       $pack->ext_data = (string)$session_str;
-       return self::sendToGateway(Context::$local_ip . ':' . Context::$local_port, $pack->getBuffer());
+       $gateway_data = GatewayProtocol::$empty;
+       $gateway_data['cmd'] = GatewayProtocol::CMD_UPDATE_SESSION;
+       $gateway_data['client_id'] = $client_id;
+       $gateway_data['ext_data'] = $session_str;
+       return self::sendToGateway(Context::$local_ip . ':' . Context::$local_port, $gateway_data);
    }
    
    /**
@@ -207,34 +202,25 @@ class Gateway
     */
    protected static function sendCmdAndMessageToClient($client_id, $cmd , $message)
    {
-       $pack = new GatewayProtocol();
-       $pack->header['cmd'] = $cmd;
        // 如果是发给当前用户则直接获取上下文中的地址
        if($client_id === Context::$client_id || $client_id === null)
        {
-           $pack->header['local_ip'] = Context::$local_ip;
-           $pack->header['local_port'] = Context::$local_port;
-           $pack->header['socket_id'] = Context::$socket_id;
-           $pack->header['client_id'] = Context::$client_id;
+           $address = Context::$local_ip.':'.Context::$local_port;
        }
-       // 不是发给当前用户则使用存储中的地址
        else
        {
-           $address = Store::instance('gateway')->get($client_id);
+           $address = Store::instance('gateway')->get('gateway-'.$client_id);
            if(!$address)
            {
                return false;
            }
-           $pack->header['local_ip'] = $address['local_ip'];
-           $pack->header['local_port'] = $address['local_port'];
-           $pack->header['socket_id'] = $address['socket_id'];
-           $pack->header['client_id'] = $client_id;
        }
-       $pack->header['client_ip'] = Context::$client_ip;
-       $pack->header['client_port'] = Context::$client_port;
-       $pack->body = (string)$message;
+       $gateway_data = GatewayProtocol::$empty;
+       $gateway_data['cmd'] = $cmd;
+       $gateway_data['client_id'] = $client_id ? $client_id : Context::$client_id;
+       $gateway_data['body'] = $message;
        
-       return self::sendToGateway("{$pack->header['local_ip']}:{$pack->header['local_port']}", $pack->getBuffer());
+       return self::sendToGateway($address, $gateway_data);
    }
    
    /**
@@ -243,8 +229,9 @@ class Gateway
     * @param string $message
     * @return boolean
     */
-   protected static function sendUdpAndRecv($address , $buffer)
+   protected static function sendUdpAndRecv($address , $data)
    {
+       $buffer = GatewayProtocol::encode($data);
        // 非workerman环境,使用udp发送数据
        $client = stream_socket_client("udp://$address", $errno, $errmsg);
        if(strlen($buffer) == stream_socket_sendto($client, $buffer))
@@ -269,47 +256,37 @@ class Gateway
     * @param string $address
     * @param string $buffer
     */
-   protected static function sendToGateway($address, $buffer)
+   protected static function sendToGateway($address, $gateway_data)
    {
        // 有$businessWorker说明是workerman环境,使用$businessWorker发送数据
        if(self::$businessWorker)
        {
-           $connections = self::$businessWorker->getGatewayConnections();
-           if(!isset($connections[$address]))
+           if(!isset(self::$businessWorker->gatewayConnections[$address]))
            {
-               throw new \Exception("sendToGateway($address, \$buffer) fail \$connections:".var_export($connections, true));
+               return false;
            }
-           return self::$businessWorker->sendToClient($buffer, $connections[$address]);
+           return self::$businessWorker->gatewayConnections[$address]->send($gateway_data);
        }
        // 非workerman环境,使用udp发送数据
+       $gateway_buffer = GatewayProtocol::encode($gateway_data);
        $client = stream_socket_client("udp://$address", $errno, $errmsg);
-       return strlen($buffer) == stream_socket_sendto($client, $buffer);
+       return strlen($gateway_buffer) == stream_socket_sendto($client, $gateway_buffer);
    }
    
    /**
     * 踢掉某个网关的socket
     * @param string $local_ip
     * @param int $local_port
-    * @param int $socket_id
+    * @param int $client_id
     * @param string $message
     * @param int $client_id
     */
-   protected  static function kickAddress($local_ip, $local_port, $socket_id)
+   protected  static function kickAddress($address, $client_id)
    {
-       $pack = new GatewayProtocol();
-       $pack->header['cmd'] = GatewayProtocol::CMD_KICK;
-       $pack->header['local_ip'] = $local_ip;
-       $pack->header['local_port'] = $local_port;
-       $pack->header['socket_id'] = $socket_id;
-       if(null !== Context::$client_ip)
-       {
-           $pack->header['client_ip'] = Context::$client_ip;
-           $pack->header['client_port'] = Context::$client_port;
-       }
-       $pack->header['client_id'] =  0;
-       $pack->body = '';
-        
-       return self::sendToGateway("{$pack->header['local_ip']}:{$pack->header['local_port']}", $pack->getBuffer());
+       $gateway_data = GatewayProtocol::$empty;
+       $gateway_data['cmd'] = GatewayProtocol::CMD_KICK;
+       $gateway_data['client_id'] = $client_id;
+       return self::sendToGateway($address, $gateway_data);
    }
    
    /**

+ 8 - 7
workerman/Core/Lib/Mutex.php → GatewayWorker/Lib/Lock.php

@@ -1,9 +1,9 @@
 <?php
-namespace Man\Core\Lib;
+namespace GatewayWorker\Lib;
 /**
- * 
+ * lock
  */
-class Mutex
+class Lock
 {
     /**
      * handle
@@ -12,8 +12,9 @@ class Mutex
     private static $fileHandle = null;
     
     /**
-     * 获取写锁
-     * @return true
+     * get lock
+     * @param bool block
+     * @return bool
      */
     public static function get($block=true)
     {
@@ -26,7 +27,7 @@ class Mutex
     }
     
     /**
-     * 释放写锁
+     * release lock
      * @return true
      */
     public static function release()
@@ -39,7 +40,7 @@ class Mutex
     }
     
     /**
-     * 获得handle
+     * get handle
      * @return resource
      */
     protected static function getHandle()

+ 2 - 2
applications/Demo/Lib/Store.php → GatewayWorker/Lib/Store.php

@@ -1,5 +1,5 @@
 <?php
-namespace Lib;
+namespace GatewayWorker\Lib;
 /**
  * 存储类
  * 这里用memcache实现
@@ -57,7 +57,7 @@ class Store
         {
             if(!isset(self::$instance[$config_name]))
             {
-                self::$instance[$config_name] = new \Lib\StoreDriver\File($config_name);
+                self::$instance[$config_name] = new \GatewayWorker\Lib\StoreDriver\File($config_name);
             }
             return self::$instance[$config_name];
         }

+ 1 - 1
applications/Demo/Lib/StoreDriver/File.php → GatewayWorker/Lib/StoreDriver/File.php

@@ -1,5 +1,5 @@
 <?php
-namespace Lib\StoreDriver;
+namespace GatewayWorker\Lib\StoreDriver;
 
 /**
  * 

+ 38 - 188
README.md

@@ -1,197 +1,47 @@
-workerman
-=========
-
-workerman 是一个高性能的PHP socket服务框架,开发者可以在这个框架下开发各种网络应用的服务端,例如移动通讯、手游服务端、网络游戏服务器、聊天室服务器、硬件通讯服务器、智能家居等
-workerman 具有以下特性
- * 支持HHVM,极大的提升应用程序性能
- * 多进程/多线程(多线程版本)
- * 支持TCP/UDP
- * 支持多端口监听
- * 支持各种应用层协议
- * 标准输入输出重定向
- * 守护进程化
- * 使用libevent事件轮询库,支持高并发
- * 支持文件更新检测及自动加载
- * 支持服务平滑重启
- * 支持telnet远程控制及监控
- * 支持异常监控及告警
- * 支持长连接
- * 支持以指定用户运行worker进程
- * 支持请求数上限配置
- * 服务端心跳支持
- * 支持多服务器部署
-
-
- [更多请访问www.workerman.net](http://www.workerman.net)  
- [文档doc.workerman.net](http://doc.workerman.net)  
-
-applications/Demo测试方法
-===============
-  * 运行 telnet ip 8480
-  * 首先输入昵称 回车
-  * 后面直接打字回车是向所有人发消息
-  * uid:聊天内容 是向uid用户发送消息    
-如下图:  
-![telnet-chat](http://www.workerman.net/img/gif/telnet-chat.gif)  
-
-可以开多个telnet窗口,窗口间可以实时聊天
-
-关于applications/Demo
-=================
- * [applications/Demo](https://github.com/walkor/workerman/tree/master/applications/Demo) 的业务逻辑全部在[applications/Demo/Event.php](https://github.com/walkor/workerman/blob/master/applications/Demo/Event.php) 中
- * 开发者看懂[applications/Demo/Event.php](https://github.com/walkor/workerman/blob/master/applications/Demo/Event.php) 的代码基本上就知道如何开发了
- * [applications/Demo](https://github.com/walkor/workerman/tree/master/applications/Demo) 使用的是及其简单的文本协议,适合非浏览器类的应用参考。例如移动通讯、手游、硬件通讯、智能家居等
- * 如果是浏览器类的即时应用,可以参考[workerman-chat](http://www.workerman.net/workerman-chat) ,使用的是websocket协议(支持各种浏览器),同样只需要看懂[applications/Chat/Event.php](https://github.com/walkor/workerman-chat/blob/master/applications/Chat/Event.php) 即可
- * 长连接类的应用 [applications/Demo](https://github.com/walkor/workerman/tree/master/applications/Demo)  [workerman-chat](http://www.workerman.net/workerman-chat)  [workerman-todpole](https://github.com/walkor/workerman-todpole) [workerman-flappy-bird](https://github.com/walkor/workerman-flappy-bird) 它们的代码结构完全相同,只是applications/XXX/Event.php实现不同
-
-一些demo连接
-==================
-[小蝌蚪聊天室workerman-todpole](http://kedou.workerman.net)  
-[多人在线flappybird](http://flap.workerman.net)  
-[workerman-chat聊天室](http://chat.workerman.net)  
-[json-rpc](http://www.workerman.net/workerman-jsonrpc)  
-[thrift-rpc](http://www.workerman.net/workerman-thrift)  
-[统计监控系统](http://www.workerman.net/workerman-statistics)  
-
-
-短链开发demo
-============
-
+## workerman 3.0 
+create test.php
 ```php
-<?php
-class EchoService extends \Man\Core\SocketWorker
+require_once './Workerman/Autoloader.php';
+use Workerman\Worker;
+
+// create socket and listen 1234 port
+$tcp_worker = new Worker("tcp://0.0.0.0:1234");
+//create 4 hello_worker processes
+$tcp_worker->count = 4;
+// when client send data to 1234 port
+$tcp_worker->onMessage = function($connection, $data)
 {
-   /**
-    * 判断telnet客户端发来的数据是否接收完整
-    */
-   public function dealInput($recv_buffer)
-   {
-        // 根据协议,判断最后一个字符是不是回车 \n
-        if($recv_buffer[strlen($recv_buffer)-1] != "\n")
-        {
-            // 不是回车返回1告诉workerman我还需要再读一个字符
-            return 1;
-        }
-        // 告诉workerman数据完整了
-        return 0;
-   }
-
-   /**
-    * 处理业务逻辑,这里只是按照telnet客户端发来的命令返回对应的数据
-    */
-   public function dealProcess($recv_buffer)
-   {
-        // 判断telnet客户端发来的是什么
-        $cmd = trim($recv_buffer);
-        switch($cmd)
-        {
-            // 获得服务器的日期
-            case 'date':
-            return $this->sendToClient(date('Y-m-d H:i:s')."\n");
-            // 获得服务器的负载
-            case 'load':
-            return $this->sendToClient(var_export(sys_getloadavg(), true)."\n");
-            case 'quit':
-            return $this->closeClient($this->currentDealFd);
-            default:
-            return $this->sendToClient("unknown cmd\n");
-        }
-   }
-}
-```
-
-长链接应用开发demo
-=============
-
-```php
-// 协议为 文本+回车
-class Event
+    // send data to client
+    $connection->send("hello $data \n");
+};
+
+// another http worker
+$http_worker = new Worker("http://0.0.0.0:2345");
+$http_worker->count = 4;
+$http_worker->onMessage = function($connection, $data)
 {
-    /**
-     * 网关有消息时,区分请求边界,分包
-     */
-    public static function onGatewayMessage($buffer)
-    {
-        // 判断最后一个字符是否是回车("\n")
-        if($buffer[strlen($buffer)-1] === "\n")
-        {
-            return 0;
-        }
-
-        // 说明还有请求数据没收到,但是由于不知道还有多少数据没收到,所以只能返回1,因为有可能下一个字符就是回车("\n")
-        return 1;
-    }
-
-   /**
-    * 有消息时触发该方法
-    * @param int $client_id 发消息的client_id
-    * @param string $message 消息
-    * @return void
-    */
-   public static function onMessage($client_id, $message)
-   {
-        // 获得客户端来发的消息具体内容,trim去掉了请求末尾的回车
-        $message_data = trim($message);
-
-        // ****如果没有$_SESSION['not_first_time']说明是第一次发消息****
-        if(empty($_SESSION['not_first_time']))
-        {
-            $_SESSION['not_first_time'] = true;
+    // send data to client
+    $connection->send("hello world \n");
+};
 
-            // 广播所有用户,xxx come
-            GateWay::sendToAll("client_id:$client_id come\n");
-        }
-
-        // 向所有人转发消息
-        return GateWay::sendToAll("client[$client_id] said :" . $message));
-   }
+// websocket worker
+$ws_worker = new Worker("websocket://0.0.0.0:5678");
+$ws_worker->onMessage =  function($connection, $data)
+{
+    // send data to client
+    $connection->send("hello world \n");
+};
 
-   /**
-    * 当用户断开连接时触发的方法
-    * @param integer $client_id 断开连接的用户id
-    * @return void
-    */
-   public static function onClose($client_id)
-   {
-       // 广播 xxx logout
-       GateWay::sendToAll("client[$client_id] logout\n");
-   }
-}
+// run all workers
+Worker::runAll();
 ```
 
- 
-性能测试
-=============
-
-###测试环境:
-系统:debian 6.0 64位  
-内存:64G  
-cpu:Intel(R) Xeon(R) CPU E5-2420 0 @ 1.90GHz (2颗物理cpu,6核心,2线程)
-Workerman:开启200个Benchark进程
-压测脚本:benchmark
-业务:发送并返回hello字符串
-
-###普通PHP(版本5.3.10)压测
-    短链接(每次请求完成后关闭链接,下次请求建立新的链接):
-        条件: 压测脚本开500个并发线程模拟500个并发用户,每个线程链接Workerman 10W次,每次链接发送1个请求
-        结果: 吞吐量:1.9W/S , cpu利用率:32% 
-
-    长链接(每次请求后不关闭链接,下次请求继续复用这个链接):
-        条件: 压测脚本开2000个并发线程模拟2000个并发用户,每个线程链接Workerman 1次,每个链接发送10W请求
-        结果: 吞吐量:36.7W/S , cpu利用率:69% 
-
-    内存:每个进程内存稳定在6444K,无内存泄漏
-
-
-###HHVM环境压测
-    短链接(每次请求完成后关闭链接,下次请求建立新的链接):
-        条件: 压测脚本开1000个并发线程模拟1000个并发用户,每个线程链接Workerman 10W次,每次链接发送1个请求
-        结果: 吞吐量:3.5W/S , cpu利用率:35% 
-
-    长链接(每次请求后不关闭链接,下次请求继续复用这个链接):
-        条件: 压测脚本开6000个并发线程模拟6000个并发用户,每个线程链接Workerman 1次,每个链接发送10W请求
-        结果: 吞吐量:45W/S , cpu利用率:67% 
-
-    内存:HHVM环境每个进程内存稳定在46M,无内存泄漏
+run width
+php test.php start
 
+## available commands
+php test.php stop  
+php test.php restart  
+php test.php status  
+php test.php reload  
 

+ 23 - 0
Workerman/Autoloader.php

@@ -0,0 +1,23 @@
+<?php
+if(!defined('WORKERMAN_ROOT_DIR'))
+{
+    define('WORKERMAN_ROOT_DIR', realpath(__DIR__  . '/../'));
+}
+
+require_once WORKERMAN_ROOT_DIR.'/Workerman/Lib/Constants.php';
+
+function workerman_loader($name)
+{
+    $class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name);
+    $class_file = WORKERMAN_ROOT_DIR . DIRECTORY_SEPARATOR . "$class_path.php";
+    if(is_file($class_file))
+    {
+        require_once($class_file);
+        if(class_exists($name, false))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+spl_autoload_register('workerman_loader');

+ 165 - 0
Workerman/Connection/AsyncTcpConnection.php

@@ -0,0 +1,165 @@
+<?php
+namespace Workerman\Connection;
+
+use Workerman\Events\Libevent;
+use Workerman\Events\Select;
+use Workerman\Events\EventInterface;
+use Workerman\Worker;
+use \Exception;
+
+/**
+ * async connection 
+ * @author walkor<walkor@workerman.net>
+ */
+class AsyncTcpConnection extends TcpConnection
+{
+    /**
+     * status
+     * @var int
+     */
+    protected $_status = self::STATUS_CONNECTING;
+    
+    /**
+     * when connect success , onConnect will be run
+     * @var callback
+     */
+    public $onConnect = null;
+    
+    /**
+     * create a connection
+     * @param resource $socket
+     * @param EventInterface $event
+     */
+    public function __construct($remote_address, EventInterface $event)
+    {
+        list($scheme, $address) = explode(':', $remote_address, 2);
+        if($scheme != 'tcp')
+        {
+            $scheme = ucfirst($scheme);
+            $this->protocol = '\\Protocols\\'.$scheme;
+            if(!class_exists($this->protocol))
+            {
+                $this->protocol = '\\Workerman\\Protocols\\' . $scheme;
+                if(!class_exists($this->protocol))
+                {
+                    throw new Exception("class \\Protocols\\$scheme not exist");
+                }
+            }
+        }
+        $this->_event = $event;
+        $this->_socket = stream_socket_client("tcp:$address", $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT);
+        if(!$this->_socket)
+        {
+            $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr);
+            return;
+        }
+        
+        $this->_event->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
+    }
+    
+    protected function emitError($code, $msg)
+    {
+        if($this->onError)
+        {
+            try{
+                call_user_func($this->onError, $this, $code, $msg);
+            }
+            catch(Exception $e)
+            {
+                echo $e;
+            }
+        }
+    }
+    
+    public function checkConnection($socket)
+    {
+        $this->_event->del($this->_socket, EventInterface::EV_WRITE);
+        // php bug ?
+        if(!feof($this->_socket) && !feof($this->_socket))
+        {
+            stream_set_blocking($this->_socket, 0);
+            $this->_event->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
+            if($this->_sendBuffer)
+            {
+                $this->_event->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
+            }
+            $this->_status = self::STATUS_ESTABLISH;
+            if($this->onConnect)
+            {
+                try 
+                {
+                    call_user_func($this->onConnect, $this);
+                }
+                catch(Exception $e)
+                {
+                    self::$statistics['throw_exception']++;
+                    echo $e;
+                }
+            }
+        }
+        else
+        {
+            $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect fail, maybe timedout');
+        }
+    }
+    
+    /**
+     * send buffer to client
+     * @param string $send_buffer
+     * @return void|boolean
+     */
+    public function send($send_buffer)
+    {
+        if($this->protocol)
+        {
+            $parser = $this->protocol;
+            $send_buffer = $parser::encode($send_buffer, $this);
+        }
+        
+        if($this->_status === self::STATUS_CONNECTING)
+        {
+            $this->_sendBuffer .= $send_buffer;
+            return null;
+        }
+        elseif($this->_status == self::STATUS_CLOSED)
+        {
+            return false;
+        }
+        
+        if($this->_sendBuffer === '')
+        {
+            $len = @fwrite($this->_socket, $send_buffer);
+            if($len === strlen($send_buffer))
+            {
+                return true;
+            }
+            
+            if($len > 0)
+            {
+                $this->_sendBuffer = substr($send_buffer, $len);
+            }
+            else
+            {
+                if(feof($this->_socket))
+                {
+                    self::$statistics['send_fail']++;
+                    if($this->onError)
+                    {
+                        call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client close');
+                    }
+                    $this->destroy();
+                    return false;
+                }
+                $this->_sendBuffer = $send_buffer;
+            }
+            
+            $this->_event->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
+            return null;
+        }
+        else
+        {
+            $this->_sendBuffer .= $send_buffer;
+        }
+    }
+    
+}

+ 66 - 0
Workerman/Connection/ConnectionInterface.php

@@ -0,0 +1,66 @@
+<?php
+namespace Workerman\Connection;
+use Workerman\Events\Libevent;
+use Workerman\Events\Select;
+use Workerman\Events\EventInterface;
+use Workerman\Worker;
+use \Exception;
+
+/**
+ * connection 
+ * @author walkor<walkor@workerman.net>
+ */
+abstract class  ConnectionInterface
+{
+    /**
+     * statistics for status
+     * @var array
+     */
+    public static $statistics = array(
+        'total_request'   => 0, 
+        'throw_exception' => 0,
+        'send_fail'       => 0,
+    );
+    
+    /**
+     * when receive data, onMessage will be run 
+     * @var callback
+     */
+    public $onMessage = null;
+    
+    /**
+     * when connection close, onClose will be run
+     * @var callback
+     */
+    public $onClose = null;
+    
+    /**
+     * when something wrong ,onError will be run
+     * @var callback
+     */
+    public $onError = null;
+    
+    /**
+     * send buffer to client
+     * @param string $send_buffer
+     * @return void|boolean
+     */
+    abstract public function send($send_buffer);
+    
+    /**
+     * get remote ip
+     * @return string
+     */
+    abstract public function getRemoteIp();
+    
+    /**
+     * get remote port
+     */
+    abstract public function getRemotePort();
+
+    /**
+     * close the connection
+     * @void
+     */
+    abstract public function close($data = null);
+}

+ 424 - 0
Workerman/Connection/TcpConnection.php

@@ -0,0 +1,424 @@
+<?php
+namespace Workerman\Connection;
+
+use Workerman\Events\Libevent;
+use Workerman\Events\Select;
+use Workerman\Events\EventInterface;
+use Workerman\Worker;
+use \Exception;
+
+/**
+ * connection 
+ * @author walkor<walkor@workerman.net>
+ */
+class TcpConnection extends ConnectionInterface
+{
+    /**
+     * when recv data from client ,how much bytes to read
+     * @var unknown_type
+     */
+    const READ_BUFFER_SIZE = 8192;
+
+    /**
+     * connection status connecting
+     * @var int
+     */
+    const STATUS_CONNECTING = 1;
+    
+    /**
+     * connection status establish
+     * @var int
+     */
+    const STATUS_ESTABLISH = 2;
+
+    /**
+     * connection status closing
+     * @var int
+     */
+    const STATUS_CLOSING = 4;
+    
+    /**
+     * connection status closed
+     * @var int
+     */
+    const STATUS_CLOSED = 8;
+    
+    /**
+     * when receive data, onMessage will be run 
+     * @var callback
+     */
+    public $onMessage = null;
+    
+    /**
+     * when connection close, onClose will be run
+     * @var callback
+     */
+    public $onClose = null;
+    
+    /**
+     * when some thing wrong ,onError will be run
+     * @var callback
+     */
+    public $onError = null;
+    
+    /**
+     * protocol
+     * @var string
+     */
+    public $protocol = '';
+    
+    /**
+     * eventloop
+     * @var EventInterface
+     */
+    protected $_event = null;
+    
+    /**
+     * max send buffer size (Bytes)
+     * @var int
+     */
+    public static $maxSendBufferSize = 1048576;
+    
+    /**
+     * max package size (Bytes)
+     * @var int
+     */
+    public static $maxPackageSize = 10485760;
+    
+    /**
+     * the socket
+     * @var resource
+     */
+    protected $_socket = null;
+
+    /**
+     * the buffer to send
+     * @var string
+     */
+    protected $_sendBuffer = '';
+    
+    /**
+     * the buffer read from socket
+     * @var string
+     */
+    protected $_recvBuffer = '';
+    
+    /**
+     * current package length
+     * @var int
+     */
+    protected $_currentPackageLength = 0;
+
+    /**
+     * connection status
+     * @var int
+     */
+    protected $_status = self::STATUS_ESTABLISH;
+    
+    /**
+     * remote ip
+     * @var string
+     */
+    protected $_remoteIp = '';
+    
+    /**
+     * remote port
+     * @var int
+     */
+    protected $_remotePort = 0;
+    
+    /**
+     * remote address
+     * @var string
+     */
+    protected $_remoteAddress = '';
+
+    /**
+     * create a connection
+     * @param resource $socket
+     * @param EventInterface $event
+     */
+    public function __construct($socket, EventInterface $event)
+    {
+        $this->_socket = $socket;
+        stream_set_blocking($this->_socket, 0);
+        $this->_event = $event;
+        $this->_event->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
+    }
+    
+    /**
+     * send buffer to client
+     * @param string $send_buffer
+     * @param bool $raw
+     * @return void|boolean
+     */
+    public function send($send_buffer, $raw = false)
+    {
+        if($this->_status == self::STATUS_CLOSED)
+        {
+            return false;
+        }
+        if(false === $raw && $this->protocol)
+        {
+            $parser = $this->protocol;
+            $send_buffer = $parser::encode($send_buffer, $this);
+        }
+        
+        if($this->_sendBuffer === '')
+        {
+            $len = @fwrite($this->_socket, $send_buffer);
+            if($len === strlen($send_buffer))
+            {
+                return true;
+            }
+            
+            if($len > 0)
+            {
+                $this->_sendBuffer = substr($send_buffer, $len);
+            }
+            else
+            {
+                if(feof($this->_socket))
+                {
+                    self::$statistics['send_fail']++;
+                    if($this->onError)
+                    {
+                        call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed');
+                    }
+                    $this->destroy();
+                    return false;
+                }
+                $this->_sendBuffer = $send_buffer;
+            }
+            
+            $this->_event->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
+            return null;
+        }
+        else
+        {
+            // check send buffer size
+            if(self::$maxSendBufferSize <= strlen($this->_sendBuffer) + strlen($send_buffer))
+            {
+                self::$statistics['send_fail']++;
+                if($this->onError)
+                {
+                    call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full');
+                }
+                return false;
+            }
+            $this->_sendBuffer .= $send_buffer;
+        }
+    }
+    
+    /**
+     * get remote ip
+     * @return string
+     */
+    public function getRemoteIp()
+    {
+        if(!$this->_remoteIp)
+        {
+            $this->_remoteAddress = stream_socket_get_name($this->_socket, true);
+            if($this->_remoteAddress)
+            {
+                list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
+                $this->_remotePort = (int)$this->_remotePort;
+            }
+        }
+        return $this->_remoteIp;
+    }
+    
+    /**
+     * get remote port
+     */
+    public function getRemotePort()
+    {
+        if(!$this->_remotePort)
+        {
+            $this->_remoteAddress = stream_socket_get_name($this->_socket, true);
+            if($this->_remoteAddress)
+            {
+                list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
+                $this->_remotePort = (int)$this->_remotePort;
+            }
+        }
+        return $this->_remotePort;
+    }
+
+    /**
+     * when socket is readable 
+     * @param resource $socket
+     * @return void
+     */
+    public function baseRead($socket)
+    {
+       while($buffer = fread($socket, self::READ_BUFFER_SIZE))
+       {
+          $this->_recvBuffer .= $buffer; 
+       }
+       
+       if($this->_recvBuffer)
+       {
+           if(!$this->onMessage)
+           {
+               return ;
+           }
+           
+           // protocol has been set
+           if($this->protocol)
+           {
+               $parser = $this->protocol;
+               while($this->_recvBuffer)
+               {
+                   // already know current package length 
+                   if($this->_currentPackageLength)
+                   {
+                       // we need more buffer
+                       if($this->_currentPackageLength > strlen($this->_recvBuffer))
+                       {
+                           break;
+                       }
+                   }
+                   else
+                   {
+                       // try to get the current package length
+                       $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this);
+                       // need more buffer
+                       if($this->_currentPackageLength === 0)
+                       {
+                           break;
+                       }
+                       elseif($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize)
+                       {
+                           // need more buffer
+                           if($this->_currentPackageLength > strlen($this->_recvBuffer))
+                           {
+                               break;
+                           }
+                       }
+                       // error package
+                       else
+                       {
+                           $this->close('error package. package_length='.var_export($this->_currentPackageLength, true));
+                       }
+                   }
+                   
+                   // recvived the  whole data 
+                   self::$statistics['total_request']++;
+                   $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength);
+                   $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength);
+                   $this->_currentPackageLength = 0;
+                   try
+                   {
+                       call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this));
+                   }
+                   catch(Exception $e)
+                   {
+                       self::$statistics['throw_exception']++;
+                       echo $e;
+                   }
+               }
+               return;
+           }
+           self::$statistics['total_request']++;
+           // protocol not set
+           try 
+           {
+               call_user_func($this->onMessage, $this, $this->_recvBuffer);
+           }
+           catch(Exception $e)
+           {
+               self::$statistics['throw_exception']++;
+               echo $e;
+           }
+           $this->_recvBuffer = '';
+       }
+       else if(feof($socket))
+       {
+           $this->destroy();
+           return;
+       }
+    }
+
+    /**
+     * when socket is writeable
+     * @return void
+     */
+    public function baseWrite()
+    {
+        $len = @fwrite($this->_socket, $this->_sendBuffer);
+        if($len === strlen($this->_sendBuffer))
+        {
+            $this->_event->del($this->_socket, EventInterface::EV_WRITE);
+            $this->_sendBuffer = '';
+            if($this->_status == self::STATUS_CLOSING)
+            {
+                $this->destroy();
+            }
+            return true;
+        }
+        if($len > 0)
+        {
+           $this->_sendBuffer = substr($this->_sendBuffer, $len);
+        }
+        else
+        {
+           if(feof($this->_socket))
+           {
+               self::$statistics['send_fail']++;
+               $this->destroy();
+           }
+        }
+    }
+    
+    /**
+     * consume recvBuffer
+     * @param int $length
+     */
+    public function consumeRecvBuffer($length)
+    {
+        $this->_recvBuffer = substr($this->_recvBuffer, $length);
+    }
+
+    /**
+     * close the connection
+     * @param mixed $data
+     * @void
+     */
+    public function close($data = null)
+    {
+        if($data !== null)
+        {
+            $this->send($data);
+        }
+        $this->_status = self::STATUS_CLOSING;
+        if($this->_sendBuffer === '')
+        {
+           $this->destroy();
+        }
+    }
+
+    /**
+     * destroy the connection
+     * @void
+     */
+    protected function destroy()
+    {
+       if($this->onClose)
+       {
+           try
+           {
+               call_user_func($this->onClose, $this);
+           }
+           catch (Exception $e)
+           {
+               self::$statistics['throw_exception']++;
+               echo $e;
+           }
+       }
+       $this->_event->del($this->_socket, EventInterface::EV_READ);
+       $this->_event->del($this->_socket, EventInterface::EV_WRITE);
+       @fclose($this->_socket);
+       $this->_status = self::STATUS_CLOSED;
+    }
+}

+ 104 - 0
Workerman/Connection/UdpConnection.php

@@ -0,0 +1,104 @@
+<?php
+namespace Workerman\Connection;
+
+use Workerman\Events\Libevent;
+use Workerman\Events\Select;
+use Workerman\Events\EventInterface;
+use Workerman\Worker;
+use \Exception;
+
+/**
+ * connection 
+ * @author walkor<walkor@workerman.net>
+ */
+class UdpConnection extends ConnectionInterface
+{
+    /**
+     * protocol
+     * @var string
+     */
+    public $protocol = '';
+    
+    /**
+     * the socket
+     * @var resource
+     */
+    protected $_socket = null;
+    
+    /**
+     * remote ip
+     * @var string
+     */
+    protected $_remoteIp = '';
+    
+    /**
+     * remote port
+     * @var int
+     */
+    protected $_remotePort = 0;
+    
+    /**
+     * remote address
+     * @var string
+     */
+    protected $_remoteAddress = '';
+
+    /**
+     * create a connection
+     * @param resource $socket
+     * @param string $remote_address
+     */
+    public function __construct($socket, $remote_address)
+    {
+        $this->_socket = $socket;
+        $this->_remoteAddress = $remote_address;
+    }
+    
+    /**
+     * send buffer to client
+     * @param string $send_buffer
+     * @return void|boolean
+     */
+    public function send($send_buffer)
+    {
+        return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress);
+    }
+    
+    /**
+     * get remote ip
+     * @return string
+     */
+    public function getRemoteIp()
+    {
+        if(!$this->_remoteIp)
+        {
+            list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
+        }
+        return $this->_remoteIp;
+    }
+    
+    /**
+     * get remote port
+     */
+    public function getRemotePort()
+    {
+        if(!$this->_remotePort)
+        {
+            list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
+        }
+        return $this->_remotePort;
+    }
+
+    /**
+     * close the connection
+     * @void
+     */
+    public function close($data = null)
+    {
+        if($data !== null)
+        {
+            $this->send($data);
+        }
+        return true;
+    }
+}

+ 16 - 21
workerman/Core/Events/interfaces.php → Workerman/Events/EventInterface.php

@@ -1,51 +1,46 @@
-<?php 
-namespace Man\Core\Events;
-/**
- * 
- * 事件轮询库的通用接口
- * 其它事件轮询库需要实现这些接口才能在这个server框架中使用
- * 
- * @author walkor <walkor@workerman.net>
- *
- */
-interface BaseEvent
+<?php
+namespace Workerman\Events;
+
+interface EventInterface
 {
     /**
-     * 数据可读事件
-     * @var integer
+     * read event
+     * @var int
      */
     const EV_READ = 1;
     
     /**
-     * 数据可写事件
-     * @var integer
+     * write event
+     * @var int
      */
     const EV_WRITE = 2;
     
     /**
-     * 信号事件
-     * @var integer
+     * signal
+     * @var int
      */
     const EV_SIGNAL = 4;
     
     /**
-     * 事件添加
+     * add 
      * @param resource $fd
      * @param int $flag
      * @param callable $func
+     * @return bool
      */
     public function add($fd, $flag, $func);
     
     /**
-     * 事件删除
+     * del
      * @param resource $fd
      * @param int $flag
+     * @return bool
      */
     public function del($fd, $flag);
     
     /**
-     * 轮询
+     * loop
+     * @return void
      */
     public function loop();
 }
-

+ 123 - 0
Workerman/Events/Libevent.php

@@ -0,0 +1,123 @@
+<?php 
+namespace Workerman\Events;
+/**
+ * libevent
+ * @author walkor <walkor@workerman.net>
+ */
+class Libevent implements EventInterface
+{
+    /**
+     * eventBase
+     * @var object
+     */
+    protected $eventBase = null;
+    
+    /**
+     * all events
+     * @var array
+     */
+    protected $allEvents = array();
+    
+    /**
+     * all signal events
+     * @var array
+     */
+    protected $eventSignal = array();
+    
+    /**
+     * create event base
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->_eventBase = event_base_new();
+    }
+   
+    /**
+     * 添加事件
+     * @see EventInterface::add()
+     */
+    public function add($fd, $flag, $func)
+    {
+        $fd_key = (int)$fd;
+        
+        if ($flag == self::EV_SIGNAL)
+        {
+            $real_flag = EV_SIGNAL | EV_PERSIST;
+            $this->_eventSignal[$fd_key] = event_new();
+            if(!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null))
+            {
+                return false;
+            }
+            if(!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase))
+            {
+                return false;
+            }
+            if(!event_add($this->_eventSignal[$fd_key]))
+            {
+                return false;
+            }
+            return true;
+        }
+        
+        $real_flag = $flag == self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST;
+        
+        $this->_allEvents[$fd_key][$flag] = event_new();
+        
+        if(!event_set($this->_allEvents[$fd_key][$flag], $fd, $real_flag, $func, null))
+        {
+            return false;
+        }
+        
+        if(!event_base_set($this->_allEvents[$fd_key][$flag], $this->_eventBase))
+        {
+            return false;
+        }
+        
+        if(!event_add($this->_allEvents[$fd_key][$flag]))
+        {
+            return false;
+        }
+        return true;
+    }
+    
+    /**
+     * del
+     * @see Events\EventInterface::del()
+     */
+    public function del($fd ,$flag)
+    {
+        $fd_key = (int)$fd;
+        switch($flag)
+        {
+            case EventInterface::EV_READ:
+            case EventInterface::EV_WRITE:
+                if(isset($this->_allEvents[$fd_key][$flag]))
+                {
+                    event_del($this->_allEvents[$fd_key][$flag]);
+                }
+                unset($this->_allEvents[$fd_key][$flag]);
+                if(empty($this->_allEvents[$fd_key]))
+                {
+                    unset($this->_allEvents[$fd_key]);
+                }
+            case  EventInterface::EV_SIGNAL:
+                if(isset($this->_eventSignal[$fd_key]))
+                {
+                    event_del($this->_eventSignal[$fd_key]);
+                }
+                unset($this->_eventSignal[$fd_key]);
+        }
+        return true;
+    }
+
+    /**
+     * loop
+     * @see EventInterface::loop()
+     */
+    public function loop()
+    {
+        event_base_loop($this->_eventBase);
+    }
+}
+

+ 145 - 0
Workerman/Events/Select.php

@@ -0,0 +1,145 @@
+<?php
+namespace Workerman\Events;
+
+class Select implements EventInterface
+{
+    /**
+     * all events
+     * @var array
+     */
+    public $_allEvents = array();
+    
+    /**
+     * all signal events
+     * @var array
+     */
+    public $_signalEvents = array();
+    
+    /**
+     * read fds
+     * @var array
+     */
+    protected $_readFds = array();
+    
+    /**
+     * write fds
+     * @var array
+     */
+    protected $_writeFds = array();
+    
+    /**
+     * add
+     * @see Events\EventInterface::add()
+     */
+    public function add($fd, $flag, $func)
+    {
+        // key
+        $fd_key = (int)$fd;
+        switch ($flag)
+        {
+            case self::EV_READ:
+                $this->_allEvents[$fd_key][$flag] = array($func, $fd);
+                $this->_readFds[$fd_key] = $fd;
+                break;
+            case self::EV_WRITE:
+                $this->_allEvents[$fd_key][$flag] = array($func, $fd);
+                $this->_writeFds[$fd_key] = $fd;
+                break;
+            case self::EV_SIGNAL:
+                $this->_signalEvents[$fd_key][$flag] = array($func, $fd);
+                pcntl_signal($fd, array($this, 'signalHandler'));
+                break;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * signal handler
+     * @param int $signal
+     */
+    public function signalHandler($signal)
+    {
+        call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal));
+    }
+    
+    /**
+     * del
+     * @see Events\EventInterface::del()
+     */
+    public function del($fd ,$flag)
+    {
+        $fd_key = (int)$fd;
+        switch ($flag)
+        {
+            case self::EV_READ:
+                unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
+                if(empty($this->_allEvents[$fd_key]))
+                {
+                    unset($this->_allEvents[$fd_key]);
+                }
+                break;
+            case self::EV_WRITE:
+                unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]);
+                if(empty($this->_allEvents[$fd_key]))
+                {
+                    unset($this->_allEvents[$fd_key]);
+                }
+                break;
+            case self::EV_SIGNAL:
+                unset($this->_signalEvents[$fd_key]);
+                pcntl_signal($fd, SIG_IGN);
+                break;
+        }
+        return true;
+    }
+    /**
+     * main loop
+     * @see Events\EventInterface::loop()
+     */
+    public function loop()
+    {
+        $e = null;
+        while (1)
+        {
+            // calls signal handlers for pending signals
+            pcntl_signal_dispatch();
+            // 
+            $read = $this->_readFds;
+            $write = $this->_writeFds;
+            // waits for $read and $write to change status
+            if(!@stream_select($read, $write, $e, PHP_INT_MAX))
+            {
+                // maybe interrupt by sianals, so calls signal handlers for pending signals
+                pcntl_signal_dispatch();
+                continue;
+            }
+            
+            // read
+            if($read)
+            {
+                foreach($read as $fd)
+                {
+                    $fd_key = (int) $fd;
+                    if(isset($this->_allEvents[$fd_key][self::EV_READ]))
+                    {
+                        call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], array($this->_allEvents[$fd_key][self::EV_READ][1]));
+                    }
+                }
+            }
+            
+            // write
+            if($write)
+            {
+                foreach($write as $fd)
+                {
+                    $fd_key = (int) $fd;
+                    if(isset($this->_allEvents[$fd_key][self::EV_WRITE]))
+                    {
+                        call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], array($this->_allEvents[$fd_key][self::EV_WRITE][1]));
+                    }
+                }
+            }
+        }
+    }
+}

+ 10 - 0
Workerman/Lib/Constants.php

@@ -0,0 +1,10 @@
+<?php 
+if(!ini_get('date.timezone') )
+{
+    date_default_timezone_set('Asia/Shanghai');
+}
+ini_set('display_errors', 'on');
+
+define('WORKERMAN_CONNECT_FAIL', 1);
+
+define('WORKERMAN_SEND_FAIL', 2);

+ 28 - 35
workerman/Core/Lib/Task.php → Workerman/Lib/Timer.php

@@ -1,22 +1,24 @@
 <?php
-namespace Man\Core\Lib;
+namespace Workerman\Lib;
+use \Workerman\Events\EventInterface;
+use \Exception;
+
 /**
  * 
- * 定时任务
+ * timer
  * 
- * <b>使用示例:</b>
+ * <b>example:</b>
  * <pre>
  * <code>
- * \Man\Core\Lib\Task::init();
- * \Man\Core\Lib\Task::add(5, array('class', 'method'), array($arg1, $arg2..));
+ * Workerman\Lib\Timer::init();
+ * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
  * <code>
  * </pre>
 * @author walkor <walkor@workerman.net>
  */
-class Task 
+class Timer 
 {
     /**
-     * 每个任务定时时长及对应的任务(函数)
      * [
      *   run_time => [[$func, $args, $persistent, timelong],[$func, $args, $persistent, timelong],..]],
      *   run_time => [[$func, $args, $persistent, timelong],[$func, $args, $persistent, timelong],..]],
@@ -28,23 +30,23 @@ class Task
     
     
     /**
-     * 初始化任务
+     * init
      * @return void
      */
     public static function init($event = null)
     {
         if($event)
         {
-            $event->add(SIGALRM, \Man\Core\Events\BaseEvent::EV_SIGNAL, array('\Man\Core\Lib\Task', 'signalHandle'));
+            $event->add(SIGALRM, EventInterface::EV_SIGNAL, array('\Workerman\Lib\Timer', 'signalHandle'));
         }
         else 
         {
-            pcntl_signal(SIGALRM, array('\Man\Core\Lib\Task', 'signalHandle'), false);
+            pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
         }
     }
     
     /**
-     * 捕捉alarm信号
+     * signal handler
      * @return void
      */
     public static function signalHandle()
@@ -55,63 +57,55 @@ class Task
     
     
     /**
-     * 
-     * 添加一个任务
-     * 
-     * @param int $time_long 多长时间运行一次 单位秒
-     * @param callback $func 任务运行的函数或方法
-     * @param mix $args 任务运行的函数或方法使用的参数
+     * add a timer
+     * @param int $time_interval
+     * @param callback $func
+     * @param mix $args
      * @return void
      */
-    public static function add($time_long, $func, $args = array(), $persistent = true)
+    public static function add($time_interval, $func, $args = array(), $persistent = true)
     {
-        if($time_long <= 0)
+        if($time_interval <= 0)
         {
             return false;
         }
         if(!is_callable($func))
         {
-            if(class_exists('\Man\Core\Lib\Log'))
-            {
-                \Man\Core\Lib\Log::add(var_export($func, true). "not callable\n");
-            }
+            echo new Exception("not callable");
             return false;
         }
         
-        // 有任务时才出发计时器
         if(empty(self::$tasks))
         {
             pcntl_alarm(1);
         }
         
         $time_now = time();
-        $run_time = $time_now + $time_long;
+        $run_time = $time_now + $time_interval;
         if(!isset(self::$tasks[$run_time]))
         {
             self::$tasks[$run_time] = array();
         }
-        self::$tasks[$run_time][] = array($func, $args, $persistent, $time_long);
+        self::$tasks[$run_time][] = array($func, $args, $persistent, $time_interval);
         return true;
     }
     
     
     /**
-     * 
-     * 定时被调用,用于触发定时任务
-     * 
+     * tick
      * @return void
      */
     public static function tick()
     {
         if(empty(self::$tasks))
         {
+            pcntl_alarm(0);
             return;
         }
         
         $time_now = time();
         foreach (self::$tasks as $run_time=>$task_data)
         {
-            // 时间到了就运行一下
             if($time_now >= $run_time)
             {
                 foreach($task_data as $index=>$one_task)
@@ -119,7 +113,7 @@ class Task
                     $task_func = $one_task[0];
                     $task_args = $one_task[1];
                     $persistent = $one_task[2];
-                    $time_long = $one_task[3];
+                    $time_interval = $one_task[3];
                     try 
                     {
                         call_user_func_array($task_func, $task_args);
@@ -128,10 +122,9 @@ class Task
                     {
                         echo $e;
                     }
-                    // 持久的放入下一个任务队列
                     if($persistent)
                     {
-                        self::add($time_long, $task_func, $task_args);
+                        self::add($time_interval, $task_func, $task_args);
                     }
                 }
                 unset(self::$tasks[$run_time]);
@@ -140,11 +133,11 @@ class Task
     }
     
     /**
-     * 删除所有的任务
+     * del all
      */
     public static function delAll()
     {
         self::$tasks = array();
+        pcntl_alarm(0);
     }
-    
 }

+ 127 - 0
Workerman/Protocols/GatewayProtocol.php

@@ -0,0 +1,127 @@
+<?php 
+namespace Workerman\Protocols;
+/**
+ * Gateway与Worker间通讯的二进制协议
+ * 
+ * struct GatewayProtocol
+ * {
+ *     unsigned int        pack_len,
+ *     unsigned char     cmd,//命令字
+ *     unsigned int        local_ip,
+ *     unsigned short    local_port,
+ *     unsigned int        client_ip,
+ *     unsigned short    client_port,
+ *     unsigned int        client_id,
+ *     unsigned int        ext_len,
+ *     char[ext_len]        ext_data,
+ *     char[pack_length-HEAD_LEN] body//包体
+ * }
+ * 
+ * 
+ * @author walkor <walkor@workerman.net>
+ */
+
+class GatewayProtocol
+{
+    // 发给worker,gateway有一个新的连接
+    const CMD_ON_CONNECTION = 1;
+    
+    // 发给worker的,客户端有消息
+    const CMD_ON_MESSAGE = 3;
+    
+    // 发给worker上的关闭链接事件
+    const CMD_ON_CLOSE = 4;
+    
+    // 发给gateway的向单个用户发送数据
+    const CMD_SEND_TO_ONE = 5;
+    
+    // 发给gateway的向所有用户发送数据
+    const CMD_SEND_TO_ALL = 6;
+    
+    // 发给gateway的踢出用户
+    const CMD_KICK = 7;
+    
+    // 发给gateway,通知用户session更改
+    const CMD_UPDATE_SESSION = 9;
+    
+    // 获取在线状态
+    const CMD_GET_ONLINE_STATUS = 10;
+    
+    // 判断是否在线
+    const CMD_IS_ONLINE = 11;
+    
+    /**
+     * 包头长度
+     * @var integer
+     */
+    const HEAD_LEN = 25;
+    
+    public static $empty = array(
+        'cmd' => 0,
+        'local_ip' => '0.0.0.0',
+        'local_port' => 0,
+        'client_ip' => '0.0.0.0',
+        'client_port' => 0,
+        'client_id' => 0,
+        'ext_data' => '',
+        'body' => '',
+    );
+     
+    /**
+     * 返回包长度
+     * @param string $buffer
+     * @return int return current package length
+     */
+    public static function input($buffer)
+    {
+        if(strlen($buffer) < self::HEAD_LEN)
+        {
+            return 0;
+        }
+        
+        $data = unpack("Npack_len", $buffer);
+        return $data['pack_len'];
+    }
+    
+    /**
+     * 获取整个包的buffer
+     * @param array $data
+     * @return string
+     */
+    public static function encode($data)
+    {
+        $ext_len = strlen($data['ext_data']);
+        $package_len = self::HEAD_LEN + $ext_len + strlen($data['body']);
+        return pack("NCNnNnNN",  $package_len,
+                        $data['cmd'], ip2long($data['local_ip']), 
+                        $data['local_port'], ip2long($data['client_ip']), 
+                        $data['client_port'], $data['client_id'],
+                       $ext_len) . $data['ext_data'] . $data['body'];
+    }
+    
+    /**
+     * 从二进制数据转换为数组
+     * @param string $buffer
+     * @return array
+     */    
+    public static function decode($buffer)
+    {
+        $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nclient_id/Next_len", $buffer);
+        $data['local_ip'] = long2ip($data['local_ip']);
+        $data['client_ip'] = long2ip($data['client_ip']);
+        if($data['ext_len'] > 0)
+        {
+            $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']);
+            $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']);
+        }
+        else
+        {
+            $data['ext_data'] = '';
+            $data['body'] = substr($buffer, self::HEAD_LEN);
+        }
+        return $data;
+    }
+}
+
+
+

+ 450 - 0
Workerman/Protocols/Http.php

@@ -0,0 +1,450 @@
+<?php 
+namespace  Workerman\Protocols;
+
+use Workerman\Connection\ConnectionInterface;
+
+/**
+ * http protocol
+ * @author walkor<walkor@workerman.net>
+ */
+class Http implements \Workerman\Protocols\ProtocolInterface
+{
+    public static function input($recv_buffer, ConnectionInterface $connection)
+    {
+        if(!strpos($recv_buffer, "\r\n\r\n"))
+        {
+            return 0;
+        }
+        
+        list($header, $body) = explode("\r\n\r\n", $recv_buffer, 2);
+        if(0 === strpos($recv_buffer, "POST"))
+        {
+            // find Content-Length
+            $match = array();
+            if(preg_match("/\r\nContent-Length: ?(\d*)\r\n/", $header, $match))
+            {
+                $content_lenght = $match[1];
+            }
+            else
+            {
+                return 0;
+            }
+            if($content_lenght <= strlen($body))
+            {
+                return strlen($header)+4+$content_lenght;
+            }
+            return 0;
+        }
+        else
+        {
+            return strlen($header)+4;
+        }
+        return;
+    }
+    
+    public static function decode($recv_buffer, ConnectionInterface $connection)
+    {
+        // 初始化
+        $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION =  array();
+        $GLOBALS['HTTP_RAW_POST_DATA'] = '';
+        // 清空上次的数据
+        HttpCache::$header = array();
+        HttpCache::$instance = new HttpCache();
+        // 需要设置的变量名
+        $_SERVER = array (
+              'QUERY_STRING' => '',
+              'REQUEST_METHOD' => '',
+              'REQUEST_URI' => '',
+              'SERVER_PROTOCOL' => '',
+              'SERVER_SOFTWARE' => 'workerman/3.0',
+              'SERVER_NAME' => '', 
+              'HTTP_HOST' => '',
+              'HTTP_USER_AGENT' => '',
+              'HTTP_ACCEPT' => '',
+              'HTTP_ACCEPT_LANGUAGE' => '',
+              'HTTP_ACCEPT_ENCODING' => '',
+              'HTTP_COOKIE' => '',
+              'HTTP_CONNECTION' => '',
+              'REQUEST_TIME' => 0,
+              'REMOTE_ADDR' => '',
+              'REMOTE_PORT' => '0',
+           );
+        
+        // 将header分割成数组
+        $header_data = explode("\r\n", $recv_buffer);
+        
+        list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', $header_data[0]);
+        // 需要解析$_POST
+        if($_SERVER['REQUEST_METHOD'] == 'POST')
+        {
+            $tmp = explode("\r\n\r\n", $recv_buffer, 2);
+            parse_str($tmp[1], $_POST);
+        
+            // $GLOBALS['HTTP_RAW_POST_DATA']
+            $GLOBALS['HTTP_RAW_POST_DATA'] = $tmp[1];
+            unset($header_data[count($header_data) - 1]);
+        }
+        
+        unset($header_data[0]);
+        foreach($header_data as $content)
+        {
+            // \r\n\r\n
+            if(empty($content))
+            {
+                continue;
+            }
+            list($key, $value) = explode(':', $content, 2);
+            $key = strtolower($key);
+            $value = trim($value);
+            switch($key)
+            {
+                // HTTP_HOST
+                case 'host':
+                    $_SERVER['HTTP_HOST'] = $value;
+                    $tmp = explode(':', $value);
+                    $_SERVER['SERVER_NAME'] = $tmp[0];
+                    if(isset($tmp[1]))
+                    {
+                        $_SERVER['SERVER_PORT'] = (int)$tmp[1];
+                    }
+                    break;
+                // cookie
+                case 'cookie':
+                    {
+                        $_SERVER['HTTP_COOKIE'] = $value;
+                        parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
+                    }
+                    break;
+                // user-agent
+                case 'user-agent':
+                    $_SERVER['HTTP_USER_AGENT'] = $value;
+                    break;
+                // accept
+                case 'accept':
+                    $_SERVER['HTTP_ACCEPT'] = $value;
+                    break;
+                // accept-language
+                case 'accept-language':
+                    $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $value;
+                    break;
+                // accept-encoding
+                case 'accept-encoding':
+                    $_SERVER['HTTP_ACCEPT_ENCODING'] = $value;
+                    break;
+                // connection
+                case 'connection':
+                    $_SERVER['HTTP_CONNECTION'] = $value;
+                    if(strtolower($value) === 'keep-alive')
+                    {
+                        HttpCache::$header['Connection'] = 'Connection: Keep-Alive';
+                    }
+                    else
+                    {
+                        HttpCache::$header['Connection'] = 'Connection: Closed';
+                    }
+                    break;
+                case 'referer':
+                    $_SERVER['HTTP_REFERER'] = $value;
+                    break;
+                case 'if-modified-since':
+                    $_SERVER['HTTP_IF_MODIFIED_SINCE'] = $value;
+                    break;
+                case 'if-none-match':
+                    $_SERVER['HTTP_IF_NONE_MATCH'] = $value;
+                    break;
+            }
+        }
+        
+        // 'REQUEST_TIME_FLOAT' => 1375774613.237,
+        $_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
+        $_SERVER['REQUEST_TIME'] = intval($_SERVER['REQUEST_TIME_FLOAT']);
+        
+        // QUERY_STRING
+        $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
+        
+        // GET
+        parse_str($_SERVER['QUERY_STRING'], $_GET);
+        
+        // REQUEST
+        $_REQUEST = array_merge($_GET, $_POST);
+        
+        // REMOTE_ADDR REMOTE_PORT
+        $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
+        $_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
+    }
+    
+    public static function encode($content, ConnectionInterface $connection)
+    {
+        // 没有http-code默认给个
+        if(!isset(HttpCache::$header['Http-Code']))
+        {
+            $header = "HTTP/1.1 200 OK\r\n";
+        }
+        else
+        {
+            $header = HttpCache::$header['Http-Code']."\r\n";
+            unset(HttpCache::$header['Http-Code']);
+        }
+        
+        // Content-Type
+        if(!isset(HttpCache::$header['Content-Type']))
+        {
+            $header .= "Content-Type: text/html;charset=utf-8\r\n";
+        }
+        
+        // other headers
+        foreach(HttpCache::$header as $key=>$item)
+        {
+            if('Set-Cookie' == $key && is_array($item))
+            {
+                foreach($item as $it)
+                {
+                    $header .= $it."\r\n";
+                }
+            }
+            else
+            {
+                $header .= $item."\r\n";
+            }
+        }
+         
+        // header
+        $header .= "Server: WorkerMan/3.0\r\nContent-Length: ".strlen($content)."\r\n\r\n";
+        
+        // save session
+        self::sessionWriteClose();
+        
+        // the whole http package
+        return $header.$content;
+    }
+    
+    /**
+     * 设置http头
+     * @return bool
+     */
+    function header($content, $replace = true, $http_response_code = 0)
+    {
+        if(strpos($content, 'HTTP') === 0)
+        {
+            $key = 'Http-Code';
+        }
+        else
+        {
+            $key = strstr($content, ":", true);
+            if(empty($key))
+            {
+                return false;
+            }
+        }
+    
+        if('location' == strtolower($key) && !$http_response_code)
+        {
+            return header($content, true, 302);
+        }
+    
+        if(isset(HttpCache::$codes[$http_response_code]))
+        {
+            HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " .  HttpCache::$codes[$http_response_code];
+            if($key == 'Http-Code')
+            {
+                return true;
+            }
+        }
+    
+        if($key == 'Set-Cookie')
+        {
+            HttpCache::$header[$key][] = $content;
+        }
+        else
+        {
+            HttpCache::$header[$key] = $content;
+        }
+    
+        return true;
+    }
+    
+    /**
+     * 删除一个header
+     * @param string $name
+     * @return void
+     */
+    function headerRemove($name)
+    {
+        unset( HttpCache::$header[$name]);
+    }
+    
+    /**
+     * 设置cookie
+     * @param string $name
+     * @param string $value
+     * @param integer $maxage
+     * @param string $path
+     * @param string $domain
+     * @param bool $secure
+     * @param bool $HTTPOnly
+     */
+    function setcookie($name, $value = '', $maxage = 0, $path = '', $domain = '', $secure = false, $HTTPOnly = false) {
+        header(
+                'Set-Cookie: ' . $name . '=' . rawurlencode($value)
+                . (empty($domain) ? '' : '; Domain=' . $domain)
+                . (empty($maxage) ? '' : '; Max-Age=' . $maxage)
+                . (empty($path) ? '' : '; Path=' . $path)
+                . (!$secure ? '' : '; Secure')
+                . (!$HTTPOnly ? '' : '; HttpOnly'), false);
+    }
+    
+    /**
+     * sessionStart
+     *
+     */
+    function sessionStart()
+    {
+        if(HttpCache::$instance->sessionStarted)
+        {
+            echo "already sessionStarted\nn";
+            return true;
+        }
+        HttpCache::$instance->sessionStarted = true;
+        // 没有sid,则创建一个session文件,生成一个sid
+        if(!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName]))
+        {
+            $file_name = tempnam(HttpCache::$sessionPath, 'sess_');
+            if(!$file_name)
+            {
+                return false;
+            }
+            HttpCache::$instance->sessionFile = $file_name;
+            $session_id = substr(basename($file_name), strlen('sess_'));
+            return setcookie(
+                    HttpCache::$sessionName
+                    , $session_id
+                    , ini_get('session.cookie_lifetime')
+                    , ini_get('session.cookie_path')
+                    , ini_get('session.cookie_domain')
+                    , ini_get('session.cookie_secure')
+                    , ini_get('session.cookie_httponly')
+            );
+        }
+        if(!HttpCache::$instance->sessionFile)
+        {
+            HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName];
+        }
+        // 有sid则打开文件,读取session值
+        if(HttpCache::$instance->sessionFile)
+        {
+            $raw = file_get_contents(HttpCache::$instance->sessionFile);
+            if($raw)
+            {
+                session_decode($raw);
+            }
+        }
+    }
+    
+    /**
+     * 保存session
+     */
+    public static function sessionWriteClose()
+    {
+        if(!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION))
+        {
+            $session_str = session_encode();
+            if($session_str && HttpCache::$instance->sessionFile)
+            {
+                return file_put_contents(HttpCache::$instance->sessionFile, $session_str);
+            }
+        }
+        return empty($_SESSION);
+    }
+    
+    /**
+     * 退出
+     * @param string $msg
+     * @throws \Exception
+     */
+    public static function end($msg = '')
+    {
+        if($msg)
+        {
+            echo $msg;
+        }
+        throw new \Exception('jump_exit');
+    }
+    
+    /**
+     * get mime types
+     */
+    public static function getMimeTypesFile()
+    {
+        return __DIR__.'/Http/mime.types';
+    }
+}
+
+/**
+ * 解析http协议数据包 缓存先关
+ * @author walkor
+ */
+class HttpCache
+{
+    public static $codes = array(
+            100 => 'Continue',
+            101 => 'Switching Protocols',
+            200 => 'OK',
+            201 => 'Created',
+            202 => 'Accepted',
+            203 => 'Non-Authoritative Information',
+            204 => 'No Content',
+            205 => 'Reset Content',
+            206 => 'Partial Content',
+            300 => 'Multiple Choices',
+            301 => 'Moved Permanently',
+            302 => 'Found',
+            303 => 'See Other',
+            304 => 'Not Modified',
+            305 => 'Use Proxy',
+            306 => '(Unused)',
+            307 => 'Temporary Redirect',
+            400 => 'Bad Request',
+            401 => 'Unauthorized',
+            402 => 'Payment Required',
+            403 => 'Forbidden',
+            404 => 'Not Found',
+            405 => 'Method Not Allowed',
+            406 => 'Not Acceptable',
+            407 => 'Proxy Authentication Required',
+            408 => 'Request Timeout',
+            409 => 'Conflict',
+            410 => 'Gone',
+            411 => 'Length Required',
+            412 => 'Precondition Failed',
+            413 => 'Request Entity Too Large',
+            414 => 'Request-URI Too Long',
+            415 => 'Unsupported Media Type',
+            416 => 'Requested Range Not Satisfiable',
+            417 => 'Expectation Failed',
+            422 => 'Unprocessable Entity',
+            423 => 'Locked',
+            500 => 'Internal Server Error',
+            501 => 'Not Implemented',
+            502 => 'Bad Gateway',
+            503 => 'Service Unavailable',
+            504 => 'Gateway Timeout',
+            505 => 'HTTP Version Not Supported',
+      );
+    public static $instance = null;
+    public static $header = array();
+    public static $sessionPath = '';
+    public static $sessionName = '';
+    public $sessionStarted = false;
+    public $sessionFile = '';
+
+    public static function init()
+    {
+        self::$sessionName = ini_get('session.name');
+        self::$sessionPath = session_save_path();
+        if(!self::$sessionPath)
+        {
+            self::$sessionPath = sys_get_temp_dir();
+        }
+        @\session_start();
+    }
+}

+ 0 - 0
workerman/Common/Protocols/Http/mime.types → Workerman/Protocols/Http/mime.types


+ 44 - 0
Workerman/Protocols/ProtocolInterface.php

@@ -0,0 +1,44 @@
+<?php
+namespace Workerman\Protocols;
+
+use \Workerman\Connection\ConnectionInterface;
+
+/**
+ * Protocol interface
+* @author walkor <walkor@workerman.net>
+ */
+interface ProtocolInterface
+{
+    /**
+     * 用于分包,即在接收的buffer中返回当前请求的长度(字节)
+     * 如果可以在$recv_buffer中得到请求包的长度则返回长度
+     * 否则返回0,表示需要更多的数据才能得到当前请求包的长度
+     * 如果返回false或者负数,则代表请求不符合协议,则连接会断开
+     * @param ConnectionInterface $connection
+     * @param string $recv_buffer
+     * @return int|false
+     */
+    public static function input($recv_buffer, ConnectionInterface $connection);
+    
+    /**
+     * 用于请求解包
+     * input返回值大于0,并且WorkerMan收到了足够的数据,则自动调用decode
+     * 然后触发onMessage回调,并将decode解码后的数据传递给onMessage回调的第二个参数
+     * 也就是说当收到完整的客户端请求时,会自动调用decode解码,无需业务代码中手动调用
+     * @param ConnectionInterface $connection
+     * @param string $recv_buffer
+     * @return mixed
+     */
+    public static function decode($recv_buffer, ConnectionInterface $connection);
+    
+    /**
+     * 用于请求打包
+     * 当需要向客户端发送数据即调用$connection->send($data);时
+     * 会自动把$data用encode打包一次,变成符合协议的数据格式,然后再发送给客户端
+     * 也就是说发送给客户端的数据会自动encode打包,无需业务代码中手动调用
+     * @param ConnectionInterface $connection
+     * @param mixed $data
+     * @return string
+     */
+    public static function encode($data, ConnectionInterface $connection);
+}

+ 51 - 0
Workerman/Protocols/Telnet.php

@@ -0,0 +1,51 @@
+<?php 
+namespace Workerman\Protocols;
+/**
+ * telnet协议
+ * 以换行为请求结束标记
+ * @author walkor <walkor@workerman.net>
+ */
+
+class Telnet
+{
+    /**
+     * 检查包的完整性
+     * 如果能够得到包长,则返回包的长度,否则返回0继续等待数据
+     * @param string $buffer
+     */
+    public static function input($buffer)
+    {
+        // 获得换行字符"\n"位置
+        $pos = strpos($buffer, "\n");
+        // 没有换行符,无法得知包长,返回0继续等待数据
+        if($pos === false)
+        {
+            return 0;
+        }
+        // 有换行符,返回当前包长,包含换行符
+        return $pos+1;
+    }
+    
+    /**
+     * 打包,当向客户端发送数据的时候会自动调用
+     * @param string $buffer
+     * @return string
+     */
+    public static function encode($buffer)
+    {
+        // 加上换行
+        return $buffer."\n";
+    }
+    
+    /**
+     * 解包,当接收到的数据字节数等于input返回的值(大于0的值)自动调用
+     * 并传递给onMessage回调函数的$data参数
+     * @param string $buffer
+     * @return string
+     */
+    public static function decode($buffer)
+    {
+        // 去掉换行
+        return trim($buffer);
+    }
+}

+ 140 - 0
Workerman/Protocols/Websocket.php

@@ -0,0 +1,140 @@
+<?php 
+namespace Workerman\Protocols;
+/**
+ * WebSocket 协议服务端解包和打包
+ * @author walkor <walkor@workerman.net>
+ */
+
+use Workerman\Connection\ConnectionInterface;
+
+class Websocket implements \Workerman\Protocols\ProtocolInterface
+{
+    /**
+     * 检查包的完整性
+     * @param string $buffer
+     */
+    public static function input($buffer, ConnectionInterface $connection)
+    {
+        // 数据长度
+        $recv_len = strlen($buffer);
+        // 长度不够
+        if($recv_len < 6)
+        {
+            return 0;
+        }
+        
+        // 还没有握手
+        if(empty($connection->handshake))
+        {
+            // 握手阶段客户端发送HTTP协议
+            if(0 === strpos($buffer, 'GET'))
+            {
+                // 判断\r\n\r\n边界
+                $heder_end_pos = strpos($buffer, "\r\n\r\n");
+                if(!$heder_end_pos)
+                {
+                    return 0;
+                }
+                // 解析Sec-WebSocket-Key
+                $Sec_WebSocket_Key = '';
+                if(preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/", $buffer, $match))
+                {
+                    $Sec_WebSocket_Key = $match[1];
+                }
+                $new_key = base64_encode(sha1($Sec_WebSocket_Key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
+                // 握手返回的数据
+                $new_message = "HTTP/1.1 101 Switching Protocols\r\n";
+                $new_message .= "Upgrade: websocket\r\n";
+                $new_message .= "Sec-WebSocket-Version: 13\r\n";
+                $new_message .= "Connection: Upgrade\r\n";
+                $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
+                $connection->handshake = true;
+                $connection->consumeRecvBuffer(strlen($buffer));
+                $connection->send($new_message, true);
+                return 0;
+            }
+            // 如果是flash的policy-file-request
+            elseif(0 === strpos($buffer,'<polic'))
+            {
+                if('>' != $buffer[strlen($buffer) - 1])
+                {
+                    return 0;
+                }
+                $policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'."\0";
+                $connection->send($policy_xml, true);
+                $connection->consumeRecvBuffer(strlen($buffer));
+                return 0;
+            }
+            // error
+            $connection->close();
+            return 0;
+        }
+        
+        // close package
+        if(ord($buffer[0]) & 0xf == 8)
+        {
+            $connection->close();
+            return 0;
+        }
+        
+        // websocket二进制数据
+        $data_len = ord($buffer[1]) & 127;
+        $head_len = 6;
+        if ($data_len === 126) {
+            $pack = unpack('ntotal_len', substr($buffer, 2, 2));
+            $data_len = $pack['total_len'];
+            $head_len = 8;
+        } else if ($data_len === 127) {
+            $arr = unpack('N2', substr($buffer, 2, 8));
+            $data_len = $arr[1]*4294967296 + $arr[2];
+            $head_len = 14;
+        }
+        return $head_len + $data_len;
+    }
+    
+    /**
+     * 打包
+     * @param string $buffer
+     */
+    public static function encode($buffer, ConnectionInterface $connection)
+    {
+        $len = strlen($buffer);
+        if($len<=125)
+        {
+            return "\x81".chr($len).$buffer;
+        }
+        else if($len<=65535)
+        {
+            return "\x81".chr(126).pack("n", $len).$buffer;
+        }
+        else
+        {
+            return "\x81".chr(127).pack("xxxxN", $len).$buffer;
+        }
+    }
+    
+    /**
+     * 解包
+     * @param string $buffer
+     * @return string
+     */
+    public static function decode($buffer, ConnectionInterface $connection)
+    {
+        $len = $masks = $data = $decoded = null;
+        $len = ord($buffer[1]) & 127;
+        if ($len === 126) {
+            $masks = substr($buffer, 4, 4);
+            $data = substr($buffer, 8);
+        } else if ($len === 127) {
+            $masks = substr($buffer, 10, 4);
+            $data = substr($buffer, 14);
+        } else {
+            $masks = substr($buffer, 2, 4);
+            $data = substr($buffer, 6);
+        }
+        for ($index = 0; $index < strlen($data); $index++) {
+            $decoded .= $data[$index] ^ $masks[$index % 4];
+        }
+        return $decoded;
+    }
+}

+ 224 - 0
Workerman/WebServer.php

@@ -0,0 +1,224 @@
+<?php
+namespace Workerman;
+
+use \Workerman\Worker;
+use \Workerman\Protocols\Http;
+use \Workerman\Protocols\HttpCache;
+
+/**
+ * 
+ *  WebServer
+ *  HTTP协议
+ *  
+ * @author walkor <walkor@workerman.net>
+ */
+class WebServer extends Worker
+{
+    /**
+     * 默认mime类型
+     * @var string
+     */
+    protected static $defaultMimeType = 'text/html; charset=utf-8';
+    
+    /**
+     * 服务器名到文件路径的转换
+     * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www']
+     */
+    protected $serverRoot = array();
+    
+    /**
+     * mime类型映射关系
+     * @var array
+     */
+    protected static $mimeTypeMap = array();
+    
+    
+    /**
+     * add server root
+     */
+    public  function addRoot($domain, $root_path)
+    {
+        $this->serverRoot[$domain] = $root_path;
+    }
+    
+    /**
+     * 
+     * @param string $socket_name
+     * @param array $context_option
+     */
+    public function __construct($socket_name, $context_option = array())
+    {
+        $this->onWorkerStart = array($this, 'onWorkerStart');
+        $this->onMessage = array($this, 'onMessage');
+        $this->name = 'WebServer';
+        list($scheme, $address) = explode(':', $socket_name, 2);
+        parent::__construct('http:'.$address, $context_option);
+    }
+    
+    /**
+     * 进程启动的时候一些初始化工作
+     */
+    public function onWorkerStart()
+    {
+        if(empty($this->serverRoot))
+        {
+            throw new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path');
+        }
+        // 初始化HttpCache
+        HttpCache::init();
+        // 初始化mimeMap
+        $this->initMimeTypeMap();
+    }
+    
+    /**
+     * 初始化mimeType
+     * @return void
+     */
+    public function initMimeTypeMap()
+    {
+        $mime_file = Http::getMimeTypesFile();
+        if(!is_file($mime_file))
+        {
+            $this->notice("$mime_file mime.type file not fond");
+            return;
+        }
+        $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+        if(!is_array($items))
+        {
+            $this->log("get $mime_file mime.type content fail");
+            return;
+        }
+        foreach($items as $content)
+        {
+            if(preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match))
+            {
+                $mime_type = $match[1];
+                $extension_var = $match[2];
+                $extension_array = explode(' ', substr($extension_var, 0, -1));
+                foreach($extension_array as $extension)
+                {
+                    self::$mimeTypeMap[$extension] = $mime_type;
+                } 
+            }
+        }
+    }
+    
+    /**
+     * 数据接收完整后处理业务逻辑
+     */
+    public function onMessage($connection, $data)
+    {
+        // 请求的文件
+        $url_info = parse_url($_SERVER['REQUEST_URI']);
+        if(!$url_info)
+        {
+            Http::header('HTTP/1.1 400 Bad Request');
+            return $connection->close('<h1>400 Bad Request</h1>');
+        }
+        
+        $path = $url_info['path'];
+        
+        $path_info = pathinfo($path);
+        $extension = isset($path_info['extension']) ? $path_info['extension'] : '' ;
+        if($extension == '')
+        {
+            $path = ($len = strlen($path)) && $path[$len -1] == '/' ? $path.'index.php' : $path . '/index.php';
+            $extension = 'php';
+        }
+        
+        $root_dir = isset($this->serverRoot[$_SERVER['HTTP_HOST']]) ? $this->serverRoot[$_SERVER['HTTP_HOST']] : current($this->serverRoot);
+        
+        $file = "$root_dir/$path";
+        
+        // 对应的php文件不存在则直接使用根目录的index.php
+        if($extension == 'php' && !is_file($file))
+        {
+            $file = "$root_dir/index.php";
+        }
+        
+        // 请求的文件存在
+        if(is_file($file))
+        {
+            // 判断是否是站点目录里的文件
+            if((!($request_realpath = realpath($file)) || !($root_dir_realpath = realpath($root_dir))) || 0 !== strpos($request_realpath, $root_dir_realpath))
+            {
+                Http::header('HTTP/1.1 400 Bad Request');
+                return $connection->close('<h1>400 Bad Request</h1>');
+            }
+            
+            $file = realpath($file);
+            
+            // 如果请求的是php文件
+            if($extension == 'php')
+            {
+                $cwd = getcwd();
+                chdir($root_dir);
+                ini_set('display_errors', 'off');
+                // 缓冲输出
+                ob_start();
+                // 载入php文件
+                try 
+                {
+                    // $_SERVER变量
+                    $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
+                    $_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
+                    include $file;
+                }
+                catch(\Exception $e) 
+                {
+                    // 如果不是exit
+                    if($e->getMessage() != 'jump_exit')
+                    {
+                        echo $e;
+                    }
+                }
+                $content = ob_get_clean();
+                ini_set('display_errors', 'on');
+                $connection->close($content);
+                chdir($cwd);
+                return ;
+            }
+            
+            // 请求的是静态资源文件
+            if(isset(self::$mimeTypeMap[$extension]))
+            {
+               Http::header('Content-Type: '. self::$mimeTypeMap[$extension]);
+            }
+            else 
+            {
+                Http::header('Content-Type: '. self::$defaultMimeType);
+            }
+            
+            // 获取文件信息
+            $info = stat($file);
+            
+            $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' GMT' : '';
+            
+            // 如果有$_SERVER['HTTP_IF_MODIFIED_SINCE']
+            if(!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info)
+            {
+                // 文件没有更改则直接304
+                if($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE'])
+                {
+                    // 304
+                    Http::header('HTTP/1.1 304 Not Modified');
+                    // 发送给客户端
+                    return $connection->close('');
+                }
+            }
+            
+            if($modified_time)
+            {
+                Http::header("Last-Modified: $modified_time");
+            }
+            // 发送给客户端
+           return $connection->close(file_get_contents($file));
+        }
+        else 
+        {
+            // 404
+            Http::header("HTTP/1.1 404 Not Found");
+            return $connection->close('<html><head><title>404 页面不存在</title></head><body><center><h3>404 Not Found</h3></center></body></html>');
+        }
+    }
+}

+ 1180 - 0
Workerman/Worker.php

@@ -0,0 +1,1180 @@
+<?php
+namespace Workerman;
+
+use \Workerman\Events\Libevent;
+use \Workerman\Events\Select;
+use \Workerman\Events\EventInterface;
+use \Workerman\Connection\ConnectionInterface;
+use \Workerman\Connection\TcpConnection;
+use \Workerman\Connection\UdpConnection;
+use \Workerman\Lib\Timer;
+use \Exception;
+
+/**
+ * 
+ * @author walkor<walkor@workerman.net>
+ */
+class Worker
+{
+    /**
+     * workerman version
+     * @var string
+     */
+    const VERSION = '3.0.0';
+    
+    /**
+     * status starting
+     * @var int
+     */
+    const STATUS_STARTING = 1;
+    
+    /**
+     * status running
+     * @var int
+     */
+    const STATUS_RUNNING = 2;
+    
+    /**
+     * status shutdown
+     * @var int
+     */
+    const STATUS_SHUTDOWN = 4;
+    
+    /**
+     * status reloading
+     * @var int
+     */
+    const STATUS_RELOADING = 8;
+    
+    /**
+     * after KILL_WORKER_TIMER_TIME seconds if worker not quit
+     * then send SIGKILL to the worker
+     * @var int
+     */
+    const KILL_WORKER_TIMER_TIME = 1;
+    
+    /**
+     * backlog
+     * @var int
+     */
+    const DEFAUL_BACKLOG = 1024;
+    
+    /**
+     * max udp package size 
+     * @var int
+     */
+    const MAX_UDP_PACKEG_SIZE = 65535;
+    
+    /**
+     * worker name for marking process
+     * @var string
+     */
+    public $name = 'none';
+    
+    /**
+     * how many processes will be created for the current worker
+     * @var unknown_type
+     */
+    public $count = 1;
+    
+    /**
+     * Set the real user of the current process . Needs appropriate privileges (usually root) 
+     * @var string
+     */
+    public $user = '';
+    
+    /**
+     * If you do not want restart current worker processes, when received reload signal
+     * just set reloadable = true 
+     * @var bool
+     */
+    public $reloadable = true;
+    
+    /**
+     * when worker start, then run onWorkerStart
+     * @var callback
+     */
+    public $onWorkerStart = null;
+    
+    /**
+     * when client connect worker, onConnect will be run
+     * @var callback
+     */
+    public $onConnect = null;
+    
+    /**
+     * when worker recv data, onMessage will be run
+     * @var callback
+     */
+    public $onMessage = null;
+    
+    /**
+     * when connection closed, onClose will be run
+     * @var callback
+     */
+    public $onClose = null;
+    
+    /**
+     * when connection has error, onError will be run
+     * @var unknown_type
+     */
+    public $onError = null;
+    
+    /**
+     * when worker stop, which function will be run
+     * @var callback
+     */
+    public $onWorkerStop = null;
+    
+    /**
+     * tcp/udp
+     * @var string
+     */
+    public $transport = 'tcp';
+    
+    /**
+     * protocol
+     * @var string
+     */
+    protected $_protocol = '';
+    
+    /**
+     * if run as daemon
+     * @var bool
+     */
+    public static $daemonize = false;
+    
+    /**
+     * all output buffer (echo var_dump etc) will write to the file 
+     * @var string
+     */
+    public static $stdoutFile = '/dev/null';
+    
+    /**
+     * pid file
+     * @var string
+     */
+    public static $pidFile = '';
+    
+    /**
+     * log file path
+     * @var unknown_type
+     */
+    public static $logFile = '';
+    
+    /**
+     * master process pid
+     * @var int
+     */
+    protected static $_masterPid = 0;
+    
+    /**
+     * event loop
+     * @var Select/Libevent
+     */
+    protected static $_globalEvent = null;
+    
+    /**
+     * stream socket of the worker
+     * @var stream
+     */
+    protected $_mainSocket = null;
+    
+    /**
+     * socket name example http://0.0.0.0:80
+     * @var string
+     */
+    protected $_socketName = '';
+    
+    /**
+     * context
+     * @var context
+     */
+    protected $_context = null;
+    
+    /**
+     * enable ssl or not
+     * @var bool
+     */
+    protected $_enableSSL = false;
+    
+    /**
+     * all instances of worker
+     * @var array
+     */
+    protected static $_workers = array();
+    
+    /**
+     * all workers and pids
+     * @var array
+     */
+    protected static $_pidMap = array();
+    
+    /**
+     * all processes to be restart [pid=>pid, pid=>pid]
+     * @var array
+     */
+    protected static $_pidsToRestart = array();
+    
+    /**
+     * current status
+     * @var int
+     */
+    protected static $_status = self::STATUS_STARTING;
+    
+    /**
+     * max length of $_workerName
+     * @var int
+     */
+    protected static $_maxWorkerNameLength = 12;
+    
+    /**
+     * max length of $_socketName
+     * @var int
+     */
+    protected static $_maxSocketNameLength = 12;
+    
+    /**
+     * max length of $user's name
+     * @var int
+     */
+    protected static $_maxUserNameLength = 12;
+    
+    /**
+     * the path of status file, witch will store status of processes
+     * @var string
+     */
+    protected static $_statisticsFile = '';
+    
+    /**
+     * start file path
+     * @var string
+     */
+    protected static $_startFile = '';
+    
+    /**
+     * global statistics
+     * @var array
+     */
+    protected static $_globalStatistics = array(
+        'start_timestamp' => 0,
+        'worker_exit_info' => array()
+    );
+    
+    /**
+     * run all workers
+     * @return void
+     */
+    public static function runAll()
+    {
+        self::init();
+        self::parseCommand();
+        self::daemonize();
+        self::initWorkers();
+        self::installSignal();
+        self::displayUI();
+        self::resetStd();
+        self::saveMasterPid();
+        self::forkWorkers();
+        self::monitorWorkers();
+    }
+    
+    /**
+     * initialize the environment variables 
+     * @return void
+     */
+    public static function init()
+    {
+        if(empty(self::$pidFile))
+        {
+            $backtrace = debug_backtrace();
+            self::$_startFile = $backtrace[count($backtrace)-1]['file'];
+            self::$pidFile = sys_get_temp_dir()."/workerman.".str_replace('/', '_', self::$_startFile).".pid";
+        }
+        if(empty(self::$logFile))
+        {
+            self::$logFile = __DIR__ . '/../workerman.log';
+        }
+        self::$_status = self::STATUS_STARTING;
+        self::$_globalStatistics['start_timestamp'] = time();
+        self::$_statisticsFile = sys_get_temp_dir().'/workerman.status';
+        self::setProcessTitle('WorkerMan: master process  start_file=' . self::$_startFile);
+        Timer::init();
+    }
+    
+    /**
+     * initialize the all the workers
+     * @return void
+     */
+    protected static function initWorkers()
+    {
+        foreach(self::$_workers as $worker)
+        {
+            // if worker->name not set then use worker->_socketName as worker->name
+            if(empty($worker->name))
+            {
+                $worker->name = 'none';
+            }
+            // get the max length of worker->name for formating status info
+            $worker_name_length = strlen($worker->name);
+            if(self::$_maxWorkerNameLength < $worker_name_length)
+            {
+                self::$_maxWorkerNameLength = $worker_name_length;
+            }
+            // get the max length of worker->_socketName
+            $socket_name_length = strlen($worker->getSocketName());
+            if(self::$_maxSocketNameLength < $socket_name_length)
+            {
+                self::$_maxSocketNameLength = $socket_name_length;
+            }
+            // get the max length user name
+            if(empty($worker->user) || posix_getuid() !== 0)
+            {
+                $worker->user = self::getCurrentUser();
+            }
+            $user_name_length = strlen($worker->user);
+            if(self::$_maxUserNameLength < $user_name_length)
+            {
+                self::$_maxUserNameLength = $user_name_length;
+            }
+            // listen
+            $worker->listen();
+        }
+    }
+    
+    protected static function getCurrentUser()
+    {
+        $user_info = posix_getpwuid(posix_getuid());
+        return $user_info['name'];
+    }
+    
+    protected static function displayUI()
+    {
+        echo "\033[1A\n\033[K-----------------------\033[47;30m WORKERMAN \033[0m-----------------------------\n\033[0m";
+        echo 'Workerman version:' . Worker::VERSION . "          PHP version:".PHP_VERSION."\n";
+        echo "------------------------\033[47;30m WORKERS \033[0m-------------------------------\n";
+        echo "\033[47;30muser\033[0m",str_pad('', self::$_maxUserNameLength+2-strlen('user')), "\033[47;30mworker\033[0m",str_pad('', self::$_maxWorkerNameLength+2-strlen('worker')), "\033[47;30mlisten\033[0m",str_pad('', self::$_maxSocketNameLength+2-strlen('listen')), "\033[47;30mprocesses\033[0m \033[47;30m","status\033[0m\n";
+        foreach(self::$_workers as $worker)
+        {
+            echo str_pad($worker->user, self::$_maxUserNameLength+2),str_pad($worker->name, self::$_maxWorkerNameLength+2),str_pad($worker->getSocketName(), self::$_maxSocketNameLength+2), str_pad(' '.$worker->count, 9), " \033[32;40m [OK] \033[0m\n";;
+        }
+        echo "----------------------------------------------------------------\n";
+    }
+    
+    /**
+     * php yourfile.php start | stop | restart | reload | status
+     * @return void
+     */
+    public static function parseCommand()
+    {
+        // check command
+        global $argv;
+        $start_file = $argv[0]; 
+        if(!isset($argv[1]))
+        {
+            exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
+        }
+        
+        $command = trim($argv[1]);
+        
+        $command2 = isset($argv[2]) ? $argv[2] : '';
+        
+        self::log("Workerman[$start_file] $command");
+        
+        // check if master process is running
+        $master_pid = @file_get_contents(self::$pidFile);
+        $master_is_alive = $master_pid && @posix_kill($master_pid, 0);
+        if($master_is_alive)
+        {
+            if($command === 'start')
+            {
+                self::log("Workerman[$start_file] is running");
+            }
+        }
+        elseif($command !== 'start' && $command !== 'restart')
+        {
+            self::log("Workerman[$start_file] not run");
+        }
+        
+        switch($command)
+        {
+            // start workerman
+            case 'start':
+                if($command2 == '-d')
+                {
+                    Worker::$daemonize = true;
+                }
+                break;
+            // show status of workerman
+            case 'status':
+                // try to delete the statistics file , avoid read dirty data
+                if(is_file(self::$_statisticsFile))
+                {
+                    @unlink(self::$_statisticsFile);
+                }
+                // send SIGUSR2 to master process ,then master process will send SIGUSR2 to all children processes
+                // all processes will write statistics data to statistics file
+                posix_kill($master_pid, SIGUSR2);
+                // wait all processes wirte statistics data
+                usleep(100000);
+                // display statistics file
+                readfile(self::$_statisticsFile);
+                exit(0);
+            // restart workerman
+            case 'restart':
+            // stop workeran
+            case 'stop':
+                self::log("Workerman[$start_file] is stoping ...");
+                // send SIGINT to master process, master process will stop all children process and exit
+                $master_pid && posix_kill($master_pid, SIGINT);
+                // if $timeout seconds master process not exit then dispaly stop failure
+                $timeout = 5;
+                // a recording start time
+                $start_time = time();
+                while(1)
+                {
+                    $master_is_alive = $master_pid && posix_kill($master_pid, 0);
+                    if($master_is_alive)
+                    {
+                        // check whether has timed out
+                        if(time() - $start_time >= $timeout)
+                        {
+                            self::log("Workerman[$start_file] stop fail");
+                            exit;
+                        }
+                        // avoid the cost of CPU time, sleep for a while
+                        usleep(10000);
+                        continue;
+                    }
+                    self::log("Workerman[$start_file] stop success");
+                    if($command === 'stop')
+                    {
+                        exit(0);
+                    }
+                    if($command2 == '-d')
+                    {
+                        Worker::$daemonize = true;
+                    }
+                    break;
+                }
+                break;
+            // reload workerman
+            case 'reload':
+                posix_kill($master_pid, SIGUSR1);
+                self::log("Workerman[$start_file] reload");
+                exit;
+            // unknow command
+            default :
+                 exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
+        }
+    }
+    
+    /**
+     * installs signal handlers for master
+     * @return void
+     */
+    protected static function installSignal()
+    {
+        // stop
+        pcntl_signal(SIGINT,  array('\Workerman\Worker', 'signalHandler'), false);
+        // reload
+        pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false);
+        // status
+        pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false);
+        // ignore
+        pcntl_signal(SIGPIPE, SIG_IGN, false);
+    }
+    
+    /**
+     * reinstall signal handlers for workers
+     * @return void
+     */
+    protected static function reinstallSignal()
+    {
+        // uninstall stop signal handler
+        pcntl_signal(SIGINT,  SIG_IGN, false);
+        // uninstall reload signal handler
+        pcntl_signal(SIGUSR1, SIG_IGN, false);
+        // uninstall  status signal handler
+        pcntl_signal(SIGUSR2, SIG_IGN, false);
+        // reinstall stop signal handler
+        self::$_globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler'));
+        //  uninstall  reload signal handler
+        self::$_globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL,array('\Workerman\Worker', 'signalHandler'));
+        // uninstall  status signal handler
+        self::$_globalEvent->add(SIGUSR2, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler'));
+    }
+    
+    /**
+     * signal handler
+     * @param int $signal
+     */
+    public static function signalHandler($signal)
+    {
+        switch($signal)
+        {
+            // stop
+            case SIGINT:
+                self::stopAll();
+                break;
+            // reload
+            case SIGUSR1:
+                self::$_pidsToRestart = self::getAllWorkerPids();;
+                self::reload();
+                break;
+            // show status
+            case SIGUSR2:
+                self::writeStatisticsToStatusFile();
+                break;
+        }
+    }
+
+    /**
+     * run workerman as daemon
+     * @throws Exception
+     */
+    protected static function daemonize()
+    {
+        if(!self::$daemonize)
+        {
+            return;
+        }
+        umask(0);
+        $pid = pcntl_fork();
+        if(-1 == $pid)
+        {
+            throw new Exception('fork fail');
+        }
+        elseif($pid > 0)
+        {
+            exit(0);
+        }
+        if(-1 == posix_setsid())
+        {
+            throw new Exception("setsid fail");
+        }
+        // fork again avoid SVR4 system regain the control of terminal
+        $pid = pcntl_fork();
+        if(-1 == $pid)
+        {
+            throw new Exception("fork fail");
+        }
+        elseif(0 !== $pid)
+        {
+            exit(0);
+        }
+    }
+
+    /**
+     * redirecting output
+     * @throws Exception
+     */
+    protected static function resetStd()
+    {
+        if(!self::$daemonize)
+        {
+            return;
+        }
+        global $STDOUT, $STDERR;
+        $handle = fopen(self::$stdoutFile,"a");
+        if($handle) 
+        {
+            unset($handle);
+            @fclose(STDOUT);
+            @fclose(STDERR);
+            $STDOUT = fopen(self::$stdoutFile,"a");
+            $STDERR = fopen(self::$stdoutFile,"a");
+        }
+        else
+        {
+            throw new Exception('can not open stdoutFile ' . self::$stdoutFile);
+        }
+    }
+    
+    /**
+     * save the pid of master for later stop/reload/restart/status command
+     * @throws Exception
+     */
+    protected static function saveMasterPid()
+    {
+        self::$_masterPid = posix_getpid();
+        if(false === @file_put_contents(self::$pidFile, self::$_masterPid))
+        {
+            throw new Exception('can not save pid to ' . self::$pidFile);
+        }
+    }
+    
+    /**
+     * get all pids of workers
+     * @return array
+     */
+    protected static function getAllWorkerPids()
+    {
+        $pid_array = array(); 
+        foreach(self::$_pidMap as $worker_pid_array)
+        {
+            foreach($worker_pid_array as $worker_pid)
+            {
+                $pid_array[$worker_pid] = $worker_pid;
+            }
+        }
+        return $pid_array;
+    }
+
+    /**
+     * fork worker processes
+     * @return void
+     */
+    protected static function forkWorkers()
+    {
+        foreach(self::$_workers as $worker)
+        {
+            // check worker->name etc
+            if(self::$_status === self::STATUS_STARTING)
+            {
+                // if worker->name not set then use worker->_socketName as worker->name
+                if(empty($worker->name))
+                {
+                    $worker->name = $worker->getSocketName();
+                }
+                // get the max length of worker->name for formating status info
+                $worker_name_length = strlen($worker->name);
+                if(self::$_maxWorkerNameLength < $worker_name_length)
+                {
+                    self::$_maxWorkerNameLength = $worker_name_length;
+                }
+            }
+            
+            // create processes
+            while(count(self::$_pidMap[$worker->workerId]) < $worker->count)
+            {
+                self::forkOneWorker($worker);
+            }
+        }
+    }
+
+    /**
+     * fork one worker and run it
+     * @param Worker $worker
+     * @throws Exception
+     */
+    protected static function forkOneWorker($worker)
+    {
+        $pid = pcntl_fork();
+        if($pid > 0)
+        {
+            self::$_pidMap[$worker->workerId][$pid] = $pid;
+        }
+        elseif(0 === $pid)
+        {
+            self::$_pidMap = array();
+            self::$_workers = array($worker->workerId => $worker);
+            Timer::delAll();
+            self::setProcessTitle('WorkerMan: worker process  ' . $worker->name . ' ' . $worker->getSocketName());
+            self::setProcessUser($worker->user);
+            $worker->run();
+            exit(250);
+        }
+        else
+        {
+            throw new Exception("forkOneWorker fail");
+        }
+    }
+    
+    /**
+     * set current process user
+     * @return void
+     */
+    protected static function setProcessUser($user_name)
+    {
+        if(empty($user_name) || posix_getuid() !== 0)
+        {
+            return;
+        }
+        $user_info = posix_getpwnam($user_name);
+        if($user_info['uid'] != posix_getuid() || $user_info['gid'] != posix_getgid())
+        {
+            if(!posix_setgid($user_info['gid']) || !posix_setuid($user_info['uid']))
+            {
+                self::log( 'Notice : Can not run woker as '.$user_name." , You shuld be root\n", true);
+            }
+        }
+    }
+
+    
+    /**
+     * set current process title
+     * @param string $title
+     * @return void
+     */
+    protected static function setProcessTitle($title)
+    {
+        // >=php 5.5
+        if (function_exists('cli_set_process_title'))
+        {
+            @cli_set_process_title($title);
+        }
+        // 需要扩展
+        elseif(extension_loaded('proctitle') && function_exists('setproctitle'))
+        {
+            @setproctitle($title);
+        }
+    }
+    
+    /**
+     * wait for the child process exit
+     * @return void
+     */
+    protected static function monitorWorkers()
+    {
+        self::$_status = self::STATUS_RUNNING;
+        while(1)
+        {
+            // calls signal handlers for pending signals
+            pcntl_signal_dispatch();
+            // suspends execution of the current process until a child has exited or  a signal is delivered
+            $status = 0;
+            $pid = pcntl_wait($status, WUNTRACED);
+            if($pid > 0)
+            {
+                foreach(self::$_pidMap as $worker_id => $worker_pid_array)
+                {
+                    if(isset($worker_pid_array[$pid]))
+                    {
+                        $worker = self::$_workers[$worker_id];
+                        // check status
+                        if($status !== 0)
+                        {
+                            self::log("worker[".$worker->name.":$pid] exit with status $status");
+                        }
+                       
+                        // statistics
+                        if(!isset(self::$_globalStatistics['worker_exit_info'][$worker_id][$status]))
+                        {
+                            self::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0;
+                        }
+                        self::$_globalStatistics['worker_exit_info'][$worker_id][$status]++;
+                        
+                        // clear pid info
+                        unset(self::$_pidMap[$worker_id][$pid]);
+                        
+                        // if realoding, continue
+                        if(isset(self::$_pidsToRestart[$pid]))
+                        {
+                            unset(self::$_pidsToRestart[$pid]);
+                            self::reload();
+                        }
+                        break;
+                    }
+                }
+                // workerman is still running
+                if(self::$_status !== self::STATUS_SHUTDOWN)
+                {
+                    self::forkWorkers();
+                }
+                else
+                {
+                    // workerman is shuting down
+                    if(!self::getAllWorkerPids())
+                    {
+                        self::exitAndClearAll();
+                    }
+                }
+            }
+            else 
+            {
+                if(self::$_status === self::STATUS_SHUTDOWN && !self::getAllWorkerPids())
+                {
+                   self::exitAndClearAll();
+                }
+            }
+        }
+    }
+    
+    /**
+     * exit
+     */
+    protected static function exitAndClearAll()
+    {
+        @unlink(self::$pidFile);
+        self::log("Workerman[".basename(self::$_startFile)."] has been stopped");
+        exit(0);
+    }
+    
+    /**
+     * reload workerman, gracefully restart child processes one by one
+     * @return void
+     */
+    protected static function reload()
+    {
+        // for master process
+        if(self::$_masterPid === posix_getpid())
+        {
+            // set status
+            if(self::$_status !== self::STATUS_RELOADING && self::$_status !== self::STATUS_SHUTDOWN)
+            {
+                self::log("Workerman[".basename(self::$_startFile)."] reloading");
+                self::$_status = self::STATUS_RELOADING;
+            }
+            
+            $reloadable_pid_array = array();
+            foreach(self::$_pidMap as $worker_id =>$worker_pid_array)
+            {
+                $worker = self::$_workers[$worker_id];
+                if($worker->reloadable)
+                {
+                    foreach($worker_pid_array as $pid)
+                    {
+                        $reloadable_pid_array[$pid] = $pid;
+                    }
+                }
+            }
+            
+            self::$_pidsToRestart = array_intersect(self::$_pidsToRestart , $reloadable_pid_array);
+            
+            // reload complete
+            if(empty(self::$_pidsToRestart))
+            {
+                if(self::$_status !== self::STATUS_SHUTDOWN)
+                {
+                    self::$_status = self::STATUS_RUNNING;
+                }
+                return;
+            }
+            // continue reload
+            $one_worker_pid = current(self::$_pidsToRestart );
+            posix_kill($one_worker_pid, SIGUSR1);
+            Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false);
+        }
+        // for children process
+        else
+        {
+            $worker = current(self::$_workers);
+            if($worker->reloadable)
+            {
+                self::stopAll();
+            }
+        }
+    } 
+    
+    /**
+     * stop all workers
+     * @return void
+     */
+    public static function stopAll()
+    {
+        self::$_status = self::STATUS_SHUTDOWN;
+        // for master process
+        if(self::$_masterPid === posix_getpid())
+        {
+            self::log("Workerman[".basename(self::$_startFile)."] Stopping ...");
+            $worker_pid_array = self::getAllWorkerPids();
+            foreach($worker_pid_array as $worker_pid)
+            {
+                posix_kill($worker_pid, SIGINT);
+                Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL),false);
+            }
+        }
+        // for worker process
+        else
+        {
+            foreach(self::$_workers as $worker)
+            {
+                $worker->stop();
+            }
+            exit(0);
+        }
+    }
+    
+    /**
+     * for workermand status command
+     * @return void
+     */
+    protected static function writeStatisticsToStatusFile()
+    {
+        // for master process
+        if(self::$_masterPid === posix_getpid())
+        {
+            $loadavg = sys_getloadavg();
+            file_put_contents(self::$_statisticsFile, "---------------------------------------GLOBAL STATUS--------------------------------------------\n");
+            file_put_contents(self::$_statisticsFile, 'Workerman version:' . Worker::VERSION . "          PHP version:".PHP_VERSION."\n", FILE_APPEND);
+            file_put_contents(self::$_statisticsFile, 'start time:'. date('Y-m-d H:i:s', self::$_globalStatistics['start_timestamp']).'   run ' . floor((time()-self::$_globalStatistics['start_timestamp'])/(24*60*60)). ' days ' . floor(((time()-self::$_globalStatistics['start_timestamp'])%(24*60*60))/(60*60)) . " hours   \n", FILE_APPEND);
+            file_put_contents(self::$_statisticsFile, 'load average: ' . implode(", ", $loadavg) . "\n", FILE_APPEND);
+            file_put_contents(self::$_statisticsFile,  count(self::$_pidMap) . ' workers       ' . count(self::getAllWorkerPids())." processes\n", FILE_APPEND);
+            file_put_contents(self::$_statisticsFile, str_pad('worker_name', self::$_maxWorkerNameLength) . " exit_status     exit_count\n", FILE_APPEND);
+            foreach(self::$_pidMap as $worker_id =>$worker_pid_array)
+            {
+                $worker = self::$_workers[$worker_id];
+                if(isset(self::$_globalStatistics['worker_exit_info'][$worker_id]))
+                {
+                    foreach(self::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status=>$worker_exit_count)
+                    {
+                        file_put_contents(self::$_statisticsFile, str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, 16). " $worker_exit_count\n", FILE_APPEND);
+                    }
+                }
+                else
+                {
+                    file_put_contents(self::$_statisticsFile, str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad(0, 16). " 0\n", FILE_APPEND);
+                }
+            }
+            file_put_contents(self::$_statisticsFile,  "---------------------------------------PROCESS STATUS-------------------------------------------\n", FILE_APPEND);
+            file_put_contents(self::$_statisticsFile, "pid\tmemory  ".str_pad('listening', self::$_maxSocketNameLength)." ".str_pad('worker_name', self::$_maxWorkerNameLength)." ".str_pad('total_request', 13)." ".str_pad('send_fail', 9)." ".str_pad('throw_exception', 15)."\n", FILE_APPEND);
+            
+            chmod(self::$_statisticsFile, 0722);
+            
+            foreach(self::getAllWorkerPids() as $worker_pid)
+            {
+                posix_kill($worker_pid, SIGUSR2);
+            }
+            return;
+        }
+        
+        // for worker process
+        $worker = current(self::$_workers);
+        $wrker_status_str = posix_getpid()."\t".str_pad(round(memory_get_usage()/(1024*1024),2)."M", 7)." " .str_pad($worker->getSocketName(), self::$_maxSocketNameLength) ." ".str_pad(($worker->name == $worker->getSocketName() ? 'none' : $worker->name), self::$_maxWorkerNameLength)." ";
+        $wrker_status_str .=  str_pad(ConnectionInterface::$statistics['total_request'], 14)." ".str_pad(ConnectionInterface::$statistics['send_fail'],9)." ".str_pad(ConnectionInterface::$statistics['throw_exception'],15)."\n";
+        file_put_contents(self::$_statisticsFile, $wrker_status_str, FILE_APPEND);
+    }
+    
+    /**
+     * log
+     * @param string $msg
+     * @return void
+     */
+    protected static function log($msg)
+    {
+        $msg = $msg."\n";
+        if(self::$_status === self::STATUS_STARTING || !self::$daemonize)
+        {
+            echo $msg;
+        }
+        file_put_contents(self::$logFile, date('Y-m-d H:i:s') . " " . $msg, FILE_APPEND);
+    }
+    
+    /**
+     * create a worker
+     * @param string $socket_name
+     * @return void
+     */
+    public function __construct($socket_name = '', $context_option = array())
+    {
+        $this->workerId = spl_object_hash($this);
+        self::$_workers[$this->workerId] = $this;
+        self::$_pidMap[$this->workerId] = array();
+        
+        if($socket_name)
+        {
+            $this->_socketName = $socket_name;
+            if(!isset($context_option['socket']['backlog']))
+            {
+                $context_option['socket']['backlog'] = self::DEFAUL_BACKLOG;
+            }
+            $this->_context = stream_context_create($context_option);
+        }
+    }
+    
+    /**
+     * listen and bind socket
+     * @throws Exception
+     */
+    public function listen()
+    {
+        if(!$this->_socketName)
+        {
+            return;
+        }
+        list($scheme, $address) = explode(':', $this->_socketName, 2);
+        if($scheme != 'tcp' && $scheme != 'udp')
+        {
+            $scheme = ucfirst($scheme);
+            $this->_protocol = '\\Protocols\\'.$scheme;
+            if(!class_exists($this->_protocol))
+            {
+                $this->_protocol = "\\Workerman\\Protocols\\$scheme";
+                if(!class_exists($this->_protocol))
+                {
+                    throw new Exception("class \\Protocols\\$scheme not exist");
+                }
+            }
+        }
+        elseif($scheme === 'udp')
+        {
+            $this->transport = 'udp';
+        }
+        
+        $flags =  $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+        if($this->_enableSSL)
+        {
+            if($this->transport == 'udp')
+            {
+                throw new Exception('udp do not support ssl');
+            }
+            $this->_mainSocket = stream_socket_server("ssl:".$address, $errno, $errmsg, $flags, $this->_context);
+            @stream_socket_enable_crypto($this->_mainSocket, false);
+        }
+        else 
+        {
+            $this->_mainSocket = stream_socket_server($this->transport.":".$address, $errno, $errmsg, $flags, $this->_context);
+        }
+        if(!$this->_mainSocket)
+        {
+            throw new Exception($errmsg);
+        }
+        
+        stream_set_blocking($this->_mainSocket, 0);
+        
+        if(self::$_globalEvent)
+        {
+            if($this->transport !== 'udp')
+            {
+                self::$_globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));
+            }
+            else
+            {
+                self::$_globalEvent->add($this->_mainSocket,  EventInterface::EV_READ, array($this, 'acceptUdpConnection'));
+            }
+        }
+    }
+    
+    /**
+     * get socket name
+     * @return string
+     */
+    public function getSocketName()
+    {
+        return $this->_socketName ? $this->_socketName : 'none';
+    }
+    
+    /**
+     * enable SSL
+     * @param array $option @see http://php.net/manual/zh/context.ssl.php
+     * example $option = array('local_cert' => '/your/path/file.pem', 'passphrase' => 'password', 'allow_self_signed' => true, 'verify_peer' => false)
+     * @return void
+     */
+    public function enableSSL(array $option)
+    {
+        $this->_enableSSL = true;
+        foreach($option as $key => $value)
+        {
+            stream_context_set_option($this->_context, 'ssl', $key, $value);
+        }
+    }
+    
+    /**
+     * run the current worker
+     */
+    public function run()
+    {
+        if(!self::$_globalEvent)
+        {
+            if(extension_loaded('libevent'))
+            {
+                self::$_globalEvent = new Libevent();
+            }
+            else
+            {
+                self::$_globalEvent = new Select();
+            }
+            if($this->_socketName)
+            {
+                if($this->transport !== 'udp')
+                {
+                    self::$_globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));
+                }
+                else
+                {
+                    self::$_globalEvent->add($this->_mainSocket,  EventInterface::EV_READ, array($this, 'acceptUdpConnection'));
+                }
+            }
+        }
+        self::reinstallSignal();
+        
+        Timer::init(self::$_globalEvent);
+        
+        if($this->onWorkerStart)
+        {
+            call_user_func($this->onWorkerStart, $this);
+        }
+        self::$_globalEvent->loop();
+    }
+    
+    /**
+     * stop the current worker
+     * @return void
+     */
+    public function stop()
+    {
+        if($this->onWorkerStop)
+        {
+            call_user_func($this->onWorkerStop, $this);
+        }
+        self::$_globalEvent->del($this->_mainSocket, EventInterface::EV_READ);
+        @fclose($this->_mainSocket);
+    }
+
+    /**
+     * accept a connection of client
+     * @param resources $socket
+     * @return void
+     */
+    public function acceptConnection($socket)
+    {
+        $new_socket = @stream_socket_accept($socket, 0);
+        if(false === $new_socket)
+        {
+            return;
+        }
+        if($this->_enableSSL)
+        {
+            // block the connection until SSL is done
+            stream_set_blocking ($socket, true); 
+            stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv3_SERVER);
+            //unblock connection
+            stream_set_blocking ($socket, false);
+        }
+        $connection = new TcpConnection($new_socket, self::$_globalEvent);
+        $connection->protocol = $this->_protocol;
+        $connection->onMessage = $this->onMessage;
+        $connection->onClose = $this->onClose;
+        $connection->onError = $this->onError;
+        if($this->onConnect)
+        {
+            try
+            {
+                call_user_func($this->onConnect, $connection);
+            }
+            catch(Exception $e)
+            {
+                ConnectionInterface::$statistics['throw_exception']++;
+                self::log($e);
+            }
+        }
+    }
+    
+    /**
+     * deall udp package
+     * @param resource $socket
+     */
+    public function acceptUdpConnection($socket)
+    {
+        $recv_buffer = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $remote_address);
+        if(false === $recv_buffer || empty($remote_address))
+        {
+            return false;
+        }
+        
+        $connection = new UdpConnection($socket, $remote_address);
+        if($this->onMessage)
+        {
+            $parser = $this->_protocol;
+            try
+            {
+               call_user_func($this->onMessage, $connection, $parser::decode($recv_buffer, $connection));
+            }
+            catch(Exception $e)
+            {
+                ConnectionInterface::$statistics['throw_exception']++;
+            }
+        }
+    }
+}

+ 0 - 227
applications/Demo/Bootstrap/BusinessWorker.php

@@ -1,227 +0,0 @@
-<?php
-/**
- * 
- * 处理具体逻辑
- * 
- * @author walkor <walkor@workerman.net>
- * 
- */
-require_once __DIR__ . '/../Lib/Autoloader.php';
-
-use \Protocols\GatewayProtocol;
-use \Lib\Store;
-use \Lib\Gateway;
-use \Lib\StatisticClient;
-use \Lib\Context;
-
-class BusinessWorker extends Man\Core\SocketWorker
-{
-    /**
-     * 与gateway的连接
-     * ['ip:port' => conn, 'ip:port' => conn, ...]
-     * @var array
-     */
-    protected $gatewayConnections = array();
-    
-    /**
-     * 连不上的gateway地址
-     * ['ip:port' => retry_count, 'ip:port' => retry_count, ...]
-     * @var array
-     */
-    protected $badGatewayAddress = array();
-    
-    /**
-     * 连接gateway失败重试次数
-     * @var int
-     */
-    const MAX_RETRY_COUNT = 5;
-    
-    /**
-     * 命令字映射 统计用到
-     * @var array
-     */
-    protected static $interfaceMap = array(
-        GatewayProtocol::CMD_ON_GATEWAY_CONNECTION => 'CMD_ON_GATEWAY_CONNECTION',
-        GatewayProtocol::CMD_ON_MESSAGE            => 'CMD_ON_MESSAGE',
-        GatewayProtocol::CMD_ON_CLOSE              => 'CMD_ON_CLOSE',
-    );
-    
-    /**
-     * 进程启动时初始化
-     * @see Man\Core.SocketWorker::onStart()
-     */
-    protected function onStart()
-    {
-        // 强制设置成长链接
-        $this->isPersistentConnection = true;
-        // 定时检查与gateway进程的连接
-        \Man\Core\Lib\Task::init($this->event);
-        \Man\Core\Lib\Task::add(1, array($this, 'checkGatewayConnections'));
-        $this->checkGatewayConnections();
-        Gateway::setBusinessWorker($this);
-    }
-    
-    /**
-     * 获取与gateway的连接
-     */
-    public function getGatewayConnections()
-    {
-        return $this->gatewayConnections;
-    }
-    
-    /**
-     * 检查gateway转发来的用户请求是否完整
-     * @see Man\Core.SocketWorker::dealInput()
-     */
-    public function dealInput($recv_buffer)
-    {
-        return GatewayProtocol::input($recv_buffer); 
-    }
-
-    /**
-     * 处理请求
-     * @see Man\Core.SocketWorker::dealProcess()
-     */
-    public function dealProcess($recv_buffer)
-    {
-        $pack = new GatewayProtocol($recv_buffer);
-        Context::$client_ip = $pack->header['client_ip'];
-        Context::$client_port = $pack->header['client_port'];
-        Context::$local_ip = $pack->header['local_ip'];
-        Context::$local_port = $pack->header['local_port'];
-        Context::$socket_id = $pack->header['socket_id'];
-        Context::$client_id = $pack->header['client_id'];
-        $_SERVER = array(
-            'REMOTE_ADDR' => Context::$client_ip,
-            'REMOTE_PORT' => Context::$client_port,
-            'GATEWAY_ADDR' => Context::$local_ip,
-            'GATEWAY_PORT'  => Context::$local_port,
-            'GATEWAY_CLIENT_ID' => Context::$client_id,
-        );
-        if($pack->ext_data != '')
-        {
-            $_SESSION = Context::sessionDecode($pack->ext_data);
-        }
-        else
-        {
-            $_SESSION = null;
-        }
-        // 备份一次$pack->ext_data,请求处理完毕后判断session是否和备份相等,不相等就更新session
-        $session_str_copy = $pack->ext_data;
-        $cmd = $pack->header['cmd'];
-        
-        $interface = isset(self::$interfaceMap[$cmd]) ? self::$interfaceMap[$cmd] : $cmd;
-        StatisticClient::tick(__CLASS__, $interface);
-        try{
-            switch($cmd)
-            {
-                case GatewayProtocol::CMD_ON_GATEWAY_CONNECTION:
-                    Event::onGatewayConnect(Context::$client_id);
-                    break;
-                case GatewayProtocol::CMD_ON_MESSAGE:
-                    Event::onMessage(Context::$client_id, $pack->body);
-                    break;
-                case GatewayProtocol::CMD_ON_CLOSE:
-                    Event::onClose(Context::$client_id);
-                    break;
-            }
-            StatisticClient::report(__CLASS__, $interface, 1, 0, '');
-        }
-        catch(\Exception $e)
-        {
-            $msg = 'client_id:'.Context::$client_id."\tclient_ip:".Context::$client_ip."\n".$e->__toString();
-            StatisticClient::report(__CLASS__, $interface, 0, $e->getCode() > 0 ? $e->getCode() : 201, $msg);
-        }
-        
-        $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : '';
-        if($session_str_copy != $session_str_now)
-        {
-            Gateway::updateSocketSession(Context::$socket_id, $session_str_now);
-        }
-        
-        Context::clear();
-    }
-    
-    /**
-     * 定时检查gateway通信端口,如果有新的gateway则去建立长连接
-     */
-    public function checkGatewayConnections()
-    {
-        $key = 'GLOBAL_GATEWAY_ADDRESS';
-        $addresses_list = Store::instance('gateway')->get($key);
-        if(empty($addresses_list))
-        {
-            return;
-        }
-        $addresses_list = array_reverse($addresses_list, true);
-        // 循环遍历,查找未连接的gateway ip 端口
-        foreach($addresses_list as $addr)
-        {
-            if(!isset($this->gatewayConnections[$addr]))
-            {
-                // 执行连接
-                $conn = @stream_socket_client("tcp://$addr", $errno, $errstr, 1);
-                if(!$conn)
-                {
-                    if(!isset($this->badGatewayAddress[$addr]))
-                    {
-                        $this->badGatewayAddress[$addr] = 0;
-                    }
-                    // 删除连不上的端口
-                    if($this->badGatewayAddress[$addr]++ > self::MAX_RETRY_COUNT)
-                    {
-                        \Man\Core\Lib\Mutex::get();
-                        $addresses_list = Store::instance('gateway')->get($key);
-                        unset($addresses_list[$addr]);
-                        Store::instance('gateway')->set($key, $addresses_list);
-                        $this->notice("tcp://$addr ".$errstr." del $addr from store", false);
-                        \Man\Core\Lib\Mutex::release();
-                    }
-                    continue;
-                }
-                unset($this->badGatewayAddress[$addr]);
-                $this->gatewayConnections[$addr] = $conn;
-                stream_set_blocking($this->gatewayConnections[$addr], 0);
-                
-                // 初始化一些值
-                $fd = (int) $this->gatewayConnections[$addr];
-                $this->connections[$fd] = $this->gatewayConnections[$addr];
-                $this->recvBuffers[$fd] = array('buf'=>'', 'remain_len'=>$this->prereadLength);
-                // 添加数据可读事件
-                $this->event->add($this->connections[$fd], \Man\Core\Events\BaseEvent::EV_READ , array($this, 'dealInputBase'), $fd);
-            }
-        }
-    }
-    
-    /**
-     * 发送数据给客户端
-     * @see Man\Core.SocketWorker::sendToClient()
-     */
-    public function sendToClient($buffer, $con = null)
-    {
-        if($con)
-        {
-            $this->currentDealFd = (int) $con;
-        }
-        return parent::sendToClient($buffer);
-    }
-    
-    /**
-     * 关闭连接
-     * @see Man\Core.SocketWorker::closeClient()
-     */
-    protected function closeClient($fd = null)
-    {
-        // 清理$this->gatewayConnections对应项
-        foreach($this->gatewayConnections as $addr => $con)
-        {
-            $the_fd = (int) $con;
-            if($the_fd == $fd)
-            {
-                unset($this->gatewayConnections[$addr], $this->badGatewayAddress[$addr]);
-            }
-        }
-        parent::closeClient($fd);
-    }
-    
-}

+ 0 - 877
applications/Demo/Bootstrap/Gateway.php

@@ -1,877 +0,0 @@
-<?php
-/**
- * 
- * 暴露给客户端的连接网关 只负责网络io
- * 1、监听客户端连接
- * 2、监听后端回应并转发回应给前端
- * 
- * @author walkor <walkor@workerman.net>
- * 
- */
-require_once __DIR__ . '/../Lib/Autoloader.php';
-use \Protocols\GatewayProtocol;
-use \Lib\Store;
-use \Lib\StatisticClient;
-
-class Gateway extends Man\Core\SocketWorker
-{
-    /**
-     * 内部通信socket udp
-     * @var resouce
-     */
-    protected $innerMainSocketUdp = null;
-    
-    /**
-     * 内部通信socket tcp
-     * @var resouce
-     */
-    protected $innerMainSocketTcp = null;
-    
-    /**
-     * 内网ip
-     * @var string
-     */
-    protected $lanIp = '127.0.0.1';
-    
-    /**
-     * 内部通信端口
-     * @var int
-     */
-    protected $lanPort = 0;
-    
-    /**
-     * client_id到连接的映射
-     * @var array
-     */
-    protected $clientConnMap = array();
-    
-    /**
-     * 连接到client_id的映射
-     * @var array
-     */
-    protected $connClientMap = array();
-    
-    /**
-     * 客户端链接和客户端远程地址映射
-     * @var array
-     */
-    protected $connRemoteAddressMap = array();
-    
-    /**
-     * client_id到session的映射
-     * @var array
-     */
-    protected $connSessionMap = array();
-    
-    /**
-     * 与worker的连接
-     * [fd=>fd, $fd=>fd, ..]
-     * @var array
-     */
-    protected $workerConnections = array();
-    
-    /**
-     * 客户端心跳信息
-     * [client_id=>not_response_count, client_id=>not_response_count, ..]
-     */
-    protected $pingInfo = array();
-    
-    /**
-     * gateway 发送心跳时间间隔 单位:秒 ,0表示不发送心跳,在配置中设置
-     * @var integer
-     */
-    protected $pingInterval = 0;
-    
-    /**
-     * 心跳数据
-     * 可以是二进制数据(二进制数据保存在文件中,在配置中设置ping数据文件路径 如 ping_data=/yourpath/ping.bin)
-     * ping数据应该是客户端能够识别的数据格式,客户端必须回复心跳,不然链接会断开
-     * @var string
-     */
-    protected $pingData = '';
-    
-    /**
-     * 客户端连续$pingNotResponseLimit次不回应心跳则断开链接
-     */
-    protected $pingNotResponseLimit = 0;
-    
-    /**
-     * 命令字,统计用到
-     * @var array
-     */
-    protected static $interfaceMap = array(
-            GatewayProtocol::CMD_SEND_TO_ONE             => 'CMD_SEND_TO_ONE',
-            GatewayProtocol::CMD_SEND_TO_ALL             => 'CMD_SEND_TO_ALL',
-            GatewayProtocol::CMD_KICK                    => 'CMD_KICK',
-            GatewayProtocol::CMD_UPDATE_SESSION          => 'CMD_UPDATE_SESSION',
-            GatewayProtocol::CMD_GET_ONLINE_STATUS       => 'CMD_GET_ONLINE_STATUS',
-            GatewayProtocol::CMD_IS_ONLINE               => 'CMD_IS_ONLINE',
-     );
-    
-    /**
-     * 由于网络延迟或者socket缓冲区大小的限制,客户端发来的数据可能不会都全部到达,需要根据协议判断数据是否完整
-     * @see Man\Core.SocketWorker::dealInput()
-     */
-    public function dealInput($recv_buffer)
-    {
-        // 处理粘包
-        return Event::onGatewayMessage($recv_buffer);
-    }
-    
-    /**
-     * 用户客户端发来消息时处理
-     * @see Man\Core.SocketWorker::dealProcess()
-     */
-    public function dealProcess($recv_buffer)
-    {
-        // 客户端发来任何一个完整的包都视为回应了服务端发的心跳
-        if(!empty($this->pingInfo[$this->connClientMap[$this->currentDealFd]]))
-        {
-            $this->pingInfo[$this->connClientMap[$this->currentDealFd]] = 0;
-        }
-        
-        // 统计打点
-        StatisticClient::tick(__CLASS__, 'CMD_ON_MESSAGE');
-        // 触发ON_MESSAGE
-        if(false === $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $this->currentDealFd, $recv_buffer))
-        {
-            return StatisticClient::report(__CLASS__, 'CMD_ON_MESSAGE', 0, 132, __CLASS__.'::dealProcess()->sendToWorker() fail');
-        }
-        StatisticClient::report(__CLASS__, 'CMD_ON_MESSAGE', 1, 0, '');
-    }
-    
-    /**
-     * 进程启动
-     */
-    public function start()
-    {
-        // 安装信号处理函数
-        $this->installSignal();
-        
-        // 添加accept事件
-        $ret = $this->event->add($this->mainSocket,  Man\Core\Events\BaseEvent::EV_READ, array($this, 'accept'));
-        
-        // 创建内部通信套接字,用于与BusinessWorker通讯
-        $start_port = Man\Core\Lib\Config::get($this->workerName.'.lan_port_start');
-        // 计算本进程监听的ip端口
-        if(strpos(\Man\Core\Master::VERSION, 'mt'))
-        {
-            $this->lanPort = $start_port + \Thread::getCurrentThreadId()%100;
-        }
-        else 
-        {
-            $this->lanPort = $start_port - posix_getppid() + posix_getpid();
-        }
-        // 如果端口不在合法范围
-        if($this->lanPort<0 || $this->lanPort >=65535)
-        {
-            $this->lanPort = rand($start_port, 65535);
-        }
-        // 如果
-        $this->lanIp = Man\Core\Lib\Config::get($this->workerName.'.lan_ip');
-        if(!$this->lanIp)
-        {
-            $this->notice($this->workerName.'.lan_ip not set');
-            $this->lanIp = '127.0.0.1';
-        }
-        $error_no_udp = $error_no_tcp = 0;
-        $error_msg_udp = $error_msg_tcp = '';
-        // 执行监听
-        $this->innerMainSocketUdp = stream_socket_server("udp://".$this->lanIp.':'.$this->lanPort, $error_no_udp, $error_msg_udp, STREAM_SERVER_BIND);
-        $this->innerMainSocketTcp = stream_socket_server("tcp://".$this->lanIp.':'.$this->lanPort, $error_no_tcp, $error_msg_tcp, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
-        // 出错,退出,下次会换个端口
-        if(!$this->innerMainSocketUdp || !$this->innerMainSocketTcp)
-        {
-            $this->notice('create innerMainSocket udp or tcp fail and exit '.$error_msg_udp.$error_msg_tcp);
-            $this->stop();
-        }
-        else
-        {
-            stream_set_blocking($this->innerMainSocketUdp , 0);
-            stream_set_blocking($this->innerMainSocketTcp , 0);
-        }
-        
-        // 注册套接字
-        if(!$this->registerAddress($this->lanIp.':'.$this->lanPort))
-        {
-            $this->notice('registerAddress fail and exit');
-            $this->stop();
-        }
-        
-        // 添加读udp/tcp事件
-        $this->event->add($this->innerMainSocketUdp,  Man\Core\Events\BaseEvent::EV_READ, array($this, 'recvInnerUdp'));
-        $this->event->add($this->innerMainSocketTcp,  Man\Core\Events\BaseEvent::EV_READ, array($this, 'acceptInnerTcp'));
-        
-        // 初始化心跳包时间间隔
-        $ping_interval = \Man\Core\Lib\Config::get($this->workerName.'.ping_interval');
-        if((int)$ping_interval > 0)
-        {
-            $this->pingInterval = (int)$ping_interval;
-        }
-        
-        // 获取心跳包数据
-        $ping_data_or_path = \Man\Core\Lib\Config::get($this->workerName.'.ping_data');
-        if(is_file($ping_data_or_path))
-        {
-            $this->pingData = file_get_contents($ping_data_or_path);
-        }
-        else
-        {
-            $this->pingData = $ping_data_or_path;
-        }
-        
-        // 不返回心跳回应(客户端发来的任何数据都算是回应)的限定值
-        $this->pingNotResponseLimit = (int)\Man\Core\Lib\Config::get($this->workerName.'.ping_not_response_limit');
-        
-        // 设置定时任务,发送心跳
-        if($this->pingInterval > 0)
-        {
-            \Man\Core\Lib\Task::init($this->event);
-            \Man\Core\Lib\Task::add($this->pingInterval, array($this, 'ping'));
-        }
-        
-        // 主体循环,整个子进程会阻塞在这个函数上
-        $ret = $this->event->loop();
-        // 下面正常不会执行到
-        $this->notice('worker loop exit');
-        // 执行到就退出
-        exit(0);
-    }
-    
-    
-    /**
-     * 接受一个链接
-     * @param resource $socket
-     * @param $null_one $flag
-     * @param $null_two $base
-     * @return void
-     */
-    public function accept($socket, $null_one = null, $null_two = null)
-    {
-        // 获得一个连接
-        $new_connection = @stream_socket_accept($socket, 0);
-        // 可能是惊群效应
-        if(false === $new_connection)
-        {
-            $this->statusInfo['thunder_herd']++;
-            return false;
-        }
-        
-        // 连接的fd序号
-        $fd = (int) $new_connection;
-        $this->connections[$fd] = $new_connection;
-        $this->recvBuffers[$fd] = array('buf'=>'', 'remain_len'=>$this->prereadLength);
-        $this->connSessionMap[$fd] = '';
- 
-        // 非阻塞
-        stream_set_blocking($this->connections[$fd], 0);
-        $this->event->add($this->connections[$fd], Man\Core\Events\BaseEvent::EV_READ , array($this, 'dealInputBase'), $fd);
-        
-        // 全局唯一client_id
-        $global_client_id = $this->createGlobalClientId();
-        $this->clientConnMap[$global_client_id] = $fd;
-        $this->connClientMap[$fd] = $global_client_id;
-        $address = array('local_ip'=>$this->lanIp, 'local_port'=>$this->lanPort, 'socket_id'=>$fd);
-        
-        // 保存客户端内部通讯地址
-        $this->storeClientAddress($global_client_id, $address);
-        
-        // 客户端保存 ip:port
-        $address= $this->getRemoteAddress($fd);
-        if($address)
-        {
-            list($client_ip, $client_port) = explode(':', $address, 2);
-        }
-        else
-        {
-            $client_ip = 0;
-            $client_port = 0;
-        }
-        $this->connRemoteAddressMap[$fd] = array('ip'=>$client_ip, 'port'=>$client_port);
-        
-        // 触发GatewayOnConnection事件
-        if(method_exists('Event','onGatewayConnect'))
-        {
-            // 统计打点
-            StatisticClient::tick(__CLASS__, 'CMD_ON_GATEWAY_CONNECTION');
-            if(false === $this->sendToWorker(GatewayProtocol::CMD_ON_GATEWAY_CONNECTION, $fd))
-            {
-                StatisticClient::report(__CLASS__, 'CMD_ON_GATEWAY_CONNECTION', 0, 131, __CLASS__.'::accept()->sendToWorker() fail');
-            }
-            else
-            {
-                StatisticClient::report(__CLASS__, 'CMD_ON_GATEWAY_CONNECTION', 1, 0, '');
-            }
-        }
-        
-        return $new_connection;
-    }
-    
-    /**
-     * 存储全局的通信地址 
-     * @param string $address
-     */
-    protected function registerAddress($address)
-    {
-        // 统计打点
-        StatisticClient::tick(__CLASS__, 'registerAddress');
-        // 这里使用了信号量只能实现单机互斥,分布式互斥需要借助于memcached incr cas 或者其他分布式存储
-        \Man\Core\Lib\Mutex::get();
-        // key
-        $key = 'GLOBAL_GATEWAY_ADDRESS';
-        // 获取实例
-        try 
-        {
-            $store = Store::instance('gateway');
-        }
-        catch(\Exception $msg)
-        {
-            StatisticClient::report(__CLASS__, 'registerAddress', 0, 107, $msg);
-            \Man\Core\Lib\Mutex::release();
-            return false;
-        }
-        // 获取数据
-        $addresses_list = $store->get($key);
-        if(empty($addresses_list))
-        {
-            $addresses_list = array();
-        }
-        // 添加数据
-        $addresses_list[$address] = $address;
-        // 存储
-        if(!$store->set($key, $addresses_list))
-        {
-            // 存储失败
-            \Man\Core\Lib\Mutex::release();
-            $msg = "注册gateway通信地址出错";
-            if(get_class($store) == 'Memcached')
-            {
-                $msg .= " 原因:".$store->getResultMessage();
-            }
-            $this->notice($msg);
-            StatisticClient::report(__CLASS__, 'registerAddress', 0, 107, new \Exception($msg));
-            return false;
-        }
-        // 存储成功
-        \Man\Core\Lib\Mutex::release();
-        StatisticClient::report(__CLASS__, 'registerAddress', 1, 0, '');
-        return true;
-    }
-    
-    /**
-     * 删除全局的通信地址
-     * @param string $address
-     */
-    protected function unregisterAddress($address)
-    {
-        // 统计打点
-        StatisticClient::tick(__CLASS__, 'unregisterAddress');
-        // 这里使用了信号量只能实现单机互斥,分布式互斥需要借助于memcached incr cas或者其他分布式存储
-        \Man\Core\Lib\Mutex::get();
-        // key
-        $key = 'GLOBAL_GATEWAY_ADDRESS';
-        // 获取存储实例
-        try 
-        {
-            $store = Store::instance('gateway');
-        }
-        catch (\Exception $msg)
-        {
-            StatisticClient::report(__CLASS__, 'unregisterAddress', 0, 108, $msg);
-            \Man\Core\Lib\Mutex::release();
-            return false;
-        }
-        // 获取数据
-        $addresses_list = $store->get($key);
-        if(empty($addresses_list))
-        {
-            $addresses_list = array();
-        }
-        // 去掉要删除的数据
-        unset($addresses_list[$address]);
-        // 保存数据
-        if(!$store->set($key, $addresses_list))
-        {
-            \Man\Core\Lib\Mutex::release();
-            $msg = "删除gateway通信地址出错";
-            if(get_class($store) == 'Memcached')
-            {
-                $msg .= " 原因:".$store->getResultMessage();
-            }
-            $this->notice($msg);
-            StatisticClient::report(__CLASS__, 'unregisterAddress', 0, 108, new \Exception($msg));
-            return;
-        }
-        // 存储成功
-        \Man\Core\Lib\Mutex::release();
-        StatisticClient::report(__CLASS__, 'unregisterAddress', 1, 0, '');
-    }
-    
-    /**
-     * 接收Udp数据
-     * 如果数据超过一个udp包长,需要业务自己解析包体,判断数据是否全部到达
-     * @param resource $socket
-     * @param $null_one $flag
-     * @param $null_two $base
-     * @return void
-     */
-    public function recvInnerUdp($socket, $null_one = null, $null_two = null)
-    {
-        $data = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $address);
-        // 惊群效应
-        if(false === $data || empty($address))
-        {
-            return false;
-        }
-         
-        $this->currentClientAddress = $address;
-       
-        $this->innerDealProcess($data);
-    }
-    
-    /**
-     * 内部通讯端口接受BusinessWorker连接请求,以便建立起长连接
-     * @param resouce $socket
-     * @param null $null_one
-     * @param null $null_two
-     */
-    public function acceptInnerTcp($socket, $null_one = null, $null_two = null)
-    {
-        // 获得一个连接
-        $new_connection = @stream_socket_accept($socket, 0);
-        if(false === $new_connection)
-        {
-            return false;
-        }
-    
-        // 连接的fd序号
-        $fd = (int) $new_connection;
-        $this->connections[$fd] = $new_connection;
-        $this->recvBuffers[$fd] = array('buf'=>'', 'remain_len'=>GatewayProtocol::HEAD_LEN);
-    
-        // 非阻塞
-        stream_set_blocking($this->connections[$fd], 0);
-        $this->event->add($this->connections[$fd], \Man\Core\Events\BaseEvent::EV_READ , array($this, 'recvInnerTcp'), $fd);
-        
-        // 标记这个连接是内部通讯长连接,区别于客户端连接
-        $this->workerConnections[$fd] = $fd;
-        return $new_connection;
-    }
-    
-    /**
-     * 内部通讯判断数据是否全部到达
-     * @param string $buffer
-     */
-    public function dealInnerInput($buffer)
-    {
-        return GatewayProtocol::input($buffer);
-    }
-    
-    /**
-     * 处理内部通讯收到的数据
-     * @param event_buffer $event_buffer
-     * @param int $fd
-     * @return void
-     */
-    public function recvInnerTcp($connection, $flag, $fd = null)
-    {
-        $this->currentDealFd = $fd;
-        $buffer = stream_socket_recvfrom($connection, $this->recvBuffers[$fd]['remain_len']);
-        // 出错了
-        if('' == $buffer && '' == ($buffer = fread($connection, $this->recvBuffers[$fd]['remain_len'])))
-        {
-            // 判断是否是链接断开
-            if(!feof($connection))
-            {
-                return;
-            }
-            
-            // 如果该链接对应的buffer有数据,说明发生错误
-            if(!empty($this->recvBuffers[$fd]['buf']))
-            {
-                $this->statusInfo['send_fail']++;
-            }
-    
-            // 关闭链接
-            $this->closeInnerClient($fd);
-            $this->notice("CLIENT:".$this->getRemoteIp()." CLOSE INNER_CONNECTION\n");
-            
-            if($this->workerStatus == self::STATUS_SHUTDOWN)
-            {
-                $this->stop();
-            }
-            return;
-        }
-    
-        $this->recvBuffers[$fd]['buf'] .= $buffer;
-    
-        $remain_len = $this->dealInnerInput($this->recvBuffers[$fd]['buf']);
-        // 包接收完毕
-        if(0 === $remain_len)
-        {
-            // 内部通讯业务处理
-            $this->innerDealProcess($this->recvBuffers[$fd]['buf']);
-            $this->recvBuffers[$fd] = array('buf'=>'', 'remain_len'=>GatewayProtocol::HEAD_LEN);
-        }
-        // 出错
-        else if(false === $remain_len)
-        {
-            // 出错
-            $this->statusInfo['packet_err']++;
-            $this->notice("INNER_PACKET_ERROR and CLOSE_INNER_CONNECTION\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:[".bin2hex($this->recvBuffers[$fd]['buf'])."]\n");
-            $this->closeInnerClient($fd);
-        }
-        else
-        {
-            $this->recvBuffers[$fd]['remain_len'] = $remain_len;
-        }
-    
-        // 检查是否是关闭状态或者是否到达请求上限
-        if($this->workerStatus == self::STATUS_SHUTDOWN )
-        {
-            // 停止服务
-            $this->stop();
-            // EXIT_WAIT_TIME秒后退出进程
-            pcntl_alarm(self::EXIT_WAIT_TIME);
-        }
-    }
-
-    /**
-     * 内部通讯处理
-     * @param string $recv_buffer
-     */
-    public function innerDealProcess($recv_buffer)
-    {
-        $pack = new GatewayProtocol($recv_buffer);
-        $cmd = $pack->header['cmd'];
-        $interface = isset(self::$interfaceMap[$cmd]) ? self::$interfaceMap[$cmd] : $cmd;
-        StatisticClient::tick(__CLASS__, $interface);
-        try
-        {
-            switch($cmd)
-            {
-                // 向某客户端发送数据
-                case GatewayProtocol::CMD_SEND_TO_ONE:
-                    if(false === $this->sendToSocketId($pack->header['socket_id'], $pack->body))
-                    {
-                        throw new \Exception('发送数据到客户端失败,可能是该客户端的发送缓冲区已满,或者客户端已经下线', 121);
-                    }
-                    break;
-                // 踢掉客户端
-                case GatewayProtocol::CMD_KICK:
-                    $this->closeClient($pack->header['socket_id']);
-                    break;
-                // 向所客户端发送数据
-                case GatewayProtocol::CMD_SEND_TO_ALL:
-                    if($pack->ext_data)
-                    {
-                        $client_id_array = unpack('N*', $pack->ext_data);
-                        foreach($client_id_array as $client_id)
-                        {
-                            if(isset($this->clientConnMap[$client_id]))
-                            {
-                                $this->sendToSocketId($this->clientConnMap[$client_id], $pack->body);
-                            }
-                        }
-                    }
-                    else
-                    {
-                        $this->broadCast($pack->body);
-                    }
-                    break;
-                // 更新某个客户端的session
-                case GatewayProtocol::CMD_UPDATE_SESSION:
-                    if(isset($this->connSessionMap[$pack->header['socket_id']]))
-                    {
-                        $this->connSessionMap[$pack->header['socket_id']] = $pack->ext_data;
-                    }
-                    break;
-                // 获得在线状态
-                case GatewayProtocol::CMD_GET_ONLINE_STATUS:
-                    $online_status = json_encode(array_values($this->connClientMap));
-                    stream_socket_sendto($this->innerMainSocketUdp, $online_status, 0, $this->currentClientAddress);
-                    break;
-                // 判断某个客户端id是否在线
-                case GatewayProtocol::CMD_IS_ONLINE:
-                    stream_socket_sendto($this->innerMainSocketUdp, (int)isset($this->clientConnMap[$pack->header['client_id']]), 0, $this->currentClientAddress);
-                    break;
-                // 未知的命令
-                default :
-                    $err_msg = "gateway inner pack err cmd=$cmd";
-                    $this->notice($err_msg);
-                    throw new \Exception($err_msg, 110);
-            }
-        }
-        catch(\Exception $msg)
-        {
-            StatisticClient::report(__CLASS__, $interface, 0, $msg->getCode() > 0 ? $msg->getCode() : 111, $msg);
-            return;
-        }
-        StatisticClient::report(__CLASS__, $interface, 1, 0, '');
-    }
-    
-    /**
-     * 广播数据
-     * @param string $bin_data
-     */
-    protected function broadCast($bin_data)
-    {
-        foreach($this->clientConnMap as $client_id=>$conn)
-        {
-            $this->sendToSocketId($conn, $bin_data);
-        }
-    }
-    
-    /**
-     * 根据client_id获取client_id对应连接的socket_id
-     * @param int $client_id
-     */
-    protected function getFdByClientId($client_id)
-    {
-        if(isset($this->clientConnMap[$client_id]))
-        {
-            return $this->clientConnMap[$client_id];
-        }
-        return 0;
-    }
-    
-    /**
-     * 根据连接socket_id获取client_id
-     * @param int $fd
-     */
-    protected function getClientIdByFd($fd)
-    {
-        if(isset($this->connClientMap[$fd]))
-        {
-            return $this->connClientMap[$fd];
-        }
-        return 0;
-    }
-    
-    /**
-     * 向某个socket_id的连接发送消息
-     * @param int $socket_id
-     * @param string $bin_data
-     */
-    public function sendToSocketId($socket_id, $bin_data)
-    {
-        if(!isset($this->connections[$socket_id]))
-        {
-            return null;
-        }
-        $this->currentDealFd = $socket_id;
-        return $this->sendToClient($bin_data);
-    }
-
-    /**
-     * 用户客户端关闭连接时触发
-     * @see Man\Core.SocketWorker::closeClient()
-     */
-    protected function closeClient($fd = null)
-    {
-        if($client_id = $this->getClientIdByFd($fd))
-        {
-            $this->sendToWorker(GatewayProtocol::CMD_ON_CLOSE, $fd);
-            $this->deleteClientAddress($client_id);
-            unset($this->clientConnMap[$client_id]);
-        }
-        unset($this->connClientMap[$fd], $this->connSessionMap[$fd], $this->connRemoteAddressMap[$fd]);
-        parent::closeClient($fd);
-    }
-    
-    /**
-     * 内部通讯socket在BusinessWorker主动关闭连接时触发
-     * @param int $fd
-     */
-    protected function closeInnerClient($fd)
-    {
-        unset($this->workerConnections[$fd]);
-        parent::closeClient($fd);
-    }
-    
-    /**
-     * 随机抽取一个与BusinessWorker的长连接,将数据发给一个BusinessWorker
-     * @param int $cmd
-     * @param int $socket_id
-     * @param string $body
-     */
-    protected function sendToWorker($cmd, $socket_id, $body = '')
-    {
-        $pack = new GatewayProtocol();
-        $pack->header['cmd'] = $cmd;
-        $pack->header['local_ip'] = $this->lanIp;
-        $pack->header['local_port'] = $this->lanPort;
-        $pack->header['socket_id'] = $socket_id;
-        $pack->header['client_ip'] = $this->connRemoteAddressMap[$socket_id]['ip'];
-        $pack->header['client_port'] = $this->connRemoteAddressMap[$socket_id]['port'];
-        $pack->header['client_id'] = $this->getClientIdByFd($socket_id);
-        $pack->body = $body;
-        $pack->ext_data = $this->connSessionMap[$pack->header['socket_id']];
-        return $this->sendBufferToWorker($pack->getBuffer());
-    }
-    
-    /**
-     * 随机抽取一个与BusinessWorker的长连接,将数据发给一个BusinessWorker
-     * @param string $bin_data
-     */
-    protected function sendBufferToWorker($bin_data)
-    {
-        if($this->currentDealFd = array_rand($this->workerConnections))
-        {
-             if(false === $this->sendToClient($bin_data))
-             {
-                 $msg = "sendBufferToWorker fail. May be the send buffer are overflow";
-                 $this->notice($msg);
-                 StatisticClient::report(__CLASS__, 'sendBufferToWorker', 0, 101, new \Exception($msg));
-                 return false;
-             }
-        }
-        else
-        {
-            $msg = "sendBufferToWorker fail. the Connections between Gateway and BusinessWorker are not ready";
-            $this->notice($msg);
-            StatisticClient::report(__CLASS__, 'sendBufferToWorker', 0, 102, new \Exception($msg));
-            return false;
-        }
-        StatisticClient::report(__CLASS__, 'sendBufferToWorker', 1, 0, '');
-    }
-    
-    /**
-     * 打印日志
-     * @see Man\Core.AbstractWorker::notice()
-     */
-    protected function notice($str, $display=true)
-    {
-        $str = 'Worker['.get_class($this).']:'."$str";
-        Man\Core\Lib\Log::add($str);
-        if($display && Man\Core\Lib\Config::get('workerman.debug') == 1)
-        {
-            echo $str."\n";
-        }
-    }
-    
-    /**
-     * 进程停止时,清除一些数据,忽略错误
-     * @see Man\Core.SocketWorker::onStop()
-     */
-    public function onStop()
-    {
-        $this->unregisterAddress($this->lanIp.':'.$this->lanPort);
-        foreach($this->connClientMap as $client_id)
-        {
-            Store::instance('gateway')->delete($client_id);
-        }
-    }
-    
-    /**
-     * 创建全局唯一的id
-     */
-    protected function createGlobalClientId()
-    {
-        StatisticClient::tick(__CLASS__, 'createGlobalClientId');
-        $global_socket_key = 'GLOBAL_SOCKET_ID_KEY';
-        $store = Store::instance('gateway');
-        $global_client_id = $store->increment($global_socket_key);
-        if(!$global_client_id || $global_client_id > 2147483646)
-        {
-            $store->set($global_socket_key, 0);
-            $global_client_id = $store->increment($global_socket_key);
-        }
-        
-        if(!$global_client_id)
-        {
-            $msg = "生成全局client_id出错";
-            if(get_class($store) == 'Memcached')
-            {
-                $msg .= " 原因:".$store->getResultMessage(); 
-            }
-            $this->notice($msg);
-            StatisticClient::report(__CLASS__, 'createGlobalClientId', 0, 104, new \Exception($msg));
-            return $global_client_id;
-        }
-        StatisticClient::report(__CLASS__, 'createGlobalClientId', 1, 0, '');
-        return $global_client_id;
-    }
-    
-    /**
-     * 保存客户端内部通讯地址
-     * @param int $global_client_id
-     * @param string $address
-     */
-    protected function storeClientAddress($global_client_id, $address)
-    {
-        StatisticClient::tick(__CLASS__, 'storeClientAddress');
-        if(!Store::instance('gateway')->set($global_client_id, $address))
-        {
-            $msg = "保存客户端通讯地址出错";
-            if(get_class(Store::instance('gateway')) == 'Memcached')
-            {
-                $msg .= " 原因:".Store::instance('gateway')->getResultMessage();
-            }
-            $this->notice($msg);
-            return StatisticClient::report(__CLASS__, 'storeClientAddress', 0, 105, new \Exception($msg));
-        }
-        StatisticClient::report(__CLASS__, 'storeClientAddress', 1, 0, '');
-    }
-    
-    /**
-     * 删除客户端内部通讯地址
-     * @param int $global_client_id
-     * @param string $address
-     */
-    protected function deleteClientAddress($global_client_id)
-    {
-        StatisticClient::tick(__CLASS__, 'deleteClientAddress');
-        if(!Store::instance('gateway')->delete($global_client_id))
-        {
-            $msg = "删除客户端通讯地址出错";
-            if(get_class(Store::instance('gateway')) == 'Memcached')
-            {
-                $msg .= " 原因:".Store::instance('gateway')->getResultMessage();
-            }
-            $this->notice($msg);
-            return StatisticClient::report(__CLASS__, 'deleteClientAddress', 0, 106, new \Exception($msg));
-        }
-        StatisticClient::report(__CLASS__, 'deleteClientAddress', 1, 0, '');
-    }
-    
-    /**
-     * 向客户端发送心跳数据
-     * 并把规定时间内没回应(客户端发来的任何数据都算是回应)的客户端踢掉
-     */
-    public function ping()
-    {
-        // 清理下线的链接
-        foreach($this->pingInfo as $client_id=>$not_response_count)
-        {
-            // 已经下线的忽略
-            if(!isset($this->clientConnMap[$client_id]))
-            {
-                unset($this->pingInfo[$client_id]);
-                continue;
-            }
-            // 上次发送的心跳还没有回复次数大于限定值就断开
-            if($this->pingNotResponseLimit > 0 && $not_response_count >= $this->pingNotResponseLimit)
-            {
-                $this->closeClient($this->clientConnMap[$client_id]);
-            }
-        }
-        // 向所有链接发送心跳数据
-        foreach($this->clientConnMap as $client_id=>$conn)
-        {
-            // 如果没设置ping的数据,则不发送心跳给客户端,但是要求客户端在规定的时间内(ping_interval*ping_not_response_limit)有请求发来,不然会断开
-            if($this->pingData)
-            {
-                $this->sendToSocketId($conn, $this->pingData);
-            }
-            if(isset($this->pingInfo[$client_id]))
-            {
-                $this->pingInfo[$client_id]++;
-            }
-            else
-            {
-                $this->pingInfo[$client_id] = 1;
-            }
-        }
-    }
-}

+ 0 - 25
applications/Demo/Config/Db.php

@@ -1,25 +0,0 @@
-<?php 
-namespace Config;
-
-/**
- * mysql配置
- * @author walkor
- */
-class Db
-{
-    /**
-     * 数据库的一个实例配置,则使用时像下面这样使用
-     * $user_array = Db::instance('one_demo')->select('name,age')->from('user')->where('age>12')->query();
-     * 等价于
-     * $user_array = Db::instance('one_demo')->query('SELECT `name`,`age` FROM `one_demo` WHERE `age`>12');
-     * @var array
-     */
-    public static $one_demo = array(
-        'host'    => '127.0.0.1',
-        'port'    => 3306,
-        'user'    => 'mysql_user',
-        'password' => 'mysql_password',
-        'dbname'  => 'db_name',
-        'charset'    => 'utf8',
-    );
-}

+ 0 - 83
applications/Demo/Event.php

@@ -1,83 +0,0 @@
-<?php
-/**
- * 聊天逻辑,使用的协议是 文本+回车
- * 测试方法 运行
- * telnet ip 8480
- * 可以开启多个telnet窗口,窗口间可以互相聊天
- * 
- * websocket协议的聊天室见workerman-chat及workerman-todpole
- * 
- * @author walkor <walkor@workerman.net>
- */
-
-use \Lib\Context;
-use \Lib\Gateway;
-use \Lib\StatisticClient;
-use \Lib\Store;
-use \Protocols\GatewayProtocol;
-use \Protocols\TextProtocol;
-
-class Event
-{
-    /**
-     * 当网关有客户端链接上来时触发,每个客户端只触发一次,如果不许要任何操作可以不实现此方法
-     * 这里当客户端一连上来就给客户端发送输入名字的提示
-     */
-    public static function onGatewayConnect($client_id)
-    {
-        Gateway::sendToCurrentClient(TextProtocol::encode("type in your name:"));
-    }
-    
-    /**
-     * 网关有消息时,判断消息是否完整
-     */
-    public static function onGatewayMessage($buffer)
-    {
-        return TextProtocol::check($buffer);
-    }
-    
-   /**
-    * 有消息时触发该方法
-    * @param int $client_id 发消息的client_id
-    * @param string $message 消息
-    * @return void
-    */
-   public static function onMessage($client_id, $message)
-   {
-        $message_data = TextProtocol::decode($message);
-        
-        // **************如果没有$_SESSION['name']说明没有设置过用户名,进入设置用户名逻辑************
-        if(empty($_SESSION['name']))
-        {
-            $_SESSION['name'] = TextProtocol::decode($message);
-            Gateway::sendToCurrentClient("chat room login success, your client_id is $client_id, name is {$_SESSION['name']}\nuse client_id:words send message to one user\nuse words send message to all\n");
-             
-            // 广播所有用户,xxx come
-            return GateWay::sendToAll(TextProtocol::encode("{$_SESSION['name']}[$client_id] come"));
-        }
-        
-        // ********* 进入聊天逻辑 ****************
-        // 判断是否是私聊
-        $explode_array = explode(':', $message, 2);
-        // 私聊数据格式 client_id:xxxxx
-        if(count($explode_array) > 1)
-        {
-            $to_client_id = (int)$explode_array[0];
-            GateWay::sendToClient($client_id, TextProtocol::encode($_SESSION['name'] . "[$client_id] said said to [$to_client_id] :" . $explode_array[1]));
-            return GateWay::sendToClient($to_client_id, TextProtocol::encode($_SESSION['name'] . "[$client_id] said to You :" . $explode_array[1]));
-        }
-        // 群聊
-        return GateWay::sendToAll(TextProtocol::encode($_SESSION['name'] . "[$client_id] said :" . $message));
-   }
-   
-   /**
-    * 当用户断开连接时触发的方法
-    * @param integer $client_id 断开连接的用户id
-    * @return void
-    */
-   public static function onClose($client_id)
-   {
-       // 广播 xxx 退出了
-       GateWay::sendToAll(TextProtocol::encode("{$_SESSION['name']}[$client_id] logout"));
-   }
-}

+ 0 - 20
applications/Demo/Lib/Autoloader.php

@@ -1,20 +0,0 @@
-<?php
-if(!defined('ROOT_DIR'))
-{
-    define('ROOT_DIR', realpath(__DIR__ . '/../') . '/');
-}
-function loadByNamespace($name)
-{
-    $class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name);
-    $class_file = ROOT_DIR . $class_path.'.php';
-    if(is_file($class_file))
-    {
-        require_once($class_file);
-        if(class_exists($name, false))
-        {
-            return true;
-        }
-    }
-    return false;
-}
-spl_autoload_register('loadByNamespace');

+ 0 - 198
applications/Demo/Lib/StatisticClient.php

@@ -1,198 +0,0 @@
-<?php
-namespace Lib;
-/**
- * 统计客户端
- * @author workerman.net
- */
-class StatisticClient
-{
-    /**
-     * [module=>[interface=>time_start, interface=>time_start ...], module=>[interface=>time_start ..], ... ]
-     * @var array
-     */
-    protected static $timeMap = array();
-    
-    /**
-     * 模块接口上报消耗时间记时
-     * @param string $module
-     * @param string $interface
-     * @return void
-     */
-    public static function tick($module = '', $interface = '')
-    {
-        return self::$timeMap[$module][$interface] = microtime(true);
-    }
-    
-    /**
-     * 上报统计数据
-     * @param string $module
-     * @param string $interface
-     * @param bool $success
-     * @param int $code
-     * @param string $msg
-     * @param string $report_address
-     * @return boolean
-     */
-    public static function report($module, $interface, $success, $code, $msg, $report_address = '')
-    {
-        $report_address = $report_address ? $report_address : 'udp://127.0.0.1:55656';
-        if(isset(self::$timeMap[$module][$interface]) && self::$timeMap[$module][$interface] > 0)
-        {
-            $time_start = self::$timeMap[$module][$interface];
-            self::$timeMap[$module][$interface] = 0;
-        }
-        else if(isset(self::$timeMap['']['']) && self::$timeMap[''][''] > 0)
-        {
-            $time_start = self::$timeMap[''][''];
-            self::$timeMap[''][''] = 0;
-        }
-        else
-        {
-            $time_start = microtime(true);
-        }
-         
-        $cost_time = microtime(true) - $time_start;
-        
-        $bin_data = StatisticProtocol::encode($module, $interface, $cost_time, $success, $code, $msg);
-        
-        if(!$success)
-        {
-            echo $msg;
-        }
-        
-        return self::sendData($report_address, $bin_data);
-    }
-    
-    /**
-     * 发送数据给统计系统
-     * @param string $address
-     * @param string $buffer
-     * @return boolean
-     */
-    public static function sendData($address, $buffer)
-    {
-        $socket = stream_socket_client($address);
-        if(!$socket)
-        {
-            return false;
-        }
-        return stream_socket_sendto($socket, $buffer) == strlen($buffer);
-    }
-    
-}
-
-/**
- *
- * struct statisticPortocol
- * {
- *     unsigned char module_name_len;
- *     unsigned char interface_name_len;
- *     float cost_time;
- *     unsigned char success;
- *     int code;
- *     unsigned short msg_len;
- *     unsigned int time;
- *     char[module_name_len] module_name;
- *     char[interface_name_len] interface_name;
- *     char[msg_len] msg;
- * }
- *
- * @author workerman.net
- */
-class StatisticProtocol
-{
-    /**
-     * 包头长度
-     * @var integer
-     */
-    const PACKAGE_FIXED_LENGTH = 17;
-
-    /**
-     * udp 包最大长度
-     * @var integer
-     */
-    const MAX_UDP_PACKGE_SIZE  = 65507;
-
-    /**
-     * char类型能保存的最大数值
-     * @var integer
-     */
-    const MAX_CHAR_VALUE = 255;
-
-    /**
-     *  usigned short 能保存的最大数值
-     * @var integer
-     */
-    const MAX_UNSIGNED_SHORT_VALUE = 65535;
-
-    /**
-     * 编码
-     * @param string $module
-     * @param string $interface
-     * @param float $cost_time
-     * @param int $success
-     * @param int $code
-     * @param string $msg
-     * @return string
-     */
-    public static function encode($module, $interface , $cost_time, $success,  $code = 0,$msg = '')
-    {
-        // 防止模块名过长
-        if(strlen($module) > self::MAX_CHAR_VALUE)
-        {
-            $module = substr($module, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止接口名过长
-        if(strlen($interface) > self::MAX_CHAR_VALUE)
-        {
-            $interface = substr($interface, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止msg过长
-        $module_name_length = strlen($module);
-        $interface_name_length = strlen($interface);
-        $avalible_size = self::MAX_UDP_PACKGE_SIZE - self::PACKAGE_FIXED_LENGTH - $module_name_length - $interface_name_length;
-        if(strlen($msg) > $avalible_size)
-        {
-            $msg = substr($msg, 0, $avalible_size);
-        }
-
-        // 打包
-        return pack('CCfCNnN', $module_name_length, $interface_name_length, $cost_time, $success ? 1 : 0, $code, strlen($msg), time()).$module.$interface.$msg;
-    }
-     
-    /**
-     * 解包
-     * @param string $bin_data
-     * @return array
-     */
-    public static function decode($bin_data)
-    {
-        // 解包
-        $data = unpack("Cmodule_name_len/Cinterface_name_len/fcost_time/Csuccess/Ncode/nmsg_len/Ntime", $bin_data);
-        $module = substr($bin_data, self::PACKAGE_FIXED_LENGTH, $data['module_name_len']);
-        $interface = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'], $data['interface_name_len']);
-        $msg = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'] + $data['interface_name_len']);
-        return array(
-                'module'          => $module,
-                'interface'        => $interface,
-                'cost_time' => $data['cost_time'],
-                'success'           => $data['success'],
-                'time'                => $data['time'],
-                'code'               => $data['code'],
-                'msg'                => $msg,
-        );
-    }
-
-}
-
-if(PHP_SAPI == 'cli' && isset($argv[0]) && $argv[0] == basename(__FILE__))
-{
-    StatisticClient::tick("TestModule", 'TestInterface');
-    usleep(rand(10000, 600000));
-    $success = rand(0,1);
-    $code = rand(300, 400);
-    $msg = '这个是测试消息';
-    var_export(StatisticClient::report('TestModule', 'TestInterface', $success, $code, $msg));;
-}

+ 0 - 167
applications/Demo/Protocols/GatewayProtocol.php

@@ -1,167 +0,0 @@
-<?php 
-namespace Protocols;
-/**
- * Gateway与Worker间通讯的二进制协议
- * 
- * struct GatewayProtocol
- * {
- *     unsigned int        pack_len,
- *     unsigned char     cmd,//命令字
- *     unsigned int        local_ip,
- *     unsigned short    local_port,
- *     unsigned int        socket_id,
- *     unsigned int        client_ip,
- *     unsigned short    client_port,
- *     unsigned int        client_id,
- *     unsigned int        ext_len,
- *     char[ext_len]        ext_data,
- *     char[pack_length-HEAD_LEN] body//包体
- * }
- * 
- * 
- * @author walkor <walkor@workerman.net>
- */
-
-class GatewayProtocol
-{
-    // 发给worker,gateway有一个新的连接
-    const CMD_ON_GATEWAY_CONNECTION = 1;
-    
-    // 发给worker的,客户端有消息
-    const CMD_ON_MESSAGE = 3;
-    
-    // 发给worker上的关闭链接事件
-    const CMD_ON_CLOSE = 4;
-    
-    // 发给gateway的向单个用户发送数据
-    const CMD_SEND_TO_ONE = 5;
-    
-    // 发给gateway的向所有用户发送数据
-    const CMD_SEND_TO_ALL = 6;
-    
-    // 发给gateway的踢出用户
-    const CMD_KICK = 7;
-    
-    // 发给gateway,通知用户session更改
-    const CMD_UPDATE_SESSION = 9;
-    
-    // 获取在线状态
-    const CMD_GET_ONLINE_STATUS = 10;
-    
-    // 判断是否在线
-    const CMD_IS_ONLINE = 11;
-    
-    /**
-     * 包头长度
-     * @var integer
-     */
-    const HEAD_LEN = 29;
-     
-    /**
-     * 协议头
-     * @var array
-     */
-    public $header = array(
-        'pack_len'       => self::HEAD_LEN,
-        'cmd'              => 0,
-        'local_ip'         => '',
-        'local_port'     => 0,
-        'socket_id'      => 0,
-        'client_ip'        => '',
-        'client_port'    => 0,
-        'client_id'        => 0,
-        'ext_len'          => 0,
-    );
-    
-    /**
-     * 扩展数据,
-     * gateway发往worker时这里存储的是session字符串
-     * worker发往gateway时,并且CMD_UPDATE_SESSION时存储的是session字符串
-     * worker发往gateway时,并且CMD_SEND_TO_ALL时存储的是接收的client_id序列,可能是空(代表向所有人发)
-     * @var string
-     */
-    public $ext_data = '';
-    
-    /**
-     * 包体
-     * @var string
-     */
-    public $body = '';
-    
-    /**
-     * 初始化
-     * @return void
-     */
-    public function __construct($buffer = null)
-    {
-        if($buffer)
-        {
-            $data = self::decode($buffer);
-            $this->ext_data = $data['ext_data'];
-            $this->body = $data['body'];
-            unset($data['ext_data'], $data['body']);
-            $this->header = $data;
-        }
-    }
-    
-    /**
-     * 判断数据包是否都到了
-     * @param string $buffer
-     * @return int int=0数据是完整的 int>0数据不完整,还要继续接收int字节
-     */
-    public static function input($buffer)
-    {
-        $len = strlen($buffer);
-        // 至少需要四字节才能解出包的长度
-        if($len < 4)
-        {
-            return 4 - $len;
-        }
-        
-        $data = unpack("Npack_len", $buffer);
-        return $data['pack_len'] - $len;
-    }
-    
-    /**
-     * 获取整个包的buffer
-     * @param string $data
-     * @return string
-     */
-    public function getBuffer()
-    {
-        $this->header['ext_len'] = strlen($this->ext_data);
-        $this->header['pack_len'] = self::HEAD_LEN + $this->header['ext_len'] + strlen($this->body);
-        return pack("NCNnNNnNN",  $this->header['pack_len'],
-                        $this->header['cmd'], ip2long($this->header['local_ip']), 
-                        $this->header['local_port'], $this->header['socket_id'], 
-                        ip2long($this->header['client_ip']), $this->header['client_port'], 
-                        $this->header['client_id'],
-                       $this->header['ext_len']) . $this->ext_data . $this->body;
-    }
-    
-    /**
-     * 从二进制数据转换为数组
-     * @param string $buffer
-     * @return array
-     */    
-    protected static function decode($buffer)
-    {
-        $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nsocket_id/Nclient_ip/nclient_port/Nclient_id/Next_len", $buffer);
-        $data['local_ip'] = long2ip($data['local_ip']);
-        $data['client_ip'] = long2ip($data['client_ip']);
-        if($data['ext_len'] > 0)
-        {
-            $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']);
-            $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']);
-        }
-        else
-        {
-            $data['ext_data'] = '';
-            $data['body'] = substr($buffer, self::HEAD_LEN);
-        }
-        return $data;
-    }
-}
-
-
-

+ 0 - 53
applications/Demo/Protocols/JsonProtocol.php

@@ -1,53 +0,0 @@
-<?php 
-namespace Protocols;
-/**
- * 以四字节int标记请求长度的json协议 
- * 协议格式int+json
- * @author walkor
- */
-class JsonProtocol
-{
-    // 根据首部四个字节(int)判断数据是否接收完毕
-    public static function check($buffer)
-    {
-        // 已经收到的长度(字节)
-        $recv_length = strlen($buffer);
-        // 接收到的数据长度不够?
-        if($recv_length<4)
-        {
-            return 4 - $recv_length;
-        }
-        // 读取首部4个字节,网络字节序int
-        $buffer_data = unpack('Ntotal_length', $buffer);
-        // 得到这次数据的整体长度(字节)
-        $total_length = $buffer_data['total_length'];
-        if($total_length>$recv_length)
-        {
-            // 还有这么多字节要接收
-            return $total_length - $recv_length;
-        }
-        // 接收完毕
-        return 0;
-    }
-
-    // 打包
-    public static function encode($data)
-    {
-        // 选用json格式化数据
-        $buffer = json_encode($data);
-        // 包的整体长度为json长度加首部四个字节(首部数据包长度存储占用空间)
-        $total_length = 4 + strlen($buffer);
-        return pack('N', $total_length) . $buffer;
-    }
-
-    // 解包
-    public static function decode($buffer)
-    {
-        $buffer_data = unpack('Ntotal_length', $buffer);
-        // 得到这次数据的整体长度(字节)
-        $total_length = $buffer_data['total_length'];
-        // json的数据
-        $json_string = substr($buffer, 4);
-        return json_decode($json_string, true);
-    }
-}

+ 0 - 48
applications/Demo/Protocols/TextProtocol.php

@@ -1,48 +0,0 @@
-<?php 
-namespace Protocols;
-/**
- * 以回车为请求结束标记的 文本协议 
- * 协议格式 文本+回车
- * 由于是逐字节读取,效率会有些影响,与JsonProtocol相比JsonProtocol效率会高一些
- * @author walkor
- */
-class TextProtocol 
-{
-    /**
-     * 判断数据边界
-     * @param string $buffer
-     * @return number
-     */
-    public static function check($buffer)
-    {
-        // 判断最后一个字符是否是回车("\n")
-        if($buffer[strlen($buffer)-1] === "\n")
-        {
-            return 0;
-        }
-        
-        // 说明还有请求数据没收到,但是由于不知道还有多少数据没收到,所以只能返回1,因为有可能下一个字符就是回车("\n")
-        return 1;
-    }
-
-    /**
-     * 打包
-     * @param mixed $data
-     * @return string
-     */
-    public static function encode($data)
-    {
-        // 选用json格式化数据
-        return $data."\n";
-    }
-
-    /**
-     * 解包
-     * @param string $buffer
-     * @return mixed
-     */
-    public static function decode($buffer)
-    {
-        return trim($buffer);
-    }
-}

+ 0 - 148
applications/Demo/Protocols/WebSocket.php

@@ -1,148 +0,0 @@
-<?php 
-namespace Protocols;
-/**
- * WebSocket 协议解包和打包
- * @author walkor <walkor@workerman.net>
- */
-
-class WebSocket
-{
-    /**
-     * 检查包的完整性
-     * @param unknown_type $buffer
-     */
-    public static function check($buffer)
-    {
-        // 数据长度
-        $recv_len = strlen($buffer);
-        // 长度不够
-        if($recv_len < 6)
-        {
-            return 6-$recv_len;
-        }
-        
-        // 握手阶段客户端发送HTTP协议
-        if(0 === strpos($buffer, 'GET'))
-        {
-            // 判断\r\n\r\n边界
-            if(strlen($buffer) - 4 === strpos($buffer, "\r\n\r\n"))
-            {
-                return 0;
-            }
-            return 1;
-        }
-        // 如果是flash的policy-file-request
-        elseif(0 === strpos($buffer,'<polic'))
-        {
-            if('>' != $buffer[strlen($buffer) - 1])
-            {
-                return 1;
-            }
-            return 0;
-        }
-        
-        // websocket二进制数据
-        $data_len = ord($buffer[1]) & 127;
-        $head_len = 6;
-        if ($data_len === 126) {
-            $pack = unpack('ntotal_len', substr($buffer, 2, 2));
-            $data_len = $pack['total_len'];
-            $head_len = 8;
-        } else if ($data_len === 127) {
-            $arr = unpack('N2', substr($buffer, 2, 8));
-            $data_len = $arr[1]*4294967296 + $arr[2];
-            $head_len = 14;
-        }
-        $remain_len = $head_len + $data_len - $recv_len;
-        if($remain_len < 0)
-        {
-            return false;
-        }
-        return $remain_len;
-    }
-    
-    /**
-     * 打包
-     * @param string $buffer
-     */
-    public static function encode($buffer)
-    {
-        $len = strlen($buffer);
-        if($len<=125)
-        {
-            return "\x81".chr($len).$buffer;
-        }
-        else if($len<=65535)
-        {
-            return "\x81".chr(126).pack("n", $len).$buffer;
-        }
-        else
-        {
-            return "\x81".chr(127).pack("xxxxN", $len).$buffer;
-        }
-    }
-    
-    /**
-     * 解包
-     * @param string $buffer
-     * @return string
-     */
-    public static function decode($buffer)
-    {
-        $len = $masks = $data = $decoded = null;
-        $len = ord($buffer[1]) & 127;
-        if ($len === 126) {
-            $masks = substr($buffer, 4, 4);
-            $data = substr($buffer, 8);
-        } else if ($len === 127) {
-            $masks = substr($buffer, 10, 4);
-            $data = substr($buffer, 14);
-        } else {
-            $masks = substr($buffer, 2, 4);
-            $data = substr($buffer, 6);
-        }
-        for ($index = 0; $index < strlen($data); $index++) {
-            $decoded .= $data[$index] ^ $masks[$index % 4];
-        }
-        return $decoded;
-    }
-    
-    /**
-     * 是否是websocket断开的数据包
-     * @param string $buffer
-     */
-    public static function isClosePacket($buffer)
-    {
-        $opcode = self::getOpcode($buffer);
-        return $opcode == 8;
-    }
-    
-    /**
-     * 是否是websocket ping的数据包
-     * @param string $buffer
-     */
-    public static function isPingPacket($buffer)
-    {
-        $opcode = self::getOpcode($buffer);
-        return $opcode == 9;
-    }
-    
-    /**
-     * 是否是websocket pong的数据包
-     * @param string $buffer
-     */
-    public static function isPongPacket($buffer)
-    {
-        $opcode = self::getOpcode($buffer);
-        return $opcode == 0xa;
-    }
-    
-    /**
-     * 获取wbsocket opcode
-     * @param string $buffer
-     */
-    public static function getOpcode($buffer)
-    {
-        return ord($buffer[0]) & 0xf;
-    }
-}

+ 0 - 176
applications/Demo/README.md

@@ -1,176 +0,0 @@
-基于TCP的一个聊天的Demo,该架构适用于绝大部分即时通讯应用,如PC\手机app IM、游戏后台、企业通讯软件、与硬件通讯等
-=========
-
-注意:强烈建议生产环境包括压测环境使用memcache,配置方法如下:
-========
- * 安装memcahced服务,例如 ubuntu 运行sudo apt-get install memcached  
- * 启动memcached ,例如 ubuntu 运行 memcached -m 256 -p 22322 -u memcache -l 127.0.0.1 -d  
- * 安装memcache扩展,例如 ubuntu 运行 sudo apt-get install php5-memcached  
- * 设置 applications/XXX/Config/Store.php 中的 public static $driver = self::DRIVER_MC;public static $gateway = array('127.0.0.1:22322');   
- * 重启workerman  
-
-### Demo测试方法 
-  * 运行 telnet ip 8480
-  * 首先输入昵称 回车
-  * 后面直接打字回车是向所有人发消息
-  * $uid:xxxxxx 是向$uid用户发送消息  
-
-可以开多个telnet窗口,窗口间可以实时聊天
-
-目录结构
-========
-
-<pre>
-.
-├── Bootstrap  // 进程入口目录,分为gateway进程和BusinessWorker进程。gateway进程负责接收用户连接,转发用户请求给BusinessWorker进程,接收BusinessWorker进程的结果转发给用户
-│   │
-│   ├── BusinessWorker.php // 业务进程,接收Gateway进程的转发来的用户请求并处理,如果有需要将结果发给其它用户则通过Gateway进程转发
-│   │
-│   └── Gateway.php  // gateway进程,负责客户端连接,转发用户请求给BusinessWorker进程处理,并接收BusinessWorker进程的处理结果转发给用户
-│ 
-├── Lib  // 通用的库
-│   │
-│   ├── StoreDriver          // 存储驱动目录
-│   │
-│   ├── Gateway.php          // gateway进程的接口,BusinessWorker进程通过此文件的接口向gateway进程发送数据
-│   │
-│   ├── Store.php            // 存储类,默认使用文件存储,配置在Config/Store.php,生产环境请使用memcache作为存储
-│   │
-│   ├── Db.php                //  Db类,用于管理数据库连接
-│   │
-│   ├── DbConnection.php       // 数据库连接类,只支持pdo
-│   │
-│   ├── Autoloader.php       // 自动加载逻辑
-│   │
-│   ├── Context.php          // Gateway与Worker通信时的上下文信息,开发者不要改动其中的内容
-│   │
-│   └── StatisticClient.php  // 统计模块客户端
-│ 
-├── Config  // 配置
-│   │
-│   ├── Db.php          // 数据库配置
-│   │
-│   └── Store.php            // 存储配置,分为两种,一种是文件存储(无法支持分布式,开发测试用),另外一种是memcache存储,支持分布式
-│ 
-├── Protocols // 应用层协议相关
-│   │
-│   ├── GatewayProtocol.php  // gateway与BusinessWorker通讯的协议,开发者无需关注
-│   │
-│   ├── TextProtocol.php     // 简单的文本协议(applications/Demo中用到)
-│   │
-│   ├── JsonProtocol.php     // json协议(还没有例子使用)
-│   │
-│   └── WebSocket.php        // WebSocket协议(workerman-chat使用)
-│ 
-│ 
-└── Event.php // 聊天所有的业务代码在此目录,群聊、私聊等
-</pre>
-
-为什么使用gateway worker模型
-===========================
-
-gateway worker模型非常适合长链接应用,例如聊天、游戏后台等。如果是短链接应用,则建议使用上面FileRecevierDemo基础的master slave模型。
-###1、gateway只负责网络IO,worker主要负责业务逻辑。各司其职,非常高效。
-打个比方,一个餐馆有4工人(进程),他们即负责招呼客人(网络IO),又负责在厨房做菜(业务逻辑)。当客人一下子来很多的时候(很多链接或很多数据),大家有可能都去招待客人了(都处理网络IO),厨房没人做菜(做业务)。当大家都做菜的时候(做业务),又没人招呼客人(接收链接),导致客人(用户)都在等待。但是当我们把工人(进程)分工一下,2个人专门招呼客人(geteway进程),两个人专门做菜(worker进程),这样每个时刻都有有人(进程)招待客人(接收数据),都有人(进程)做菜(处理业务)。当gateway不够用的时候(一般都是够用的)增加gateway,worker忙不过来的时候增加worker进程。这样效率会提升很多。
-###2、提高稳定性
-gateway进程因为要维持用户链接,这要求gateway进程一定要非常稳定,不然如果gateway进程出问题,则这个进程上的所有用户都会断开链接。让gateway只负责网络IO,不负责业务,就是因为业务频繁变化,可能会有致命的错误(例如调用了一个不存在的函数)导致进程退出,进而导致用户链接断开。而让gateway只负责网络IO,就是要避免这种风险。而worker进程是无状态的(没有保存用户链接等状态信息),即使偶尔出现FatalErr,也只会影响当前的这次请求,而不会对整个服务造成大的影响。
-###3、热更新
-由于gateway进程没有业务逻辑,所以geteway进程极少有代码更新。而worker进程由于负责业务逻辑,会有经常性的代码更新。这样看来我们每次代码更新,只要重启worker进程就可以实现运行新的业务代码。实际上也是这样,当更新程序逻辑时,我们只需要重启worker进程就可以了,这样就不会导致更新代码的时候用户链接会断开,达到不影响用户的情况下热更新后台程序。
-###4、扩展容易
-当worker进程不够用的时候,我们可以水平扩展它,可增加worker的进程数量,甚至可以增加服务器专门运行worker进程,达到水平扩展的目的,以支持更大的用户量。gateway进程也是同样的道理。
-
-数据库类的使用方法
-=========
-
-##注意这个数据库类需要mysql_pdo扩展
-
-## 配置
-在Config/Db.php中配置数据库信息,如果有多个数据库,可以按照one_demo的配置在Db.php中配置多个实例  
-例如下面配置了两个数据库实例
-
-```php
-<?php
-namespace Config;
-class Db
-{
-    // 数据库实例1
-    public static $db1 = array(
-        'host'    => '127.0.0.1',
-        'port'    => 3306,
-        'user'    => 'mysql_user',
-        'password' => 'mysql_password',
-        'dbname'  => 'db1',
-        'charset'    => 'utf8',
-    );
-  
-    // 数据库实例2
-    public static $db2 = array(
-        'host'    => '127.0.0.1',
-        'port'    => 3306,
-        'user'    => 'mysql_user',
-        'password' => 'mysql_password',
-        'dbname'  => 'db2',
-        'charset'    => 'utf8',
-    );
-}
-```
-2、使用方法
-
-```php
-$db1 = \Lib\Db::instance('db1');  
-$db2 = \Lib\Db::instance('db2');  
-
-// 获取所有数据
-$db1->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->query();  
-//等价于  
-$db1->select('ID,Sex')->from('Persons')->where("sex= 'F' ")->query(); 
-//等价于  
-$db1->query("SELECT ID,Sex FROM `Persons` WHERE sex=‘M’");  
-
-
-// 获取一行数据
-$db1->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->row();  
-//等价于  
-$db1->select('ID,Sex')->from('Persons')->where("sex= 'F' ")->row(); 
-//等价于  
-$db1->row("SELECT ID,Sex FROM `Persons` WHERE sex=‘M’");  
-
-
-// 获取一列数据
-$db1->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->column();  
-//等价于  
-$db1->select('ID,Sex')->from('Persons')->where("sex= 'F' ")->column(); 
-//等价于  
-$db1->column("SELECT ID,Sex FROM `Persons` WHERE sex=‘M’");  
-
-// 获取单个值
-$db1->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->single();  
-//等价于  
-$db1->select('ID,Sex')->from('Persons')->where("sex= 'F' ")->single(); 
-//等价于  
-$db1->single("SELECT ID,Sex FROM `Persons` WHERE sex=‘M’");  
-
-// 复杂查询
-$db1->select('*')->from('table1')->innerJoin('table2','table1.uid = table2.uid')->where('age > :age')->groupBy(array('aid'))->having('foo="foo"')->orderBy(array('did'))->limit(10)->offset(20)->bindValues(arra
-y('age' => 13));
-// 等价于
-$db1->query(SELECT * FROM `table1` INNER JOIN `table2` ON `table1`.`uid` = `table2`.`uid` WHERE age > 13 GROUP BY aid HAVING foo="foo" ORDER BY did LIMIT 10 OFFSET 20“);
-
-// 插入
-$insert_id = $db1->insert('Persons')->cols(array('Firstname'=>'abc', 'Lastname'=>'efg', 'Sex'=>'M', 'Age'=>13))->query();
-等价于
-$insert_id = $db1->query("INSERT INTO `Persons` ( `Firstname`,`Lastname`,`Sex`,`Age`) VALUES ( 'abc', 'efg', 'M', 13)");
-
-// 更新
-$row_count = $db1->update('Persons')->cols(array('sex'))->where('ID=1')->bindValue('sex', 'F')->query();
-// 等价于
-$row_count = $db1->update('Persons')->cols(array('sex'=>'F'))->where('ID=1')->query();
-// 等价于
-$row_count = $db1->query("UPDATE `Persons` SET `sex` = 'F' WHERE ID=1");
-
-// 删除
-$row_count = $db1->delete('Persons')->where('ID=9')->query();
-// 等价于
-$row_count = $db1->query("DELETE FROM `Persons` WHERE ID=9");
-
-```

+ 0 - 10
applications/Demo/conf.d/BusinessWorker.conf

@@ -1,10 +0,0 @@
-;业务进程入口文件,相对于本配置文件相对位置,可以只用绝对路径
-worker_file = ../Bootstrap/BusinessWorker.php
-;启动多少服务进程
-start_workers = 32
-;以哪个用户运行该进程,为了安全请使用权限较低的用户,例如www-data nobody
-user = root
-;请求到来时预读长度,这里固定29
-preread_length = 29
-;必须是长链接
-persistent_connection = 1

+ 0 - 45
applications/Demo/conf.d/Gateway.conf

@@ -1,45 +0,0 @@
-;进程入口文件,相对于本配置文件相对位置,可以只用绝对路径
-worker_file = ../Bootstrap/Gateway.php
-
-;传输层协议及监听的ip端口
-listen = tcp://0.0.0.0:8480
-
-;是否是长连接
-persistent_connection = 1
-
-;开多少服务进程
-start_workers = 4
-
-;以哪个用户运行,为了安全,应该使用权限较低的用户,例如www-data nobody
-user = root
-
-;每个请求预读长度,避免读取数据超过一个协议包,
-;一般设置为协议头的长度,当请求到来时在dealInput中根据头部标识的数据包长度计算还有多少数据没接收完毕,并返回这个值
-preread_length = 1
-
-;不reload,当有reload命令时是否安全重启这个进程
-no_reload = 1
-
-;workerman.conf.debug=1 时有效。echo var_dump 等输出是否打印到终端
-no_debug = 1
-
-
-;;;;;;;;;以上是workerman子进程通用配置;;;;;;;;;;;;;;
-;;;;;;;;;以下是gateway进程私有配置;;;;;;;;;;;;
-
-;内部通讯的局域网ip,worker进程会向这个ip发送数据
-lan_ip = 127.0.0.1
-
-;内部通讯端口起始值,假如开启5个gateway进程,则每个进程会监听一个端口,40001 40002 40003 40004 40005
-lan_port_start = 40000
-
-;此gateway进程向客户端发送心跳时间间隔 单位:秒,如果是0表示不发送心跳
-ping_interval = 0
-
-;客户端连续ping_not_response_limit次ping_interval时间内不回应心跳则断开链接
-ping_not_response_limit = 1
-
-;要发送的心跳请求数据,将心跳请求保存成文件,然后配置文件路径 如ping_data=/yourpath/ping.bin,
-;workerman会将此文件中的内容当作心跳请求发送给客户端
-;注意 心跳请求数据一定要符合你的通讯协议
-ping_data = ../ping.data

+ 0 - 8
applications/README.md

@@ -1,8 +0,0 @@
-ChatDemo Gateway/Worker进程模型框架
-============
-适合绝大多数长链接应用,workerman-chat workerman-todpole workerman-flappy-bird 都是基于此框架开发的
-开发者只需要关注./ChatDemo/Event.php一个文件即可
-
-Statistics 统计系统
-============
-用于统计各个接口调用情况,包括系统可用性、各个接口调用量、延迟、成功率、错误信息等

+ 0 - 537
applications/Statistics/Bootstrap/StatisticProvider.php

@@ -1,537 +0,0 @@
-<?php 
-/**
- * 
-* @author walkor <walkor@workerman.net>
- */
-class StatisticProvider extends Man\Core\SocketWorker
-{
-    /**
-     *  最大日志buffer,大于这个值就写磁盘
-     * @var integer
-     */
-    const MAX_LOG_BUFFER_SZIE = 1024000;
-    
-    /**
-     * 多长时间写一次数据到磁盘
-     * @var integer
-     */
-    const WRITE_PERIOD_LENGTH = 60;
-    
-    /**
-     * 多长时间清理一次老的磁盘数据
-     * @var integer
-     */
-    const CLEAR_PERIOD_LENGTH = 86400;
-    
-    /**
-     * 数据多长时间过期
-     * @var integer
-     */
-    const EXPIRED_TIME = 1296000;
-    
-    /**
-     * 统计数据 
-     * ip=>modid=>interface=>['code'=>[xx=>count,xx=>count],'suc_cost_time'=>xx,'fail_cost_time'=>xx, 'suc_count'=>xx, 'fail_count'=>xx]
-     * @var array
-     */
-    protected $statisticData = array();
-    
-    /**
-     * 日志的buffer
-     * @var string
-     */
-    protected $logBuffer = '';
-    
-    /**
-     * 放统计数据的目录(相对于workerman/logs/)
-     * @var string
-     */
-    protected $statisticDir = 'statistic/statistic/';
-    
-    /**
-     * 存放统计日志的目录(相对于workerman/logs/)
-     * @var string
-     */
-    protected $logDir = 'statistic/log/';
-    
-    /**
-     * 用于接收广播的udp socket
-     * @var resource
-     */
-    protected $broadcastSocket = null;
-    
-    public function onStart()
-    {
-        $listen = \Man\Core\Lib\Config::get($this->workerName . '.listen');
-        $udp_address = str_replace('tcp', 'udp', $listen);
-        $this->broadcastSocket = stream_socket_server($udp_address, $error_no, $error_msg, STREAM_SERVER_BIND);
-        $this->event->add($this->broadcastSocket,  \Man\Core\Events\BaseEvent::EV_READ, array($this, 'dealBroadcastUdp'));
-    }
-    
-    
-    /**
-     * 接收Udp数据
-     * 如果数据超过一个udp包长,需要业务自己解析包体,判断数据是否全部到达
-     * @param resource $socket
-     * @param $null_one $flag
-     * @param $null_two $base
-     * @return void
-     */
-    public function dealBroadcastUdp($socket, $null_one = null, $null_two = null)
-    {
-        $data = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $address);
-        // 可能是惊群效应
-        if(false === $data || empty($address))
-        {
-            return false;
-        }
-        // 解析包体
-        $data = json_decode(trim($data), true);
-        if(empty($data))
-        {
-            return false;
-        }
-        
-        // 无法解析的包
-        if(empty($data['cmd']) || $data['cmd'] != 'REPORT_IP' )
-        {
-            return false;
-        }
-        
-        // 回应
-        return stream_socket_sendto($this->broadcastSocket, json_encode(array('result'=>'ok')), 0, $address);
-    }
-    
-    /**
-     * udp 默认全部接收完毕
-     * @see Man\Core.SocketWorker::dealInput()
-     */
-    public function dealInput($recv_buffer)
-    {
-        return 0;
-    }
-    
-    /**
-     * 处理请求统计
-     * @param string $recv_buffer
-     */
-    public function dealProcess($recv_buffer)
-    {
-        $req_data = json_decode(trim($recv_buffer), true);
-        $module = $req_data['module'];
-        $interface = $req_data['interface'];
-        $cmd = $req_data['cmd'];
-        $start_time = isset($req_data['start_time']) ? $req_data['start_time'] : '';
-        $end_time = isset($req_data['end_time']) ? $req_data['end_time'] : '';
-        $date = isset($req_data['date']) ? $req_data['date'] : '';
-        $code = isset($req_data['code']) ? $req_data['code'] : '';
-        $msg = isset($req_data['msg']) ? $req_data['msg'] : '';
-        $offset = isset($req_data['offset']) ? $req_data['offset'] : '';
-        $count = isset($req_data['count']) ? $req_data['count'] : 10;
-        switch($cmd)
-        {
-            case 'get_statistic':
-                $buffer = json_encode(array('modules'=>$this->getModules($module), 'statistic' => $this->getStatistic($date, $module, $interface)))."\n";
-                $this->sendToClient($buffer);
-                break;
-            case 'get_log':
-                $buffer = json_encode($this->getStasticLog($module, $interface , $start_time , $end_time, $code, $msg, $offset, $count))."\n";
-                $this->sendToClient($buffer);
-                break;
-            default :
-                $this->sendToClient('pack err');
-        }
-    }
-    
-    /**
-     * 获取模块
-     * @return array
-     */
-    public function getModules($current_module = '')
-    {
-        $st_dir = WORKERMAN_LOG_DIR . $this->statisticDir;
-        $modules_name_array = array();
-        foreach(glob($st_dir."/*", GLOB_ONLYDIR) as $module_file)
-        {
-            $tmp = explode("/", $module_file);
-            $module = end($tmp);
-            $modules_name_array[$module] = array();
-            if($current_module == $module)
-            {
-                $st_dir = $st_dir.$current_module.'/';
-                $all_interface = array();
-                foreach(glob($st_dir."*") as $file)
-                {
-                    if(is_dir($file))
-                    {
-                        continue;
-                    }
-                    list($interface, $date) = explode(".", basename($file));
-                    $all_interface[$interface] = $interface;
-                }
-                $modules_name_array[$module] = $all_interface;
-            }
-        }
-        return $modules_name_array;
-    }
-    
-    /**
-     * 获得统计数据
-     * @param string $module
-     * @param string $interface
-     * @param int $date
-     * @return bool/string
-     */
-    protected function getStatistic($date, $module, $interface)
-    {
-        if(empty($module) || empty($interface))
-        {
-            return '';
-        }
-        // log文件
-        $log_file = WORKERMAN_LOG_DIR . $this->statisticDir."{$module}/{$interface}.{$date}";
-        
-        $handle = @fopen($log_file, 'r');
-        if(!$handle)
-        {
-            return '';
-        }
-        
-        // 预处理统计数据,每5分钟一行
-        // [time=>[ip=>['suc_count'=>xx, 'suc_cost_time'=>xx, 'fail_count'=>xx, 'fail_cost_time'=>xx, 'code_map'=>[code=>count, ..], ..], ..]
-        $statistics_data = array();
-        while(!feof($handle))
-        {
-            $line = fgets($handle, 4096);
-            if($line)
-            {
-                $explode = explode("\t", $line);
-                if(count($explode) < 7)
-                {
-                    continue;
-                }
-                list($ip, $time, $suc_count, $suc_cost_time, $fail_count, $fail_cost_time, $code_map) = $explode;
-                $time = ceil($time/300)*300;
-                if(!isset($statistics_data[$time]))
-                {
-                    $statistics_data[$time] = array();
-                }
-                if(!isset($statistics_data[$time][$ip]))
-                {
-                    $statistics_data[$time][$ip] = array(
-                            'suc_count'       =>0,
-                            'suc_cost_time' =>0,
-                            'fail_count'       =>0,
-                            'fail_cost_time' =>0,
-                            'code_map'      =>array(),
-                     );
-                }
-                $statistics_data[$time][$ip]['suc_count'] += $suc_count;
-                $statistics_data[$time][$ip]['suc_cost_time'] += round($suc_cost_time, 5);
-                $statistics_data[$time][$ip]['fail_count'] += $fail_count;
-                $statistics_data[$time][$ip]['fail_cost_time'] += round($fail_cost_time, 5);
-                $code_map = json_decode(trim($code_map), true);
-                if($code_map && is_array($code_map))
-                {
-                    foreach($code_map as $code=>$count)
-                    {
-                        if(!isset($statistics_data[$time][$ip]['code_map'][$code]))
-                        {
-                            $statistics_data[$time][$ip]['code_map'][$code] = 0;
-                        }
-                        $statistics_data[$time][$ip]['code_map'][$code] +=$count;
-                    }
-                }
-            } // end if
-        } // end while
-        
-        fclose($handle);
-        ksort($statistics_data);
-        
-        // 整理数据
-        $statistics_str = '';
-        foreach($statistics_data as $time => $items)
-        {
-            foreach($items as $ip => $item)
-            {
-                $statistics_str .= "$ip\t$time\t{$item['suc_count']}\t{$item['suc_cost_time']}\t{$item['fail_count']}\t{$item['fail_cost_time']}\t".json_encode($item['code_map'])."\n";
-            }
-        }
-        return $statistics_str;
-    }
-    
-    
-    /**
-     * 获取指定日志
-     *
-     */
-    protected function getStasticLog($module, $interface , $start_time = '', $end_time = '', $code = '', $msg = '', $offset='', $count=100)
-    {
-        // log文件
-        $log_file = WORKERMAN_LOG_DIR . $this->logDir. (empty($start_time) ? date('Y-m-d') : date('Y-m-d', $start_time));
-        if(!is_readable($log_file))
-        {
-            return array('offset'=>0, 'data'=>'');
-        }
-        // 读文件
-        $h = fopen($log_file, 'r');
-    
-        // 如果有时间,则进行二分查找,加速查询
-        if($start_time && $offset == 0 && ($file_size = filesize($log_file)) > 1024000)
-        {
-            $offset = $this->binarySearch(0, $file_size, $start_time-1, $h);
-            $offset = $offset < 100000 ? 0 : $offset - 100000;
-        }
-    
-        // 正则表达式
-        $pattern = "/^([\d: \-]+)\t\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\t";
-    
-        if($module && $module != 'WorkerMan')
-        {
-            $pattern .= $module."::";
-        }
-        else
-        {
-            $pattern .= ".*::";
-        }
-    
-        if($interface && $module != 'WorkerMan')
-        {
-            $pattern .= $interface."\t";
-        }
-        else
-        {
-            $pattern .= ".*\t";
-        }
-    
-        if($code !== '')
-        {
-            $pattern .= "code:$code\t";
-        }
-        else
-        {
-            $pattern .= "code:\d+\t";
-        }
-    
-        if($msg)
-        {
-            $pattern .= "msg:$msg";
-        }
-         
-        $pattern .= '/';
-    
-        // 指定偏移位置
-        if($offset > 0)
-        {
-            fseek($h, (int)$offset-1);
-        }
-    
-        // 查找符合条件的数据
-        $now_count = 0;
-        $log_buffer = '';
-    
-        while(1)
-        {
-            if(feof($h))
-            {
-                break;
-            }
-            // 读1行
-            $line = fgets($h);
-            if(preg_match($pattern, $line, $match))
-            {
-                // 判断时间是否符合要求
-                $time = strtotime($match[1]);
-                if($start_time)
-                {
-                    if($time<$start_time)
-                    {
-                        continue;
-                    }
-                }
-                if($end_time)
-                {
-                    if($time>$end_time)
-                    {
-                        break;
-                    }
-                }
-                // 收集符合条件的log
-                $log_buffer .= $line;
-                if(++$now_count >= $count)
-                {
-                    break;
-                }
-            }
-        }
-        // 记录偏移位置
-        $offset = ftell($h);
-        return array('offset'=>$offset, 'data'=>$log_buffer);
-    }
-    /**
-     * 日志二分查找法
-     * @param int $start_point
-     * @param int $end_point
-     * @param int $time
-     * @param fd $fd
-     * @return int
-     */
-    protected function binarySearch($start_point, $end_point, $time, $fd)
-    {
-        if($end_point - $start_point < 65535)
-        {
-            return $start_point;
-        }
-        
-        // 计算中点
-        $mid_point = (int)(($end_point+$start_point)/2);
-    
-        // 定位文件指针在中点
-        fseek($fd, $mid_point - 1);
-    
-        // 读第一行
-        $line = fgets($fd);
-        if(feof($fd) || false === $line)
-        {
-            return $start_point;
-        }
-    
-        // 第一行可能数据不全,再读一行
-        $line = fgets($fd);
-        if(feof($fd) || false === $line || trim($line) == '')
-        {
-            return $start_point;
-        }
-    
-        // 判断是否越界
-        $current_point = ftell($fd);
-        if($current_point>=$end_point)
-        {
-            return $start_point;
-        }
-    
-        // 获得时间
-        $tmp = explode("\t", $line);
-        $tmp_time = strtotime($tmp[0]);
-    
-        // 判断时间,返回指针位置
-        if($tmp_time > $time)
-        {
-            return $this->binarySearch($start_point, $current_point, $time, $fd);
-        }
-        elseif($tmp_time < $time)
-        {
-            return $this->binarySearch($current_point, $end_point, $time, $fd);
-        }
-        else
-        {
-            return $current_point;
-        }
-    }
-    
-} 
-
-/**
- *
- * struct statisticPortocol
- * {
- *     unsigned char module_name_len;
- *     unsigned char interface_name_len;
- *     float cost_time;
- *     unsigned char success;
- *     int code;
- *     unsigned short msg_len;
- *     unsigned int time;
- *     char[module_name_len] module_name;
- *     char[interface_name_len] interface_name;
- *     char[msg_len] msg;
- * }
- *
- * @author workerman.net
- */
-class StatisticProtocol
-{
-    /**
-     * 包头长度
-     * @var integer
-     */
-    const PACKAGE_FIXED_LENGTH = 17;
-
-    /**
-     * udp 包最大长度
-     * @var integer
-     */
-    const MAX_UDP_PACKGE_SIZE  = 65507;
-
-    /**
-     * char类型能保存的最大数值
-     * @var integer
-     */
-    const MAX_CHAR_VALUE = 255;
-
-    /**
-     *  usigned short 能保存的最大数值
-     * @var integer
-     */
-    const MAX_UNSIGNED_SHORT_VALUE = 65535;
-
-    /**
-     * 编码
-     * @param string $module
-     * @param string $interface
-     * @param float $cost_time
-     * @param int $success
-     * @param int $code
-     * @param string $msg
-     * @return string
-     */
-    public static function encode($module, $interface , $cost_time, $success,  $code = 0,$msg = '')
-    {
-        // 防止模块名过长
-        if(strlen($module) > self::MAX_CHAR_VALUE)
-        {
-            $module = substr($module, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止接口名过长
-        if(strlen($interface) > self::MAX_CHAR_VALUE)
-        {
-            $interface = substr($interface, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止msg过长
-        $module_name_length = strlen($module);
-        $interface_name_length = strlen($interface);
-        $avalible_size = self::MAX_UDP_PACKGE_SIZE - self::PACKAGE_FIXED_LENGTH - $module_name_length - $interface_name_length;
-        if(strlen($msg) > $avalible_size)
-        {
-            $msg = substr($msg, 0, $avalible_size);
-        }
-
-        // 打包
-        return pack('CCfCNnN', $module_name_length, $interface_name_length, $cost_time, $success ? 1 : 0, $code, strlen($msg), time()).$module.$interface.$msg;
-    }
-     
-    /**
-     * 解包
-     * @param string $bin_data
-     * @return array
-     */
-    public static function decode($bin_data)
-    {
-        // 解包
-        $data = unpack("Cmodule_name_len/Cinterface_name_len/fcost_time/Csuccess/Ncode/nmsg_len/Ntime", $bin_data);
-        $module = substr($bin_data, self::PACKAGE_FIXED_LENGTH, $data['module_name_len']);
-        $interface = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'], $data['interface_name_len']);
-        $msg = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'] + $data['interface_name_len']);
-        return array(
-                'module'          => $module,
-                'interface'        => $interface,
-                'cost_time' => $data['cost_time'],
-                'success'           => $data['success'],
-                'time'                => $data['time'],
-                'code'               => $data['code'],
-                'msg'                => $msg,
-        );
-    }
-}

+ 0 - 368
applications/Statistics/Bootstrap/StatisticWorker.php

@@ -1,368 +0,0 @@
-<?php 
-/**
- * 
-* @author walkor <walkor@workerman.net>
- */
-class StatisticWorker extends Man\Core\SocketWorker
-{
-    /**
-     *  最大日志buffer,大于这个值就写磁盘
-     * @var integer
-     */
-    const MAX_LOG_BUFFER_SZIE = 1024000;
-    
-    /**
-     * 多长时间写一次数据到磁盘
-     * @var integer
-     */
-    const WRITE_PERIOD_LENGTH = 60;
-    
-    /**
-     * 多长时间清理一次老的磁盘数据
-     * @var integer
-     */
-    const CLEAR_PERIOD_LENGTH = 86400;
-    
-    /**
-     * 数据多长时间过期
-     * @var integer
-     */
-    const EXPIRED_TIME = 1296000;
-    
-    /**
-     * 统计数据 
-     * ip=>modid=>interface=>['code'=>[xx=>count,xx=>count],'suc_cost_time'=>xx,'fail_cost_time'=>xx, 'suc_count'=>xx, 'fail_count'=>xx]
-     * @var array
-     */
-    protected $statisticData = array();
-    
-    /**
-     * 日志的buffer
-     * @var string
-     */
-    protected $logBuffer = '';
-    
-    /**
-     * 放统计数据的目录(相对于workerman/logs/)
-     * @var string
-     */
-    protected $statisticDir = 'statistic/statistic/';
-    
-    /**
-     * 存放统计日志的目录(相对于workerman/logs/)
-     * @var string
-     */
-    protected $logDir = 'statistic/log/';
-    
-    /**
-     * 提供统计查询的socket
-     * @var resource
-     */
-    protected $providerSocket = null;
-    
-    /**
-     * udp 默认全部接收完毕
-     * @see Man\Core.SocketWorker::dealInput()
-     */
-    public function dealInput($recv_buffer)
-    {
-        return 0;
-    }
-    
-    /**
-     * 业务处理
-     * @see Man\Core.SocketWorker::dealProcess()
-     */
-    public function dealProcess($recv_buffer)
-    {
-        // 解码
-        $unpack_data = StatisticProtocol::decode($recv_buffer);
-        $module = $unpack_data['module'];
-        $interface = $unpack_data['interface'];
-        $cost_time = $unpack_data['cost_time'];
-        $success = $unpack_data['success'];
-        $time = $unpack_data['time'];
-        $code = $unpack_data['code'];
-        $msg = str_replace("\n", "<br>", $unpack_data['msg']);
-        $ip = $this->getRemoteIp();
-        
-        // 模块接口统计
-        $this->collectStatistics($module, $interface, $cost_time, $success, $ip, $code, $msg);
-        // 全局统计
-        $this->collectStatistics('WorkerMan', 'Statistics', $cost_time, $success, $ip, $code, $msg);
-        
-        // 失败记录日志
-        if(!$success)
-        {
-            $this->logBuffer .= date('Y-m-d H:i:s',$time)."\t$ip\t$module::$interface\tcode:$code\tmsg:$msg\n";
-            if(strlen($this->logBuffer) >= self::MAX_LOG_BUFFER_SZIE)
-            {
-                $this->writeLogToDisk();
-            }
-        }
-    }
-    
-    /**
-     * 收集统计数据
-     * @param string $module
-     * @param string $interface
-     * @param float $cost_time
-     * @param int $success
-     * @param string $ip
-     * @param int $code
-     * @param string $msg
-     * @return void
-     */
-   protected function collectStatistics($module, $interface , $cost_time, $success, $ip, $code, $msg)
-   {
-       // 统计相关信息
-       if(!isset($this->statisticData[$ip]))
-       {
-           $this->statisticData[$ip] = array();
-       }
-       if(!isset($this->statisticData[$ip][$module]))
-       {
-           $this->statisticData[$ip][$module] = array();
-       }
-       if(!isset($this->statisticData[$ip][$module][$interface]))
-       {
-           $this->statisticData[$ip][$module][$interface] = array('code'=>array(), 'suc_cost_time'=>0, 'fail_cost_time'=>0, 'suc_count'=>0, 'fail_count'=>0);
-       }
-       if(!isset($this->statisticData[$ip][$module][$interface]['code'][$code]))
-       {
-           $this->statisticData[$ip][$module][$interface]['code'][$code] = 0;
-       }
-       $this->statisticData[$ip][$module][$interface]['code'][$code]++;
-       if($success)
-       {
-           $this->statisticData[$ip][$module][$interface]['suc_cost_time'] += $cost_time;
-           $this->statisticData[$ip][$module][$interface]['suc_count'] ++;
-       }
-       else
-       {
-           $this->statisticData[$ip][$module][$interface]['fail_cost_time'] += $cost_time;
-           $this->statisticData[$ip][$module][$interface]['fail_count'] ++;
-       }
-   }
-    
-   /**
-    * 将统计数据写入磁盘
-    * @return void
-    */
-   public function writeStatisticsToDisk()
-   {
-       $time = time();
-       // 循环将每个ip的统计数据写入磁盘
-       foreach($this->statisticData as $ip => $mod_if_data)
-       {
-           foreach($mod_if_data as $module=>$items)
-           {
-               // 文件夹不存在则创建一个
-               $file_dir = WORKERMAN_LOG_DIR . $this->statisticDir.$module;
-               if(!is_dir($file_dir))
-               {
-                   umask(0);
-                   mkdir($file_dir, 0777, true);
-               }
-               // 依次写入磁盘
-               foreach($items as $interface=>$data)
-               {
-                   file_put_contents($file_dir. "/{$interface}.".date('Y-m-d'), "$ip\t$time\t{$data['suc_count']}\t{$data['suc_cost_time']}\t{$data['fail_count']}\t{$data['fail_cost_time']}\t".json_encode($data['code'])."\n", FILE_APPEND | LOCK_EX);
-               }
-           }
-       }
-       // 清空统计
-       $this->statisticData = array();
-   }
-    
-    /**
-     * 将日志数据写入磁盘
-     * @return void
-     */    
-    public function writeLogToDisk()
-    {
-        // 没有统计数据则返回
-        if(empty($this->logBuffer))
-        {
-            return;
-        }
-        // 写入磁盘
-        file_put_contents(WORKERMAN_LOG_DIR . $this->logDir . date('Y-m-d'), $this->logBuffer, FILE_APPEND | LOCK_EX);
-        $this->logBuffer = '';
-    }
-    
-    /**
-     * 初始化
-     * 统计目录检查
-     * 初始化任务
-     * @see Man\Core.SocketWorker::onStart()
-     */
-    protected function onStart()
-    {
-        // 初始化目录
-        umask(0);
-        $statistic_dir = WORKERMAN_LOG_DIR . $this->statisticDir;
-        if(!is_dir($statistic_dir))
-        {
-            mkdir($statistic_dir, 0777, true);
-        }
-        $log_dir = WORKERMAN_LOG_DIR . $this->logDir;
-        if(!is_dir($log_dir))
-        {
-            mkdir($log_dir, 0777, true);
-        }
-        // 初始化任务
-        \Man\Core\Lib\Task::init($this->event);
-        // 定时保存统计数据
-        \Man\Core\Lib\Task::add(self::WRITE_PERIOD_LENGTH, array($this, 'writeStatisticsToDisk'));
-        \Man\Core\Lib\Task::add(self::WRITE_PERIOD_LENGTH, array($this, 'writeLogToDisk'));
-        // 定时清理不用的统计数据
-        \Man\Core\Lib\Task::add(self::CLEAR_PERIOD_LENGTH, array($this, 'clearDisk'), array(WORKERMAN_LOG_DIR . $this->statisticDir, self::EXPIRED_TIME));
-        \Man\Core\Lib\Task::add(self::CLEAR_PERIOD_LENGTH, array($this, 'clearDisk'), array(WORKERMAN_LOG_DIR . $this->logDir, self::EXPIRED_TIME));
-        
-    }
-    
-    /**
-     * 进程停止时需要将数据写入磁盘
-     * @see Man\Core.SocketWorker::onStop()
-     */
-    protected function onStop()
-    {
-        $this->writeLogToDisk();
-        $this->writeStatisticsToDisk();
-    }
-    
-    /**
-     * 清除磁盘数据
-     * @param string $file
-     * @param int $exp_time
-     */
-    public function clearDisk($file = null, $exp_time = 86400)
-    {
-        $time_now = time();
-        if(is_file($file))
-        {
-            $mtime = filemtime($file);
-            if(!$mtime)
-            {
-                $this->notice("filemtime $file fail");
-                return;
-            }
-            if($time_now - $mtime > $exp_time)
-            {
-                unlink($file);
-            }
-            return;
-        }
-        foreach (glob($file."/*") as $file_name) 
-        {
-            $this->clearDisk($file_name, $exp_time);
-        }
-    }
-    
-} 
-
-/**
- *
- * struct statisticPortocol
- * {
- *     unsigned char module_name_len;
- *     unsigned char interface_name_len;
- *     float cost_time;
- *     unsigned char success;
- *     int code;
- *     unsigned short msg_len;
- *     unsigned int time;
- *     char[module_name_len] module_name;
- *     char[interface_name_len] interface_name;
- *     char[msg_len] msg;
- * }
- *
- * @author workerman.net
- */
-class StatisticProtocol
-{
-    /**
-     * 包头长度
-     * @var integer
-     */
-    const PACKAGE_FIXED_LENGTH = 17;
-
-    /**
-     * udp 包最大长度
-     * @var integer
-     */
-    const MAX_UDP_PACKGE_SIZE  = 65507;
-
-    /**
-     * char类型能保存的最大数值
-     * @var integer
-     */
-    const MAX_CHAR_VALUE = 255;
-
-    /**
-     *  usigned short 能保存的最大数值
-     * @var integer
-     */
-    const MAX_UNSIGNED_SHORT_VALUE = 65535;
-
-    /**
-     * 编码
-     * @param string $module
-     * @param string $interface
-     * @param float $cost_time
-     * @param int $success
-     * @param int $code
-     * @param string $msg
-     * @return string
-     */
-    public static function encode($module, $interface , $cost_time, $success,  $code = 0,$msg = '')
-    {
-        // 防止模块名过长
-        if(strlen($module) > self::MAX_CHAR_VALUE)
-        {
-            $module = substr($module, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止接口名过长
-        if(strlen($interface) > self::MAX_CHAR_VALUE)
-        {
-            $interface = substr($interface, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止msg过长
-        $module_name_length = strlen($module);
-        $interface_name_length = strlen($interface);
-        $avalible_size = self::MAX_UDP_PACKGE_SIZE - self::PACKAGE_FIXED_LENGTH - $module_name_length - $interface_name_length;
-        if(strlen($msg) > $avalible_size)
-        {
-            $msg = substr($msg, 0, $avalible_size);
-        }
-
-        // 打包
-        return pack('CCfCNnN', $module_name_length, $interface_name_length, $cost_time, $success ? 1 : 0, $code, strlen($msg), time()).$module.$interface.$msg;
-    }
-     
-    /**
-     * 解包
-     * @param string $bin_data
-     * @return array
-     */
-    public static function decode($bin_data)
-    {
-        // 解包
-        $data = unpack("Cmodule_name_len/Cinterface_name_len/fcost_time/Csuccess/Ncode/nmsg_len/Ntime", $bin_data);
-        $module = substr($bin_data, self::PACKAGE_FIXED_LENGTH, $data['module_name_len']);
-        $interface = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'], $data['interface_name_len']);
-        $msg = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'] + $data['interface_name_len']);
-        return array(
-                'module'          => $module,
-                'interface'        => $interface,
-                'cost_time' => $data['cost_time'],
-                'success'           => $data['success'],
-                'time'                => $data['time'],
-                'code'               => $data['code'],
-                'msg'                => $msg,
-        );
-    }
-}

+ 0 - 192
applications/Statistics/Clients/StatisticClient.php

@@ -1,192 +0,0 @@
-<?php
-/**
- * 统计客户端
- * @author workerman.net
- */
-class StatisticClient
-{
-    /**
-     * [module=>[interface=>time_start, interface=>time_start ...], module=>[interface=>time_start ..], ... ]
-     * @var array
-     */
-    protected static $timeMap = array();
-    
-    /**
-     * 模块接口上报消耗时间记时
-     * @param string $module
-     * @param string $interface
-     * @return void
-     */
-    public static function tick($module = '', $interface = '')
-    {
-        return self::$timeMap[$module][$interface] = microtime(true);
-    }
-    
-    /**
-     * 上报统计数据
-     * @param string $module
-     * @param string $interface
-     * @param bool $success
-     * @param int $code
-     * @param string $msg
-     * @param string $report_address
-     * @return boolean
-     */
-    public static function report($module, $interface, $success, $code, $msg, $report_address = '')
-    {
-        $report_address = $report_address ? $report_address : 'udp://127.0.0.1:55656';
-        if(isset(self::$timeMap[$module][$interface]) && self::$timeMap[$module][$interface] > 0)
-        {
-            $time_start = self::$timeMap[$module][$interface];
-            self::$timeMap[$module][$interface] = 0;
-        }
-        else if(isset(self::$timeMap['']['']) && self::$timeMap[''][''] > 0)
-        {
-            $time_start = self::$timeMap[''][''];
-            self::$timeMap[''][''] = 0;
-        }
-        else
-        {
-            $time_start = microtime(true);
-        }
-         
-        $cost_time = microtime(true) - $time_start;
-        
-        $bin_data = StatisticProtocol::encode($module, $interface, $cost_time, $success, $code, $msg);
-        
-        return self::sendData($report_address, $bin_data);
-    }
-    
-    /**
-     * 发送数据给统计系统
-     * @param string $address
-     * @param string $buffer
-     * @return boolean
-     */
-    public static function sendData($address, $buffer)
-    {
-        $socket = stream_socket_client($address);
-        if(!$socket)
-        {
-            return false;
-        }
-        return stream_socket_sendto($socket, $buffer) == strlen($buffer);
-    }
-    
-}
-
-/**
- *
- * struct statisticPortocol
- * {
- *     unsigned char module_name_len;
- *     unsigned char interface_name_len;
- *     float cost_time;
- *     unsigned char success;
- *     int code;
- *     unsigned short msg_len;
- *     unsigned int time;
- *     char[module_name_len] module_name;
- *     char[interface_name_len] interface_name;
- *     char[msg_len] msg;
- * }
- *
- * @author workerman.net
- */
-class StatisticProtocol
-{
-    /**
-     * 包头长度
-     * @var integer
-     */
-    const PACKAGE_FIXED_LENGTH = 17;
-
-    /**
-     * udp 包最大长度
-     * @var integer
-     */
-    const MAX_UDP_PACKGE_SIZE  = 65507;
-
-    /**
-     * char类型能保存的最大数值
-     * @var integer
-     */
-    const MAX_CHAR_VALUE = 255;
-
-    /**
-     *  usigned short 能保存的最大数值
-     * @var integer
-     */
-    const MAX_UNSIGNED_SHORT_VALUE = 65535;
-
-    /**
-     * 编码
-     * @param string $module
-     * @param string $interface
-     * @param float $cost_time
-     * @param int $success
-     * @param int $code
-     * @param string $msg
-     * @return string
-     */
-    public static function encode($module, $interface , $cost_time, $success,  $code = 0,$msg = '')
-    {
-        // 防止模块名过长
-        if(strlen($module) > self::MAX_CHAR_VALUE)
-        {
-            $module = substr($module, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止接口名过长
-        if(strlen($interface) > self::MAX_CHAR_VALUE)
-        {
-            $interface = substr($interface, 0, self::MAX_CHAR_VALUE);
-        }
-
-        // 防止msg过长
-        $module_name_length = strlen($module);
-        $interface_name_length = strlen($interface);
-        $avalible_size = self::MAX_UDP_PACKGE_SIZE - self::PACKAGE_FIXED_LENGTH - $module_name_length - $interface_name_length;
-        if(strlen($msg) > $avalible_size)
-        {
-            $msg = substr($msg, 0, $avalible_size);
-        }
-
-        // 打包
-        return pack('CCfCNnN', $module_name_length, $interface_name_length, $cost_time, $success ? 1 : 0, $code, strlen($msg), time()).$module.$interface.$msg;
-    }
-     
-    /**
-     * 解包
-     * @param string $bin_data
-     * @return array
-     */
-    public static function decode($bin_data)
-    {
-        // 解包
-        $data = unpack("Cmodule_name_len/Cinterface_name_len/fcost_time/Csuccess/Ncode/nmsg_len/Ntime", $bin_data);
-        $module = substr($bin_data, self::PACKAGE_FIXED_LENGTH, $data['module_name_len']);
-        $interface = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'], $data['interface_name_len']);
-        $msg = substr($bin_data, self::PACKAGE_FIXED_LENGTH + $data['module_name_len'] + $data['interface_name_len']);
-        return array(
-                'module'          => $module,
-                'interface'        => $interface,
-                'cost_time' => $data['cost_time'],
-                'success'           => $data['success'],
-                'time'                => $data['time'],
-                'code'               => $data['code'],
-                'msg'                => $msg,
-        );
-    }
-
-}
-
-if(PHP_SAPI == 'cli' && isset($argv[0]) && $argv[0] == basename(__FILE__))
-{
-    StatisticClient::tick("TestModule", 'TestInterface');
-    usleep(rand(10000, 600000));
-    $success = rand(0,1);
-    $code = rand(300, 400);
-    $msg = '这个是测试消息';
-    var_export(StatisticClient::report('TestModule', 'TestInterface', $success, $code, $msg));;
-}

+ 0 - 0
applications/Statistics/Config/Cache/empty


+ 0 - 13
applications/Statistics/Config/Config.php

@@ -1,13 +0,0 @@
-<?php
-namespace Statistics\Config;
-class Config
-{
-    // 数据源端口,会向这个端口发送udp广播获取ip,然后从这个端口以tcp协议获取统计信息
-    public static $ProviderPort = 55858;
-    
-    // 管理员用户名,用户名密码都为空字符串时说明不用验证
-    public static $adminName = '';
-    
-    // 管理员密码,用户名密码都为空字符串时说明不用验证
-    public static $adminPassword = '';
-}

+ 0 - 10
applications/Statistics/Lib/Cache.php

@@ -1,10 +0,0 @@
-<?php
-namespace Statistics\Lib;
-class Cache
-{
-    public static $statisticDataCache = array();
-    public static $ServerIpList = array();
-    public static $modulesDataCache = array();
-    public static $lastFailedIpArray = array();
-    public static $lastSuccessIpArray = array();
-}

+ 0 - 142
applications/Statistics/Lib/functions.php

@@ -1,142 +0,0 @@
-<?php
-
-/**
- * 批量请求
- * @param array $request_buffer_array ['ip:port'=>req_buf, 'ip:port'=>req_buf, ...]
- * @return multitype:unknown string
- */
-function multiRequest($request_buffer_array)
-{
-    \Statistics\Lib\Cache::$lastSuccessIpArray = array();
-    $client_array = $sock_to_ip = $ip_list = array();
-    foreach($request_buffer_array as $address => $buffer)
-    {
-        list($ip, $port) = explode(':', $address);
-        $ip_list[$ip] = $ip;
-        $client = stream_socket_client("tcp://$address", $errno, $errmsg, 1);
-        if(!$client)
-        {
-            continue;
-        }
-        $client_array[$address] = $client;
-        stream_set_timeout($client_array[$address], 0, 100000);
-        fwrite($client_array[$address], $buffer);
-        stream_set_blocking($client_array[$address], 0);
-        $sock_to_address[(int)$client] = $address;
-    }
-    $read = $client_array;
-    $write = $except = $read_buffer = array();
-    $time_start = microtime(true);
-    $timeout = 0.99;
-    // 轮询处理数据
-    while(count($read) > 0)
-    {
-        if(@stream_select($read, $write, $except, 0, 200000))
-        {
-            foreach($read as $socket)
-            {
-                $address = $sock_to_address[(int)$socket];
-                $buf = fread($socket, 8192);
-                if(!$buf)
-                {
-                    if(feof($socket))
-                    {
-                        unset($client_array[$address]);
-                    }
-                    continue;
-                }
-                if(!isset($read_buffer[$address]))
-                {
-                    $read_buffer[$address] = $buf;
-                }
-                else
-                {
-                    $read_buffer[$address] .= $buf;
-                }
-                // 数据接收完毕
-                if(($len = strlen($read_buffer[$address])) && $read_buffer[$address][$len-1] === "\n")
-                {
-                    unset($client_array[$address]);
-                }
-            }
-        }
-        // 超时了
-        if(microtime(true) - $time_start > $timeout)
-        {
-            break;
-        }
-        $read = $client_array;
-    }
-
-    foreach($read_buffer as $address => $buf)
-    {
-        list($ip, $port) = explode(':', $address);
-        \Statistics\Lib\Cache::$lastSuccessIpArray[$ip] = $ip;
-    }
-
-     \Statistics\Lib\Cache::$lastFailedIpArray = array_diff($ip_list,  \Statistics\Lib\Cache::$lastSuccessIpArray);
-
-    ksort($read_buffer);
-
-    return $read_buffer;
-}
-
-/**
- * 检查是否登录
- */
-function check_auth()
-{
-    // 如果配置中管理员用户名密码为空则说明不用验证
-    if(Statistics\Config\Config::$adminName == '' && Statistics\Config\Config::$adminPassword == '')
-    {
-        return true;
-    }
-    // 进入验证流程
-    _session_start();
-    if(!isset($_SESSION['admin']))
-    {
-        if(!isset($_POST['admin_name']) || !isset($_POST['admin_password']))
-        {
-            include ST_ROOT . '/Views/login.tpl.php';
-            _exit();
-        }
-        else 
-        {
-            $admin_name = $_POST['admin_name'];
-            $admin_password = $_POST['admin_password'];
-            if($admin_name != Statistics\Config\Config::$adminName || $admin_password != Statistics\Config\Config::$adminPassword)
-            {
-                $msg = "用户名或者密码不正确";
-                include ST_ROOT . '/Views/login.tpl.php';
-                _exit();
-            }
-            $_SESSION['admin'] = $admin_name;
-        }
-    }
-    return true;
-}
-
-/**
- * 启动session,兼容fpm
- */
-function _session_start()
-{
-    if(defined('WORKERMAN_ROOT_DIR'))
-    {
-        return \Man\Common\Protocols\Http\session_start();
-    }
-    return session_start();
-}
-
-/**
- * 退出
- * @param string $str
- */
-function _exit($str = '')
-{
-    if(defined('WORKERMAN_ROOT_DIR'))
-    {
-        return \Man\Common\Protocols\Http\jump_exit($str);
-    }
-    return exit($str);
-}

+ 0 - 118
applications/Statistics/Modules/admin.php

@@ -1,118 +0,0 @@
-<?php
-namespace Statistics\Modules;
-
-function admin()
-{
-    $act = isset($_GET['act'])? $_GET['act'] : 'home';
-    $err_msg = $notice_msg = $suc_msg = $ip_list_str = '';
-    $action = 'save_server_list';
-    switch($act)
-    {
-        case 'detect_server':
-            // 创建udp socket
-            $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
-            socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
-            $buffer = json_encode(array('cmd'=>'REPORT_IP'))."\n";
-            // 广播
-            socket_sendto($socket, $buffer, strlen($buffer), 0, '255.255.255.255', \Statistics\Config\Config::$ProviderPort);
-            // 超时相关
-            $time_start = microtime(true);
-            $global_timeout = 1;
-            $ip_list = array();
-            $recv_timeout = array('sec'=>0,'usec'=>8000);
-            socket_set_option($socket,SOL_SOCKET,SO_RCVTIMEO,$recv_timeout);
-            
-            // 循环读数据
-            while(microtime(true) - $time_start < $global_timeout)
-            {
-                $buf = $host = $port = '';
-                if(@socket_recvfrom($socket, $buf, 65535, 0, $host, $port))
-                {
-                    $ip_list[$host] = $host;
-                }
-            }
-            
-            // 过滤掉已经保存的ip
-            $count = 0;
-            foreach($ip_list as $ip)
-            {
-                if(!isset(\Statistics\Lib\Cache::$ServerIpList[$ip]))
-                {
-                    $ip_list_str .= $ip."\r\n";
-                    $count ++;
-                }
-            }
-            $action = 'add_to_server_list';
-            $notice_msg = "探测到{$count}个新数据源";
-            break;
-        case 'add_to_server_list':
-            if(empty($_POST['ip_list']))
-            {
-                $err_msg = "保存的ip列表为空";
-                break;
-            }
-            $ip_list = explode("\n", $_POST['ip_list']);
-            if($ip_list)
-            {
-                foreach($ip_list as $ip)
-                {
-                    $ip = trim($ip);
-                    if(false !== ip2long($ip))
-                    {
-                        \Statistics\Lib\Cache::$ServerIpList[$ip] = $ip;
-                    }
-                }
-            }
-            $suc_msg = "添加成功";
-            foreach(\Statistics\Lib\Cache::$ServerIpList as $ip)
-            {
-                $ip_list_str .= $ip."\r\n";
-            }
-            saveServerIpListToCache();
-            break;
-        case 'save_server_list':
-            if(empty($_POST['ip_list']))
-            {
-                $err_msg = "保存的ip列表为空";
-                break;
-            }
-            \Statistics\Lib\Cache::$ServerIpList = array();
-            $ip_list = explode("\n", $_POST['ip_list']);
-            if($ip_list)
-            {
-                foreach($ip_list as $ip)
-                {
-                    $ip = trim($ip);
-                    if(false !== ip2long($ip))
-                    {
-                        \Statistics\Lib\Cache::$ServerIpList[$ip] = $ip;
-                    }
-                }
-            }
-            $suc_msg = "保存成功";
-            foreach(\Statistics\Lib\Cache::$ServerIpList as $ip)
-            {
-                $ip_list_str .= $ip."\r\n";
-            }
-            saveServerIpListToCache();
-            break;
-        default:
-            foreach(\Statistics\Lib\Cache::$ServerIpList as $ip)
-            {
-                $ip_list_str .= $ip."\r\n";
-            }
-    }
-    
-    include ST_ROOT . '/Views/header.tpl.php';
-    include ST_ROOT . '/Views/admin.tpl.php';
-    include ST_ROOT . '/Views/footer.tpl.php';
-}
-
-function saveServerIpListToCache()
-{
-    foreach(glob(ST_ROOT . '/Config/Cache/*.iplist.cache.php') as $php_file)
-    {
-        unlink($php_file);
-    }
-    file_put_contents(ST_ROOT . '/Config/Cache/'.time().'.iplist.cache.php', "<?php\n\\Statistics\\Lib\\Cache::\$ServerIpList=".var_export(\Statistics\Lib\Cache::$ServerIpList,true).';');
-}

+ 0 - 64
applications/Statistics/Modules/logger.php

@@ -1,64 +0,0 @@
-<?php
-namespace Statistics\Modules;
-function logger($module, $interface, $date, $start_time, $offset, $count)
-{
-        $module_str ='';
-        foreach(\Statistics\Lib\Cache::$modulesDataCache as $mod => $interfaces)
-        {
-                if($mod == 'WorkerMan')
-                {
-                    continue;
-                }
-                $module_str .= '<li><a href="/?fn=statistic&module='.$mod.'">'.$mod.'</a></li>';
-                if($module == $mod)
-                {
-                    foreach ($interfaces as $if)
-                    {
-                        $module_str .= '<li>&nbsp;&nbsp;<a href="/?fn=statistic&module='.$mod.'&interface='.$if.'">'.$if.'</a></li>';
-                    }
-                }
-        } 
-        
-        $log_data_arr = getStasticLog($module, $interface, $start_time ,$offset, $count);
-        unset($_GET['fn'], $_GET['ip'], $_GET['offset']);
-        $log_str = '';
-        foreach($log_data_arr as $address => $log_data)
-        {
-            list($ip, $port) = explode(':', $address);
-            $log_str .= $log_data['data'];
-            $_GET['ip'][] = $ip;
-            $_GET['offset'][] = $log_data['offset'];
-        }
-        $log_str = nl2br(str_replace("\n", "\n\n", $log_str));
-        $next_page_url = http_build_query($_GET);
-        $log_str .= "</br><center><a href='/?fn=logger&$next_page_url'>下一页</a></center>";
-
-        include ST_ROOT . '/Views/header.tpl.php';
-        include ST_ROOT . '/Views/log.tpl.php';
-        include ST_ROOT . '/Views/footer.tpl.php';
-}
-
-function getStasticLog($module, $interface , $start_time, $offset = '', $count = 10)
-{
-    $ip_list = (!empty($_GET['ip']) && is_array($_GET['ip'])) ? $_GET['ip'] : \Statistics\Lib\Cache::$ServerIpList;
-    $offset_list = (!empty($_GET['offset']) && is_array($_GET['offset'])) ? $_GET['offset'] : array();
-    $port = \Statistics\Config\Config::$ProviderPort;
-    $request_buffer_array = array();
-    foreach($ip_list as $key=>$ip)
-    {
-        $offset = isset($offset_list[$key]) ? $offset_list[$key] : 0;
-        $request_buffer_array["$ip:$port"] = json_encode(array('cmd'=>'get_log', 'module'=>$module, 'interface'=>$interface, 'start_time'=>$start_time,  'offset'=>$offset, 'count'=>$count));
-    }
-
-    $read_buffer_array = multiRequest($request_buffer_array);
-    ksort($read_buffer_array);
-    foreach($read_buffer_array as $address => $buf)
-    {
-        list($ip, $port) = explode(':', $address);
-        $body_data = json_decode(trim($buf), true);
-        $log_data = isset($body_data['data']) ? $body_data['data'] : '';
-        $offset = isset($body_data['offset']) ? $body_data['offset'] : 0;
-        $read_buffer_array[$address] = array('offset'=>$offset,'data'=>$log_data);
-    }
-    return $read_buffer_array;
-}

+ 0 - 273
applications/Statistics/Modules/main.php

@@ -1,273 +0,0 @@
-<?php
-namespace Statistics\Modules;
-function main($module, $interface, $date, $start_time, $offset)
-{
-    $err_msg = $notice_msg=  '';
-    $module = 'WorkerMan';
-    $interface = 'Statistics';
-    $today = date('Y-m-d');
-    $time_now = time();
-    multiRequestStAndModules($module, $interface, $date);
-    $all_st_str = '';
-    if(is_array(\Statistics\Lib\Cache::$statisticDataCache['statistic']))
-    {
-        foreach(\Statistics\Lib\Cache::$statisticDataCache['statistic'] as $ip=>$st_str)
-        {
-            $all_st_str .= $st_str;
-        }
-    }
-    
-    $code_map = array();
-    $data = formatSt($all_st_str, $date, $code_map);
-    $interface_name = '整体';
-    $success_series_data = $fail_series_data = $success_time_series_data = $fail_time_series_data = array();
-    $total_count = $fail_count = 0;
-    foreach($data as $time_point=>$item)
-    {
-        if($item['total_count'])
-        {
-            $success_series_data[] = "[".($time_point*1000).",{$item['total_count']}]";
-            $total_count += $item['total_count'];
-        }
-        $fail_series_data[] = "[".($time_point*1000).",{$item['fail_count']}]";
-        $fail_count += $item['fail_count'];
-        if($item['total_avg_time'])
-        {
-            $success_time_series_data[] = "[".($time_point*1000).",{$item['total_avg_time']}]";
-        }
-        $fail_time_series_data[] = "[".($time_point*1000).",{$item['fail_avg_time']}]";
-    }
-    $success_series_data = implode(',', $success_series_data);
-    $fail_series_data = implode(',', $fail_series_data);
-    $success_time_series_data = implode(',', $success_time_series_data);
-    $fail_time_series_data = implode(',', $fail_time_series_data);
-    
-    // 总体成功率
-    $global_rate =  $total_count ? round((($total_count - $fail_count)/$total_count)*100, 4) : 100;
-    // 返回码分布
-    $code_pie_data = '';
-    $code_pie_array = array(); 
-    unset($code_map[0]);
-    if(empty($code_map))
-    {
-        $code_map[0] = $total_count > 0 ? $total_count : 1;
-    }
-    if(is_array($code_map))
-    {
-        $total_item_count = array_sum($code_map);
-        foreach($code_map as $code=>$count)
-        {
-            $code_pie_array[] = "[\"$code:{$count}个\", ".round($count*100/$total_item_count, 4)."]";
-        }
-        $code_pie_data = implode(',', $code_pie_array);
-    }
-    
-    unset($_GET['start_time'], $_GET['end_time'], $_GET['date'], $_GET['fn']);
-    $query = http_build_query($_GET);
-    
-    // 删除末尾0的记录
-    if($today == $date)
-    {
-        while(!empty($data) && ($item = end($data)) && $item['total_count'] == 0 && ($key = key($data)) &&  $time_now < $key)
-        {
-            unset($data[$key]);
-        }
-    }
-    
-    $table_data = '';
-    if($data)
-    {
-        $first_line = true;
-        foreach($data as $item)
-        {
-            if($first_line)
-            {
-                $first_line = false;
-                if($item['total_count'] == 0)
-                {
-                    continue;
-                }
-            }
-            $html_class = 'class="danger"';
-            if($item['total_count'] == 0)
-            {
-                $html_class = '';
-            }
-            elseif($item['precent']>=99.99)
-            {
-                $html_class = 'class="success"';
-            }
-            elseif($item['precent']>=99)
-            {
-                $html_class = '';
-            }
-            elseif($item['precent']>=98)
-            {
-                $html_class = 'class="warning"';
-            }
-            $table_data .= "\n<tr $html_class>
-                       <td>{$item['time']}</td>
-                       <td>{$item['total_count']}</td>
-                        <td> {$item['total_avg_time']}</td>
-                        <td>{$item['suc_count']}</td>
-                        <td>{$item['suc_avg_time']}</td>
-                        <td>".($item['fail_count']>0?("<a href='/?fn=logger&$query&start_time=".(strtotime($item['time'])-300)."&end_time=".(strtotime($item['time']))."'>{$item['fail_count']}</a>"):$item['fail_count'])."</td>
-                        <td>{$item['fail_avg_time']}</td>
-                        <td>{$item['precent']}%</td>
-                    </tr>
-            ";
-        }
-    }
-    
-    // date btn
-    $date_btn_str = '';
-    for($i=13;$i>=1;$i--)
-    {
-        $the_time = strtotime("-$i day");
-        $the_date = date('Y-m-d',$the_time);
-        $html_the_date = $date == $the_date ? "<b>$the_date</b>" : $the_date;
-        $date_btn_str .= '<a href="/?date='."$the_date&$query".'" class="btn '.$html_class.'" type="button">'.$html_the_date.'</a>';
-        if($i == 7)
-        {
-            $date_btn_str .= '</br>';
-        }
-    }
-    $the_date = date('Y-m-d');
-    $html_the_date = $date == $the_date ? "<b>$the_date</b>" : $the_date;
-    $date_btn_str .=  '<a href="/?date='."$the_date&$query".'" class="btn" type="button">'.$html_the_date.'</a>';
-    
-    if( \Statistics\Lib\Cache::$lastFailedIpArray)
-    {
-        $err_msg = '<strong>无法从以下数据源获取数据:</strong>';
-        foreach (\Statistics\Lib\Cache::$lastFailedIpArray as $ip)
-        {
-            $err_msg .= $ip.'::'.\Statistics\Config\Config::$ProviderPort . '&nbsp;';
-        }
-    }
-    
-    if(empty(\Statistics\Lib\Cache::$ServerIpList))
-    {
-        $notice_msg = <<<EOT
-<h4>数据源为空</h4>
-您可以 <a href="/?fn=admin&act=detect_server" class="btn" type="button"><strong>探测数据源</strong></a>或者<a href="/?fn=admin" class="btn" type="button"><strong>添加数据源</strong></a>
-EOT;
-    }
-
-    include ST_ROOT . '/Views/header.tpl.php';
-    include ST_ROOT . '/Views/main.tpl.php';
-    include ST_ROOT . '/Views/footer.tpl.php';
-}
-
-function multiRequestStAndModules($module, $interface, $date)
-{
-    \Statistics\Lib\Cache::$statisticDataCache['statistic'] = '';
-    $buffer = json_encode(array('cmd'=>'get_statistic','module'=>$module, 'interface'=>$interface, 'date'=>$date))."\n";
-    $ip_list = (!empty($_GET['ip']) && is_array($_GET['ip'])) ? $_GET['ip'] : \Statistics\Lib\Cache::$ServerIpList;
-    $reqest_buffer_array = array();
-    $port = \Statistics\Config\Config::$ProviderPort;;
-    foreach($ip_list as $ip)
-    {
-        $reqest_buffer_array["$ip:$port"] = $buffer;
-    }
-    $read_buffer_array = multiRequest($reqest_buffer_array);
-    foreach($read_buffer_array as $address => $buf)
-    {
-        list($ip, $port) = explode(':',$address);
-        $body_data = json_decode(trim($buf), true);
-        $statistic_data = isset($body_data['statistic']) ? $body_data['statistic'] : '';
-        $modules_data = isset($body_data['modules']) ? $body_data['modules'] : array();
-        // 整理modules
-        foreach($modules_data as $mod => $interfaces)
-        {
-            if(!isset(\Statistics\Lib\Cache::$modulesDataCache[$mod]))
-            {
-                \Statistics\Lib\Cache::$modulesDataCache[$mod] = array();
-            }
-            foreach($interfaces as $if)
-            {
-                \Statistics\Lib\Cache::$modulesDataCache[$mod][$if] = $if;
-            }
-        }
-        \Statistics\Lib\Cache::$statisticDataCache['statistic'][$ip] = $statistic_data;
-    }
-}
-
-function formatSt($str, $date, &$code_map)
-{
-    // time:[suc_count:xx,suc_cost_time:xx,fail_count:xx,fail_cost_time:xx]
-    $st_data = $code_map = array();
-    $st_explode = explode("\n", $str);
-    // 汇总计算
-    foreach($st_explode as $line)
-    {
-        // line = IP time suc_count suc_cost_time fail_count fail_cost_time code_json
-        $line_data = explode("\t", $line);
-        if(!isset($line_data[5]))
-        {
-            continue;
-        }
-        $time_line = $line_data[1];
-        $time_line = ceil($time_line/300)*300;
-        $suc_count = $line_data[2];
-        $suc_cost_time = $line_data[3];
-        $fail_count = $line_data[4];
-        $fail_cost_time = $line_data[5];
-        $tmp_code_map = json_decode($line_data[6], true);
-        if(!isset($st_data[$time_line]))
-        {
-            $st_data[$time_line] = array('suc_count'=>0, 'suc_cost_time'=>0, 'fail_count'=>0, 'fail_cost_time'=>0);
-        }
-        $st_data[$time_line]['suc_count'] += $suc_count;
-        $st_data[$time_line]['suc_cost_time'] += $suc_cost_time;
-        $st_data[$time_line]['fail_count'] += $fail_count;
-        $st_data[$time_line]['fail_cost_time'] += $fail_cost_time;
-        
-        if(is_array($tmp_code_map))
-        {
-            foreach($tmp_code_map as $code=>$count)
-            {
-                if(!isset($code_map[$code]))
-                {
-                    $code_map[$code] = 0;
-                }
-                $code_map[$code] += $count;
-            }
-        }
-    }
-    // 按照时间排序
-    ksort($st_data);
-    // time => [total_count:xx,suc_count:xx,suc_avg_time:xx,fail_count:xx,fail_avg_time:xx,percent:xx]
-    $data = array();
-    // 计算成功率 耗时
-    foreach($st_data as $time_line=>$item)
-    {
-        $data[$time_line] = array(
-                'time'          => date('Y-m-d H:i:s', $time_line),
-                'total_count'   => $item['suc_count']+$item['fail_count'],
-                'total_avg_time'=> $item['suc_count']+$item['fail_count'] == 0 ? 0 : round(($item['suc_cost_time']+$item['fail_cost_time'])/($item['suc_count']+$item['fail_count']), 6),
-                'suc_count'     => $item['suc_count'],
-                'suc_avg_time'  => $item['suc_count'] == 0 ? $item['suc_count'] : round($item['suc_cost_time']/$item['suc_count'], 6),
-                'fail_count'    => $item['fail_count'],
-                'fail_avg_time' => $item['fail_count'] == 0 ? 0 : round($item['fail_cost_time']/$item['fail_count'], 6),
-                'precent'       => $item['suc_count']+$item['fail_count'] == 0 ? 0 : round(($item['suc_count']*100/($item['suc_count']+$item['fail_count'])), 4),
-        );
-    }
-    $time_point =  strtotime($date);
-    for($i=0;$i<288;$i++)
-    {
-        $data[$time_point] = isset($data[$time_point]) ? $data[$time_point] :
-        array(
-            'time' => date('Y-m-d H:i:s', $time_point),
-            'total_count'   => 0,
-            'total_avg_time'=> 0,
-            'suc_count'     => 0,
-            'suc_avg_time'  => 0,
-            'fail_count'    => 0,
-            'fail_avg_time' => 0,
-            'precent'       => 100,
-        );
-        $time_point +=300;
-    }
-    ksort($data);
-    return $data;
-}

+ 0 - 43
applications/Statistics/Modules/setting.php

@@ -1,43 +0,0 @@
-<?php
-namespace Statistics\Modules;
-
-function setting()
-{
-    $act = isset($_GET['act'])? $_GET['act'] : 'home';
-    $err_msg = $notice_msg = $suc_msg = $ip_list_str = '';
-    switch($act)
-    {
-        case 'save':
-            if(empty($_POST['detect_port']))
-            {
-                $err_msg = "探测端口不能为空";
-                break;
-            }
-           $detect_port = (int)$_POST['detect_port'];
-           
-           if($detect_port<0 || $detect_port > 65535)
-           {
-               $err_msg = "探测端口不合法";
-               break;
-           }
-            $suc_msg = "保存成功";
-            \Statistics\Config\Config::$ProviderPort = $detect_port;
-            saveDetectPortToCache();
-            break;
-        default:
-            $detect_port = \Statistics\Config\Config::$ProviderPort;
-    }
-    
-    include ST_ROOT . '/Views/header.tpl.php';
-    include ST_ROOT . '/Views/setting.tpl.php';
-    include ST_ROOT . '/Views/footer.tpl.php';
-}
-
-function saveDetectPortToCache()
-{
-    foreach(glob(ST_ROOT . '/Config/Cache/*detect_port.cache.php') as $php_file)
-    {
-        unlink($php_file);
-    }
-    file_put_contents(ST_ROOT . '/Config/Cache/'.time().'.detect_port.cache.php', "<?php\n\\Statistics\\Config\\Config::\$ProviderPort=".var_export(\Statistics\Config\Config::$ProviderPort,true).';');
-}

+ 0 - 146
applications/Statistics/Modules/statistic.php

@@ -1,146 +0,0 @@
-<?php
-namespace Statistics\Modules;
-function statistic($module, $interface, $date, $start_time, $offset)
-{
-    $err_msg = '';
-    $today = date('Y-m-d');
-    $time_now = time();
-    multiRequestStAndModules($module, $interface, $date);
-    $all_st_str = '';
-    if(is_array(\Statistics\Lib\Cache::$statisticDataCache['statistic']))
-    {
-        foreach(\Statistics\Lib\Cache::$statisticDataCache['statistic'] as $ip=>$st_str)
-        {
-            $all_st_str .= $st_str;
-        }
-    }
-
-    $code_map = array();
-    $data = formatSt($all_st_str, $date, $code_map);
-    $interface_name = "$module::$interface";
-    $success_series_data = $fail_series_data = $success_time_series_data = $fail_time_series_data = array();
-    $total_count = $fail_count = 0;
-    foreach($data as $time_point=>$item)
-    {
-        if($item['total_count'])
-        {
-            $success_series_data[] = "[".($time_point*1000).",{$item['total_count']}]";
-            $total_count += $item['total_count'];
-        }
-        $fail_series_data[] = "[".($time_point*1000).",{$item['fail_count']}]";
-        $fail_count += $item['fail_count'];
-        if($item['total_avg_time'])
-        {
-            $success_time_series_data[] = "[".($time_point*1000).",{$item['total_avg_time']}]";
-        }
-        $fail_time_series_data[] = "[".($time_point*1000).",{$item['fail_avg_time']}]";
-    }
-    $success_series_data = implode(',', $success_series_data);
-    $fail_series_data = implode(',', $fail_series_data);
-    $success_time_series_data = implode(',', $success_time_series_data);
-    $fail_time_series_data = implode(',', $fail_time_series_data);
-
-    unset($_GET['start_time'], $_GET['end_time'], $_GET['date'], $_GET['fn']);
-    $query = http_build_query($_GET);
-
-    // 删除末尾0的记录
-    if($today == $date)
-    {
-        while(!empty($data) && ($item = end($data)) && $item['total_count'] == 0 && ($key = key($data)) &&  $time_now < $key)
-        {
-            unset($data[$key]);
-        }
-    }
-
-    $table_data = $html_class = '';
-    if($data)
-    {
-        $first_line = true;
-        foreach($data as $item)
-        {
-            if($first_line)
-            {
-                $first_line = false;
-                if($item['total_count'] == 0)
-                {
-                    continue;
-                }
-            }
-            $html_class = 'class="danger"';
-            if($item['total_count'] == 0)
-            {
-                $html_class = '';
-            }
-            elseif($item['precent']>=99.99)
-            {
-                $html_class = 'class="success"';
-            }
-            elseif($item['precent']>=99)
-            {
-                $html_class = '';
-            }
-            elseif($item['precent']>=98)
-            {
-                $html_class = 'class="warning"';
-            }
-            $table_data .= "\n<tr $html_class>
-            <td>{$item['time']}</td>
-            <td>{$item['total_count']}</td>
-            <td> {$item['total_avg_time']}</td>
-            <td>{$item['suc_count']}</td>
-            <td>{$item['suc_avg_time']}</td>
-            <td>".($item['fail_count']>0?("<a href='/?fn=logger&$query&start_time=".(strtotime($item['time'])-300)."&end_time=".(strtotime($item['time']))."'>{$item['fail_count']}</a>"):$item['fail_count'])."</td>
-            <td>{$item['fail_avg_time']}</td>
-            <td>{$item['precent']}%</td>
-            </tr>
-            ";
-        }
-        }
-
-        // date btn
-        $date_btn_str = '';
-        for($i=13;$i>=1;$i--)
-        {
-        $the_time = strtotime("-$i day");
-        $the_date = date('Y-m-d',$the_time);
-        $html_the_date = $date == $the_date ? "<b>$the_date</b>" : $the_date;
-        $date_btn_str .= '<a href="/?fn=statistic&date='."$the_date&$query".'" class="btn '.$html_class.'" type="button">'.$html_the_date.'</a>';
-        if($i == 7)
-        {
-            $date_btn_str .= '</br>';
-        }
-        }
-        $the_date = date('Y-m-d');
-        $html_the_date = $date == $the_date ? "<b>$the_date</b>" : $the_date;
-        $date_btn_str .=  '<a href="/?date='."$the_date&$query".'" class="btn" type="button">'.$html_the_date.'</a>';
-        
-        $module_str ='';
-        foreach(\Statistics\Lib\Cache::$modulesDataCache as $mod => $interfaces)
-        {
-                if($mod == 'WorkerMan')
-                {
-                    continue;
-                }
-                $module_str .= '<li><a href="/?fn=statistic&module='.$mod.'">'.$mod.'</a></li>';
-                if($module == $mod)
-                {
-                    foreach ($interfaces as $if)
-                    {
-                        $module_str .= '<li>&nbsp;&nbsp;<a href="/?fn=statistic&module='.$mod.'&interface='.$if.'">'.$if.'</a></li>';
-                    }
-                }
-        }
-        
-        if( \Statistics\Lib\Cache::$lastFailedIpArray)
-        {
-            $err_msg = '<strong>无法从以下数据源获取数据:</strong>';
-            foreach (\Statistics\Lib\Cache::$lastFailedIpArray as $ip)
-            {
-                $err_msg .= $ip.'::'.\Statistics\Config\Config::$ProviderPort . '&nbsp;';
-            }
-        }
-
-        include ST_ROOT . '/Views/header.tpl.php';
-        include ST_ROOT . '/Views/statistic.tpl.php';
-        include ST_ROOT . '/Views/footer.tpl.php';
-}

+ 0 - 7
applications/Statistics/README.md

@@ -1,7 +0,0 @@
-
-统计模块
-=======
-
-  *  管理员用户名密码默认都为空,即不需要登录就可以查看监控数据
-  *  如果需要登录验证,在applications/Statistics/Config/Config.php里面设置管理员密码
-

+ 0 - 84
applications/Statistics/Views/admin.tpl.php

@@ -1,84 +0,0 @@
-<div class="container">
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="nav nav-tabs">
-				<li>
-					<a href="/">概述</a>
-				</li>
-				<li>
-					<a href="/?fn=statistic">监控</a>
-				</li>
-				<li>
-					<a href="/?fn=logger">日志</a>
-				</li>
-				<li class="disabled">
-					<a href="#">告警</a>
-				</li>
-				<li class="dropdown pull-right">
-					 <a href="#" data-toggle="dropdown" class="dropdown-toggle">其它<strong class="caret"></strong></a>
-					<ul class="dropdown-menu">
-						<li>
-							<a href="/?fn=admin&act=detect_server">探测数据源</a>
-						</li>
-						<li>
-							<a href="/?fn=admin">数据源管理</a>
-						</li>
-						<li>
-							<a href="/?fn=setting">设置</a>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="breadcrumb">
-				<li>
-					<a href="/?fn=admin<?php echo $act == 'detect_server' ? '&act=detect_server' : '';?>"><?php echo $act == 'detect_server' ? '数据源探测' : '数据源管理';?></a> <span class="divider">/</span>
-				</li>
-				<li class="active">
-					<?php if($act == 'home')echo '数据源列表';elseif($act == 'detect_server')echo '探测结果';elseif($act == 'add_to_server_list')echo '添加结果';elseif($act == 'save_server_list')echo '保存结果';?>
-				</li>
-			</ul>
-			<?php if($suc_msg){?>
-				<div class="alert alert-dismissable alert-success">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				 <strong><?php echo $suc_msg;?></strong> 
-				</div>
-			<?php }elseif($err_msg){?>
-				<div class="alert alert-dismissable alert-danger">
-					<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-					<strong><?php echo $err_msg;?></strong> 
-				</div>
-			<?php }elseif($notice_msg){?>
-			<div class="alert alert-dismissable alert-info">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				<strong><?php echo $notice_msg;?></strong>
-			</div>
-			<?php }?>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-3 column">
-		</div>
-		<div class="col-md-6 column">
-		<?php if($act!='add_to_server_list'){?>
-			<form action="/?fn=admin&act=<?php echo $action;?>" method="post">
-			<div class="form-group">
-				<textarea rows="22" cols="30" name="ip_list"><?php echo $ip_list_str;?></textarea>
-			</div>
-			<div class="form-group">
-				<div class="col-sm-offset-1 col-sm-11">
-					<button type="submit" class="btn btn-default"><?php echo $act == 'detect_server' ? '添加到数据源列表' : '保存'?></button>
-				</div>
-			</div>
-			</form>
-			<?php }else{?>
-			<a type="button" class="btn btn-default" href="/">返回主页</a>&nbsp;<a type="button" class="btn btn-default" href="/?fn=admin">继续添加</a>
-			<?php }?>
-		</div>
-		<div class="col-md-3 column">
-		</div>
-	</div>
-</div>

+ 0 - 3
applications/Statistics/Views/footer.tpl.php

@@ -1,3 +0,0 @@
-<div class="footer">Powered by <a href="http://www.workerman.net" target="_blank"><strong>Workerman!</strong></a></div>
-</body>
-</html>

+ 0 - 36
applications/Statistics/Views/header.tpl.php

@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh">
-<head>
-  <meta charset="utf-8">
-  <title>WorkerMan-集群统计与监控</title>
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <meta name="description" content="">
-  <meta name="author" content="">
-
-	<!--link rel="stylesheet/less" href="/less/bootstrap.less" type="text/css" /-->
-	<!--link rel="stylesheet/less" href="/less/responsive.less" type="text/css" /-->
-	<!--script src="/js/less-1.3.3.min.js"></script-->
-	<!--append ‘#!watch’ to the browser URL, then refresh the page. -->
-	
-	<link href="/css/bootstrap.min.css" rel="stylesheet">
-	<link href="/css/style.css" rel="stylesheet">
-
-  <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
-  <!--[if lt IE 9]>
-    <script src="js/html5shiv.js"></script>
-  <![endif]-->
-
-  <!-- Fav and touch icons -->
-  <link rel="apple-touch-icon-precomposed" sizes="144x144" href="img/apple-touch-icon-144-precomposed.png">
-  <link rel="apple-touch-icon-precomposed" sizes="114x114" href="img/apple-touch-icon-114-precomposed.png">
-  <link rel="apple-touch-icon-precomposed" sizes="72x72" href="img/apple-touch-icon-72-precomposed.png">
-  <link rel="apple-touch-icon-precomposed" href="img/apple-touch-icon-57-precomposed.png">
-  <link rel="shortcut icon" href="img/favicon.png">
-  
-	<script type="text/javascript" src="/js/jquery.min.js"></script>
-	<script type="text/javascript" src="/js/bootstrap.min.js"></script>
-	<script type="text/javascript" src="/js/scripts.js"></script>
-	 <script type="text/javascript" src="/js/jquery.min.js"></script>
-	 <script type="text/javascript" src="/js/highcharts.js"></script>
-</head>
-<body>

+ 0 - 39
applications/Statistics/Views/log.tpl.php

@@ -1,39 +0,0 @@
-<div class="container">
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="nav nav-tabs">
-				<li>
-					<a href="/">概述</a>
-				</li>
-				<li>
-					<a href="/?fn=statistic">监控</a>
-				</li>
-				<li class="active" >
-					<a href="/?fn=logger">日志</a>
-				</li>
-				<li class="disabled">
-					<a href="#">告警</a>
-				</li>
-				<li class="dropdown pull-right">
-					 <a href="#" data-toggle="dropdown" class="dropdown-toggle">其它<strong class="caret"></strong></a>
-					<ul class="dropdown-menu">
-						<li>
-							<a href="/?fn=admin&act=detect_server">探测数据源</a>
-						</li>
-						<li>
-							<a href="/?fn=admin">数据源管理</a>
-						</li>
-						<li>
-							<a href="/?fn=setting">设置</a>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-		<?php echo $log_str;?>
-		</div>
-	</div>
-</div>

+ 0 - 67
applications/Statistics/Views/login.tpl.php

@@ -1,67 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh">
-<head>
-  <meta charset="utf-8">
-  <title>WorkerMan-集群统计与监控</title>
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <meta name="description" content="">
-  <meta name="author" content="">
-
-	<!--link rel="stylesheet/less" href="/less/bootstrap.less" type="text/css" /-->
-	<!--link rel="stylesheet/less" href="/less/responsive.less" type="text/css" /-->
-	<!--script src="/js/less-1.3.3.min.js"></script-->
-	<!--append ‘#!watch’ to the browser URL, then refresh the page. -->
-	
-	<link href="/css/bootstrap.min.css" rel="stylesheet">
-	<link href="/css/style.css" rel="stylesheet">
-
-  <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
-  <!--[if lt IE 9]>
-    <script src="js/html5shiv.js"></script>
-  <![endif]-->
-
-  <!-- Fav and touch icons -->
-  <link rel="apple-touch-icon-precomposed" sizes="144x144" href="img/apple-touch-icon-144-precomposed.png">
-  <link rel="apple-touch-icon-precomposed" sizes="114x114" href="img/apple-touch-icon-114-precomposed.png">
-  <link rel="apple-touch-icon-precomposed" sizes="72x72" href="img/apple-touch-icon-72-precomposed.png">
-  <link rel="apple-touch-icon-precomposed" href="img/apple-touch-icon-57-precomposed.png">
-  <link rel="shortcut icon" href="img/favicon.png">
-  
-	<script type="text/javascript" src="/js/jquery.min.js"></script>
-	<script type="text/javascript" src="/js/bootstrap.min.js"></script>
-	<script type="text/javascript" src="/js/scripts.js"></script>
-	 <script type="text/javascript" src="/js/jquery.min.js"></script>
-	 <script type="text/javascript" src="/js/highcharts.js"></script>
-</head>
-<body>
-<div class="container">
-	<div class="row clearfix">
-		<div class="col-md-4 column">
-		</div>
-		<div class="col-md-4 column">
-		<?php if(!empty($msg)){?>
-			<div class="alert alert-dismissable alert-danger">
-				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				<h4>
-					<?php echo $msg;?>
-				</h4> 
-			</div>
-		<?php }?>
-			<h1>workerman管理员登录</h1>
-			<form role="form" method="POST" action="">
-				<div class="form-group">
-					 <label>用户名</label><input type="text" name="admin_name" class="form-control" />
-				</div>
-				<div class="form-group">
-					 <label for="exampleInputPassword1">密码</label><input type="password" name="admin_password"  class="form-control" id="exampleInputPassword1" />
-				</div>
-				<button type="submit" class="btn btn-default">登录</button>
-			</form>
-		</div>
-		<div class="col-md-4 column">
-		</div>
-	</div>
-</div>
-<div class="footer">Powered by <a href="http://www.workerman.net" target="_blank"><strong>Workerman!</strong></a></div>
-</body>
-</html>

+ 0 - 283
applications/Statistics/Views/main.tpl.php

@@ -1,283 +0,0 @@
-<div class="container">
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="nav nav-tabs">
-				<li class="active">
-					<a href="/">概述</a>
-				</li>
-				<li>
-					<a href="/?fn=statistic">监控</a>
-				</li>
-				<li>
-					<a href="/?fn=logger">日志</a>
-				</li>
-				<li class="disabled">
-					<a href="#">告警</a>
-				</li>
-				<li class="dropdown pull-right">
-					 <a href="#" data-toggle="dropdown" class="dropdown-toggle">其它<strong class="caret"></strong></a>
-					<ul class="dropdown-menu">
-						<li>
-							<a href="/?fn=admin&act=detect_server">探测数据源</a>
-						</li>
-						<li>
-							<a href="/?fn=admin">数据源管理</a>
-						</li>
-						<li>
-							<a href="/?fn=setting">设置</a>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<?php if($err_msg){?>
-			<div class="alert alert-dismissable alert-danger">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				<strong><?php echo $err_msg;?></strong> 
-			</div>
-			<?php }elseif($notice_msg){?>
-			<div class="alert alert-dismissable alert-info">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				<strong><?php echo $notice_msg;?></strong>
-			</div>
-			<?php }?>
-			<div class="row clearfix">
-				<div class="col-md-12 column text-center">
-					<?php echo $date_btn_str;?>
-				</div>
-			</div>
-			<div class="row clearfix">
-				<div class="col-md-6 column height-400" id="suc-pie">
-				</div>
-				<div class="col-md-6 column height-400" id="code-pie">
-				</div>
-			</div>
-			<div class="row clearfix">
-				<div class="col-md-12 column height-400" id="req-container" >
-				</div>
-			</div>
-			<div class="row clearfix">
-				<div class="col-md-12 column height-400" id="time-container" >
-				</div>
-			</div>
-			<script>
-Highcharts.setOptions({
-	global: {
-		useUTC: false
-	}
-});
-	$('#suc-pie').highcharts({
-		chart: {
-			plotBackgroundColor: null,
-			plotBorderWidth: null,
-			plotShadow: false
-		},
-		title: {
-			text: '<?php echo $date;?> 可用性'
-		},
-		tooltip: {
-			pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
-		},
-		plotOptions: {
-			pie: {
-				allowPointSelect: true,
-				cursor: 'pointer',
-				dataLabels: {
-					enabled: true,
-					color: '#000000',
-					connectorColor: '#000000',
-					format: '<b>{point.name}</b>: {point.percentage:.1f} %'
-				}
-			}
-		},
-		credits: {
-			enabled: false,
-		},
-		series: [{
-			type: 'pie',
-			name: '可用性',
-			data: [
-				{
-					name: '可用',
-					y: <?php echo $global_rate;?>,
-					sliced: true,
-					selected: true,
-					color: '#2f7ed8'
-				},
-				{
-					name: '不可用',
-					y: <?php echo (100-$global_rate);?>,
-					sliced: true,
-					selected: true,
-					color: '#910000'
-				}
-			]
-		}]
-	});
-	$('#code-pie').highcharts({
-		chart: {
-			plotBackgroundColor: null,
-			plotBorderWidth: null,
-			plotShadow: false
-		},
-		title: {
-			text: '<?php echo $date;?> 返回码分布'
-		},
-		tooltip: {
-			pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
-		},
-		plotOptions: {
-			pie: {
-				allowPointSelect: true,
-				cursor: 'pointer',
-				dataLabels: {
-					enabled: true,
-					color: '#000000',
-					connectorColor: '#000000',
-					format: '<b>{point.name}</b>: {point.percentage:.1f} %'
-				}
-			}
-		},
-		credits: {
-			enabled: false,
-		},
-		series: [{
-			type: 'pie',
-			name: '返回码分布',
-			data: [
-				<?php echo $code_pie_data;?>
-			]
-		}]
-	});
-	$('#req-container').highcharts({
-		chart: {
-			type: 'spline'
-		},
-		title: {
-			text: '<?php echo "$date $interface_name";?>  请求量曲线'
-		},
-		subtitle: {
-			text: ''
-		},
-		xAxis: {
-			type: 'datetime',
-			dateTimeLabelFormats: { 
-				hour: '%H:%M'
-			}
-		},
-		yAxis: {
-			title: {
-				text: '请求量(次/5分钟)'
-			},
-			min: 0
-		},
-		tooltip: {
-			formatter: function() {
-				return '<p style="color:'+this.series.color+';font-weight:bold;">'
-				 + this.series.name + 
-				 '</p><br /><p style="color:'+this.series.color+';font-weight:bold;">时间:' + Highcharts.dateFormat('%m月%d日 %H:%M', this.x) + 
-				 '</p><br /><p style="color:'+this.series.color+';font-weight:bold;">数量:'+ this.y + '</p>';
-			}
-		},
-		credits: {
-			enabled: false,
-		},
-		series: [		{
-			name: '成功曲线',
-			data: [
-				<?php echo $success_series_data;?>
-			],
-			lineWidth: 2,
-			marker:{
-				radius: 1
-			},
-			
-			pointInterval: 300*1000
-		},
-		{
-			name: '失败曲线',
-			data: [
-				<?php echo $fail_series_data;?>
-			],
-			lineWidth: 2,
-			marker:{
-				radius: 1
-			},
-			pointInterval: 300*1000,
-			color : '#9C0D0D'
-		}]
-	});
-	$('#time-container').highcharts({
-		chart: {
-			type: 'spline'
-		},
-		title: {
-			text: '<?php echo "$date $interface_name";?>  请求耗时曲线'
-		},
-		subtitle: {
-			text: ''
-		},
-		xAxis: {
-			type: 'datetime',
-			dateTimeLabelFormats: { 
-				hour: '%H:%M'
-			}
-		},
-		yAxis: {
-			title: {
-				text: '平均耗时(单位:秒)'
-			},
-			min: 0
-		},
-		tooltip: {
-			formatter: function() {
-				return '<p style="color:'+this.series.color+';font-weight:bold;">'
-				 + this.series.name + 
-				 '</p><br /><p style="color:'+this.series.color+';font-weight:bold;">时间:' + Highcharts.dateFormat('%m月%d日 %H:%M', this.x) + 
-				 '</p><br /><p style="color:'+this.series.color+';font-weight:bold;">平均耗时:'+ this.y + '</p>';
-			}
-		},
-		credits: {
-			enabled: false,
-		},
-		series: [		{
-			name: '成功曲线',
-			data: [
-				<?php echo $success_time_series_data;?>
-			],
-			lineWidth: 2,
-			marker:{
-				radius: 1
-			},
-			pointInterval: 300*1000
-		},
-		{
-			name: '失败曲线',
-			data: [
-				   <?php echo $fail_time_series_data;?>
-			],
-			lineWidth: 2,
-			marker:{
-				radius: 1
-			},
-			pointInterval: 300*1000,
-			color : '#9C0D0D'
-		}			]
-	});
-</script>
-			<table class="table table-hover table-condensed table-bordered">
-				<thead>
-					<tr>
-						<th>时间</th><th>调用总数</th><th>平均耗时</th><th>成功调用总数</th><th>成功平均耗时</th><th>失败调用总数</th><th>失败平均耗时</th><th>成功率</th>
-					</tr>
-				</thead>
-				<tbody>
-				<?php echo $table_data;?>
-				</tbody>
-			</table>
-		</div>
-	</div>
-</div>

+ 0 - 83
applications/Statistics/Views/setting.tpl.php

@@ -1,83 +0,0 @@
-<div class="container">
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="nav nav-tabs">
-				<li>
-					<a href="/">概述</a>
-				</li>
-				<li>
-					<a href="/?fn=statistic">监控</a>
-				</li>
-				<li>
-					<a href="/?fn=logger">日志</a>
-				</li>
-				<li class="disabled">
-					<a href="#">告警</a>
-				</li>
-				<li class="dropdown pull-right">
-					 <a href="#" data-toggle="dropdown" class="dropdown-toggle">其它<strong class="caret"></strong></a>
-					<ul class="dropdown-menu">
-						<li>
-							<a href="/?fn=admin&act=detect_server">探测数据源</a>
-						</li>
-						<li>
-							<a href="/?fn=admin">数据源管理</a>
-						</li>
-						<li>
-							<a href="/?fn=setting">设置</a>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="breadcrumb">
-				<li>
-					<a href="/?fn=setting">设置</a> <span class="divider">/</span>
-				</li>
-				<li class="active">
-					选项列表
-				</li>
-			</ul>
-			<?php if($suc_msg){?>
-				<div class="alert alert-dismissable alert-success">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				 <strong><?php echo $suc_msg;?></strong> 
-				</div>
-			<?php }elseif($err_msg){?>
-				<div class="alert alert-dismissable alert-danger">
-					<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-					<strong><?php echo $err_msg;?></strong> 
-				</div>
-			<?php }elseif($notice_msg){?>
-			<div class="alert alert-dismissable alert-info">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				<strong><?php echo $notice_msg;?></strong>
-			</div>
-			<?php }?>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-3 column">
-		</div>
-		<div class="col-md-6 column">
-			<form class="form-horizontal" role="form" action="/?fn=setting&act=save" method="post">
-				<div class="form-group">
-					 <label class="col-sm-3 control-label">数据源探测端口</label>
-					<div class="col-sm-9">
-						<input class="form-control" name="detect_port" value="<?php echo $detect_port;?>"/>
-					</div>
-				</div>
-				<div class="form-group">
-					<div class="col-sm-offset-3 col-sm-9">
-						 <button type="submit" class="btn btn-default">保存</button>
-					</div>
-				</div>
-			</form>
-		</div>
-		<div class="col-md-3 column">
-		</div>
-	</div>
-</div>

+ 0 - 196
applications/Statistics/Views/statistic.tpl.php

@@ -1,196 +0,0 @@
-<div class="container">
-	<div class="row clearfix">
-		<div class="col-md-12 column">
-			<ul class="nav nav-tabs">
-				<li>
-					<a href="/">概述</a>
-				</li>
-				<li class="active" >
-					<a href="/?fn=statistic">监控</a>
-				</li>
-				<li>
-					<a href="/?fn=logger">日志</a>
-				</li>
-				<li class="disabled">
-					<a href="#">告警</a>
-				</li>
-				<li class="dropdown pull-right">
-					 <a href="#" data-toggle="dropdown" class="dropdown-toggle">其它<strong class="caret"></strong></a>
-					<ul class="dropdown-menu">
-						<li>
-							<a href="/?fn=admin&act=detect_server">探测数据源</a>
-						</li>
-						<li>
-							<a href="/?fn=admin">数据源管理</a>
-						</li>
-						<li>
-							<a href="/?fn=setting">设置</a>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-	</div>
-	<div class="row clearfix">
-		<div class="col-md-3 column">
-			<ul><?php echo $module_str;?></ul>
-		</div>
-		<div class="col-md-9 column">
-		<?php if($err_msg){?>
-			<div class="alert alert-dismissable alert-danger">
-				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-				<strong><?php echo $err_msg;?></strong> 
-			</div>
-		<?php }?>
-		<?php if($module && $interface){?>
-			<div class="row clearfix">
-				<div class="col-md-12 column text-center">
-					<?php echo $date_btn_str;?>
-				</div>
-			</div>
-			<div class="row clearfix">
-				<div class="col-md-12 column height-400" id="req-container" >
-				</div>
-			</div>
-			<div class="row clearfix">
-				<div class="col-md-12 column height-400" id="time-container" >
-				</div>
-			</div>
-			<?php if($module && $interface){?>
-			<script>
-			Highcharts.setOptions({
-				global: {
-					useUTC: false
-				}
-			});
-				$('#req-container').highcharts({
-					chart: {
-						type: 'spline'
-					},
-					title: {
-						text: '<?php echo "$date $interface_name";?>  请求量曲线'
-					},
-					subtitle: {
-						text: ''
-					},
-					xAxis: {
-						type: 'datetime',
-						dateTimeLabelFormats: { 
-							hour: '%H:%M'
-						}
-					},
-					yAxis: {
-						title: {
-							text: '请求量(次/5分钟)'
-						},
-						min: 0
-					},
-					tooltip: {
-						formatter: function() {
-							return '<p style="color:'+this.series.color+';font-weight:bold;">'
-							+ this.series.name + 
-							'</p><br /><p style="color:'+this.series.color+';font-weight:bold;">时间:' + Highcharts.dateFormat('%m月%d日 %H:%M', this.x) + 
-							'</p><br /><p style="color:'+this.series.color+';font-weight:bold;">数量:'+ this.y + '</p>';
-						}
-					},
-					credits: {
-						enabled: false,
-					},
-					series: [{
-						name: '成功曲线',
-						data: [
-							<?php echo $success_series_data;?>
-						],
-						lineWidth: 2,
-						marker:{
-							radius: 1
-						},
-						
-						pointInterval: 300*1000
-					},
-					{
-						name: '失败曲线',
-						data: [
-							<?php echo $fail_series_data;?>
-						],
-						lineWidth: 2,
-						marker:{
-							radius: 1
-						},
-						pointInterval: 300*1000,
-						color : '#9C0D0D'
-					}]
-				});
-				$('#time-container').highcharts({
-					chart: {
-						type: 'spline'
-					},
-					title: {
-						text: '<?php echo "$date $interface_name";?>  请求耗时曲线'
-					},
-					subtitle: {
-						text: ''
-					},
-					xAxis: {
-						type: 'datetime',
-						dateTimeLabelFormats: { 
-							hour: '%H:%M'
-						}
-					},
-					yAxis: {
-						title: {
-							text: '平均耗时(单位:秒)'
-						},
-						min: 0
-					},
-					tooltip: {
-						formatter: function() {
-							return '<p style="color:'+this.series.color+';font-weight:bold;">'
-							 + this.series.name + 
-							 '</p><br /><p style="color:'+this.series.color+';font-weight:bold;">时间:' + Highcharts.dateFormat('%m月%d日 %H:%M', this.x) + 
-							 '</p><br /><p style="color:'+this.series.color+';font-weight:bold;">平均耗时:'+ this.y + '</p>';
-						}
-					},
-					credits: {
-						enabled: false,
-					},
-					series: [{
-						name: '成功曲线',
-						data: [
-							<?php echo $success_time_series_data;?>
-						],
-						lineWidth: 2,
-						marker:{
-							radius: 1
-						},
-						pointInterval: 300*1000
-					},
-					{
-						name: '失败曲线',
-						data: [
-								<?php echo $fail_time_series_data;?>
-						],
-						lineWidth: 2,
-						marker:{
-							radius: 1
-						},
-						pointInterval: 300*1000,
-						color : '#9C0D0D'
-					}]
-				});
-			</script>
-			<?php }?>
-			<table class="table table-hover table-condensed table-bordered">
-				<thead>
-					<tr>
-						<th>时间</th><th>调用总数</th><th>平均耗时</th><th>成功调用总数</th><th>成功平均耗时</th><th>失败调用总数</th><th>失败平均耗时</th><th>成功率</th>
-					</tr>
-				</thead>
-				<tbody>
-				<?php echo $table_data;?>
-				</tbody>
-			</table>
-			<?php }?>
-		</div>
-	</div>
-</div>

+ 0 - 10
applications/Statistics/Web/_init.php

@@ -1,10 +0,0 @@
-<?php
-define('ST_ROOT', realpath(__DIR__.'/../'));
-require_once ST_ROOT .'/Lib/functions.php';
-require_once ST_ROOT .'/Lib/Cache.php';
-require_once ST_ROOT .'/Config/Config.php';
-// 覆盖配置文件
-foreach(glob(ST_ROOT . '/Config/Cache/*.php')  as $php_file)
-{
-    require_once $php_file;
-}

+ 0 - 7
applications/Statistics/Web/config.php

@@ -1,7 +0,0 @@
-<?php
-namespace Statistics\Web;
-class Config
-{
-    // 数据源端口,会向这个端口发送udp广播获取ip,然后从这个端口以tcp协议获取统计信息
-    public static $ProviderPort = 55858;
-}

+ 0 - 459
applications/Statistics/Web/css/bootstrap-theme.css

@@ -1,459 +0,0 @@
-/*!
- * Bootstrap v3.0.1 by @fat and @mdo
- * Copyright 2013 Twitter, Inc.
- * Licensed under http://www.apache.org/licenses/LICENSE-2.0
- *
- * Designed and built with all the love in the world by @mdo and @fat.
- */
-
-.btn-default,
-.btn-primary,
-.btn-success,
-.btn-info,
-.btn-warning,
-.btn-danger {
-  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
-  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.btn-default:active,
-.btn-primary:active,
-.btn-success:active,
-.btn-info:active,
-.btn-warning:active,
-.btn-danger:active,
-.btn-default.active,
-.btn-primary.active,
-.btn-success.active,
-.btn-info.active,
-.btn-warning.active,
-.btn-danger.active {
-  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-}
-
-.btn:active,
-.btn.active {
-  background-image: none;
-}
-
-.btn-default {
-  text-shadow: 0 1px 0 #fff;
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0));
-  background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
-  background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
-  background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
-  background-repeat: repeat-x;
-  border-color: #dbdbdb;
-  border-color: #ccc;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-default:hover,
-.btn-default:focus {
-  background-color: #e0e0e0;
-  background-position: 0 -15px;
-}
-
-.btn-default:active,
-.btn-default.active {
-  background-color: #e0e0e0;
-  border-color: #dbdbdb;
-}
-
-.btn-primary {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#2d6ca2));
-  background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
-  background-image: -moz-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
-  background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
-  background-repeat: repeat-x;
-  border-color: #2b669a;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-primary:hover,
-.btn-primary:focus {
-  background-color: #2d6ca2;
-  background-position: 0 -15px;
-}
-
-.btn-primary:active,
-.btn-primary.active {
-  background-color: #2d6ca2;
-  border-color: #2b669a;
-}
-
-.btn-success {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#419641));
-  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
-  background-image: -moz-linear-gradient(top, #5cb85c 0%, #419641 100%);
-  background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
-  background-repeat: repeat-x;
-  border-color: #3e8f3e;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-success:hover,
-.btn-success:focus {
-  background-color: #419641;
-  background-position: 0 -15px;
-}
-
-.btn-success:active,
-.btn-success.active {
-  background-color: #419641;
-  border-color: #3e8f3e;
-}
-
-.btn-warning {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#eb9316));
-  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
-  background-image: -moz-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
-  background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
-  background-repeat: repeat-x;
-  border-color: #e38d13;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-warning:hover,
-.btn-warning:focus {
-  background-color: #eb9316;
-  background-position: 0 -15px;
-}
-
-.btn-warning:active,
-.btn-warning.active {
-  background-color: #eb9316;
-  border-color: #e38d13;
-}
-
-.btn-danger {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c12e2a));
-  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
-  background-image: -moz-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
-  background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
-  background-repeat: repeat-x;
-  border-color: #b92c28;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-danger:hover,
-.btn-danger:focus {
-  background-color: #c12e2a;
-  background-position: 0 -15px;
-}
-
-.btn-danger:active,
-.btn-danger.active {
-  background-color: #c12e2a;
-  border-color: #b92c28;
-}
-
-.btn-info {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#2aabd2));
-  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
-  background-image: -moz-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
-  background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
-  background-repeat: repeat-x;
-  border-color: #28a4c9;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-info:hover,
-.btn-info:focus {
-  background-color: #2aabd2;
-  background-position: 0 -15px;
-}
-
-.btn-info:active,
-.btn-info.active {
-  background-color: #2aabd2;
-  border-color: #28a4c9;
-}
-
-.thumbnail,
-.img-thumbnail {
-  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
-          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
-}
-
-.dropdown-menu > li > a:hover,
-.dropdown-menu > li > a:focus {
-  background-color: #e8e8e8;
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
-  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
-  background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
-  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
-}
-
-.dropdown-menu > .active > a,
-.dropdown-menu > .active > a:hover,
-.dropdown-menu > .active > a:focus {
-  background-color: #357ebd;
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
-  background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
-  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
-  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
-}
-
-.navbar-default {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8));
-  background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
-  background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
-  background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
-  background-repeat: repeat-x;
-  border-radius: 4px;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
-}
-
-.navbar-default .navbar-nav > .active > a {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f3f3f3));
-  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
-  background-image: -moz-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
-  background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
-  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
-}
-
-.navbar-brand,
-.navbar-nav > li > a {
-  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
-}
-
-.navbar-inverse {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222));
-  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
-  background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%);
-  background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.navbar-inverse .navbar-nav > .active > a {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#222222), to(#282828));
-  background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%);
-  background-image: -moz-linear-gradient(top, #222222 0%, #282828 100%);
-  background-image: linear-gradient(to bottom, #222222 0%, #282828 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
-  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
-          box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
-}
-
-.navbar-inverse .navbar-brand,
-.navbar-inverse .navbar-nav > li > a {
-  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
-}
-
-.navbar-static-top,
-.navbar-fixed-top,
-.navbar-fixed-bottom {
-  border-radius: 0;
-}
-
-.alert {
-  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
-  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
-          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.alert-success {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc));
-  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
-  background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
-  background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
-  background-repeat: repeat-x;
-  border-color: #b2dba1;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
-}
-
-.alert-info {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0));
-  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
-  background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
-  background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
-  background-repeat: repeat-x;
-  border-color: #9acfea;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
-}
-
-.alert-warning {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0));
-  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
-  background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
-  background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
-  background-repeat: repeat-x;
-  border-color: #f5e79e;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
-}
-
-.alert-danger {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3));
-  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
-  background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
-  background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
-  background-repeat: repeat-x;
-  border-color: #dca7a7;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
-}
-
-.progress {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5));
-  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
-  background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
-  background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
-}
-
-.progress-bar {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
-  background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
-  background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
-  background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
-}
-
-.progress-bar-success {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
-  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
-  background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
-  background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
-}
-
-.progress-bar-info {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
-  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
-  background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
-  background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
-}
-
-.progress-bar-warning {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
-  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
-  background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
-  background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
-}
-
-.progress-bar-danger {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
-  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
-  background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
-  background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
-}
-
-.list-group {
-  border-radius: 4px;
-  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
-          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
-}
-
-.list-group-item.active,
-.list-group-item.active:hover,
-.list-group-item.active:focus {
-  text-shadow: 0 -1px 0 #3071a9;
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3));
-  background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
-  background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%);
-  background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
-  background-repeat: repeat-x;
-  border-color: #3278b3;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
-}
-
-.panel {
-  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
-          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.panel-default > .panel-heading {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
-  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
-  background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
-  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
-}
-
-.panel-primary > .panel-heading {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
-  background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
-  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
-  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
-}
-
-.panel-success > .panel-heading {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6));
-  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
-  background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
-  background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
-}
-
-.panel-info > .panel-heading {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3));
-  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
-  background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
-  background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
-}
-
-.panel-warning > .panel-heading {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc));
-  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
-  background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
-  background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
-}
-
-.panel-danger > .panel-heading {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc));
-  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
-  background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
-  background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
-}
-
-.well {
-  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5));
-  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
-  background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
-  background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
-  background-repeat: repeat-x;
-  border-color: #dcdcdc;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
-  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
-          box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
-}

File diff ditekan karena terlalu besar
+ 0 - 8
applications/Statistics/Web/css/bootstrap-theme.min.css


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini