├── .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 |
21 |
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 | }
--------------------------------------------------------------------------------