├── .gitignore ├── LICENSE ├── clients ├── client.php └── websocket │ ├── client.html │ ├── css │ ├── bootstrap.css │ ├── im.css │ └── style.css │ ├── images │ └── im │ │ ├── 1cut.png │ │ ├── cut.png │ │ └── face.png │ └── js │ ├── client.js │ ├── datetime.js │ ├── jquery-2.1.1.min.js │ └── string.js ├── composer.json.linux ├── composer.json.win ├── config └── app.php.example ├── readme.md ├── services ├── parse_worker ├── tcp ├── tcp.go ├── tcp.php ├── websocket ├── websocket.go └── websocket.php ├── src ├── Bin │ ├── Auth │ │ ├── Auth.php │ │ └── ServerInfo.php │ ├── BinlogPacket.php │ ├── Constant │ │ ├── CapabilityFlag.php │ │ ├── CharacterSet.php │ │ ├── Column.php │ │ ├── CommandType.php │ │ ├── Cursor.php │ │ ├── EventType.php │ │ ├── FieldFlag.php │ │ ├── FieldType.php │ │ ├── ServerStatus.php │ │ └── Trans.php │ ├── Db.php │ ├── Mysql.php │ ├── Net.php │ └── Packet.php ├── Cache │ ├── File.php │ └── Redis.php ├── Command │ ├── Help.php │ ├── ServerBase.php │ ├── ServerRestart.php │ ├── ServerStart.php │ ├── ServerStatus.php │ ├── ServerStop.php │ └── ServerVersion.php ├── Exception │ └── NetCloseException.php ├── Library │ ├── Binlog.php │ ├── Cpu.php │ ├── FileFormat.php │ ├── ICache.php │ ├── IDb.php │ ├── IRedis.php │ ├── ISubscribe.php │ ├── Mysql │ │ ├── Driver.php │ │ ├── PDO.php │ │ ├── Result.php │ │ ├── Stmt.php │ │ └── Warning.php │ ├── PDO.php │ ├── Redis.php │ ├── Worker.php │ └── Workers │ │ ├── BaseWorker.php │ │ ├── BinlogWorker.php │ │ └── EventWorker.php ├── Net │ ├── Tcp.php │ ├── TcpClient.php │ ├── TcpServer.php │ ├── WebSocket.php │ ├── WebSocketClient.php │ └── WsClient.php ├── Subscribe │ ├── Go.php │ ├── Redis.php │ ├── Tcp.php │ ├── WMTcp.php │ ├── WMWebSocket.php │ └── WebSocket.php ├── helpers.php └── windows.php ├── tests ├── 1.php ├── 2.php ├── 3.php ├── end.php ├── exec.php ├── explode.php ├── file_exists.php ├── kill.php ├── log.log ├── mysql-client │ ├── 2.php │ ├── Client-backup.php │ ├── Client.php │ ├── query.php │ └── slave.php ├── pdo.php ├── proc.php ├── proc │ ├── 1.php │ └── 2.php ├── proc_open.php ├── queue.php ├── rename.php ├── run.php ├── select_timeout.go ├── sendwebsocket.php ├── server │ ├── server.exe │ └── test.go ├── status.php ├── statuso.php ├── test123.go ├── unit │ ├── MysqlProtocol │ │ └── PdoTest.php │ └── test ├── unixsocket │ ├── client.php │ └── server.php ├── websocket.php └── websocketclient.php └── wing /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /dev/vendor/ 3 | /dev/cache 4 | /dev/*.pid 5 | /config/*.php 6 | /old_version/config/*.php 7 | /cache 8 | /old_version/vendor 9 | /old_version/cache 10 | /old_version/http_process_cache 11 | /old_version/process_cache 12 | /old_version/logs 13 | /old_version/data 14 | /old_version/*.pid 15 | /old_version/*.info 16 | /old_version/*.pos 17 | /old_version/*.last 18 | /old_version/*.status 19 | /old_version/session 20 | /old_version/update 21 | /old_version/*.read 22 | /old_version/*.lock 23 | /.idea/ 24 | /composer.lock 25 | /dev/composer.lock 26 | /.DS_Store 27 | /src/Doc/.DS_Store 28 | /src/.DS_Store 29 | /*.log 30 | /*/*/*.log 31 | /cache/* 32 | /seals.info 33 | /log 34 | /log/* 35 | /*.pid 36 | /*/*.pid 37 | /process_cache 38 | /mysql.last 39 | /mysql.pos 40 | /logs 41 | /http_process_cache 42 | /http_process_cache/* 43 | /*/.DS_Store 44 | /*/*/.DS_Store 45 | /*/*/*/.DS_Store 46 | /session 47 | /*.sh 48 | /data/user/login/*.token 49 | /web/public/* 50 | /*.info 51 | /update 52 | /general.last 53 | /general.read 54 | /master.status 55 | /data/table/*.table 56 | /data/report/*.report 57 | /data/user/*.user 58 | /data/user/roles/*.role 59 | /composer 60 | /composer.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 yuyi <297341015@qq.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /clients/client.php: -------------------------------------------------------------------------------- 1 | 0) { 19 | return; 20 | } 21 | 22 | //子进程发送心跳包 23 | while(1) { 24 | try { 25 | set_error_handler(function(){ 26 | var_dump(func_get_args()); 27 | exit; 28 | }); 29 | socket_write($socket, "tick-php\r\n\r\n\r\n"); 30 | usleep(500000); 31 | } catch (\Exception $e) { 32 | var_dump($e->getMessage()); 33 | exit; 34 | } 35 | } 36 | } 37 | 38 | 39 | function start_service() 40 | { 41 | 42 | $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 43 | $con = socket_connect($socket, '127.0.0.1', 9997); 44 | 45 | if (!$con) { 46 | socket_close($socket); 47 | echo "无法连接服务器\r\n"; 48 | exit; 49 | } 50 | 51 | echo "连接成功\n"; 52 | fork_child($socket); 53 | //父进程接收消息 54 | $count = 0; 55 | $msg_all = ""; 56 | $split = "\r\n\r\n\r\n"; 57 | while ($msg = socket_read($socket, 10240)) { 58 | $msg_all .= $msg; 59 | $temp = explode($split, $msg_all); 60 | if (count($temp) >= 2) { 61 | $msg_all = array_pop($temp); 62 | foreach ($temp as $v) { 63 | if (!$v) { 64 | continue; 65 | } 66 | $count++; 67 | echo $v, "\r\n"; 68 | echo "收到消息次数:", $count, "\r\n\r\n"; 69 | } 70 | } 71 | unset($temp); 72 | } 73 | 74 | echo "连接关闭\r\n"; 75 | socket_shutdown($socket); 76 | socket_close($socket); 77 | 78 | $start = 0; 79 | while (1) { 80 | $status = 0; 81 | $pid = pcntl_wait($status); 82 | if ($pid > 0) { 83 | break; 84 | } 85 | if ((time() - $start) > 5) break; 86 | } 87 | } 88 | 89 | while (1) { 90 | start_service(); 91 | } -------------------------------------------------------------------------------- /clients/websocket/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wing-binlog Websocket客户端测试 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 | 18 | 19 | 离线 20 |
21 |
22 |
23 |
发送
24 |
25 | 26 | -------------------------------------------------------------------------------- /clients/websocket/css/im.css: -------------------------------------------------------------------------------- 1 | body{ 2 | width: 100%; height: 100%; overflow: hidden; 3 | } 4 | .msg-win{ 5 | background: #fff; border: #666 solid 1px; width: 98%; height: 88%; 6 | overflow-y: scroll; 7 | position: absolute; left: 0; bottom: 100px; 8 | } 9 | .tool-bar{ 10 | background: #fff; border: #666 solid 1px; width: 98%; height: 20px; 11 | position: absolute; left: 0; bottom: 80px; 12 | } 13 | .send-box{ 14 | background: #fff; 15 | border: #666 solid 1px; 16 | width: 98%; 17 | height: 8%; 18 | position: absolute; left: 0; bottom: 0px; 19 | } 20 | .tool-icon{ 21 | cursor: pointer; 22 | width: 20px; 23 | } 24 | .send-bth{ 25 | position: absolute; 26 | right: 0; 27 | bottom: 0; 28 | height: 80px; 29 | line-height: 80px; 30 | width: 15%; 31 | border-left: #666 solid 1px; 32 | text-align: center; 33 | cursor: pointer; 34 | background: #fff; 35 | z-index: 999; 36 | } 37 | .send-msg{ 38 | position: absolute; 39 | padding: 5px 12px; 40 | left: 0; 41 | bottom: 0; 42 | height: 70px; 43 | line-height: 20px; 44 | width: 85%; 45 | border-left: #666 solid 1px; 46 | text-align: left; 47 | font-size: 12px; 48 | overflow-x: hidden; 49 | overflow-y: scroll; 50 | } 51 | .msg-list{ 52 | /* position: absolute; 53 | left:0; 54 | bottom:0;*/ 55 | width: 100%; 56 | } 57 | .msg-time{ 58 | font-size: 12px; 59 | text-align: center; 60 | } 61 | .client-status{ 62 | position: absolute; 63 | text-align: left; 64 | height: 20px; line-height: 20px; 65 | border-left: #666 solid 1px; 66 | top:0; 67 | right:0; 68 | width:15%; 69 | font-size: 12px; 70 | 71 | } -------------------------------------------------------------------------------- /clients/websocket/images/im/1cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilieryuyi/wing-binlog/5020e045cfad9fcdbef51003fb16b7060c3456e1/clients/websocket/images/im/1cut.png -------------------------------------------------------------------------------- /clients/websocket/images/im/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilieryuyi/wing-binlog/5020e045cfad9fcdbef51003fb16b7060c3456e1/clients/websocket/images/im/cut.png -------------------------------------------------------------------------------- /clients/websocket/images/im/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilieryuyi/wing-binlog/5020e045cfad9fcdbef51003fb16b7060c3456e1/clients/websocket/images/im/face.png -------------------------------------------------------------------------------- /clients/websocket/js/client.js: -------------------------------------------------------------------------------- 1 | var im={ 2 | my:null, 3 | socket:null, 4 | online:false, 5 | msg_count:0, 6 | showStatus:function(str){ 7 | $(".client-status").html(str); 8 | }, 9 | send:function(){ 10 | 11 | }, 12 | sendMessage:function(sendto,msg){ 13 | this.log("\nsend msg==>sendto:"+sendto+"\nmsg:"+msg); 14 | var msg_id = sendto+"-"+(new Date().getTime()); 15 | var _msg='{"service":"sendMessage","to":"'+sendto+'","msg":"'+encodeURIComponent(msg)+'","msg_id":"'+msg_id+'"}'; 16 | console.log("send msg:"+_msg); 17 | $(".msg-win").append( 18 | '
'+ 19 | '
'+window.date.getTime()+'
'+ 20 | '
my
'+ 21 | '
'+ 22 | this.msg_count+"=>"+ msg+ 23 | '
'+ 24 | '
'); 25 | this.msg_count++; 26 | $(".msg-win").scrollTop(999999); 27 | return this.socket.send(_msg+"\r\n\r\n\r\n"); 28 | }, 29 | login:function(username,password){ 30 | var _msg='{"service":"login","username":"'+username+'","password":"'+password+'"}'; 31 | return this.socket.send(_msg+"\r\n\r\n\r\n"); 32 | }, 33 | onDisConnect:function(){ 34 | this.log("onDisConnect"); 35 | this.online=false; 36 | this.showStatus("离线"); 37 | }, 38 | onConnect:function(){ 39 | this.log("onConnect"); 40 | this.online=true; 41 | this.showStatus("在线"); 42 | }, 43 | onLogin:function(status,err_msg,user_id){ 44 | this.log("\nlogin==>status:"+status+"\nerr_msg:"+err_msg+"\nuser_id:"+user_id); 45 | if(status==1000) 46 | { 47 | //登陆成功 48 | this.my=user_id; 49 | //this.onMessage("system","system","login success"); 50 | im.onConnect(); 51 | im.online=true; 52 | } 53 | else{ 54 | //登陆失败 用户名或者密码错误 55 | alert(err_msg); 56 | } 57 | }, 58 | onMessage:function(msg){ 59 | 60 | this.log(msg); 61 | $(".msg-win").append( 62 | '
'+ 63 | '
'+window.date.getTime()+'
'+ 64 | '
sys
'+ 65 | '
'+ 66 | this.msg_count+"=>"+ decodeURIComponent(msg)+ 67 | '
'+ 68 | '
'); 69 | this.msg_count++; 70 | $(".msg-win").scrollTop(999999); 71 | }, 72 | log:function(content){ 73 | 74 | }, 75 | onSendError:function(msg){ 76 | /* console.log("send error",msg); 77 | $(".msg-list").each(function(i,v){ 78 | if($(v).attr("msg-id")==msg.msg_id) 79 | $(v).css("background","#f00"); 80 | });*/ 81 | } 82 | }; 83 | 84 | 85 | $(document).ready(function(){ 86 | $(".send-bth").click(function(){ 87 | im.sendMessage(1, $(".send-msg").html()); 88 | $(".send-msg").html(""); 89 | }); 90 | }); 91 | 92 | 93 | var interval = null; 94 | var msg_count = 0; 95 | function start_service(){ 96 | //114.55.56.167 97 | 98 | if (null != interval) { 99 | window.clearInterval(interval); 100 | } 101 | 102 | var ws = new WebSocket("ws://127.0.0.1:9998/"); 103 | im.socket = ws; 104 | 105 | ws.onopen = function() { 106 | im.online = 1; 107 | var _msg='tick'; 108 | ws.send(_msg+"\r\n\r\n\r\n"); 109 | im.onConnect(); 110 | }; 111 | 112 | interval = window.setInterval(function(){ 113 | if (im.online) { 114 | ws.send("tick\r\n\r\n\r\n"); 115 | } 116 | },1000); 117 | 118 | ws.onmessage = function(e) { 119 | msg_count++; 120 | console.log("第"+msg_count+"条消息=>",e.data); 121 | im.onMessage(e.data); 122 | }; 123 | ws.onclose=function() { 124 | im.onDisConnect(); 125 | } 126 | 127 | ws.onerror = function(e) { 128 | im.onDisConnect(); 129 | console.log(e); 130 | } 131 | } 132 | 133 | 134 | start_service(); 135 | 136 | window.setInterval(function(){ 137 | if(!im.online){ 138 | start_service(); 139 | } 140 | },1000); 141 | -------------------------------------------------------------------------------- /clients/websocket/js/datetime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Administrator on 2016/1/25. 3 | */ 4 | if(typeof window.date=='undefined') 5 | window.date={}; 6 | window.date.getTime=function(Milliseconds){ 7 | //获取时间 8 | var myDate = new Date(); 9 | var month=myDate.getMonth()+1; 10 | month=month<10?"0"+month:month; 11 | var day=myDate.getDate(); 12 | day=day<10?"0"+day:day; 13 | var hour=myDate.getHours(); 14 | hour=hour<10?"0"+hour:hour; 15 | var minute=myDate.getMinutes(); 16 | minute=minute<10?"0"+minute:minute; 17 | var seconds=myDate.getSeconds(); 18 | seconds=seconds<10?"0"+seconds:seconds; 19 | var time=myDate.getFullYear()+'-'+month + '-' + day + ' '+hour+':'+minute+":"+seconds; 20 | return Milliseconds==true?(time+":"+myDate.getMilliseconds()):time; 21 | } 22 | window.date.getDay=function(){ 23 | var myDate = new Date(); 24 | var month=myDate.getMonth()+1; 25 | month=month<10?"0"+month:month; 26 | var day=myDate.getDate(); 27 | day=day<10?"0"+day:day; 28 | var hour=myDate.getHours(); 29 | hour=hour<10?"0"+hour:hour; 30 | var minute=myDate.getMinutes(); 31 | minute=minute<10?"0"+minute:minute; 32 | var seconds=myDate.getSeconds(); 33 | seconds=seconds<10?"0"+seconds:seconds; 34 | var time=myDate.getFullYear()+'-'+month + '-' + day; 35 | return time; 36 | 37 | } 38 | window.date.getDayEx=function(){ 39 | var myDate = new Date(); 40 | var month=myDate.getMonth()+1; 41 | month=month<10?"0"+month:month; 42 | var day=myDate.getDate(); 43 | day=day<10?"0"+day:day; 44 | var hour=myDate.getHours(); 45 | hour=hour<10?"0"+hour:hour; 46 | var minute=myDate.getMinutes(); 47 | minute=minute<10?"0"+minute:minute; 48 | var seconds=myDate.getSeconds(); 49 | seconds=seconds<10?"0"+seconds:seconds; 50 | var time=myDate.getFullYear()+''+month + '' + day; 51 | return time; 52 | 53 | } 54 | 55 | window.date.getimestamp=function(){ 56 | var datetime=window.date.getTime(true); 57 | datetime=datetime.replace(/-/g,'/'); 58 | var time=new Date(datetime); 59 | return time.getTime(); 60 | } 61 | window.date.timestamp=function(datetime){ 62 | datetime=datetime.replace(/-/g,'/'); 63 | var time=new Date(datetime); 64 | return time.getTime(); 65 | } -------------------------------------------------------------------------------- /clients/websocket/js/string.js: -------------------------------------------------------------------------------- 1 | String.prototype.trim=function(){ 2 | return this.replace(/(^\s+)|(\s+$)/g,""); 3 | } 4 | String.prototype.ltrim=function(){ 5 | return this.replace(/(^\s+)/g,""); 6 | } 7 | String.prototype.rtrim=function(){ 8 | return this.replace(/(\s+$)/g,""); 9 | } 10 | String.prototype.delSpaces=function(){ 11 | return this.replace(/\s/g,''); 12 | } 13 | String.prototype.lengthX=function(){ 14 | //每个中文按三个字符返回 15 | if(arguments.length>0)l=arguments[0]; 16 | else l=3; 17 | //if(typeof l=='undefined')l=3; 18 | var len=this.length; 19 | var lenX=0; 20 | for(i=0;i256){lenX +=l;} 22 | else{lenX++;} 23 | } 24 | return lenX; 25 | } 26 | String.prototype.lengthFliter=function(minLen,maxLen){ 27 | var len=this.length; 28 | if(lenmaxLen)return false; 29 | return true; 30 | } 31 | String.prototype.isPhone=function(){ 32 | var reg =/^13[0-9]{1}[0-9]{8}$|14[0-9]{1}[0-9]{8}$|15[0-9]{1}[0-9]{8}$|18[0-9]{1}[0-9]{8}$/; 33 | if(reg.test(this))return true; 34 | return false; 35 | } 36 | String.prototype.isEmail=function(){ 37 | var reg = /^([\w-\.]+)@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; 38 | if(reg.test(this))return true; 39 | return false; 40 | } 41 | String.prototype.startWith=function(start){ 42 | eval("var re = /^"+start+"/gi"); 43 | return re.test(this); 44 | } 45 | 46 | String.prototype.isEmailX=function(){ 47 | var sReg =/[_a-zA-Z\d\-\.]+@[_a-zA-Z\d\-]+(\.[_a-zA-Z\d\-]+)+$/; 48 | if(sReg.test(this))return true; 49 | return false; 50 | } 51 | String.prototype.isEmpty=function(){ 52 | var str=this.replace(/\s/g,''); 53 | if(str=="")return true; 54 | return false; 55 | } 56 | String.prototype.ajaxFilter=function(){ 57 | var str=this.replace(/\&/g, "%26"); 58 | str=str.replace(/\+/g, "%2B"); 59 | return encodeURI(str) ; 60 | } 61 | String.prototype.subString=function(start,end){ 62 | //start 大于0 正向提取 小于0 逆向提取 63 | return this.slice(start,end); 64 | } 65 | String.prototype.isUrl=function(){ 66 | var strRegex = "^((https|http|ftp|rtsp|mms)?://)" 67 | + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //ftp的user@ 68 | + "(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP形式的URL- 199.194.52.184 69 | + "|" // 允许IP和DOMAIN(域名) 70 | + "([0-9a-z_!~*'()-]+\.)*" // 域名- www. 71 | + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\." // 二级域名 72 | + "[a-z]{2,6})" // first level domain- .com or .museum 73 | + "(:[0-9]{1,4})?" // 端口- :80 74 | + "((/?)|" // a slash isn't required if there is no file name 75 | + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"; 76 | var re=new RegExp(strRegex); 77 | if(re.test(this))return true; 78 | return false; 79 | } 80 | String.prototype.delSpecialChars=function(){ 81 | return this.replace(/(\~|\`|\·|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\+|\-|\/|\\|\[|\]|\{|\}|\||\>|\<|\,|\.|\'|\"|。|\:|\;|\=|\:|\;|\¥|\!|\(|\)|\【|\】)|\?/g,''); 82 | } 83 | String.prototype.hasSpecialChars=function(){ 84 | var re=/(\~|\`|\·|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\+|\-|\/|\\|\[|\]|\{|\}|\||\>|\<|\,|\.|\'|\"|。|\:|\;|\=|\:|\;|\¥|\!|\(|\)|\【|\】)|\?/g; 85 | if(re.test(this))return true; 86 | return false; 87 | } 88 | String.prototype.isNumber=function(){ 89 | var re = /^-?[0-9]+(\.\d+)?$|^-?0(\.\d+)?$|^-?[1-9]+[0-9]*(\.\d+)?$/; 90 | var reg = new RegExp(re); 91 | if(reg.test(this))return true; 92 | return false; 93 | } 94 | String.prototype.jsonFilter=function(){ 95 | /* 96 | 换行符影响php json的解析 97 | 双引号影响php json的解析 98 | \ 影响PHP json的解析 99 | */ 100 | return this.replace(/(\n)|(\r\n)/g,"").replace(/\"/g,"0x22").replace(/\\/g,"/"); 101 | } 102 | String.prototype.hasString=function(str){ 103 | //是否包含字符串 区分大小写 104 | var index=this.indexOf(str); 105 | if(index>=0)return true; 106 | return false; 107 | } 108 | String.prototype.hasStringX=function(str){ 109 | //是否包含字符串 不区分大小写 110 | var index=this.toLowerCase().indexOf(str.toLowerCase()); 111 | if(index>=0)return true; 112 | return false; 113 | } -------------------------------------------------------------------------------- /composer.json.linux: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wing/MysqlBinlog", 3 | "type": "library", 4 | "description": "基于mysql数据库binlog的增量订阅&消费", 5 | "keywords": ["mysql", "binlog", "php", "中间件", "数据监控"], 6 | "homepage": "http://www.itdfy.com/", 7 | "support": { 8 | "issues": "https://www.itdfy.com/", 9 | "email":"297341015@qq.com" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "yuyi", 14 | "email": "297341015@qq.com", 15 | "homepage": "http://www.itdfy.com/" 16 | } 17 | ], 18 | "repositories":[ 19 | { 20 | "type":"vcs", 21 | "url":"https://github.com/jilieryuyi/wing-file-system.git" 22 | }, 23 | { 24 | "type": "composer", 25 | "url": "http://packagist.phpcomposer.com" 26 | }, 27 | { 28 | "packagist": false 29 | } 30 | ], 31 | "config": { 32 | "preferred-install": "dist", 33 | "secure-http": false 34 | }, 35 | "require": { 36 | "php": ">=5.0", 37 | "symfony/console":"~3.1", 38 | "wing/FileSystem":"dev-master", 39 | "psr/log": "~1.0", 40 | "php-amqplib/php-amqplib": ">=2.6", 41 | "workerman/workerman":"dev-master" 42 | }, 43 | "require-dev": { 44 | "phpunit/phpunit": "~5.0" 45 | }, 46 | "autoload": { 47 | "psr-4": {"Wing\\": "src/"}, 48 | "classmap": ["tests/unit/"], 49 | "files": [ 50 | "src/helpers.php" 51 | ] 52 | }, 53 | "extra": { 54 | "branch-alias": { 55 | "dev-master": "1.0-dev" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /composer.json.win: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wing/MysqlBinlog", 3 | "type": "library", 4 | "description": "基于mysql数据库binlog的增量订阅&消费", 5 | "keywords": ["mysql", "binlog", "php", "中间件", "数据监控"], 6 | "homepage": "http://www.itdfy.com/", 7 | "support": { 8 | "issues": "https://www.itdfy.com/", 9 | "email":"297341015@qq.com" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "yuyi", 14 | "email": "297341015@qq.com", 15 | "homepage": "http://www.itdfy.com/" 16 | } 17 | ], 18 | "repositories":[ 19 | { 20 | "type":"vcs", 21 | "url":"https://github.com/jilieryuyi/wing-file-system.git" 22 | }, 23 | { 24 | "type": "composer", 25 | "url": "http://packagist.phpcomposer.com" 26 | }, 27 | { 28 | "packagist": false 29 | } 30 | ], 31 | "config": { 32 | "preferred-install": "dist", 33 | "secure-http": false 34 | }, 35 | "require": { 36 | "php": ">=5.0", 37 | "symfony/console":"~3.1", 38 | "wing/FileSystem":"dev-master", 39 | "psr/log": "~1.0", 40 | "php-amqplib/php-amqplib": ">=2.6" 41 | }, 42 | "require-dev": { 43 | "phpunit/phpunit": "~5.0" 44 | }, 45 | "autoload": { 46 | "psr-4": {"Wing\\": "src/"}, 47 | "classmap": ["tests/unit/"], 48 | "files": [ 49 | "src/helpers.php" 50 | ] 51 | }, 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "1.0-dev" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /config/app.php.example: -------------------------------------------------------------------------------- 1 | "mysqlbinlog", 6 | //从库id 7 | "slave_server_id" => 9999, 8 | //必须配置:mysql 9 | "mysql" => [ 10 | "db_name" => "wordpress", 11 | "host" => "127.0.0.1", 12 | "user" => "root", 13 | "password" => "123456", 14 | "port" => 3306 15 | ], 16 | //以下配置均属于可选订阅 可以任意增加 只需要遵循接口ISubscribe实现即可 17 | //\Wing\Subscribe\Redis::class 是实现 后面的数组是构造函数参数 18 | "subscribe" => [ 19 | //可选redis队列订阅 20 | \Wing\Subscribe\Redis::class => [ 21 | "host" => "127.0.0.1", 22 | "port" => 6397, 23 | "password" => null, //无密码时必须为null 24 | "queue" => "----wing-mysql-events-queue----" //默认的redis队列名称,队列使用rpush从尾部进入队列 25 | ], 26 | 27 | //可选websocket订阅 28 | \Wing\Subscribe\WebSocket::class => [ 29 | "host" => "0.0.0.0", 30 | "port" => 9998, 31 | ], 32 | 33 | //可选tcp订阅 34 | \Wing\Subscribe\Tcp::class => [ 35 | "host" => "0.0.0.0", 36 | "port" => 9997 37 | ], 38 | ] 39 | ]; 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 基于mysql数据库binlog的增量订阅&消费 2 | ==== 3 | >wing-binlog是一个高性能php中间件 4 | wing-binlog是一个轻量化mysql数据库监控系统 5 | wing-binlog可轻松实现不改变一句业务代码实现整库数据变化实时监控 6 | ...... 7 | 8 | ### 2.2升级要点 9 | 1. 去除本地redis依赖 10 | 2. 支持websocket事件通知 11 | 3. 支持tcp事件通知 12 | 4. 简化安装流程 13 | 5. 优化性能问题,使binlog处理速度能达到binlog的写入速度,避免延迟 14 | 6. 支持windows 15 | 7. mysql协议支持 16 | 17 | ### 安装 18 | 1. 开启mysql binlog支持,并且指定格式为row,如下配置 19 | ```` 20 | [mysqld] 21 | server_id = 1 22 | log_bin = mysql-bin 23 | binlog_format=ROW 24 | ```` 25 | 2. 将config下的配置文件.example去除后修改其配置为自己的配置 26 | ```` 27 | cd config && cp app.php.example app.php 28 | ```` 29 | 3. 执行 php wing start --debug 开启服务进程(需要预先安装composer和php,并将php和composer添加到环境变量),可选参数 --d 以守护进程执行, --debug 启用debug模式, --n 指定进程数量,如: 30 | ```` 31 | php wing start --d --debug --n 8 32 | ```` 33 | 4. clients下面有两个测试的客户端,一个websocket和一个php实现的tcp client 34 | 5. 停止所有服务 35 | ```` 36 | php wing stop 37 | ```` 38 | 6. 查看服务状态 39 | ```` 40 | php wing status 41 | ```` 42 | 7. src/Subscribe目录为可选的订阅者服务插件,只需要配置到app.php的subscribe下即可! 43 | wing-binlog提供tcp和websocket服务,可选使用go或者workerman,workerman仅支持linux,go支持所有的平台。 44 | 使用go服务需要安装go,已安装的忽略。 45 | 编译go服务(如需使用,请先编译后再启动Binlog服务): 46 | ```` 47 | cd services 48 | go build -o tcp tcp.go 49 | go build -o websocket websocket.go 50 | ```` 51 | 52 | ### 使用场景 53 | 1. 数据库实时备份 (按业务表自定义或者整库同步) 54 | 2. 异地机房业务,双master机房(两地写入,互相同步) 55 | 3. 业务cache/store数据更新 (根据数据库变更日志,直接更新内存cache或者分布式cache) 56 | 4. 敏感业务数据变更服务(典型的就是金额变化通知,库存变化的通知) 57 | 5. 实时数据增量计算统计 58 | ...... 59 | 60 | ### 帮助 61 | 目录详解:http://www.itdfy.com/details/1156 62 | QQ群咨询 535218312 63 | 64 | ### 致谢 65 | https://github.com/fengxiangyun/mysql-replication 66 | https://github.com/jeremycole/mysql_binlog.git 67 | -------------------------------------------------------------------------------- /services/parse_worker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getSessions($start_pos, $end_pos); 62 | //解析原始数据,得到事件数据(数组) 63 | $file = new \Wing\Library\FileFormat($raw_data, $pdo, $event_index); 64 | $datas = $file->parse(); 65 | 66 | //结果输出 67 | echo json_encode($datas); -------------------------------------------------------------------------------- /services/tcp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilieryuyi/wing-binlog/5020e045cfad9fcdbef51003fb16b7060c3456e1/services/tcp -------------------------------------------------------------------------------- /services/tcp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "log" 7 | "os" 8 | "strings" 9 | "runtime" 10 | "bytes" 11 | "strconv" 12 | "syscall" 13 | "io/ioutil" 14 | "os/signal" 15 | "path/filepath" 16 | ) 17 | 18 | type BODY struct { 19 | conn net.Conn 20 | msg bytes.Buffer 21 | } 22 | 23 | type SEND_BODY struct { 24 | conn net.Conn 25 | msg string 26 | } 27 | 28 | //所有的连接进来的客户端 29 | var clients map[int]net.Conn = make(map[int]net.Conn) 30 | //所有的连接进来的客户端数量 31 | var clients_count int = 0 32 | //收到的消息缓冲区 用于解决粘包 33 | //var msg_buffer bytes.Buffer// = "" 34 | //粘包分隔符 35 | var msg_split string = "\r\n\r\n\r\n"; 36 | //发送次数 37 | var send_times int = 0 38 | //收到消息次数 39 | var msg_times int = 0 40 | //发送失败次数 41 | var failure_times int = 0 42 | 43 | var DEBUG bool = true 44 | //最大的频道长度 可用于并发控制 45 | const MAX_QUEUE = 102400 46 | var MSG_SEND_QUEUE chan SEND_BODY = make(chan SEND_BODY, MAX_QUEUE) 47 | //var MSG_RECEIVE_QUEUE = make(chan BODY, MAX_QUEUE) 48 | func SignalHandle() { 49 | c := make(chan os.Signal) 50 | signal.Notify(c, syscall.SIGTERM) 51 | 52 | //当调用了该方法后,下面的for循环内<-c接收到一个信号就退出了。 53 | signal.Stop(c) 54 | 55 | for { 56 | s := <-c 57 | Log("进程收到退出信号",s) 58 | os.Exit(0) 59 | } 60 | } 61 | 62 | func ResetStd() { 63 | dir := GetParentPath(GetCurrentPath()) 64 | handle, _ := os.OpenFile(dir+"/logs/tcp.log", os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0755) 65 | os.Stdout = handle 66 | os.Stderr = handle 67 | } 68 | 69 | func GetCurrentPath() string { 70 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | return strings.Replace(dir, "\\", "/", -1) 75 | } 76 | 77 | func substr(s string, pos, length int) string { 78 | runes := []rune(s) 79 | l := pos + length 80 | if l > len(runes) { 81 | l = len(runes) 82 | } 83 | return string(runes[pos:l]) 84 | } 85 | func GetParentPath(dirctory string) string { 86 | return substr(dirctory, 0, strings.LastIndex(dirctory, "/")) 87 | } 88 | 89 | func main() { 90 | if len(os.Args) < 2 { 91 | fmt.Println("请使用如下模式启动") 92 | fmt.Println("1、指定端口为9998:tcp 9997") 93 | fmt.Println("2、指定端口为9998并且启用debug模式:tcp 9997 --debug") 94 | return 95 | } 96 | 97 | if (os.Args[1] == "stop") { 98 | dat, _ := ioutil.ReadFile(GetCurrentPath() + "/tcp.pid") 99 | fmt.Print(string(dat)) 100 | pid, _ := strconv.Atoi(string(dat)) 101 | Log("给进程发送终止信号:", pid) 102 | err := syscall.Kill(pid, syscall.SIGTERM) 103 | Log(err) 104 | return 105 | } 106 | 107 | Log(GetParentPath(GetCurrentPath())) 108 | Log(os.Getpid()) 109 | 110 | //写入pid 111 | //handle, _ := os.OpenFile(GetCurrentPath() + "/tcp.pid", os.O_WRONLY | os.O_CREATE | os.O_SYNC, 0755) 112 | //io.WriteString(handle, fmt.Sprintf("%d", os.Getpid())) 113 | 114 | var data_str = []byte(fmt.Sprintf("%d", os.Getpid())); 115 | ioutil.WriteFile(GetCurrentPath() + "/tcp.pid", data_str, 0777) //写入文件(字节数组) 116 | 117 | 118 | if len(os.Args) == 3 { 119 | if os.Args[2] == "debug" || os.Args[2] == "--debug" { 120 | DEBUG = true 121 | } 122 | } 123 | Log(DEBUG) 124 | if !DEBUG { 125 | ResetStd() 126 | } else { 127 | Log("debug模式") 128 | } 129 | 130 | go MainThread() 131 | go SignalHandle() 132 | //建立socket,监听端口 133 | listen, err := net.Listen("tcp", "0.0.0.0:" + os.Args[1]) 134 | DealError(err) 135 | defer func() { 136 | listen.Close(); 137 | close(MSG_SEND_QUEUE) 138 | //close(MSG_RECEIVE_QUEUE) 139 | }() 140 | Log("等待新的连接...") 141 | 142 | // runtime.GOMAXPROCS(32) 143 | // 限制同时运行的goroutines数量 144 | //go MainThread() 145 | 146 | for { 147 | conn, err := listen.Accept() 148 | if err != nil { 149 | continue 150 | } 151 | go OnConnect(conn) 152 | } 153 | } 154 | 155 | //添加客户端到集合 156 | func AddClient(conn net.Conn) { 157 | clients[clients_count] = conn 158 | clients_count++ 159 | } 160 | 161 | //将客户端从集合移除 由于移除操作不会重建索引clients_count就是当前最后的索引 162 | func RemoveClient(conn net.Conn){ 163 | // 遍历map 164 | for k, v := range clients { 165 | if v.RemoteAddr().String() == conn.RemoteAddr().String() { 166 | delete(clients, k) 167 | } 168 | } 169 | } 170 | 171 | func Broadcast(msg SEND_BODY) { 172 | size, err := msg.conn.Write([]byte(msg.msg+"\r\n\r\n\r\n")) 173 | if (size <= 0 || err != nil) { 174 | failure_times++ 175 | } 176 | Log("失败次数:", failure_times) 177 | } 178 | 179 | /** 180 | * 广播 181 | * 182 | * @param string msg 183 | */ 184 | func MainThread() { 185 | //to := time.NewTimer(time.Second*3) 186 | cpu := runtime.NumCPU() 187 | for i := 0; i < cpu; i ++ { 188 | go func() { 189 | for { 190 | select { 191 | case body := <-MSG_SEND_QUEUE: 192 | Broadcast(body) 193 | //case <-to.C://time.After(time.Second*3): 194 | // Log("发送超时...") 195 | } 196 | } 197 | } () 198 | } 199 | } 200 | 201 | 202 | //处理连接 203 | func OnConnect(conn net.Conn) { 204 | Log(conn.RemoteAddr().String(), "连接成功") 205 | AddClient(conn) 206 | read_buffer := make([]byte, 20480) 207 | var msg_buffer bytes.Buffer 208 | body := BODY{conn, msg_buffer} 209 | for { 210 | 211 | size, err := conn.Read(read_buffer) 212 | 213 | if err != nil { 214 | Log(conn.RemoteAddr().String(), "连接发生错误: ", err) 215 | OnClose(conn); 216 | conn.Close(); 217 | return 218 | } 219 | 220 | msg_times++ 221 | Log("收到消息的次数:", msg_times) 222 | 223 | body.msg.Write(read_buffer[:size]) 224 | //MSG_RECEIVE_QUEUE <- BODY{conn, string(buffer[:size])} 225 | OnMessage(&body) 226 | } 227 | 228 | } 229 | 230 | //收到消息回调函数 231 | func OnMessage(body *BODY) { 232 | 233 | //html := "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nContent-Type: text/html\r\n\r\nhello" 234 | //msg_buffer.WriteString(msg)// += msg 235 | 236 | //粘包处理 237 | temp := strings.Split(body.msg.String(), msg_split) 238 | temp_len := len(temp) 239 | 240 | if (temp_len >= 2) { 241 | body.msg.Reset() 242 | body.msg.WriteString(temp[temp_len - 1]) 243 | 244 | for _, v := range temp { 245 | if strings.EqualFold(v, "") { 246 | continue 247 | } 248 | 249 | 250 | send_times++; 251 | Log("广播次数:", send_times) 252 | 253 | for _, client := range clients { 254 | //非常关键的一步 如果这里也给接发来的人广播 接收端不消费 255 | //发送会被阻塞 256 | if client.RemoteAddr().String() == body.conn.RemoteAddr().String() { 257 | continue 258 | } 259 | //fmt.Println("广播----", v, msg) 260 | //v.SetWriteDeadline() 261 | //go func () { 262 | //wg.Add(1)//为同步等待组增加一个成员 263 | //v.SetWriteDeadline(time.Now().Add(time.Millisecond * 100)) 264 | //size, err := v.Write([]byte(msg)) 265 | 266 | MSG_SEND_QUEUE <- SEND_BODY{client, v} 267 | 268 | //if (size <= 0 || err != nil) { 269 | // failure_times++ 270 | //} 271 | //}() 272 | } 273 | 274 | 275 | } 276 | } 277 | } 278 | 279 | func OnClose(conn net.Conn) { 280 | RemoveClient(conn) 281 | } 282 | 283 | func Log(v ...interface{}) { 284 | if (DEBUG) { 285 | log.Println(v...) 286 | } 287 | } 288 | 289 | func DealError(err error) { 290 | if err != nil { 291 | fmt.Fprintf(os.Stderr, "发生严重错误: %s", err.Error()) 292 | os.Exit(1) 293 | } 294 | } -------------------------------------------------------------------------------- /services/tcp.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | count = $workers; 35 | 36 | // Emitted when new connection come 37 | $ws_worker->onConnect = function($connection) 38 | { 39 | $connection->maxSendBufferSize = 104857600; 40 | echo "New connection\n"; 41 | }; 42 | 43 | $ws_worker->onWorkerStart = function($ws_worker) 44 | { 45 | echo "onWorkerStart"; 46 | // 只在id编号为0的进程上设置定时器,其它1、2、3号进程不设置定时器 47 | // if($worker->id === 0) 48 | { 49 | // \Workerman\Lib\Timer::add(0.001, function() use($ws_worker){ 50 | // ob_start(); 51 | // $path[] = HOME . "/cache/tcp/".$ws_worker->id."/*"; 52 | // while (count($path) != 0) { 53 | // $v = array_shift($path); 54 | // foreach (glob($v) as $item) { 55 | // if (is_file($item)) { 56 | // //$send_count++; 57 | // $content = file_get_contents($item); 58 | // if (!$content) { 59 | // continue; 60 | // } 61 | // foreach ($ws_worker->connections as $c) { 62 | // echo "发送tcp消息:", $content,"\r\n"; 63 | // $res = $c->send($content."\r\n\r\n\r\n"); 64 | // if ($res) { 65 | // echo "成功\r\n"; 66 | // } else { 67 | // echo "失败\r\n"; 68 | // } 69 | // // $send_count2++; 70 | // } 71 | // 72 | // //echo $send_count.":".$send_count2,"\r\n\r\n"; 73 | // //file_put_contents(HOME."/logs/tcp", $send_count.":".$send_count2); 74 | // 75 | // unlink($item); 76 | // } 77 | // } 78 | // } 79 | // $debug = ob_get_contents(); 80 | // ob_end_clean(); 81 | // 82 | // if ($debug) { 83 | // echo $debug; 84 | // } 85 | // }); 86 | } 87 | }; 88 | 89 | 90 | $ws_worker->onError = function () 91 | { 92 | echo "发生错误".json_encode(func_get_args(), JSON_UNESCAPED_UNICODE), "\r\n"; 93 | }; 94 | $ws_worker->onBufferFull = function() 95 | { 96 | echo "发送缓冲区满".json_encode(func_get_args(), JSON_UNESCAPED_UNICODE), "\r\n"; 97 | }; 98 | $ws_worker->onBufferDrain = function() 99 | { 100 | echo "发送缓冲可以继续".json_encode(func_get_args(), JSON_UNESCAPED_UNICODE), "\r\n"; 101 | }; 102 | 103 | //$msg_all = ""; 104 | 105 | // Emitted when data received 106 | $ws_worker->onMessage = function($connection, $data) use($ws_worker) 107 | { 108 | 109 | // global $ws_worker; 110 | static $msg_all; 111 | static $count = 0; 112 | $split = "\r\n\r\n\r\n"; 113 | //while ($msg = socket_read($socket, 10240)) 114 | { 115 | //echo $msg,"\r\n\r\n"; 116 | $msg_all .= $data; 117 | $temp = explode($split, $msg_all); 118 | if (count($temp) >= 2) { 119 | $msg_all = array_pop($temp); 120 | foreach ($temp as $v) { 121 | $arr = json_decode($v , true); 122 | if (!$v || !is_array($arr) || count($arr) <= 0) { 123 | echo $v,"格式不正确\r\n"; 124 | continue; 125 | } 126 | $count++; 127 | echo $v, "\r\n"; 128 | echo "收到消息次数:", $count, "\r\n\r\n"; 129 | 130 | foreach ($ws_worker->connections as $c) { 131 | if ($c == $connection) { 132 | echo "当前链接不发送\r\n"; 133 | continue; 134 | } 135 | // echo "发送tcp消息:", $content,"\r\n"; 136 | $res = $c->send($v."\r\n\r\n\r\n"); 137 | if ($res) { 138 | echo "成功\r\n"; 139 | } else { 140 | echo "失败\r\n"; 141 | } 142 | // $send_count2++; 143 | } 144 | 145 | 146 | 147 | } 148 | } 149 | unset($temp); 150 | } 151 | 152 | 153 | // \Workerman\Lib\Timer::add(0.001, function() use($ws_worker){ 154 | // ob_start(); 155 | // $path[] = HOME . "/cache/tcp/".$ws_worker->id."/*"; 156 | // while (count($path) != 0) { 157 | // $v = array_shift($path); 158 | // foreach (glob($v) as $item) { 159 | // if (is_file($item)) { 160 | // //$send_count++; 161 | // $content = file_get_contents($item); 162 | // if (!$content) { 163 | // continue; 164 | // } 165 | // foreach ($ws_worker->connections as $c) { 166 | // echo "发送tcp消息:", $content,"\r\n"; 167 | // $res = $c->send($content."\r\n\r\n\r\n"); 168 | // if ($res) { 169 | // echo "成功\r\n"; 170 | // } else { 171 | // echo "失败\r\n"; 172 | // } 173 | // // $send_count2++; 174 | // } 175 | // 176 | // //echo $send_count.":".$send_count2,"\r\n\r\n"; 177 | // //file_put_contents(HOME."/logs/tcp", $send_count.":".$send_count2); 178 | // 179 | // unlink($item); 180 | // } 181 | // } 182 | // } 183 | // $debug = ob_get_contents(); 184 | // ob_end_clean(); 185 | // 186 | // if ($debug) { 187 | // echo $debug; 188 | // } 189 | // }); 190 | 191 | 192 | // global $ws_worker,$send_count,$send_count2; 193 | //$current_process_id = get_current_processid(); 194 | 195 | // Send hello $data 196 | //$connection->send('hello ' . $data); 197 | // ob_start(); 198 | // $path[] = HOME . "/cache/tcp/*"; 199 | // while (count($path) != 0) { 200 | // $v = array_shift($path); 201 | // foreach (glob($v) as $item) { 202 | // if (is_file($item)) { 203 | // $send_count++; 204 | // $content = file_get_contents($item); 205 | // if (!$content) { 206 | // continue; 207 | // } 208 | // foreach ($ws_worker->connections as $c) { 209 | // echo "发送tcp消息:", $content,"\r\n"; 210 | // $res = $c->send($content."\r\n\r\n\r\n"); 211 | // if ($res) { 212 | // echo "成功\r\n"; 213 | // } else { 214 | // echo "失败\r\n"; 215 | // } 216 | // $send_count2++; 217 | // } 218 | // 219 | // echo $send_count.":".$send_count2,"\r\n\r\n"; 220 | // file_put_contents(HOME."/logs/tcp", $send_count.":".$send_count2); 221 | // 222 | // unlink($item); 223 | // } 224 | // } 225 | // } 226 | // $debug = ob_get_contents(); 227 | // ob_end_clean(); 228 | // 229 | // if ($debug) { 230 | // echo $debug; 231 | // } 232 | 233 | // unset($debug, $recv_msg); 234 | }; 235 | 236 | // Emitted when connection closed 237 | $ws_worker->onClose = function($connection) 238 | { 239 | echo "Connection closed\n"; 240 | }; 241 | 242 | //$ws_worker->onWorkerStart = function(){ 243 | // //file_put_contents(HOME."/tcp.pid",get_current_processid()." ", FILE_APPEND); 244 | //}; 245 | // Run worker 246 | Worker::runAll(); 247 | -------------------------------------------------------------------------------- /services/websocket: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilieryuyi/wing-binlog/5020e045cfad9fcdbef51003fb16b7060c3456e1/services/websocket -------------------------------------------------------------------------------- /services/websocket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-martini/martini" 6 | "github.com/gorilla/websocket" 7 | "log" 8 | "net/http" 9 | "strings" 10 | "bytes" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | "path/filepath" 15 | "io/ioutil" 16 | "strconv" 17 | ) 18 | 19 | const ( 20 | readBufferSize = 10240 21 | writeBufferSize = 10240 22 | ) 23 | 24 | type BODY struct { 25 | conn *websocket.Conn 26 | msg bytes.Buffer 27 | } 28 | 29 | type SEND_BODY struct { 30 | conn *websocket.Conn 31 | msg string 32 | } 33 | 34 | //所有的连接进来的客户端 35 | var clients map[int]*websocket.Conn = make(map[int]*websocket.Conn) 36 | //所有的连接进来的客户端数量 37 | var clients_count int = 0 38 | const MAX_SEND_QUEUE int = 102400 39 | var send_msg_chan chan SEND_BODY = make(chan SEND_BODY, MAX_SEND_QUEUE) 40 | var msg_split string = "\r\n\r\n\r\n"; 41 | var DEBUG bool = true 42 | var send_times int = 0 43 | var send_error_times int = 0 44 | 45 | func OnConnect(conn *websocket.Conn) { 46 | 47 | clients[clients_count] = conn 48 | clients_count++ 49 | var buffer bytes.Buffer 50 | body := BODY{conn, buffer} 51 | 52 | for { 53 | _, message, err := conn.ReadMessage() 54 | if err != nil { 55 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { 56 | log.Printf("error: %v", err) 57 | } 58 | 59 | for key, client := range clients { 60 | if (conn.RemoteAddr().String() == client.RemoteAddr().String()) { 61 | delete(clients, key) 62 | //delete(msg_buffer, conn.RemoteAddr().String()) 63 | } 64 | } 65 | 66 | conn.Close(); 67 | break 68 | } 69 | msg := fmt.Sprintf("%s", message) 70 | Log("收到消息:", msg) 71 | body.msg.Write(message) 72 | OnMessage(&body) 73 | } 74 | } 75 | 76 | func OnMessage(conn *BODY) { 77 | 78 | //html := "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nContent-Type: text/html\r\n\r\nhello" 79 | //粘包处理 80 | temp := strings.Split(conn.msg.String(), msg_split) 81 | temp_len := len(temp) 82 | 83 | if (temp_len >= 2) { 84 | conn.msg.Reset() 85 | conn.msg.WriteString(temp[temp_len - 1]) 86 | 87 | for _, v := range temp { 88 | if strings.EqualFold(v, "") { 89 | continue 90 | } 91 | 92 | v += "\r\n\r\n\r\n" 93 | send_times++; 94 | Log("广播次数:", send_times) 95 | 96 | for _, client := range clients { 97 | if (conn.conn.RemoteAddr().String() == client.RemoteAddr().String()) { 98 | Log("不给自己发广播...") 99 | continue 100 | } 101 | if (len(send_msg_chan) >= MAX_SEND_QUEUE) { 102 | Log("发送缓冲区满") 103 | } else { 104 | send_msg_chan <- SEND_BODY{client, v} 105 | } 106 | } 107 | 108 | } 109 | } 110 | } 111 | 112 | func MainThread() { 113 | go func() { 114 | for { 115 | select { 116 | case body := <-send_msg_chan: 117 | //body.conn.SetWriteDeadline(time.Now().Add(time.Second * 3)) 118 | Log("发送:", body.msg) 119 | err := body.conn.WriteMessage(1, []byte(body.msg)) 120 | if err != nil { 121 | send_error_times++ 122 | Log("发送失败次数:", send_error_times) 123 | Log(err) 124 | } 125 | //case <-to.C://time.After(time.Second*3): 126 | // Log("发送超时...") 127 | } 128 | } 129 | }() 130 | } 131 | 132 | 133 | func Log(v ...interface{}) { 134 | if (DEBUG) { 135 | log.Println(v...) 136 | } 137 | } 138 | 139 | func SignalHandle() { 140 | c := make(chan os.Signal) 141 | signal.Notify(c, syscall.SIGTERM) 142 | 143 | //当调用了该方法后,下面的for循环内<-c接收到一个信号就退出了。 144 | signal.Stop(c) 145 | 146 | for { 147 | s := <-c 148 | Log("进程收到退出信号",s) 149 | os.Exit(0) 150 | } 151 | } 152 | 153 | func ResetStd() { 154 | dir := GetParentPath(GetCurrentPath()) 155 | handle, _ := os.OpenFile(dir+"/logs/websocket.log", os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0755) 156 | os.Stdout = handle 157 | os.Stderr = handle 158 | } 159 | 160 | func GetCurrentPath() string { 161 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 162 | if err != nil { 163 | log.Fatal(err) 164 | } 165 | return strings.Replace(dir, "\\", "/", -1) 166 | } 167 | 168 | func substr(s string, pos, length int) string { 169 | runes := []rune(s) 170 | l := pos + length 171 | if l > len(runes) { 172 | l = len(runes) 173 | } 174 | return string(runes[pos:l]) 175 | } 176 | func GetParentPath(dirctory string) string { 177 | return substr(dirctory, 0, strings.LastIndex(dirctory, "/")) 178 | } 179 | 180 | func main() { 181 | if len(os.Args) < 2 { 182 | fmt.Println("请使用如下模式启动") 183 | fmt.Println("1、指定端口为9998:websocket 9998") 184 | fmt.Println("2、指定端口为9998并且启用debug模式:websocket 9998 --debug") 185 | return 186 | } 187 | 188 | if (os.Args[1] == "stop") { 189 | dat, _ := ioutil.ReadFile(GetCurrentPath() + "/websocket.pid") 190 | fmt.Print(string(dat)) 191 | pid, _ := strconv.Atoi(string(dat)) 192 | Log("给进程发送终止信号:", pid) 193 | 194 | err := syscall.Kill(pid, syscall.SIGTERM) 195 | Log(err) 196 | return 197 | } 198 | 199 | Log(GetParentPath(GetCurrentPath())) 200 | Log(os.Getpid()) 201 | 202 | //写入pid 203 | Log("写入pid", os.Getpid(), "---",fmt.Sprintf("%d", os.Getpid())) 204 | //handle, _ := os.OpenFile(GetCurrentPath() + "/websocket.pid", os.O_WRONLY | os.O_CREATE | os.O_SYNC, 0755) 205 | //io.WriteString(handle, fmt.Sprintf("%d ", os.Getpid())) 206 | 207 | var data_str = []byte(fmt.Sprintf("%d", os.Getpid())); 208 | ioutil.WriteFile(GetCurrentPath() + "/websocket.pid", data_str, 0777) //写入文件(字节数组) 209 | 210 | if len(os.Args) == 3 { 211 | if os.Args[2] == "debug" || os.Args[2] == "--debug" { 212 | DEBUG = true 213 | } 214 | } 215 | Log(DEBUG) 216 | if !DEBUG { 217 | ResetStd() 218 | } else { 219 | Log("debug模式") 220 | } 221 | 222 | go MainThread() 223 | go SignalHandle() 224 | 225 | m := martini.Classic() 226 | 227 | m.Get("/", func(res http.ResponseWriter, req *http.Request) { 228 | // res and req are injected by Martini 229 | 230 | u := websocket.Upgrader{ReadBufferSize: readBufferSize, WriteBufferSize: writeBufferSize} 231 | u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { 232 | Log(w, r, status, reason) 233 | // don't return errors to maintain backwards compatibility 234 | } 235 | u.CheckOrigin = func(r *http.Request) bool { 236 | // allow all connections by default 237 | return true 238 | } 239 | conn, err := u.Upgrade(res, req, nil) 240 | 241 | if err != nil { 242 | log.Println(err) 243 | return 244 | } 245 | 246 | Log("新的连接:" + conn.RemoteAddr().String()) 247 | go OnConnect(conn) 248 | }) 249 | 250 | m.RunOnAddr(":" + os.Args[1]) 251 | } -------------------------------------------------------------------------------- /services/websocket.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | count = 1;//$workers; 35 | 36 | // Emitted when new connection come 37 | $ws_worker->onConnect = function($connection) 38 | { 39 | $connection->maxSendBufferSize = 104857600; 40 | echo "New connection\n"; 41 | }; 42 | 43 | 44 | // Emitted when data received 45 | $ws_worker->onMessage = function($connection, $data) use($ws_worker) 46 | { 47 | echo "收到消息:", $data, "\r\n"; 48 | static $msg_all; 49 | static $count = 0; 50 | $split = "\r\n\r\n\r\n"; 51 | //while ($msg = socket_read($socket, 10240)) 52 | { 53 | //echo $msg,"\r\n\r\n"; 54 | $msg_all .= $data; 55 | $temp = explode($split, $msg_all); 56 | if (count($temp) >= 2) { 57 | $msg_all = array_pop($temp); 58 | foreach ($temp as $v) { 59 | $arr = json_decode($v , true); 60 | if (!$v) { 61 | echo "消息为空\r\n"; 62 | continue; 63 | } 64 | if (!is_array($arr) || count($arr) <= 0) { 65 | echo "不是标准数组\r\n"; 66 | echo "=====>".$v."<====="; 67 | continue; 68 | } 69 | $count++; 70 | echo $v, "\r\n"; 71 | echo "收到消息次数:", $count, "\r\n\r\n"; 72 | 73 | foreach ($ws_worker->connections as $c) { 74 | if ($c == $connection) { 75 | echo "当前的连接不发送\r\n"; 76 | continue; 77 | } 78 | // echo "发送tcp消息:", $content,"\r\n"; 79 | $res = $c->send($v."\r\n\r\n\r\n"); 80 | if ($res) { 81 | echo "成功\r\n"; 82 | } else { 83 | echo "失败\r\n"; 84 | } 85 | // $send_count2++; 86 | } 87 | } 88 | } 89 | unset($temp); 90 | } 91 | 92 | 93 | 94 | // }); 95 | 96 | 97 | 98 | }; 99 | $ws_worker->onError = function () 100 | { 101 | echo "发生错误".json_encode(func_get_args(), JSON_UNESCAPED_UNICODE), "\r\n"; 102 | }; 103 | $ws_worker->onBufferFull = function() 104 | { 105 | echo "发送缓冲区满".json_encode(func_get_args(), JSON_UNESCAPED_UNICODE), "\r\n"; 106 | }; 107 | $ws_worker->onBufferDrain = function() 108 | { 109 | echo "发送缓冲可以继续".json_encode(func_get_args(), JSON_UNESCAPED_UNICODE), "\r\n"; 110 | }; 111 | // Emitted when connection closed 112 | $ws_worker->onClose = function($connection) 113 | { 114 | echo "Connection closed\n"; 115 | }; 116 | $ws_worker->onWorkerStart = function($ws_worker){ 117 | // \Workerman\Lib\Timer::add(0.001, function() use($ws_worker){ 118 | // ob_start(); 119 | // $path[] = HOME . "/cache/websocket/".$ws_worker->id."/*"; 120 | // while (count($path) != 0) { 121 | // $v = array_shift($path); 122 | // foreach (glob($v) as $item) { 123 | // if (is_file($item)) { 124 | // $content = file_get_contents($item); 125 | // if (!$content) { 126 | // continue; 127 | // } 128 | // foreach ($ws_worker->connections as $c) { 129 | // $c->send($content); 130 | // } 131 | // unlink($item); 132 | // } 133 | // } 134 | // } 135 | // $debug = ob_get_contents(); 136 | // ob_end_clean(); 137 | // 138 | // if ($debug) { 139 | // echo $debug; 140 | // } 141 | // 142 | // }); 143 | }; 144 | 145 | // Run worker 146 | Worker::runAll(); 147 | -------------------------------------------------------------------------------- /src/Bin/Auth/Auth.php: -------------------------------------------------------------------------------- 1 | socket, SOL_SOCKET,SO_SNDTIMEO, ['sec' => 2, 'usec' => 5000]); 40 | //socket_set_option($this->socket, SOL_SOCKET,SO_RCVTIMEO, ['sec' => 2, 'usec' => 5000]); 41 | 42 | //连接到mysql 43 | if (!socket_connect($socket, $host, $port)) { 44 | $error_code = socket_last_error(); 45 | $error_msg = sprintf('error:%s, msg:%s', socket_last_error(), socket_strerror($error_code)); 46 | throw new \Exception($error_msg, $error_code); 47 | } 48 | 49 | self::$socket = Net::$socket = $socket; 50 | $serverinfo = self::auth($user, $password, $db_name); 51 | 52 | return [self::$socket, $serverinfo]; 53 | } 54 | 55 | /** 56 | * 认证 57 | * 58 | * @param string $user 59 | * @param string $password 60 | * @param string $db 61 | * @throws \Exception 62 | * @return ServerInfo|null 63 | */ 64 | private static function auth($user, $password, $db) 65 | { 66 | //mysql认证流程 67 | // 1、socket连接服务器 68 | // 2、读取服务器返回的信息,关键是获取到加盐信息,用于后面生成加密的password 69 | // 3、生成auth协议包 70 | // 4、发送协议包,认证完成 71 | 72 | // 获取server信息 加密salt 73 | $pack = Net::readPacket(); 74 | $server_info = ServerInfo::parse($pack); 75 | 76 | //希望的服务器权能信息 77 | $flag = CapabilityFlag::DEFAULT_CAPABILITIES; 78 | if ($db) { 79 | $flag |= CapabilityFlag::CLIENT_CONNECT_WITH_DB; 80 | } 81 | /** 82 | clientFlags := clientProtocol41 | 83 | clientSecureConn | 84 | clientLongPassword | 85 | clientTransactions | 86 | clientLocalFiles | 87 | clientPluginAuth | 88 | clientMultiResults | 89 | mc.flags&clientLongFlag 90 | * if mc.cfg.ClientFoundRows { 91 | clientFlags |= clientFoundRows 92 | } 93 | 94 | // To enable TLS / SSL 95 | if mc.cfg.tls != nil { 96 | clientFlags |= clientSSL 97 | } 98 | 99 | if mc.cfg.MultiStatements { 100 | clientFlags |= clientMultiStatements 101 | } 102 | 103 | */ 104 | 105 | 106 | //认证 107 | $data = Packet::getAuth($flag, $user, $password, $server_info->salt, $db); 108 | if (!Net::send($data)) { 109 | return null; 110 | } 111 | 112 | $result = Net::readPacket(); 113 | // 认证是否成功 114 | Packet::success($result); 115 | return $server_info; 116 | } 117 | 118 | /** 119 | * 释放socket资源,关闭socket连接 120 | */ 121 | public static function free() 122 | { 123 | socket_close(self::$socket); 124 | } 125 | } -------------------------------------------------------------------------------- /src/Bin/Auth/ServerInfo.php: -------------------------------------------------------------------------------- 1 | protocol version 13 | string server version (MariaDB server version is by default prefixed by "5.5.5-") 14 | int<4> connection id 15 | string<8> scramble 1st part (authentication seed) 16 | string<1> reserved byte 17 | int<2> server capabilities (1st part) 18 | int<1> server default collation 19 | int<2> status flags 20 | int<2> server capabilities (2nd part) 21 | int<1> length of scramble's 2nd part 22 | if (server_capabilities & PLUGIN_AUTH) 23 | int<1> plugin data length 24 | else 25 | int<1> 0x00 26 | string<6> filler 27 | if (server_capabilities & CLIENT_MYSQL) 28 | string<4> filler 29 | else 30 | int<4> server capabilities 3rd part . MariaDB specific flags /-- MariaDB 10.2 or later 31 | if (server_capabilities & CLIENT_SECURE_CONNECTION) 32 | string scramble 2nd part . Length = max(12, plugin data length - 9) 33 | string<1> reserved byte 34 | if (server_capabilities & PLUGIN_AUTH) 35 | string authentication plugin name 36 | */ 37 | class ServerInfo 38 | { 39 | const CLIENT_PLUGIN_AUTH = (1 << 19); 40 | /*服务协议版本号:该值由 PROTOCOL_VERSION 41 | 宏定义决定(参考MySQL源代码/include/mysql_version.h头文件定义) 42 | mysql-server/config.h.cmake 399 43 | */ 44 | public $protocol_version = ''; 45 | /** 46 | * @var string $server_info 47 | * mysql-server/include/mysql_version.h.in 12行 48 | * mysql-server/cmake/mysql_version.cmake 59行 49 | */ 50 | public $server_info = ''; 51 | public $character_set = ''; 52 | public $salt = ''; 53 | public $thread_id = 0; 54 | //mysql-server/sql/auth/sql_authentication.cc 567行 55 | public $auth_plugin_name = ''; 56 | //mysql-server/include/mysql_com.h 204 238 57 | //mysql初始化的权能信息为 CapabilityFlag::CLIENT_BASIC_FLAGS 58 | public $capability_flag; 59 | //define Wing\Bin\Constant\ServerStatus 60 | public $server_status; 61 | 62 | public static function parse($pack){ 63 | return new self($pack); 64 | } 65 | 66 | public function __construct($pack) 67 | { 68 | $offset = 0; 69 | $length = strlen($pack); 70 | //1byte协议版本号 71 | //int<1> protocol version 72 | $this->protocol_version = ord($pack[$offset]); 73 | 74 | //string server version (MariaDB server version is by default prefixed by "5.5.5-") 75 | //服务器版本信息 以null(0x00)结束 76 | while ($pack[$offset++] !== chr(0x00)) { 77 | $this->server_info .= $pack[$offset]; 78 | } 79 | 80 | //int<4> connection id 81 | //thread_id 4 bytes 线程id 82 | $this->thread_id = unpack("V", substr($pack, $offset, 4))[1]; 83 | $offset += 4; 84 | 85 | //string<8> scramble 1st part (authentication seed) 86 | //8bytes加盐信息 用于握手认证 87 | $this->salt .= substr($pack, $offset,8); 88 | $offset = $offset + 8; 89 | 90 | //string<1> reserved byte 1byte保留值 91 | //1byte填充值 -- 0x00 92 | $offset++; 93 | 94 | //int<2> server capabilities (1st part) 95 | //2bytes 低位服务器权能信息 96 | $this->capability_flag = $pack[$offset]. $pack[$offset+1]; 97 | $offset = $offset + 2; 98 | 99 | //int<1> server default collation 100 | //1byte字符编码 101 | $this->character_set = ord($pack[$offset]); 102 | $offset++; 103 | 104 | //int<2> status flags 105 | //2byte服务器状态 106 | //SERVER_STATUS_AUTOCOMMIT == 2 107 | $this->server_status = unpack("v", $pack[$offset].$pack[$offset+1])[1]; 108 | $offset += 2; 109 | 110 | //int<2> server capabilities (2nd part) 111 | //服务器权能标志 高16位 112 | $this->capability_flag = unpack("V", $this->capability_flag.$pack[$offset]. $pack[$offset+1])[1]; 113 | $offset += 2; 114 | 115 | //int<1> length of scramble's 2nd part 116 | //1byte加盐长度 117 | $salt_len = ord($pack[$offset]); 118 | $offset++; 119 | 120 | /** 121 | if (server_capabilities & PLUGIN_AUTH) 122 | int<1> plugin data length 123 | else 124 | int<1> 0x00 125 | */ 126 | /*$plugin_data_length = 0; 127 | if ($this->capability_flag & self::CLIENT_PLUGIN_AUTH) { 128 | //int<1> plugin data length 129 | $plugin_data_length = ord($offset); 130 | //$offset++; 131 | } else { 132 | //int<1> 0x00 133 | //$offset++; 134 | }*/ 135 | 136 | //string<6> filler 137 | //$offset += 6; 138 | 139 | // if (server_capabilities & CLIENT_MYSQL) 140 | // string<4> filler 141 | // else 142 | // int<4> server capabilities 3rd part . MariaDB specific flags /-- MariaDB 10.2 or later 143 | // 144 | //$offset += 4; 145 | 146 | //mysql-server/sql/auth/sql_authentication.cc 2696 native_password_authenticate 147 | $salt_len = max(12, $salt_len - 9); 148 | 149 | //10bytes填充值 0x00 150 | $offset += 10; 151 | 152 | /** 153 | if (server_capabilities & CLIENT_SECURE_CONNECTION) 154 | string scramble 2nd part . Length = max(12, plugin data length - 9) 155 | */ 156 | //第二部分加盐信息,至少12字符 157 | $this->salt .= substr($pack, $offset, $salt_len); 158 | $offset += $salt_len; 159 | 160 | //string<1> reserved byte 161 | $offset += 1; 162 | 163 | //if (server_capabilities & PLUGIN_AUTH) 164 | // string authentication plugin name 165 | //$length - 1 去除null字符 166 | $len = $length-$offset-1; 167 | if ($len > 0) { 168 | $this->auth_plugin_name = substr($pack,$offset, $len); 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /src/Bin/Constant/CapabilityFlag.php: -------------------------------------------------------------------------------- 1 | value = $value; 92 | if ($type === -1) { 93 | $this->type = $this->getType(); 94 | } else { 95 | $this->type = $type; 96 | } 97 | } 98 | 99 | private function isInt() 100 | { 101 | return (intval($this->value) - $this->value) == 0; 102 | } 103 | 104 | /** 105 | * @return int 106 | */ 107 | public function getType() 108 | { 109 | if(!is_numeric($this->value)) { 110 | return self::VAR_STRING; 111 | } 112 | 113 | if ($this->isInt()) { 114 | $this->value = intval($this->value); 115 | //tinyint 116 | if((-1 << 7) <= $this->value && $this->value <= ((1 << 7)-1)) { 117 | return self::TINY; 118 | } 119 | //2字节 120 | else if((-1 << 15) <= $this->value && $this->value <= ((1 << 15)-1)) { 121 | self::SHORT; 122 | } 123 | //3个字节 124 | else if((-1 << 23) <= $this->value && $this->value <= ((1 << 23)-1)) { 125 | self::INT24; 126 | } 127 | //4个字节 128 | else if((-1 << 31) <= $this->value && $this->value <= ((1 << 31)-1)) { 129 | return self::BIGINT; 130 | } 131 | return self::BIGINT; 132 | } else { 133 | //浮点数 134 | //float型数据的取值范围在-3.4*10^38到+3.4*10^38次之间 135 | // if ($this->value >= -3.4*pow(10, 38) && $this->value < 3.4 * pow(10, 38)) { 136 | // return self::FLOAT; 137 | // } 138 | } 139 | 140 | //其他的一律以字符串处理,有待验证 141 | return self::VAR_STRING; 142 | } 143 | 144 | /** 145 | * string 146 | */ 147 | private function storeLength() 148 | { 149 | $length = strlen($this->value); 150 | if ($length < 251) { 151 | return chr($length); 152 | } 153 | 154 | /* 251 is reserved for NULL */ 155 | if ($length < 65536) { 156 | return chr(252).chr($length).chr($length >> 8); 157 | //pack("v", $length); 158 | } 159 | 160 | if ($length < 16777216) { 161 | $data = chr(253); 162 | $data .= chr($length).chr($length >> 8).chr($length >> 16); 163 | return $data; 164 | } 165 | return chr(254).self::packIn64($length);//pack("P", $length); 166 | } 167 | 168 | private function packIn64($value) 169 | { 170 | $def_temp = $value; 171 | $def_temp2 = ($value >> 32); 172 | $data = chr($def_temp).chr($def_temp>> 8).chr($def_temp >> 16).chr($def_temp >> 24); 173 | $data .= chr($def_temp2).chr($def_temp2>> 8).chr($def_temp2 >> 16).chr($def_temp2 >> 24); 174 | return $data; 175 | } 176 | 177 | public function pack() 178 | { 179 | switch ($this->type) { 180 | case self::TINY: 181 | return chr($this->value); 182 | break; 183 | case self::SHORT: 184 | return chr($this->value).chr($this->value >> 8); 185 | break; 186 | case self::INT24: 187 | return chr($this->value).chr($this->value >> 8).chr($this->value >> 16); 188 | break; 189 | case self::BIGINT: 190 | $def_temp = $this->value; 191 | $def_temp2 = ($this->value >> 32); 192 | $data = chr($def_temp).chr($def_temp>> 8).chr($def_temp >> 16).chr($def_temp >> 24); 193 | $data .= chr($def_temp2).chr($def_temp2>> 8).chr($def_temp2 >> 16).chr($def_temp2 >> 24); 194 | return $data; 195 | break; 196 | case self::VAR_STRING: 197 | return $this->storeLength().$this->value; 198 | 199 | } 200 | 201 | //include/big_endian.h 202 | //self::FLOAT; 203 | //float4store 204 | 205 | //self::DOUBLE; 206 | //float8store 207 | 208 | return null; 209 | } 210 | 211 | 212 | public static function parse(array $params) 213 | { 214 | $res = []; 215 | foreach ($params as $value) { 216 | $res[] = new self($value); 217 | } 218 | return $res; 219 | } 220 | 221 | public static function fieldtype2str($type) 222 | { 223 | switch ($type) { 224 | case self::BIT: return "BIT"; 225 | case self::BLOB: return "BLOB"; 226 | case self::DATE: return "DATE"; 227 | case self::DATETIME: return "DATETIME"; 228 | case self::NEWDECIMAL: return "NEWDECIMAL"; 229 | case self::DECIMAL: return "DECIMAL"; 230 | case self::DOUBLE: return "DOUBLE"; 231 | case self::ENUM: return "ENUM"; 232 | case self::FLOAT: return "FLOAT"; 233 | case self::GEOMETRY: return "GEOMETRY"; 234 | case self::INT24: return "INT24"; 235 | case self::JSON: return "JSON"; 236 | case self::LONG: return "LONG"; 237 | case self::LONGLONG: return "LONGLONG"; 238 | case self::LONG_BLOB: return "LONG_BLOB"; 239 | case self::MEDIUM_BLOB: return "MEDIUM_BLOB"; 240 | case self::NEWDATE: return "NEWDATE"; 241 | case self::NULL: return "NULL"; 242 | case self::SET: return "SET"; 243 | case self::SHORT: return "SHORT"; 244 | case self::STRING: return "STRING"; 245 | case self::TIME: return "TIME"; 246 | case self::TIMESTAMP: return "TIMESTAMP"; 247 | case self::TINY: return "TINY"; 248 | case self::TINY_BLOB: return "TINY_BLOB"; 249 | case self::VAR_STRING: return "VAR_STRING"; 250 | case self::YEAR: return "YEAR"; 251 | default: return "?-unknown-?"; 252 | } 253 | } 254 | } -------------------------------------------------------------------------------- /src/Bin/Constant/ServerStatus.php: -------------------------------------------------------------------------------- 1 | row("SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'"); 29 | return $res['Value']; 30 | } 31 | 32 | public static function getPos() 33 | { 34 | self::PdoInit(); 35 | $sql = "SHOW MASTER STATUS"; 36 | $result = self::$pdo->row($sql); 37 | return $result; 38 | } 39 | 40 | public static function getFields($schema, $table) 41 | { 42 | self::PdoInit(); 43 | $sql = "SELECT 44 | COLUMN_NAME,COLLATION_NAME,CHARACTER_SET_NAME,COLUMN_COMMENT,COLUMN_TYPE,COLUMN_KEY 45 | FROM 46 | information_schema.columns 47 | WHERE 48 | table_schema = '{$schema}' AND table_name = '{$table}'"; 49 | $result = self::$pdo->query($sql); 50 | return $result; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Bin/Net.php: -------------------------------------------------------------------------------- 1 | cache_dir; 22 | 23 | $this->cache_dir = $dir; 24 | 25 | $dir = new WDir($this->cache_dir); 26 | $dir->mkdir(); 27 | unset($dir); 28 | } 29 | public function set($key, $value, $timeout = 0) 30 | { 31 | return file_put_contents( 32 | $this->cache_dir."/".$key, 33 | json_encode([ 34 | "value" => $value, 35 | "timeout" => $timeout, 36 | "created" => time() 37 | ]) 38 | ); 39 | } 40 | public function get($key) 41 | { 42 | $file = $this->cache_dir."/".$key; 43 | if (!is_file($file) || !file_exists($file)) 44 | return null; 45 | $res = file_get_contents($file); 46 | $res = json_decode($res,true); 47 | 48 | if (!is_array($res)) { 49 | unlink($file); 50 | return null; 51 | } 52 | 53 | $timeout = $res["timeout"]; 54 | if ($timeout > 0 && (time()-$timeout) > $res["created"]) { 55 | unlink($file); 56 | return null; 57 | } 58 | return $res["value"]; 59 | } 60 | public function del($key) 61 | { 62 | if (!is_array($key)) { 63 | $file = $this->cache_dir."/".$key; 64 | 65 | if (!is_file($file) || !file_exists($file)) 66 | return 0; 67 | $success = unlink($file); 68 | if ($success) 69 | return 1; 70 | return 0; 71 | } else { 72 | $count = 0; 73 | foreach ($key as $_key) { 74 | $file = $this->cache_dir."/".$_key; 75 | 76 | if (!is_file($file) || !file_exists($file)) 77 | continue; 78 | 79 | $success = unlink($file); 80 | if ($success) 81 | $count++; 82 | } 83 | return $count; 84 | } 85 | } 86 | 87 | public function keys($p = ".*") 88 | { 89 | $dir = new WDir($this->cache_dir); 90 | $files = $dir->scandir(); 91 | 92 | $keys = []; 93 | foreach ($files as $file) { 94 | $name = pathinfo($file,PATHINFO_FILENAME); 95 | if (preg_match("/".$p."/",$name)) 96 | $keys[] = $name; 97 | } 98 | return $keys; 99 | } 100 | } -------------------------------------------------------------------------------- /src/Cache/Redis.php: -------------------------------------------------------------------------------- 1 | redis = $redis; 17 | } 18 | 19 | public function set($key, $value, $timeout = 0) 20 | { 21 | if (is_array($value)) 22 | $value = json_encode($value); 23 | $success = $this->redis->set($key, $value); 24 | if ($timeout>0) 25 | $this->redis->expire($key, $timeout); 26 | return $success; 27 | } 28 | public function get($key) 29 | { 30 | $data = $this->redis->get($key); 31 | 32 | // $_data = @@json_decode($data, true); 33 | // 34 | // if (is_array($_data)) 35 | // return $_data; 36 | 37 | return $data; 38 | 39 | } 40 | public function del($key) 41 | { 42 | return $this->redis->del($key); 43 | } 44 | public function keys($p = "*") 45 | { 46 | return $this->redis->keys($p); 47 | } 48 | } -------------------------------------------------------------------------------- /src/Command/Help.php: -------------------------------------------------------------------------------- 1 | setName('help') 12 | ->setDescription('帮助信息'); 13 | } 14 | 15 | protected function execute(InputInterface $input, OutputInterface $output) 16 | { 17 | echo "执行 php wing start 开启服务进程,可选参数 --d 以守护进程执行, --debug 启用debug模式, --n 指定进程数量\r\n"; 18 | echo "如:php wing start --d --debug --n 8 \r\n"; 19 | echo "更多帮助加QQ群咨询 535218312 \r\n"; 20 | } 21 | } -------------------------------------------------------------------------------- /src/Command/ServerBase.php: -------------------------------------------------------------------------------- 1 | setName('server:restart') 13 | ->setAliases(["restart"]) 14 | ->setDescription('重新启动'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) 18 | { 19 | exec("php ".HOME."/services/tcp stop"); 20 | exec("php ".HOME."/services/websocket stop"); 21 | Worker::stopAll(); 22 | 23 | $worker_info = Worker::getWorkerProcessInfo(); 24 | $daemon = $worker_info["daemon"]; 25 | $debug = $worker_info["debug"]; 26 | $workers = $worker_info["workers"]; 27 | 28 | $worker = new Worker([ 29 | "daemon" => !!$daemon, 30 | "debug" => !!$debug, 31 | "workers" => $workers 32 | ]); 33 | $worker->start(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Command/ServerStart.php: -------------------------------------------------------------------------------- 1 | setName('server:start') 14 | ->setAliases(["start"]) 15 | ->setDescription('服务启动') 16 | ->addOption("d", null, InputOption::VALUE_NONE, "守护进程") 17 | ->addOption("debug", null, InputOption::VALUE_NONE, "调试模式") 18 | ->addOption("n", null, InputOption::VALUE_REQUIRED, "进程数量", 4); 19 | } 20 | 21 | protected function execute(InputInterface $input, OutputInterface $output) 22 | { 23 | $daemon = $input->getOption("d"); 24 | $debug = $input->getOption("debug"); 25 | $workers = $input->getOption("n"); 26 | $worker = new Worker([ 27 | "daemon" => !!$daemon, 28 | "debug" => !!$debug, 29 | "workers" => $workers 30 | ]); 31 | $worker->start(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Command/ServerStatus.php: -------------------------------------------------------------------------------- 1 | setName('server:status') 13 | ->setAliases(["status"]) 14 | ->setDescription('服务状态'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) 18 | { 19 | Worker::showStatus(); 20 | sleep(1); 21 | echo file_get_contents(HOME."/logs/status.log"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Command/ServerStop.php: -------------------------------------------------------------------------------- 1 | setName('server:stop') 13 | ->setAliases(["stop"]) 14 | ->setDescription('停止服务'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) 18 | { 19 | exec("php ".HOME."/services/tcp.php stop"); 20 | exec("php ".HOME."/services/websocket.php stop"); 21 | Worker::stopAll(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Command/ServerVersion.php: -------------------------------------------------------------------------------- 1 | setName('server:version') 13 | ->setAliases(["version"]) 14 | ->setDescription('版本号'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) 18 | { 19 | 20 | echo "wing-binlog版本号 : ",Worker::VERSION,"\r\n"; 21 | echo "作者 : yuyi\r\n"; 22 | echo "邮箱 : 297341015@qq.com\r\n"; 23 | echo "QQ群 : 535218312\r\n"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Exception/NetCloseException.php: -------------------------------------------------------------------------------- 1 | sysLinux(); 21 | break; 22 | case "Darwin": 23 | exec("sysctl -n machdep.cpu.core_count", $output); 24 | $this->cpu_num = $output[0]; 25 | /** 26 | echo -n "CPU型号: " 27 | sysctl -n machdep.cpu.brand_string 28 | echo -n "CPU核心数: " 29 | sysctl -n machdep.cpu.core_count 30 | echo -n "CPU线程数: " 31 | sysctl -n machdep.cpu.thread_count 32 | echo "其它信息:" 33 | system_profiler SPDisplaysDataType SPMemoryDataType 34 | * SPStorageDataType | grep 'Graphics/Displays:\| 35 | * Chipset Model:\|VRAM (Total):\|Resolution:\| 36 | * Memory Slots:\|Size:\|Speed:\|Storage:\|Media Name:\|Medium Type:' 37 | */ 38 | break; 39 | default: 40 | break; 41 | } 42 | 43 | $this->cpu_num = intval($this->cpu_num); 44 | 45 | if ($this->cpu_num <= 0) { 46 | $this->cpu_num = 1; 47 | } 48 | } 49 | 50 | /** 51 | * linux cpu数量解析获取 52 | */ 53 | private function sysLinux() 54 | { 55 | if (false === ($str = @file("/proc/cpuinfo"))) { 56 | $this->cpu_num = 1; 57 | return; 58 | } 59 | 60 | $str = implode("", $str); 61 | @preg_match_all("/model\s+name\s{0,}\:+\s{0,}([\w\s\)\(\@.-]+)([\r\n]+)/s", $str, $model); 62 | 63 | if (false !== is_array($model[1])) { 64 | $this->cpu_num = sizeof($model[1]); 65 | return; 66 | } 67 | 68 | $this->cpu_num = 1; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Library/FileFormat.php: -------------------------------------------------------------------------------- 1 | file = $file; 53 | $this->db_handler = $db_handler; 54 | $this->start_time = time(); 55 | $this->event_index = $event_index; 56 | } 57 | 58 | public function parse() 59 | { 60 | $file_size = strlen($this->file); 61 | $read_size = 0; 62 | $all_lines = explode("\n", $this->file); 63 | $all_res = []; 64 | $lines = []; 65 | foreach ($all_lines as $line) { 66 | $_line = ltrim($line, "#"); 67 | $_line = trim($_line); 68 | $e = strtolower(substr($_line, 0, 6)); 69 | unset($_line); 70 | //遇到分隔符 重置 71 | if (preg_match("/#[\s]{1,}at[\s]{1,}[0-9]{1,}/", $line) || 72 | $e == "insert" || 73 | $e == "update" || 74 | $e == "delete") { 75 | if ($lines) { 76 | $res = $this->linesParse($lines); 77 | foreach ($res as $item) { 78 | $all_res[] = $item; 79 | } 80 | } 81 | unset($lines); 82 | $lines = []; 83 | } 84 | $lines[] = $line; 85 | unset($line); 86 | if ($read_size >= $file_size) { 87 | break; 88 | } 89 | } 90 | if ($lines) { 91 | $res = $this->linesParse($lines); 92 | foreach ($res as $item) { 93 | $all_res[] = $item; 94 | } 95 | } 96 | return $all_res; 97 | } 98 | 99 | /** 100 | * @获取事件发生的时间 101 | * @param string $item 102 | * @return string 103 | */ 104 | protected function getEventTime($item) 105 | { 106 | preg_match_all("/[0-9]{6}\s+?[0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}/", $item, $time_match); 107 | if (!isset($time_match[0][0])) { 108 | return $this->daytime; 109 | } 110 | $daytime = $this->daytime = date("Y-m-d H:i:s", strtotime(substr(date("Y"), 0, 2) . $time_match[0][0])); 111 | return $daytime; 112 | } 113 | 114 | /** 115 | * 行解析 116 | * 117 | * @param array $lines 行 118 | * @return array 119 | */ 120 | protected function linesParse($lines) 121 | { 122 | $result = []; 123 | do { 124 | //处理流程 125 | $item = implode("", $lines); 126 | //得到事件发生的时间 127 | $daytime = $this->getEventTime($item); 128 | 129 | if (!$daytime) { 130 | break; 131 | } 132 | 133 | //得到事件发生的数据库和表 134 | list($database_name, $table_name) = $this->getTables($item); 135 | if (!$database_name || !$table_name) { 136 | break; 137 | } 138 | 139 | //得到事件 类型 这里只在乎 Delete_rows|Write_rows|Update_rows 140 | //因为这三种事件影响了数据,也就是数据发生了变化 141 | $event_type = $this->getEventType($item); 142 | if (!$event_type) { 143 | break; 144 | } 145 | 146 | unset($item); 147 | 148 | //得到表字段 149 | $columns = $this->getColumns($database_name, $table_name); 150 | if (!$columns) { 151 | break; 152 | } 153 | 154 | //按行解析 155 | $event = $this->eventDatasFormat($lines, $daytime, $event_type, $columns); 156 | unset($columns); 157 | 158 | if ($event) { 159 | //事件计数器 160 | $this->events_times++; 161 | 162 | $str1 = md5(rand(0, 999999)); 163 | $str2 = md5(rand(0, 999999)); 164 | $str3 = md5(rand(0, 999999)); 165 | 166 | $event["__enevt_id"] = "wing_binlog_" . time() . "_" . 167 | substr($str1, rand(0, strlen($str1) - 16), 16) . "_" . 168 | substr($str2, rand(0, strlen($str2) - 16), 16) . "_" . 169 | substr($str3, rand(0, strlen($str3) - 16), 16); 170 | 171 | //执行事件回调函数 172 | $result[] = [ 173 | "database" => $database_name, 174 | "table" => $table_name, 175 | "event" => $event, 176 | "event_index" => $this->event_index 177 | ]; 178 | $this->event_index++; 179 | } 180 | } while (0); 181 | 182 | return $result; 183 | } 184 | 185 | /** 186 | * 获取数据库和数据表 187 | * @param string $item 188 | * @return array 189 | */ 190 | protected function getTables($item) 191 | { 192 | preg_match_all("/`[\s\S].*?`.`[\s\S].*?`/", $item, $match_tables); 193 | 194 | if (!isset($match_tables[0][0])) { 195 | return [false,false]; 196 | } 197 | 198 | list($database_name, $table_name) = explode(".", $match_tables[0][0]); 199 | 200 | $database_name = trim($database_name, "`"); 201 | $table_name = trim($table_name, "`"); 202 | 203 | return [$database_name, $table_name]; 204 | } 205 | 206 | /** 207 | * 获取事件类型 208 | * @param string $item 209 | * @return string 210 | */ 211 | protected function getEventType($item) 212 | { 213 | preg_match("/\s(Delete_rows|Write_rows|Update_rows):/", $item, $ematch); 214 | 215 | if (!isset($ematch[1])) { 216 | $_item = ltrim($item, "#"); 217 | $_item = trim($_item); 218 | $e = strtolower(substr($_item, 0, 6)); 219 | if ($e == "insert") { 220 | return "write_rows"; 221 | } 222 | if ($e == "update") { 223 | return "update_rows"; 224 | } 225 | if ($e == "delete") { 226 | return "delete_rows"; 227 | } 228 | return $this->event_type; 229 | } 230 | 231 | $this->event_type = strtolower($ematch[1]); 232 | return $this->event_type; 233 | } 234 | 235 | 236 | /** 237 | * @事件数据格式化 238 | * 239 | * @param array $target_lines 240 | * @param string $daytime 241 | * @param string $event_type 242 | * @param array $columns 243 | * @return array 244 | */ 245 | protected function eventDatasFormat($target_lines, $daytime, $event_type, $columns) 246 | { 247 | $event_data = [ 248 | "event_type" => $event_type, 249 | "time" => $daytime 250 | ]; 251 | 252 | $is_old_data = true; 253 | $old_data = []; 254 | $new_data = []; 255 | $set_data = []; 256 | $index = 0; 257 | 258 | foreach ($target_lines as $target_line) { 259 | //去掉行的开始#和空格 260 | $target_line = ltrim($target_line, "#"); 261 | $target_line = trim($target_line); 262 | 263 | //所有的字段开始的字符都是@ 264 | if (substr($target_line, 0, 1) == "@") { 265 | $target_line = preg_replace("/@[0-9]{1,}=/", "", $target_line); 266 | /* 267 | if (strpos($target_line,"/*")) { 268 | $temp = explode("/*",$target_line); 269 | $target_line = $temp[0]; 270 | unset($temp); 271 | $target_line = trim($target_line); 272 | } 273 | */ 274 | $target_line = trim($target_line, "'"); 275 | 276 | //如果是update操作 有两组数据 一组是旧数据 一组是新数据 277 | if ($event_type == "update_rows") { 278 | if ($is_old_data) { 279 | $old_data[$columns[$index]] = $target_line; 280 | } else { 281 | $new_data[$columns[$index]] = $target_line; 282 | } 283 | } else { 284 | $set_data[$columns[$index]] = $target_line; 285 | } 286 | 287 | $index++; 288 | } 289 | 290 | //遇到set关键字 重置索引 开始记录老数据 291 | if (strtolower($target_line) == "set") { 292 | $is_old_data = false; 293 | $index = 0; 294 | } 295 | } 296 | 297 | if ($event_type == "update_rows") { 298 | //这里忽略空数据 299 | if (count($old_data) <= 0 || count($new_data) <= 0) { 300 | return null; 301 | } 302 | 303 | $event_data["data"] = [ 304 | "old_data" => $old_data, 305 | "new_data" => $new_data 306 | ]; 307 | } else { 308 | //这里忽略空数据 309 | if (count($set_data) <= 0) { 310 | return null; 311 | } 312 | $event_data["data"] = $set_data; 313 | } 314 | 315 | return $event_data; 316 | } 317 | 318 | /** 319 | * @获取数据表行 320 | * @param string $database_name 321 | * @param string $table_name 322 | * @return array 323 | */ 324 | protected function getColumns($database_name, $table_name) 325 | { 326 | if (isset($this->caches[$database_name][$table_name]) && 327 | (time() - $this->start_time) < 5 //5秒缓存 328 | ) { 329 | return $this->caches[$database_name][$table_name]; 330 | } 331 | 332 | $sql = 'SHOW COLUMNS FROM `' . $database_name . '`.`' . $table_name . '`'; 333 | $columns = $this->db_handler->query($sql); 334 | 335 | if (!$columns) { 336 | return null; 337 | } 338 | 339 | $columns = array_column($columns, "Field"); 340 | $this->caches[$database_name][$table_name] = $columns; 341 | $this->start_time = time(); 342 | 343 | return $columns; 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/Library/ICache.php: -------------------------------------------------------------------------------- 1 | socket, $server_info) = \Wing\Bin\Auth\Auth::execute($host,$username,$passwd, $dbname, $port); 83 | $this->autocommit(true); 84 | 85 | $this->protocol_version = $server_info->protocol_version; 86 | $this->server_info = $server_info->server_info; 87 | $this->thread_id = $server_info->thread_id; 88 | $this->character_set = $server_info->character_set; 89 | $this->salt = $server_info->salt; 90 | $this->auth_plugin_name = $server_info->auth_plugin_name; 91 | $this->capability_flag = $server_info->capability_flag; 92 | 93 | //var_dump($server_info); 94 | 95 | //main_version*10000 + minor_version *100 + sub_version 96 | list($main_version, $minor_version, $sub_version) = explode(".", $this->server_info); 97 | $sub_version = preg_replace("/\D/","", $sub_version); 98 | $this->server_version = $main_version*10000 + $minor_version *100 + $sub_version; 99 | } 100 | 101 | public function __destruct() 102 | { 103 | $res = $this->close(); 104 | Auth::free(); 105 | return $res; 106 | } 107 | 108 | /* 109 | * Closes a previously opened database connection 110 | */ 111 | public function close() 112 | { 113 | return Mysql::close(); 114 | } 115 | 116 | /** 117 | * Returns the default character set for the database connection 118 | * 119 | * @return string like "utf8_general_ci" 120 | */ 121 | public function character_set_name() 122 | { 123 | return CharacterSet::getCharacterSet($this->character_set); 124 | } 125 | 126 | /** 127 | * set autocommit 128 | * 129 | * @throws \Exception 130 | * @param bool $auto 131 | * @return bool 132 | */ 133 | public function autocommit($auto = true) 134 | { 135 | $auto = $auto?1:0; 136 | return Mysql::query('set autocommit='.$auto); 137 | } 138 | 139 | /** 140 | * Starts a transaction 141 | * 142 | * @param int $mode 143 | * @param string $name Savepoint name for the transaction. 144 | */ 145 | public function begin_transaction($mode = Trans::NO_OPT, $name = '') 146 | { 147 | $sql = ''; 148 | if ($mode & Trans::WITH_CONSISTENT_SNAPSHOT) { 149 | if ($sql) { 150 | $sql .= ','; 151 | } 152 | $sql .=" WITH CONSISTENT SNAPSHOT"; 153 | } 154 | 155 | //5.6.5之前的版本不支持 156 | if ($this->server_version >= 50605) { 157 | if ($mode & (Trans::READ_WRITE | Trans::READ_ONLY)) { 158 | if ($mode & Trans::READ_WRITE) { 159 | if ($sql) { 160 | $sql .= ','; 161 | } 162 | $sql .= " READ WRITE"; 163 | } else if ($mode & Trans::READ_ONLY) { 164 | if ($sql) { 165 | $sql .= ','; 166 | } 167 | $sql .= " READ ONLY"; 168 | } 169 | } 170 | } 171 | 172 | $parse_sql = 'START TRANSACTION'; 173 | 174 | if ($name) { 175 | //mysql-server/mysys/charset.c 777 需要过滤 176 | $parse_sql .= ' '.$this->real_escape_string($name); 177 | } 178 | 179 | $parse_sql .= $sql; 180 | 181 | echo $parse_sql; 182 | $this->autocommit(false); 183 | return Mysql::query($parse_sql); 184 | } 185 | 186 | //Commits the current transaction 187 | public function commit() 188 | { 189 | return mysql::query('commit'); 190 | } 191 | 192 | //Rolls back current transaction 193 | public function rollback() 194 | { 195 | return mysql::query('rollback'); 196 | } 197 | 198 | //Changes the user of the specified database connection 199 | public function change_user() 200 | { 201 | 202 | } 203 | 204 | 205 | public function debug(){}//Performs debugging operations 206 | public function dump_debug_info(){}//Dump debugging information into the log 207 | public function get_charset(){}//Returns a character set object 208 | //public function get_client_info(){}//Get MySQL client info 209 | //public function get_client_stats(){}//Returns client per-process statistics 210 | public function get_client_version(){}//Returns the MySQL client version as an integer 211 | public function get_connection_stats(){}//Returns statistics about the client connection 212 | public function get_warnings(){}//Get result of SHOW WARNINGS 213 | public function init(){}//Initializes MySQLi and returns a resource for use with mysqli_real_connect() 214 | public function kill(){}//Asks the server to kill a MySQL thread 215 | public function more_results(){}//Check if there are any more query results from a multi query 216 | public function multi_query(){}//Performs a query on the database 217 | public function next_result(){}//Prepare next result from multi_query 218 | public function options(){}//Set options 219 | public function ping(){}//Pings a server connection, or tries to reconnect if the connection has gone down 220 | public function poll(){}//Poll connections 221 | public function prepare(){}//Prepare an SQL statement for execution 222 | public function query(){}//Performs a query on the database 223 | public function real_connect(){}//Opens a connection to a mysql server 224 | public function real_escape_string($str){ 225 | //mysql-server/mysys/charset.c 777 需要过滤 226 | return $str; 227 | }//Escapes special characters in a string for use in an SQL statement, taking into account the current charset of the connection 228 | public function real_query(){}//Execute an SQL query 229 | public function reap_async_query(){}//Get result from async query 230 | public function refresh(){}//Refreshes 231 | public function release_savepoint(){}//Removes the named savepoint from the set of savepoints of the current transaction 232 | public function rpl_query_type(){}//Returns RPL query type 233 | public function savepoint(){}//Set a named transaction savepoint 234 | public function select_db(){}//Selects the default database for database queries 235 | public function send_query(){}//Send the query and return 236 | public function set_charset(){}//Sets the default client character set 237 | public function set_local_infile_default(){}//Unsets user defined handler for load local infile command 238 | public function set_local_infile_handler(){}//Set callback function for LOAD DATA LOCAL INFILE command 239 | public function ssl_set(){}//Used for establishing secure connections using SSL 240 | public function stat(){}//Gets the current system status 241 | public function stmt_init(){}//Initializes a statement and returns an object for use with mysqli_stmt_prepare 242 | public function store_result(){}//Transfers a result set from the last query 243 | public function thread_safe(){}//Returns whether thread safety is given or not 244 | public function use_result(){}//Initiate a result set retrieval 245 | //mysqli_stmt(){}//The mysqli_stmt c 246 | public function disable_reads_from_master(){}// — Disable reads from master 247 | public function set_opt(){}// — Alias of mysqli_options 248 | } -------------------------------------------------------------------------------- /src/Library/Mysql/Result.php: -------------------------------------------------------------------------------- 1 | parameters = array(); 53 | $this->dbname = $config["db_name"]; 54 | $this->host = $config["host"]; 55 | $this->password = $config["password"]; 56 | $this->user = $config["user"]; 57 | $this->port = $config["port"]; 58 | 59 | $this->connect(); 60 | } 61 | 62 | /** 63 | * @析构函数 64 | */ 65 | public function __destruct() 66 | { 67 | $this->close(); 68 | } 69 | 70 | /** 71 | * 获取db名称 72 | * 73 | * @return string 74 | */ 75 | public function getDatabaseName() 76 | { 77 | return $this->dbname; 78 | } 79 | 80 | /** 81 | * 获取host 82 | * 83 | * @return string 84 | */ 85 | public function getHost() 86 | { 87 | return $this->host; 88 | } 89 | 90 | /** 91 | * 获取user 92 | * 93 | * @return string 94 | */ 95 | public function getUser() 96 | { 97 | return $this->user; 98 | } 99 | 100 | /** 101 | * 获取password 102 | * 103 | * @return string 104 | */ 105 | public function getPassword() 106 | { 107 | return $this->password; 108 | } 109 | 110 | /** 111 | * 获取连接端口 112 | * 113 | * @return int 114 | */ 115 | public function getPort() 116 | { 117 | return $this->port; 118 | } 119 | 120 | public function getTables() 121 | { 122 | $datas = $this->query("show tables"); 123 | return $datas; 124 | } 125 | 126 | /** 127 | * @链接数据库 128 | */ 129 | private function connect() 130 | { 131 | $dsn = 'mysql:dbname=' . $this->dbname . ';host=' . $this->host . ';port='.$this->port; 132 | try { 133 | $this->pdo = new \PDO( 134 | $dsn, 135 | $this->user, 136 | $this->password, 137 | [\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"] 138 | ); 139 | 140 | $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 141 | $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); 142 | 143 | $this->bconnected = true; 144 | } catch (\PDOException $e) { 145 | if (WING_DEBUG) { 146 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 147 | } 148 | 149 | sleep(1); 150 | $this->connect(); 151 | 152 | if (WING_DEBUG) { 153 | ("mysql连接异常"); 154 | } 155 | } 156 | } 157 | 158 | 159 | /** 160 | * @关闭链接 161 | */ 162 | private function close() 163 | { 164 | $this->pdo = null; 165 | $this->bconnected = false; 166 | } 167 | 168 | /** 169 | * 初始化数据库连接以及参数绑定 170 | * 171 | * @param string $query 172 | * @param array $parameters 173 | * @return bool 174 | */ 175 | private function init($query, $parameters = null) 176 | { 177 | if ($parameters && !is_array($parameters)) { 178 | $parameters = [$parameters]; 179 | } 180 | 181 | $this->lastSql = $query; 182 | 183 | if ($parameters) { 184 | $this->lastSql .= ", with data: " . json_encode($parameters, JSON_UNESCAPED_UNICODE); 185 | } 186 | 187 | if (!$this->bconnected) { 188 | $this->connect(); 189 | } 190 | 191 | try { 192 | if (!$this->pdo) { 193 | return false; 194 | } 195 | 196 | $this->statement = $this->pdo->prepare($query); 197 | 198 | if (!$this->statement) { 199 | return false; 200 | } 201 | 202 | return $this->statement->execute($parameters); 203 | } catch (\PDOException $e) { 204 | $this->close(); 205 | $this->connect(); 206 | 207 | if (WING_DEBUG) { 208 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 209 | } 210 | } 211 | $this->parameters = array(); 212 | return false; 213 | } 214 | 215 | 216 | /** 217 | * 执行SQL语句 218 | * 219 | * @param string $query 220 | * @param array $params 221 | * @param int $fetchmode 222 | * @return mixed 223 | */ 224 | public function query($query, $params = null, $fetchmode = \PDO::FETCH_ASSOC) 225 | { 226 | $query = preg_replace("/\s+|\t+|\n+/", " ", $query); 227 | $init_res = $this->init($query, $params); 228 | 229 | try { 230 | $rawStatement = explode(" ", $query); 231 | $statement = strtolower($rawStatement[0]); 232 | 233 | if ($statement === 'select' || $statement === 'show') { 234 | if (!$this->statement) { 235 | return null; 236 | } 237 | 238 | return $this->statement->fetchAll($fetchmode); 239 | } 240 | 241 | if ($statement === 'insert') { 242 | if (!$this->pdo) { 243 | return null; 244 | } 245 | 246 | return $this->pdo->lastInsertId(); 247 | } 248 | 249 | if ($statement === 'update' || $statement === 'delete') { 250 | if (!$this->statement) { 251 | return 0; 252 | } 253 | 254 | return $this->statement->rowCount(); 255 | } 256 | } catch (\PDOException $e) { 257 | if (WING_DEBUG) { 258 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 259 | } 260 | 261 | $this->close(); 262 | $this->connect(); 263 | } 264 | 265 | return $init_res; 266 | } 267 | 268 | /** 269 | * 获取最后的自增id 270 | * 271 | * @return string 272 | */ 273 | public function lastInsertId() 274 | { 275 | try { 276 | if (!$this->pdo) { 277 | return 0; 278 | } 279 | return $this->pdo->lastInsertId(); 280 | } catch (\PDOException $e) { 281 | if (WING_DEBUG) { 282 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 283 | } 284 | 285 | $this->close(); 286 | $this->connect(); 287 | } 288 | return 0; 289 | } 290 | 291 | /** 292 | * 开启事务 293 | * 294 | * @return boolean, true 成功或者 false 失败 295 | */ 296 | public function startTransaction() 297 | { 298 | try { 299 | if (!$this->pdo) { 300 | return false; 301 | } 302 | 303 | return $this->pdo->beginTransaction(); 304 | } catch (\PDOException $e) { 305 | if (WING_DEBUG) { 306 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 307 | } 308 | 309 | $this->close(); 310 | $this->connect(); 311 | } 312 | return false; 313 | } 314 | 315 | /** 316 | * 提交事务 317 | * 318 | * @return boolean, true 成功或者 false 失败 319 | */ 320 | public function commit() 321 | { 322 | try { 323 | if (!$this->pdo) { 324 | return false; 325 | } 326 | 327 | return $this->pdo->commit(); 328 | } catch (\PDOException $e) { 329 | if (WING_DEBUG) { 330 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 331 | } 332 | 333 | $this->close(); 334 | $this->connect(); 335 | } 336 | return false; 337 | } 338 | 339 | /** 340 | * 回滚事务 341 | * 342 | * @return boolean, true 成功或者 false 失败 343 | */ 344 | public function rollBack() 345 | { 346 | try { 347 | if (!$this->pdo) { 348 | return false; 349 | } 350 | 351 | return $this->pdo->rollBack(); 352 | } catch (\PDOException $e) { 353 | if (WING_DEBUG) { 354 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 355 | } 356 | 357 | $this->close(); 358 | $this->connect(); 359 | } 360 | return false; 361 | } 362 | 363 | 364 | /** 365 | * 查询返回行 366 | * 367 | * @param string $query 368 | * @param array $params 369 | * @param int $fetchmode 370 | * @return array 371 | */ 372 | public function row($query, $params = null, $fetchmode = \PDO::FETCH_ASSOC) 373 | { 374 | try { 375 | $this->init($query, $params); 376 | 377 | if ($this->statement) { 378 | $result = $this->statement->fetch($fetchmode); 379 | $this->statement->closeCursor(); 380 | return $result; 381 | } 382 | } catch (\PDOException $e) { 383 | if (WING_DEBUG) { 384 | var_dump(__CLASS__."::".__FUNCTION__, $e->errorInfo); 385 | } 386 | 387 | $this->close(); 388 | $this->connect(); 389 | } 390 | return []; 391 | } 392 | 393 | /** 394 | * @获取最后执行的sql 395 | * 396 | * @return string 397 | */ 398 | public function getLastSql() 399 | { 400 | return $this->lastSql; 401 | } 402 | 403 | public function getDatabases() 404 | { 405 | $data = $this->query('show databases'); 406 | $res = []; 407 | 408 | foreach ($data as $row) { 409 | $res[] = $row["Database"]; 410 | } 411 | 412 | return $res; 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/Library/Redis.php: -------------------------------------------------------------------------------- 1 | port = $port; 20 | $this->host = $host; 21 | $this->password = $password; 22 | } 23 | 24 | protected function connect() 25 | { 26 | try { 27 | $this->instance = null; 28 | $this->is_connect = false; 29 | 30 | if (!class_exists("Redis")) { 31 | return; 32 | } 33 | 34 | $redis = new \Redis(); 35 | $this->is_connect = $redis->connect('127.0.0.1', $this->port); 36 | 37 | if (!$this->is_connect) { 38 | return; 39 | } 40 | 41 | if ($this->password !== null) { 42 | $this->is_connect = $redis->auth($this->password); 43 | if (!$this->is_connect) { 44 | return; 45 | } 46 | } 47 | 48 | $this->is_connect = true; 49 | $this->instance = $redis; 50 | } catch (\Exception $e) { 51 | $this->is_connect = false; 52 | var_dump($e->getMessage()); 53 | } 54 | } 55 | 56 | public function __call($name, $arguments) 57 | { 58 | if (!$this->is_connect) { 59 | $this->connect(); 60 | } 61 | 62 | if (!$this->is_connect) { 63 | wing_debug("redis连接错误"); 64 | } 65 | 66 | try { 67 | if (!$this->instance || !$this->is_connect) { 68 | return false; 69 | } 70 | 71 | return call_user_func_array([$this->instance, $name], $arguments); 72 | } catch (\Exception $e) { 73 | var_dump($e->getMessage()); 74 | $this->is_connect = false; 75 | } 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Library/Workers/BaseWorker.php: -------------------------------------------------------------------------------- 1 | binlog = new Binlog(new PDO); 30 | $this->connect($config); 31 | 32 | if ($config 33 | && isset($config["subscribe"]) 34 | && is_array($config["subscribe"]) 35 | && count($config["subscribe"]) > 0 36 | ) { 37 | foreach ($config["subscribe"] as $class => $params) { 38 | $params["daemon"] = $daemon; 39 | $params["workers"] = $workers; 40 | $this->notify[] = new $class($params); 41 | } 42 | } 43 | } 44 | protected function notice($result) 45 | { 46 | //通知订阅者 47 | if (is_array($this->notify) && count($this->notify) > 0) { 48 | $datas = $result["event"]["data"]; 49 | foreach ($datas as $row) { 50 | $result["event"]["data"] = $row; 51 | var_dump($result); 52 | foreach ($this->notify as $notify) { 53 | $notify->onchange($result); 54 | } 55 | } 56 | } 57 | } 58 | 59 | protected function connect($config) 60 | { 61 | try { 62 | //认证 63 | Auth::execute( 64 | $config["mysql"]["host"], 65 | $config["mysql"]["user"], 66 | $config["mysql"]["password"], 67 | $config["mysql"]["db_name"], 68 | $config["mysql"]["port"] 69 | ); 70 | 71 | //注册为slave 72 | $this->binlog->registerSlave($config["slave_server_id"]); 73 | } catch (\Exception $e) { 74 | var_dump($e->getMessage()); 75 | } 76 | } 77 | 78 | public function start() 79 | { 80 | $daemon = $this->daemon; 81 | 82 | if (!is_env(WINDOWS)) { 83 | $process_id = pcntl_fork(); 84 | 85 | if ($process_id < 0) { 86 | wing_debug("创建子进程失败"); 87 | exit; 88 | } 89 | 90 | if ($process_id > 0) { 91 | return $process_id; 92 | } 93 | 94 | if ($daemon) { 95 | reset_std(); 96 | } 97 | } 98 | 99 | $process_name = "wing php >> events collector process"; 100 | self::$process_title = $process_name; 101 | 102 | //设置进程标题 mac 会有warning 直接忽略 103 | set_process_title($process_name); 104 | 105 | $times = 0; 106 | $start = time(); 107 | 108 | while (1) { 109 | ob_start(); 110 | 111 | try { 112 | pcntl_signal_dispatch(); 113 | do { 114 | $result = $this->binlog->getBinlogEvents(); 115 | 116 | if (!$result) { 117 | break; 118 | } 119 | 120 | $times += count($result["event"]["data"]); 121 | $span_time = time() - $start; 122 | 123 | if ($span_time > 0) { 124 | echo $times, "次,", $times / ($span_time) . "/次事件每秒,耗时", $span_time, "秒\r\n"; 125 | } 126 | 127 | //通知订阅者 128 | $this->notice($result); 129 | } while (0); 130 | } catch (NetCloseException $e) { 131 | usleep(500000); 132 | $this->connect(load_config("app")); 133 | } catch (\Exception $e) { 134 | if (WING_DEBUG) { 135 | var_dump($e->getMessage()); 136 | } 137 | unset($e); 138 | } 139 | 140 | $output = ob_get_contents(); 141 | ob_end_clean(); 142 | 143 | if ($output && WING_DEBUG) { 144 | wing_debug($output); 145 | } 146 | unset($output); 147 | usleep(self::USLEEP); 148 | } 149 | 150 | return 0; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Net/TcpClient.php: -------------------------------------------------------------------------------- 1 | tcp = $tcp; 18 | $this->client = $client; 19 | $this->buffer = $buffer; 20 | } 21 | 22 | public function send($msg) 23 | { 24 | return $this->tcp->send($this->buffer, $msg, $this->client); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Net/TcpServer.php: -------------------------------------------------------------------------------- 1 | on(self::ON_RECEIVE,[$this, "onMessage"]); 16 | $this->on(self::ON_CONNECT, [$this, "onConnect"]); 17 | $this->on(self::ON_CLOSE, [$this, "onClose"]); 18 | $this->on(self::ON_ERROR, [$this, "onClose"]); 19 | } 20 | 21 | 22 | public function onMessage($client, $buffer, $recv_msg) 23 | { 24 | $_client = new TcpClient($this,$client, $buffer); 25 | $this->call(self::TCP_ON_MESSAGE, [$_client, $recv_msg]); 26 | } 27 | 28 | public function onConnect($client, $buffer) 29 | { 30 | $_client = new TcpClient($this,$client, $buffer); 31 | $this->call(self::TCP_ON_CONNECT, [$_client]); 32 | } 33 | 34 | public function onClose($client, $buffer, $error=null) 35 | { 36 | $_client = new TcpClient($this,$client, $buffer); 37 | $this->call(self::TCP_ON_CLOSE, [$_client]); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/Net/WebSocket.php: -------------------------------------------------------------------------------- 1 | on(self::ON_RECEIVE,[$this, "onMessage"]); 16 | $this->on(self::ON_CONNECT, [$this, "onConnect"]); 17 | $this->on(self::ON_CLOSE, [$this, "onClose"]); 18 | $this->on(self::ON_ERROR, [$this, "onClose"]); 19 | } 20 | 21 | 22 | public function onMessage($client, $buffer, $recv_msg) 23 | { 24 | if (0 === strpos($recv_msg, 'GET')) { 25 | $this->handshake($buffer, $recv_msg, $client);//, $recv_msg), $client ); 26 | return; 27 | } 28 | 29 | $_client = new WebSocketClient($this,$client, $buffer); 30 | $this->call(self::WEBSOCKET_ON_MESSAGE, [$_client, $recv_msg]); 31 | } 32 | 33 | public function onConnect($client, $buffer) 34 | { 35 | $_client = new WebSocketClient($this,$client, $buffer); 36 | $this->call(self::WEBSOCKET_ON_CONNECT, [$_client]); 37 | } 38 | 39 | public function onClose($client, $buffer, $error=null) 40 | { 41 | $_client = new WebSocketClient($this,$client, $buffer); 42 | $this->call(self::WEBSOCKET_ON_CLOSE, [$_client]); 43 | } 44 | 45 | /** 46 | * @获取websocket握手消息 47 | */ 48 | public function handshake($buffer, $recv_msg, $client ){ 49 | 50 | $heder_end_pos = strpos($recv_msg, "\r\n\r\n"); 51 | 52 | if ( !$heder_end_pos ) { 53 | return ''; 54 | } 55 | 56 | $Sec_WebSocket_Key = ''; 57 | if ( preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $recv_msg, $match) ) { 58 | $Sec_WebSocket_Key = $match[1]; 59 | } 60 | 61 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 62 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; 63 | $handshake_message .= "Upgrade: websocket\r\n"; 64 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; 65 | $handshake_message .= "Connection: Upgrade\r\n"; 66 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; 67 | 68 | return $this->send($buffer, $handshake_message, $client); 69 | } 70 | 71 | /** 72 | * @消息编码 73 | */ 74 | public static function encode( $buffer ) 75 | { 76 | $len = strlen($buffer); 77 | $first_byte = "\x81"; 78 | 79 | if ($len <= 125) { 80 | $encode_buffer = $first_byte . chr($len) . $buffer; 81 | } else { 82 | if ($len <= 65535) { 83 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; 84 | } else { 85 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; 86 | } 87 | } 88 | 89 | return $encode_buffer; 90 | } 91 | 92 | /** 93 | * @消息解码 94 | */ 95 | public static function decode($buffer) 96 | { 97 | $len = $masks = $data = $decoded = null; 98 | $len = ord($buffer[1]) & 127; 99 | if ($len === 126) { 100 | $masks = substr($buffer, 4, 4); 101 | $data = substr($buffer, 8); 102 | } else { 103 | if ($len === 127) { 104 | $masks = substr($buffer, 10, 4); 105 | $data = substr($buffer, 14); 106 | } else { 107 | $masks = substr($buffer, 2, 4); 108 | $data = substr($buffer, 6); 109 | } 110 | } 111 | for ($index = 0; $index < strlen($data); $index++) { 112 | $decoded .= $data[$index] ^ $masks[$index % 4]; 113 | } 114 | 115 | return $decoded; 116 | 117 | } 118 | 119 | public function send($buffer, $data, $client) 120 | { 121 | $data = self::encode($data); 122 | if ($buffer) { 123 | $success = event_buffer_write($buffer,$data); 124 | } 125 | else{ 126 | $success = $this->sendSocket($client, $data); 127 | } 128 | if (!$success) { 129 | $this->send_fail_times++; 130 | $i = array_search($client, $this->clients); 131 | fclose($client); 132 | if ($buffer) { 133 | event_buffer_free($buffer); 134 | unset($this->buffers[$i]); 135 | } 136 | unset($this->clients[$i]); 137 | } 138 | return $success; 139 | } 140 | 141 | 142 | } -------------------------------------------------------------------------------- /src/Net/WebSocketClient.php: -------------------------------------------------------------------------------- 1 | tcp = $tcp; 18 | $this->client = $client; 19 | $this->buffer = $buffer; 20 | } 21 | 22 | public function send($msg) 23 | { 24 | return $this->tcp->send($this->buffer, $msg, $this->client); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Net/WsClient.php: -------------------------------------------------------------------------------- 1 | _host = $host; 20 | $this->_port = $port; 21 | $this->_path = $path; 22 | $this->_origin = $origin; 23 | 24 | $this->connect(); 25 | } 26 | 27 | public function __destruct() 28 | { 29 | $this->disconnect(); 30 | } 31 | 32 | public function send($data, $type = 'text', $masked = true) 33 | { 34 | if ($this->_connected === false) { 35 | $this->connect(); 36 | } 37 | 38 | if (!is_string($data)) { 39 | wing_debug("发送的数据必须是字符串类型的数据"); 40 | return false; 41 | } 42 | 43 | if (strlen($data) == 0) { 44 | return false; 45 | } 46 | 47 | $res = @fwrite($this->_Socket, $this->_hybi10Encode($data, $type, $masked)); 48 | 49 | if ($res === 0 || $res === false) { 50 | $this->connect(); 51 | @fwrite($this->_Socket, $this->_hybi10Encode($data, $type, $masked)); 52 | 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | protected function connect() 60 | { 61 | 62 | $path = $this->_path; 63 | $origin = $this->_origin; 64 | $host = $this->_host; 65 | $port = $this->_port; 66 | $key = base64_encode($this->_generateRandomString(16, false, true)); 67 | $header = "GET " . $path . " HTTP/1.1\r\n"; 68 | $header.= "Host: ".$host.":".$port. "\r\n"; 69 | $header.= "Upgrade: websocket\r\n"; 70 | $header.= "Connection: Upgrade\r\n"; 71 | //$header.= "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"; 72 | $header.= "Sec-WebSocket-Key: " . $key . "\r\n"; 73 | 74 | if ($origin !== false) 75 | { 76 | $header.= "Sec-WebSocket-Origin: " . $origin . "\r\n"; 77 | } 78 | $header.= "Sec-WebSocket-Version: 13\r\n\r\n"; 79 | 80 | $this->_Socket = fsockopen($host, $port, $errno, $errstr, 2); 81 | socket_set_timeout($this->_Socket, 2, 10000); 82 | //socket_write($this->_Socket, $header); 83 | $res = @fwrite($this->_Socket, $header); 84 | if ($res === false ){ 85 | wing_debug("发送websocket链接头错误"); 86 | } 87 | 88 | $response = @fread($this->_Socket, 1500); 89 | //$response = socket_read($this->_Socket); 90 | preg_match('#Sec-WebSocket-Accept:\s(.*)$#mU', $response, $matches); 91 | if ($matches) { 92 | $keyAccept = trim($matches[1]); 93 | $expectedResonse = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); 94 | $this->_connected = ($keyAccept === $expectedResonse) ? true : false; 95 | } 96 | return $this->_connected; 97 | } 98 | 99 | public function checkConnection() 100 | { 101 | $this->_connected = false; 102 | 103 | // send ping: 104 | $data = 'ping?'; 105 | @fwrite($this->_Socket, $this->_hybi10Encode($data, 'ping', true)); 106 | $response = @fread($this->_Socket, 300); 107 | if (empty($response)) 108 | { 109 | return false; 110 | } 111 | $response = $this->_hybi10Decode($response); 112 | if (!is_array($response)) 113 | { 114 | return false; 115 | } 116 | if (!isset($response['type']) || $response['type'] !== 'pong') 117 | { 118 | return false; 119 | } 120 | $this->_connected = true; 121 | return true; 122 | } 123 | 124 | 125 | public function disconnect() 126 | { 127 | $this->_connected = false; 128 | is_resource($this->_Socket) and fclose($this->_Socket); 129 | } 130 | 131 | public function reconnect() 132 | { 133 | sleep(10); 134 | $this->_connected = false; 135 | fclose($this->_Socket); 136 | $this->connect(); 137 | } 138 | 139 | private function _generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) 140 | { 141 | $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"ยง$%&/()=[]{}'; 142 | $useChars = array(); 143 | // select some random chars: 144 | for($i = 0; $i < $length; $i++) 145 | { 146 | $useChars[] = $characters[mt_rand(0, strlen($characters)-1)]; 147 | } 148 | // add spaces and numbers: 149 | if ($addSpaces === true) 150 | { 151 | array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); 152 | } 153 | if ($addNumbers === true) 154 | { 155 | array_push($useChars, rand(0,9), rand(0,9), rand(0,9)); 156 | } 157 | shuffle($useChars); 158 | $randomString = trim(implode('', $useChars)); 159 | $randomString = substr($randomString, 0, $length); 160 | return $randomString; 161 | } 162 | 163 | private function _hybi10Encode($payload, $type = 'text', $masked = true) 164 | { 165 | $frameHead = array(); 166 | $frame = ''; 167 | $payloadLength = strlen($payload); 168 | 169 | switch($type) 170 | { 171 | case 'text': 172 | // first byte indicates FIN, Text-Frame (10000001): 173 | $frameHead[0] = 129; 174 | break; 175 | 176 | case 'close': 177 | // first byte indicates FIN, Close Frame(10001000): 178 | $frameHead[0] = 136; 179 | break; 180 | 181 | case 'ping': 182 | // first byte indicates FIN, Ping frame (10001001): 183 | $frameHead[0] = 137; 184 | break; 185 | 186 | case 'pong': 187 | // first byte indicates FIN, Pong frame (10001010): 188 | $frameHead[0] = 138; 189 | break; 190 | } 191 | 192 | // set mask and payload length (using 1, 3 or 9 bytes) 193 | if ($payloadLength > 65535) 194 | { 195 | $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); 196 | $frameHead[1] = ($masked === true) ? 255 : 127; 197 | for($i = 0; $i < 8; $i++) 198 | { 199 | $frameHead[$i+2] = bindec($payloadLengthBin[$i]); 200 | } 201 | // most significant bit MUST be 0 (close connection if frame too big) 202 | if ($frameHead[2] > 127) 203 | { 204 | // $this->close(1004); 205 | return false; 206 | } 207 | } 208 | elseif ($payloadLength > 125) 209 | { 210 | $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8); 211 | $frameHead[1] = ($masked === true) ? 254 : 126; 212 | $frameHead[2] = bindec($payloadLengthBin[0]); 213 | $frameHead[3] = bindec($payloadLengthBin[1]); 214 | } 215 | else 216 | { 217 | $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength; 218 | } 219 | 220 | // convert frame-head to string: 221 | foreach(array_keys($frameHead) as $i) 222 | { 223 | $frameHead[$i] = chr($frameHead[$i]); 224 | } 225 | if ($masked === true) 226 | { 227 | // generate a random mask: 228 | $mask = array(); 229 | for($i = 0; $i < 4; $i++) 230 | { 231 | $mask[$i] = chr(rand(0, 255)); 232 | } 233 | 234 | $frameHead = array_merge($frameHead, $mask); 235 | } 236 | $frame = implode('', $frameHead); 237 | 238 | // append payload to frame: 239 | $framePayload = array(); 240 | for($i = 0; $i < $payloadLength; $i++) 241 | { 242 | $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; 243 | } 244 | 245 | return $frame; 246 | } 247 | 248 | private function _hybi10Decode($data) 249 | { 250 | $payloadLength = ''; 251 | $mask = ''; 252 | $unmaskedPayload = ''; 253 | $decodedData = array(); 254 | 255 | // estimate frame type: 256 | $firstByteBinary = sprintf('%08b', ord($data[0])); 257 | $secondByteBinary = sprintf('%08b', ord($data[1])); 258 | $opcode = bindec(substr($firstByteBinary, 4, 4)); 259 | $isMasked = ($secondByteBinary[0] == '1') ? true : false; 260 | $payloadLength = ord($data[1]) & 127; 261 | 262 | switch($opcode) 263 | { 264 | // text frame: 265 | case 1: 266 | $decodedData['type'] = 'text'; 267 | break; 268 | 269 | case 2: 270 | $decodedData['type'] = 'binary'; 271 | break; 272 | 273 | // connection close frame: 274 | case 8: 275 | $decodedData['type'] = 'close'; 276 | break; 277 | 278 | // ping frame: 279 | case 9: 280 | $decodedData['type'] = 'ping'; 281 | break; 282 | 283 | // pong frame: 284 | case 10: 285 | $decodedData['type'] = 'pong'; 286 | break; 287 | 288 | default: 289 | return false; 290 | break; 291 | } 292 | 293 | if ($payloadLength === 126) 294 | { 295 | $mask = substr($data, 4, 4); 296 | $payloadOffset = 8; 297 | $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))) + $payloadOffset; 298 | } 299 | elseif ($payloadLength === 127) 300 | { 301 | $mask = substr($data, 10, 4); 302 | $payloadOffset = 14; 303 | $tmp = ''; 304 | for($i = 0; $i < 8; $i++) 305 | { 306 | $tmp .= sprintf('%08b', ord($data[$i+2])); 307 | } 308 | $dataLength = bindec($tmp) + $payloadOffset; 309 | unset($tmp); 310 | } 311 | else 312 | { 313 | $mask = substr($data, 2, 4); 314 | $payloadOffset = 6; 315 | $dataLength = $payloadLength + $payloadOffset; 316 | } 317 | 318 | if ($isMasked === true) 319 | { 320 | for($i = $payloadOffset; $i < $dataLength; $i++) 321 | { 322 | $j = $i - $payloadOffset; 323 | if (isset($data[$i])) 324 | { 325 | $unmaskedPayload .= $data[$i] ^ $mask[$j % 4]; 326 | } 327 | } 328 | $decodedData['payload'] = $unmaskedPayload; 329 | } 330 | else 331 | { 332 | $payloadOffset = $payloadOffset - 4; 333 | $decodedData['payload'] = substr($data, $payloadOffset); 334 | } 335 | 336 | return $decodedData; 337 | } 338 | } -------------------------------------------------------------------------------- /src/Subscribe/Go.php: -------------------------------------------------------------------------------- 1 | host = $config["host"]; 23 | $this->port = $config["port"]; 24 | $this->client = null; 25 | } 26 | 27 | private function send($msg) 28 | { 29 | $this->send_times++; 30 | wing_debug("tcp client总发送次数:", $this->send_times); 31 | 32 | try { 33 | if (!$this->client) { 34 | $this->tryCreateClient(); 35 | } 36 | 37 | if (!fwrite($this->client, $msg . "\r\n\r\n\r\n")) { 38 | $this->client = null; 39 | $this->failure_times++; 40 | 41 | $this->tryCreateClient(); 42 | fwrite($this->client, $msg . "\r\n\r\n\r\n"); 43 | $this->send_times++; 44 | wing_debug("tcp client总发送次数:", $this->send_times); 45 | } 46 | 47 | wing_debug("tcp client总发送失败次数:", $this->failure_times); 48 | }catch (\Exception $e) { 49 | var_dump($e->getMessage()); 50 | $this->client = null; 51 | } 52 | } 53 | 54 | private function tryCreateClient() 55 | { 56 | try { 57 | $this->client = stream_socket_client("tcp://" . $this->host . ":" . $this->port, $errno, $errstr, 30); 58 | if (!$this->client) { 59 | wing_debug("发生错误:$errstr ($errno)"); 60 | $this->client = null; 61 | } 62 | } catch (\Exception $e) { 63 | var_dump($e->getMessage()); 64 | $this->client = null; 65 | } 66 | } 67 | 68 | public function onchange($event) 69 | { 70 | $this->send(json_encode($event)); 71 | } 72 | } -------------------------------------------------------------------------------- /src/Subscribe/Redis.php: -------------------------------------------------------------------------------- 1 | redis = new \Wing\Library\Redis( 26 | $host, 27 | $port, 28 | $password 29 | ); 30 | $this->queue = $queue; 31 | } 32 | 33 | public function onchange($event) 34 | { 35 | $this->redis->rpush($this->queue, json_encode($event)); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Subscribe/Tcp.php: -------------------------------------------------------------------------------- 1 | host = $config["host"]; 22 | $this->port = $config["port"]; 23 | $this->client = null; 24 | 25 | $daemon = $config["daemon"]; 26 | $workers = $config["workers"]; 27 | $this->startTcpService($this->host, $this->port, $daemon, $workers); 28 | register_shutdown_function(function(){ 29 | wing_debug("退出tcp服务"); 30 | exec(HOME."/services/tcp stop"); 31 | }); 32 | 33 | } 34 | 35 | private function startTcpService($host, $port,$deamon, $workers) 36 | { 37 | $command = HOME."/services/tcp ".$port; 38 | if (WING_DEBUG) { 39 | $command .= " --debug"; 40 | } 41 | wing_debug($command); 42 | $handle = popen($command." >>".HOME."/logs/tcp.log&","r"); 43 | if ($handle) { 44 | pclose($handle); 45 | } 46 | } 47 | 48 | private function send($msg) 49 | { 50 | $this->send_times++; 51 | wing_debug("tcp client总发送次数=》", $this->send_times); 52 | try { 53 | 54 | if (!$this->client) { 55 | $this->tryCreateClient(); 56 | } 57 | if (!fwrite($this->client, $msg . "\r\n\r\n\r\n")) { 58 | $this->client = null; 59 | $this->failure_times++; 60 | $this->tryCreateClient(); 61 | fwrite($this->client, $msg . "\r\n\r\n\r\n"); 62 | $this->send_times++; 63 | wing_debug("tcp client总发送次数=》", $this->send_times); 64 | } 65 | wing_debug("tcp client总发送失败次数=》", $this->failure_times); 66 | }catch (\Exception $e) { 67 | var_dump($e->getMessage()); 68 | $this->client = null; 69 | } 70 | } 71 | 72 | private function tryCreateClient() { 73 | try { 74 | $this->client = stream_socket_client("tcp://" . $this->host . ":" . $this->port, $errno, $errstr, 30); 75 | if (!$this->client) { 76 | wing_debug("stream_socket_client错误:$errstr ($errno)"); 77 | $this->client = null; 78 | } 79 | } catch (\Exception $e) { 80 | var_dump($e->getMessage()); 81 | $this->client = null; 82 | } 83 | } 84 | 85 | 86 | public function onchange($event) 87 | { 88 | $this->send(json_encode($event)); 89 | } 90 | } -------------------------------------------------------------------------------- /src/Subscribe/WMTcp.php: -------------------------------------------------------------------------------- 1 | host = $config["host"]; 22 | $this->port = $config["port"]; 23 | $this->client = null; 24 | 25 | $daemon = $config["daemon"]; 26 | $workers = $config["workers"]; 27 | $this->startTcpService($this->host, $this->port, $daemon, $workers); 28 | } 29 | 30 | private function startTcpService($host, $port,$deamon, $workers) 31 | { 32 | 33 | $command = "php " . HOME . "/services/tcp.php start --host=" . $host . " --port=" . $port . " --workers=" . $workers; 34 | if ($deamon) { 35 | $command .= " -d"; 36 | } 37 | $handle = popen("/bin/sh -c \"".$command."\" >>".HOME."/logs/tcp.log&","r"); 38 | if ($handle) { 39 | pclose($handle); 40 | } 41 | wing_debug($command); 42 | } 43 | 44 | private function send($msg) 45 | { 46 | $this->send_times++; 47 | wing_debug("tcp client总发送次数=》", $this->send_times); 48 | try { 49 | 50 | if (!$this->client) { 51 | $this->tryCreateClient(); 52 | } 53 | if (!fwrite($this->client, $msg . "\r\n\r\n\r\n")) { 54 | $this->client = null; 55 | $this->failure_times++; 56 | $this->tryCreateClient(); 57 | fwrite($this->client, $msg . "\r\n\r\n\r\n"); 58 | $this->send_times++; 59 | wing_debug("tcp client总发送次数=》", $this->send_times); 60 | } 61 | wing_debug("tcp client总发送失败次数=》", $this->failure_times); 62 | }catch (\Exception $e) { 63 | var_dump($e->getMessage()); 64 | $this->client = null; 65 | } 66 | } 67 | 68 | private function tryCreateClient() { 69 | try { 70 | $this->client = stream_socket_client("tcp://" . $this->host . ":" . $this->port, $errno, $errstr, 30); 71 | if (!$this->client) { 72 | wing_debug("stream_socket_client错误:$errstr ($errno)"); 73 | $this->client = null; 74 | } 75 | } catch (\Exception $e) { 76 | var_dump($e->getMessage()); 77 | $this->client = null; 78 | } 79 | } 80 | 81 | public function onchange($event) 82 | { 83 | $this->send(json_encode($event)); 84 | } 85 | } -------------------------------------------------------------------------------- /src/Subscribe/WMWebSocket.php: -------------------------------------------------------------------------------- 1 | workers = $workers; 32 | $this->host = $host; 33 | $this->port = $port; 34 | 35 | $this->startWebsocketService($host, $port, $daemon, $workers); 36 | sleep(1); 37 | $this->tryConnect(); 38 | } 39 | 40 | 41 | private function tryConnect() 42 | { 43 | $this->client = null; 44 | try { 45 | $this->client = new WsClient($this->host, $this->port, '/'); 46 | } catch (\Exception $e) { 47 | var_dump($e->getMessage()); 48 | $this->client = null; 49 | } 50 | } 51 | 52 | private function send($msg) 53 | { 54 | $msg .= "\r\n\r\n\r\n"; 55 | try { 56 | $this->send_times++; 57 | 58 | if (!$this->client->send($msg)) { 59 | $this->send_failure_times++; 60 | $this->client = null; 61 | $this->tryConnect(); 62 | $this->send_times++; 63 | $this->client->send($msg); 64 | } 65 | 66 | wing_debug("websocket发送次数:", $this->send_times," 失败次数:", $this->send_failure_times); 67 | } catch(\Exception $e){ 68 | var_dump($e->getMessage()); 69 | } 70 | } 71 | 72 | private function startWebsocketService($host, $port, $deamon, $workers) 73 | { 74 | $command = "php ".HOME."/services/websocket.php start --host=".$host." --port=".$port." --workers=".$workers; 75 | 76 | if ($deamon) { 77 | $command .= " -d"; 78 | } 79 | 80 | $command = "/bin/sh -c \"".$command."\" >>".HOME."/logs/websocket.log&"; 81 | wing_debug($command); 82 | $handle = popen($command,"r"); 83 | 84 | if ($handle) { 85 | pclose($handle); 86 | } 87 | } 88 | 89 | public function onchange($event) 90 | { 91 | $this->send(json_encode($event)); 92 | } 93 | } -------------------------------------------------------------------------------- /src/Subscribe/WebSocket.php: -------------------------------------------------------------------------------- 1 | workers = $workers; 28 | $this->host = $host; 29 | $this->port = $port; 30 | 31 | $this->startWebsocketService($host, $port, $daemon, $workers); 32 | sleep(1); 33 | $this->tryConnect(); 34 | 35 | register_shutdown_function(function(){ 36 | wing_debug("退出websocket服务"); 37 | exec(HOME."/services/websocket stop"); 38 | }); 39 | } 40 | 41 | 42 | private function tryConnect() 43 | { 44 | $this->client = null; 45 | try { 46 | $this->client = new \Wing\Net\WsClient($this->host, $this->port, '/'); 47 | } catch (\Exception $e){ 48 | var_dump($e->getMessage()); 49 | $this->client = null; 50 | } 51 | } 52 | 53 | 54 | private function send($msg) 55 | { 56 | $msg .= "\r\n\r\n\r\n"; 57 | try { 58 | $this->send_times++; 59 | if (!$this->client->send($msg)) { 60 | $this->send_failure_times++; 61 | $this->client = null; 62 | $this->tryConnect(); 63 | $this->send_times++; 64 | $this->client->send($msg); 65 | } 66 | wing_debug("websocket发送次数:", $this->send_times," 失败次数:", $this->send_failure_times); 67 | } catch(\Exception $e){ 68 | var_dump($e->getMessage()); 69 | } 70 | } 71 | 72 | private function startWebsocketService($host, $port, $deamon, $workers) 73 | { 74 | $command = HOME."/services/websocket ".$port; 75 | if (WING_DEBUG) { 76 | $command .= " --debug"; 77 | } 78 | wing_debug($command); 79 | $handle = popen($command." >>".HOME."/logs/websocket.log&","r"); 80 | if ($handle) { 81 | pclose($handle); 82 | } 83 | } 84 | 85 | public function onchange($event) 86 | { 87 | $this->send(json_encode($event)); 88 | } 89 | } -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | 0) { 81 | //父进程直接退出 82 | exit(0); 83 | } 84 | //创建进程会话 85 | if (-1 === posix_setsid()) { 86 | throw new \Exception("setsid fail"); 87 | } 88 | } 89 | } 90 | 91 | if (!function_exists("reset_std")) { 92 | /** 93 | * 设置输出重定向到文件日志 94 | */ 95 | function reset_std() 96 | { 97 | $os_name = php_uname('s'); 98 | $short_os_name = substr($os_name, 0, 3); 99 | $short_os_name = strtolower($short_os_name); 100 | if ($short_os_name== "win") { 101 | return; 102 | } 103 | global $STDOUT, $STDERR; 104 | $file = HOME."/logs/wing.log"; 105 | $obj_file = new \Wing\FileSystem\WFile($file); 106 | $obj_file->touch(); 107 | @fclose(STDOUT); 108 | @fclose(STDERR); 109 | $STDOUT = fopen($file, "a+"); 110 | $STDERR = fopen($file, "a+"); 111 | } 112 | } 113 | 114 | 115 | if (!function_exists("load_config")) { 116 | static $all_configs = []; 117 | /** 118 | * 加载配置文件 119 | * @param string $name 文件名称,不带php后缀 120 | * @return mixed 121 | */ 122 | function load_config($name) 123 | { 124 | global $all_configs; 125 | $config_file = HOME . "/config/" . $name . ".php"; 126 | if (isset($all_configs[$name])) { 127 | return $all_configs[$name]; 128 | } else { 129 | $all_configs[$name] = include $config_file; 130 | } 131 | return $all_configs[$name]; 132 | } 133 | } 134 | 135 | if (!function_exists("try_lock")) { 136 | /** 137 | * 使用文件锁尝试加锁 138 | * @param string $key 锁定的key 139 | * @return bool true加锁成功,false加锁失败 140 | */ 141 | function try_lock($key) 142 | { 143 | $dir = HOME."/cache/lock"; 144 | if (!is_dir($dir)) { 145 | $obj_dir = new \Wing\FileSystem\WDir($dir); 146 | $obj_dir->mkdir(); 147 | unset($obj_dir); 148 | } 149 | $file = $dir."/".md5($key); 150 | if (file_exists($file)) { 151 | return false; 152 | } 153 | touch($file); 154 | return file_exists($file); 155 | } 156 | } 157 | 158 | if (!function_exists("lock_free")) { 159 | /** 160 | * 释放锁 161 | * @param string $key 需要释放的key 162 | * @return bool 163 | */ 164 | function lock_free($key) 165 | { 166 | $dir = HOME."/cache/lock"; 167 | if (!is_dir($dir)) { 168 | $obj_dir = new \Wing\FileSystem\WDir($dir); 169 | $obj_dir->mkdir(); 170 | unset($obj_dir); 171 | } 172 | $file = $dir."/".md5($key); 173 | if (!file_exists($file)) { 174 | return true; 175 | } 176 | return unlink($file); 177 | } 178 | } 179 | 180 | if (!function_exists("timelen_format")) { 181 | /** 182 | * 时间长度格式化 183 | * @param int $time_len 时间长度,单位为秒,比如 60, 最终会转换为 "1分钟" 或者 "1minutes" 184 | */ 185 | function timelen_format($time_len) 186 | { 187 | $lang = "en"; 188 | if ($time_len < 60) { 189 | if ($lang == "en") { 190 | return $time_len . " seconds"; 191 | } 192 | return $time_len . "秒"; 193 | } elseif ($time_len < 3600 && $time_len >= 60) { 194 | $m = intval($time_len / 60); 195 | $s = $time_len - $m * 60; 196 | if ($lang == "en") { 197 | return $m . " minutes " . $s . " seconds"; 198 | } 199 | return $m . "分钟" . $s . "秒"; 200 | } elseif ($time_len < (24 * 3600) && $time_len >= 3600) { 201 | $h = intval($time_len / 3600); 202 | $s = $time_len - $h * 3600; 203 | if ($s >= 60) { 204 | $m = intval($s / 60); 205 | } else { 206 | $m = 0; 207 | } 208 | $s = $s-$m * 60; 209 | if ($lang == "en") { 210 | return $h . " hours " . $m . " minutes " . $s . " seconds"; 211 | } 212 | return $h . "小时" . $m . "分钟" . $s . "秒"; 213 | } else { 214 | $d = intval($time_len / (24 * 3600)); 215 | $s = $time_len - $d * (24 * 3600); 216 | $h = 0; 217 | $m = 0; 218 | if ($s < 60) { 219 | //do nothing 220 | } elseif ($s >= 60 && $s < 3600) { 221 | $m = intval($s / 60); 222 | $s = $s - $m * 60; 223 | } else { 224 | $h = intval($s / 3600); 225 | $s = $s - $h * 3600; 226 | $m = 0; 227 | if ($s >= 60) { 228 | $m = intval($s / 60); 229 | $s = $s - $m * 60; 230 | } 231 | } 232 | if ($lang == "en") { 233 | return $d." days ".$h . " hours " . $m . " minutes " . $s . " seconds"; 234 | } 235 | return $d."天".$h . "小时" . $m . "分钟" . $s . "秒"; 236 | 237 | } 238 | } 239 | } 240 | 241 | if (!function_exists("scan")) { 242 | function scan($dir, $callback) 243 | { 244 | ob_start(); 245 | $path[] = $dir . "/*"; 246 | while (count($path) != 0) { 247 | $v = array_shift($path); 248 | foreach (glob($v) as $item) { 249 | if (is_file($item)) { 250 | $t = explode("/", $item); 251 | $t = array_pop($t); 252 | $sub = substr($t, 0, 4); 253 | if ($sub == "lock") { 254 | unset($t, $sub); 255 | continue; 256 | } 257 | unset($t, $sub); 258 | $callback($item); 259 | unlink($item); 260 | } 261 | } 262 | } 263 | $debug = ob_get_contents(); 264 | ob_end_clean(); 265 | if ($debug) { 266 | wing_debug($debug); 267 | } 268 | } 269 | } 270 | 271 | if (!function_exists("wing_debug")) { 272 | function wing_debug($log) 273 | { 274 | if (!WING_DEBUG) { 275 | return; 276 | } 277 | echo date("Y-m-d H:i:s")." "; 278 | foreach (func_get_args() as $item) { 279 | if (is_scalar($item)) { 280 | echo $item." "; 281 | } else { 282 | var_dump($item); 283 | } 284 | } 285 | echo PHP_EOL; 286 | } 287 | } 288 | 289 | if (!function_exists("wing_log")) { 290 | function wing_log($level = "log", $msg = "") 291 | { 292 | $log = date("Y-m-d H:i:s")." "; 293 | $argvs = func_get_args(); 294 | array_shift($argvs); 295 | foreach ($argvs as $item) { 296 | if (is_scalar($item)) { 297 | $log .= $item." "; 298 | } else { 299 | $log.= json_encode($item, JSON_UNESCAPED_UNICODE)." "; 300 | } 301 | } 302 | $log .= "\r\n"; 303 | file_put_contents(HOME."/logs/".$level.".log", $log, FILE_APPEND); 304 | } 305 | } 306 | 307 | if (!function_exists("is_env")) { 308 | function is_env($env) 309 | { 310 | switch ($env) { 311 | case WINDOWS: 312 | return strtoupper(substr(PHP_OS, 0, 3))==='WIN' || "CYGWIN" == PHP_OS; 313 | case LINUX: 314 | return "Linux" == PHP_OS; 315 | } 316 | return false; 317 | } 318 | } 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/windows.php: -------------------------------------------------------------------------------- 1 | 1.2,3.4,5 6'; 10 | preg_match("/\[\s\S]{1,}\<\/coordinates>/", 11 | $str, $mac); 12 | var_dump($mac); 13 | preg_match_all("/[\d]+(\.[\d]+)?/", $mac[0], $matches); 14 | var_dump($matches); -------------------------------------------------------------------------------- /tests/end.php: -------------------------------------------------------------------------------- 1 | >".HOME."/logs/tcp.log&","r"); -------------------------------------------------------------------------------- /tests/exec.php: -------------------------------------------------------------------------------- 1 | array("pipe", "r"), 16 | 1 => array("pipe", "w"), 17 | 2 => array("pipe", "w") 18 | ); 19 | //$cmd = "php " . HOME . "/services/parse_worker --file=".$cache_file; 20 | //echo "开启新的解析进程,", $cmd,"\r\n"; 21 | proc_open($command, $descriptorspec, $pipes); 22 | //不阻塞 23 | stream_set_blocking($pipes[1], 0); 24 | fclose($pipes[0]); 25 | fclose($pipes[2]); //标准错误直接关闭 不需要 26 | 27 | 28 | $read = [$pipes[1]];//$this->parse_pipes; 29 | $write = null; 30 | $except = null; 31 | $timeleft = 60; 32 | 33 | $ret = stream_select( 34 | $read, 35 | $write, 36 | $except, 37 | $timeleft 38 | ); 39 | 40 | if ($ret === false || $ret === 0) { 41 | //foreach ($this->parse_pipes as $id => $sock) { 42 | fclose($pipes[1]); 43 | // unset($this->parse_pipes[$id]); 44 | // proc_close($this->parse_processes[$id]); 45 | // unset($this->parse_processes[$id]); 46 | // } 47 | // return; 48 | } 49 | 50 | foreach ($read as $sock) { 51 | 52 | $events = stream_get_contents($sock); 53 | //$events = json_decode($events, true); 54 | var_dump($events); 55 | 56 | // self::$event_times += count($events); 57 | //echo "总事件次数:", self::$event_times, "\r\n"; 58 | fclose($sock); 59 | // $id = array_search($sock, $this->parse_pipes); 60 | // unset($this->parse_pipes[$id]); 61 | // proc_close($this->parse_processes[$id]); 62 | // unset($this->parse_processes[$id]); 63 | // $all_count--; 64 | } 65 | 66 | //if ($all_count <= 0) { 67 | // break; 68 | //} 69 | //} 70 | 71 | 72 | 73 | //$res = system($command); 74 | //var_dump($res); 75 | 76 | // 77 | //$handle = popen($command,"r"); 78 | // 79 | //if (!$handle) { 80 | // while(!feof($handle)) 81 | // { 82 | // // send the current file part to the browser 83 | // print fread($handle, 1024); 84 | // // flush the content to the browser 85 | // // flush(); 86 | // } 87 | // //echo stream_get_contents($handle); 88 | // pclose($handle); 89 | //} 90 | -------------------------------------------------------------------------------- /tests/explode.php: -------------------------------------------------------------------------------- 1 | prepare("SELECT id FROM x_logs WHERE id=?")) { 15 | 16 | /* bind parameters for markers */ 17 | $stmt->bind_param("i", $city); 18 | 19 | /* execute query */ 20 | $stmt->execute(); 21 | 22 | /* bind result variables */ 23 | $stmt->bind_result($district); 24 | 25 | /* fetch value */ 26 | $stmt->fetch(); 27 | 28 | printf("%s is in district %s\n", $city, $district); 29 | 30 | /* close statement */ 31 | $stmt->close(); 32 | } 33 | 34 | /* close connection */ 35 | $mysqli->close(); 36 | -------------------------------------------------------------------------------- /tests/mysql-client/query.php: -------------------------------------------------------------------------------- 1 | pdo = \Wing\Bin\Db::$pdo = $pdo; 29 | $context->host = $mysql_config["mysql"]["host"]; 30 | $context->db_name = $mysql_config["mysql"]["db_name"]; 31 | $context->user = $mysql_config["mysql"]["user"]; 32 | $context->password = $mysql_config["mysql"]["password"]; 33 | $context->port = $mysql_config["mysql"]["port"]; 34 | $context->checksum = !!\Wing\Bin\Db::getChecksum(); 35 | 36 | $context->slave_server_id = $mysql_config["slave_server_id"]; 37 | $context->last_binlog_file = null; 38 | $context->last_pos = 0; 39 | 40 | //认证 41 | \Wing\Bin\Auth\Auth::execute($context); 42 | 43 | $res = \Wing\Bin\Mysql::execute( 44 | //'INSERT INTO xsl.`x_logs`(`id`,`module_name`,`message`) VALUES (999998, "test","test")');// 45 | 'select * from wp_posts where id=?', [12]); 46 | 47 | var_dump($res);*/ 48 | \Wing\Bin\Mysql::$debug = true; 49 | $pdo = new \Wing\Library\Mysql\PDO( 50 | $mysql_config["mysql"]["host"], 51 | $mysql_config["mysql"]["user"], 52 | $mysql_config["mysql"]["password"], 53 | $mysql_config["mysql"]["db_name"], 54 | $mysql_config["mysql"]["port"] 55 | ); 56 | //test ok 57 | echo $pdo->character_set_name(), "\r\n"; 58 | 59 | //close 后,后面再执行sql相关的东西,直接抛出异常了,说明关闭正常 60 | //\Wing\Bin\Mysql::close(); 61 | 62 | 63 | //预处理查询 ok 64 | var_dump($rows = \Wing\Bin\Mysql::execute('select * from wp_posts where 1 limit 800')); 65 | var_dump(count($rows)); 66 | 67 | //test ok 68 | // $pdo->autocommit(false); 69 | // //设置automit false之后,后面查询的值为0,设置为true以后,后面查询的值为1,说明正确 70 | // var_dump(\Wing\Bin\Mysql::query('select @@autocommit')); 71 | 72 | 73 | //开启事务 74 | //var_dump($pdo->begin_transaction( 75 | // \Wing\Bin\Constant\Trans::WITH_CONSISTENT_SNAPSHOT | 76 | // \Wing\Bin\Constant\Trans::READ_ONLY| 77 | // \Wing\Bin\Constant\Trans::READ_WRITE 78 | //)); 79 | 80 | } catch (\Exception $e) { 81 | var_dump($e); 82 | } 83 | 84 | echo "\r\nend\r\n"; -------------------------------------------------------------------------------- /tests/mysql-client/slave.php: -------------------------------------------------------------------------------- 1 | getEvent();//\Wing\Bin\Binlog::getEvent(); 46 | if ($result) { 47 | var_dump($result); 48 | $times+=count($result["event"]["data"]); 49 | $s = time()-$start; 50 | if ($s>0) 51 | echo $times,"次,",$times/($s)."/次事件每秒,耗时",$s,"秒\r\n"; 52 | } 53 | } -------------------------------------------------------------------------------- /tests/pdo.php: -------------------------------------------------------------------------------- 1 | row($sql); 15 | var_dump($res); 16 | 17 | $sql = 'show binary logs'; 18 | $res = $pdo->query($sql); 19 | var_dump($res); 20 | //$sql1 = 'update new_yonglibao_c.bl_city set provinces_id=(provinces_id+1) where id=5753598'; 21 | //$sql2 = 'update new_yonglibao_c.bl_city set provinces_id=(provinces_id-1) where id=5753598'; 22 | // 23 | //$sql1 = 'update xsl.x_messages set phone=(phone+1) where 1'; 24 | //$sql2 = 'update xsl.x_messages set phone=(phone-1) where 1'; 25 | // 26 | //$sql1 = 'update xl.content_type set sort=(sort+1) where 1'; 27 | //$sql2 = 'update xl.content_type set sort=(sort-1) where 1'; 28 | //$count = 0; 29 | //while ($count<1000) 30 | //{//$count<10000 31 | // $count +=2; 32 | // $pdo->query($sql1); 33 | // $pdo->query($sql2); 34 | // echo "事件次数:",$count,"\r\n"; 35 | //} -------------------------------------------------------------------------------- /tests/proc.php: -------------------------------------------------------------------------------- 1 | array("pipe", "r"), 4 | 1 => array("pipe", "w"), 5 | 2 => array("pipe", "r") 6 | ); 7 | $process = proc_open('php 2.php', $descriptorspec, $pipes, null, null); //run test_gen.php 8 | 9 | function try_read($r){ 10 | $read = [$r];//$this->dispatch_pipes; 11 | $write = null; 12 | $except = null; 13 | $timeleft = 60; 14 | 15 | $ret = stream_select( 16 | $read, 17 | $write,// = null, 18 | $except,// = null, 19 | $timeleft 20 | ); 21 | 22 | if ($ret === false || $ret === 0) { 23 | return; 24 | } 25 | 26 | foreach ($read as $sock) { 27 | $raw = stream_get_contents($sock); 28 | echo $raw,"\r\n"; 29 | if (strpos($raw,"processexit") !== false) { 30 | echo "\r\nchild process exit2"; 31 | exit; 32 | } 33 | } 34 | 35 | } 36 | 37 | if (is_resource($process)) 38 | { 39 | stream_set_blocking($pipes[1], 0); 40 | stream_set_blocking($pipes[0], 0); 41 | $i = 0; 42 | while(1) { 43 | fwrite($pipes[0], "hello_".$i."\r\n"); 44 | $i++; 45 | // while($res = stream_get_contents($pipes[1])) 46 | // echo $res,"\r\n"; 47 | //usleep(10000); 48 | try_read($pipes[1]); 49 | echo "send times : ", $i, "\r\n"; 50 | } 51 | 52 | fclose($pipes[0]); 53 | fclose($pipes[1]); 54 | fclose($pipes[2]); 55 | proc_close($process); 56 | } 57 | -------------------------------------------------------------------------------- /tests/proc/2.php: -------------------------------------------------------------------------------- 1 | array("pipe", "r"), 17 | 1 => array("pipe", "w"), 18 | 2 => array("pipe", "w") 19 | ); 20 | $cmd = "php " . __DIR__ . "/run.php"; 21 | $processes[$i] = proc_open($cmd, $descriptorspec, $pipes); 22 | $all_pipes[] = $pipes[1]; 23 | //$all_pipes[] = $pipes[2]; 24 | stream_set_blocking($pipes[1], 0); 25 | //stream_set_blocking($pipes[2], 0); //不阻塞 26 | fwrite($pipes[0], "hello proc_open_".$i." "); 27 | fclose($pipes[0]); 28 | fclose($pipes[2]); //标准错误直接关闭 不需要 29 | 30 | } 31 | 32 | //$stdout_str = $stderr_str = $stdin_str =""; 33 | 34 | $timeout = 100; 35 | 36 | 37 | 38 | //if (is_resource($process)) 39 | { 40 | //执行成功 41 | 42 | 43 | //设置超时时钟 44 | $endtime = time() + $timeout; 45 | 46 | do { 47 | $read = $all_pipes;//array($pipes[1],$pipes[2]); 48 | $write = null; 49 | $except = null; 50 | $timeleft = 1;//$endtime - time(); 51 | $ret = stream_select( 52 | $read, 53 | $write,// = null, 54 | $except,// = null, 55 | $timeleft 56 | ); 57 | 58 | if ($ret === false) { 59 | $err = true; 60 | break; 61 | } else if ($ret === 0) { 62 | $timeleft = 0; 63 | break; 64 | } else { 65 | var_dump($ret); 66 | foreach ($read as $sock) { 67 | //if ($sock === $pipes[1]) { 68 | echo fread($sock, 4096), "\r\n"; 69 | $id = array_search($sock, $all_pipes); 70 | unset($all_pipes[$id]); 71 | // } else if ($sock === $pipes[2]) { 72 | // echo fread($sock, 4096),"\r\n"; 73 | // } 74 | fclose($sock); 75 | } 76 | } 77 | }while(count($all_pipes) > 0 && $timeleft > 0 ); 78 | 79 | // fclose($pipes[1]); 80 | // fclose($pipes[2]); 81 | if($timeleft <= 0) { 82 | foreach ($processes as $process) 83 | proc_terminate($process); 84 | $stderr_str = "操作已超时(".$timeout."秒)"; 85 | } 86 | 87 | if (isset($err) && $err === true){ //这种情况的出现是通过信号发送中断时产生 88 | foreach ($processes as $process) 89 | proc_terminate($process); 90 | $stderr_str = "操作被用户取消"; 91 | } 92 | foreach ($processes as $process) 93 | proc_close($process); 94 | //return true; 95 | } 96 | //else { 97 | // return false; 98 | //} -------------------------------------------------------------------------------- /tests/queue.php: -------------------------------------------------------------------------------- 1 | push(rand(0,999999)); 14 | 15 | $queue->save(); 16 | 17 | while($data = $queue->pop()) { 18 | //echo $data,"\r\n"; 19 | } 20 | 21 | $queue->save(); 22 | 23 | echo "耗时:",time()-$start,"秒\r\n"; -------------------------------------------------------------------------------- /tests/rename.php: -------------------------------------------------------------------------------- 1 | onchange("11", "22", "33"); -------------------------------------------------------------------------------- /tests/server/server.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jilieryuyi/wing-binlog/5020e045cfad9fcdbef51003fb16b7060c3456e1/tests/server/server.exe -------------------------------------------------------------------------------- /tests/server/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var a map[int]int = make(map[int]int) 9 | a[0] = 1 10 | a[1] = 2 11 | a[2] = 3 12 | a[3] = 4 13 | fmt.Println(a) 14 | delete(a, 2) 15 | fmt.Println(a) 16 | } 17 | -------------------------------------------------------------------------------- /tests/status.php: -------------------------------------------------------------------------------- 1 | send('hello'); 34 | 35 | -------------------------------------------------------------------------------- /tests/statuso.php: -------------------------------------------------------------------------------- 1 | > parse process - 1"); 14 | echo "--------------------------------------------------------------------------------------------------------------------------\r\n"; 15 | -------------------------------------------------------------------------------- /tests/test123.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | comQuit byte = iota + 1 9 | comInitDB 10 | comQuery 11 | comFieldList 12 | comCreateDB 13 | comDropDB 14 | comRefresh 15 | comShutdown 16 | comStatistics 17 | comProcessInfo 18 | comConnect 19 | comProcessKill 20 | comDebug 21 | comPing 22 | comTime 23 | comDelayedInsert 24 | comChangeUser 25 | comBinlogDump 26 | comTableDump 27 | comConnectOut 28 | comRegisterSlave 29 | comStmtPrepare 30 | comStmtExecute 31 | comStmtSendLongData 32 | comStmtClose 33 | comStmtReset 34 | comSetOption 35 | comStmtFetch 36 | ) 37 | 38 | func main(){ 39 | fmt.Println(comStmtPrepare) 40 | } 41 | -------------------------------------------------------------------------------- /tests/unit/MysqlProtocol/PdoTest.php: -------------------------------------------------------------------------------- 1 | pdo = new \Wing\Library\Mysql\PDO( 22 | $mysql_config["mysql"]["host"], 23 | $mysql_config["mysql"]["user"], 24 | $mysql_config["mysql"]["password"], 25 | $mysql_config["mysql"]["db_name"], 26 | $mysql_config["mysql"]["port"] 27 | ); 28 | } 29 | 30 | public function testcharacter_set_name() 31 | { 32 | $this->assertNotEmpty($this->pdo->character_set_name()); 33 | } 34 | } -------------------------------------------------------------------------------- /tests/unit/test: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /tests/unixsocket/client.php: -------------------------------------------------------------------------------- 1 | send(json_encode([ 14 | "event_index"=>0, 15 | "event"=>1 16 | ])."\r\n\r\n\r\n"); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /tests/websocketclient.php: -------------------------------------------------------------------------------- 1 | setCatchExceptions(true); 90 | 91 | $commands = [ 92 | \Wing\Command\ServerStart::class, 93 | \Wing\Command\ServerStop::class, 94 | \Wing\Command\ServerVersion::class, 95 | \Wing\Command\ServerStatus::class, 96 | \Wing\Command\Help::class, 97 | \Wing\Command\ServerRestart::class 98 | ]; 99 | foreach ($commands as $command) { 100 | $application->add(new $command); 101 | } 102 | 103 | $application->run(); 104 | } catch (\Exception $e) { 105 | var_dump($e); 106 | } --------------------------------------------------------------------------------