├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── benchmark ├── main.go ├── pub │ └── main.go └── sub │ └── main.go ├── client └── client.go ├── config ├── config.go └── topics.go ├── dashboard ├── api │ ├── api.go │ └── run.go ├── dashboard_test.go ├── dashbord.go ├── var.go └── www │ └── index.html ├── mac.sh ├── main.go ├── mqttdebug.crt ├── mqttdebug.csr ├── mqttdebug.key ├── mqttdebug.txt ├── network ├── tcp │ ├── tcp.go │ └── tls.go └── websocket │ ├── tls.go │ └── websocket.go ├── restful_api ├── api.go ├── router.go └── run.go ├── safe-runtine └── safe-runtine.go ├── session ├── channel.go ├── msg-handler.go ├── session-info.go ├── session-mgr.go ├── session.go ├── session_test.go └── token.go ├── store ├── .DS_Store ├── mem-provider │ └── mem-store.go ├── mem-store_test.go └── store.go ├── ticker └── second-ticker.go ├── topic ├── topic-mgr.go ├── topic_test.go └── tree.go ├── ubuntu.sh ├── upload.sh ├── utils ├── event.go ├── event_test.go ├── http.go ├── id.go └── list.go ├── vendor └── github.com │ └── eclipse │ └── paho.mqtt.golang │ ├── .gitignore │ └── packets │ ├── connack.go │ ├── connect.go │ ├── disconnect.go │ ├── packets.go │ ├── packets_test.go │ ├── pingreq.go │ ├── pingresp.go │ ├── puback.go │ ├── pubcomp.go │ ├── publish.go │ ├── pubrec.go │ ├── pubrel.go │ ├── suback.go │ ├── subscribe.go │ ├── unsuback.go │ └── unsubscribe.go └── www ├── .project ├── client ├── index.html └── tls.html ├── dashboard ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── index.html ├── index_prod.html ├── package.json ├── src │ ├── app.vue │ ├── config │ │ └── config.js │ ├── font │ │ ├── demo.css │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ ├── imgs │ │ ├── iotalking.ico │ │ └── iotalking.png │ ├── libs │ │ └── util.js │ ├── main.js │ ├── router.js │ ├── template │ │ └── index.ejs │ ├── vendors.js │ └── views │ │ ├── about.vue │ │ ├── charts.vue │ │ ├── home.vue │ │ └── menu.vue ├── webpack.base.config.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── libs ├── mqtt.js ├── mqtt.min.js ├── vue.js └── vue.min.js ├── locker ├── img │ ├── close.png │ └── open.png └── index.html ├── router.go ├── run.go └── whiteboard ├── img ├── Clear.png └── exit.png └── index.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=go 2 | *.css linguist-language=go 3 | *.html linguist-language=go 4 | *.go linguist-language=go 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqtt-broker 2 | 一个高并发,高性能,高可靠的mqtt服务器 3 | 4 | [TOC] 5 | 6 | 7 | ## TODO: 8 | - [x] 1. Session的发送窗口,即发送窗口满时,不能发其它消息 9 | - [x] 2. Retain消息发送给有订阅此消息主题的新客户端 10 | - [ ] 3. CleanSession处理 11 | - [x] 4. 实现websocket 12 | - [x] 5. tls支持 13 | - [x] 6. 遗嘱处理 14 | - [ ] 7. 登录验证 15 | - [x] 8. Session做为客户端使用,benchmark中使用Session 16 | - [ ] 9. dashboard页面实现 17 | - [ ] 10. 实现管理配置后台 18 | - [ ] 11. 发布1.0版本 19 | - [ ] 12. sqlite3 store支持 20 | - [ ] 13. 实现集群 21 | - [ ] 14. 实现多集群管理 22 | - [ ] 15. 服务器监控 23 | - [ ] 16. 发布2.0版本 24 | 25 | #Session操作流程 26 | 27 | ##PUBLISH qos=0 28 | ```sequence 29 | send->recv:PUBLISH qos=0 30 | send->session:onPublicDone 31 | ``` 32 | 33 | 34 | ##PUBLISH qos=1 35 | ```sequence 36 | send->recv:PUBLISH qos=1 37 | recv-->send:PUBACK 38 | send->session:onPublicDone 39 | send->other:PUBLISH topic 40 | ``` 41 | ##PUBLISH qos=2 42 | ```sequence 43 | send->recv:PUBLISH qos=2 44 | recv-->send:PUBREC 45 | send->send:保存消息, 46 | send->recv:PUBREL 47 | recv-->send:PUBCOMP 48 | send->send:删除消息 49 | send->session:onPublicDone 50 | send->other:PUBLISH topic 51 | ``` 52 | 53 | ##给session发送消息 54 | ```sequence 55 | other->session:Publish 56 | session->session:checkInflightList 57 | ``` 58 | 59 | ##checkInflightList 60 | ```flow 61 | st=>start: 开始 62 | e=>end: 结束 63 | inflightcheck=>condition: inflight队列满? 64 | chanel.send=>operation: 调用Channel发送一个包 65 | insertpendding=>operation: 插入等待队列 66 | insertinfilight=>operation: 将PUBLISH消息插入inflight队列 67 | st->inflightcheck 68 | inflightcheck(yes)->insertpendding->e 69 | inflightcheck(no)->chanel.send->insertinfilight->e 70 | ``` 71 | ##onTick 72 | ```flow 73 | st=>start: 开始 74 | e=>end: 结束 75 | loopInflightList=>operation: 遍历inflight列表 76 | loopInflightListIsEnd=>condition: 遍历完成? 77 | condMsgTimeout=>condition: 消息是否超时? 78 | saveTimeoutMsg=>operation: 记录超时消息 79 | loopTimeoutMsg=>operation: 遍历超时消息 80 | loopTimeoutMsgIsEnd=>condition: 遍历完成? 81 | chanelresend=>operation: 调用Channel重发超时包 82 | 83 | st->loopInflightList->loopInflightListIsEnd 84 | loopInflightListIsEnd(no)->condMsgTimeout 85 | loopInflightListIsEnd(yes)->loopTimeoutMsg->loopTimeoutMsgIsEnd 86 | condMsgTimeout(yes)->saveTimeoutMsg(left)->loopInflightList 87 | condMsgTimeout(no)->loopInflightList 88 | loopTimeoutMsgIsEnd(yes)->chanelresend->e 89 | loopTimeoutMsgIsEnd(no)->loopTimeoutMsg 90 | ``` 91 | 92 | ##如何生产自签名证书 93 | ```bash 94 | openssl genrsa -des3 -out server.key 2048 95 | openssl req -new -key mqttdebug.key mqttdebug.csr 96 | openssl req -new -key mqttdebug.key -out mqttdebug.csr 97 | openssl rsa -in mqttdebug.key.org -out mqttdebug.key 98 | openssl x509 -req -days 365 -in mqttdebug.csr -signkey mqttdebug.key -out mqttdebug.crt 99 | ``` 100 | -------------------------------------------------------------------------------- /benchmark/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | * 9 | * Contributors: 10 | * Seth Hoenig 11 | * Allan Stockdill-Mander 12 | * Mike Robertson 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "log" 21 | "os" 22 | "os/signal" 23 | "time" 24 | 25 | "github.com/eclipse/paho.mqtt.golang" 26 | 27 | "github.com/iotalking/mqtt-broker/utils" 28 | ) 29 | 30 | var f mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) { 31 | // fmt.Printf("TOPIC: %s\n", msg.Topic()) 32 | // fmt.Printf("MSG: %s\n", msg.Payload()) 33 | } 34 | 35 | var clients []mqtt.Client 36 | var newClientChan = make(chan mqtt.Client) 37 | var closeAll = make(chan bool) 38 | 39 | func push(host string, port int, i int) { 40 | // mqtt.DEBUG = log.New(os.Stdout, "", 0) 41 | mqtt.ERROR = log.New(os.Stdout, "", 0) 42 | opts := mqtt.NewClientOptions().AddBroker("tcp://" + host + fmt.Sprintf(":%d", port)).SetClientID(utils.NewId()) 43 | opts.SetKeepAlive(20 * time.Second) 44 | opts.SetDefaultPublishHandler(f) 45 | opts.SetPingTimeout(10 * time.Second) 46 | 47 | c := mqtt.NewClient(opts) 48 | if token := c.Connect(); token.Wait() && token.Error() != nil { 49 | panic(token.Error()) 50 | } 51 | 52 | if token := c.Subscribe("go-mqtt/sample", 0, nil); token.Wait() && token.Error() != nil { 53 | fmt.Println(token.Error()) 54 | os.Exit(1) 55 | } 56 | 57 | for i := 0; true; i++ { 58 | text := fmt.Sprintf("this is msg #%d!", i) 59 | token := c.Publish("go-mqtt/sample", 0, false, text) 60 | token.Wait() 61 | time.Sleep(time.Second) 62 | } 63 | } 64 | func subscribe(host string, port int, i int, resultCh chan<- bool) { 65 | // mqtt.DEBUG = log.New(os.Stdout, "", 0) 66 | mqtt.ERROR = log.New(os.Stdout, "", 0) 67 | opts := mqtt.NewClientOptions().AddBroker("tcp://" + host + fmt.Sprintf(":%d", port)).SetClientID(utils.NewId()) 68 | opts.SetConnectTimeout(100 * time.Second) 69 | 70 | opts.SetKeepAlive(20 * time.Second) 71 | opts.SetDefaultPublishHandler(f) 72 | opts.SetPingTimeout(10 * time.Second) 73 | 74 | defer func() { 75 | recover() 76 | close(resultCh) 77 | }() 78 | c := mqtt.NewClient(opts) 79 | if token := c.Connect(); token.Wait() && token.Error() != nil { 80 | panic(token.Error()) 81 | } 82 | 83 | if token := c.Subscribe("go-mqtt/sample", 0, nil); token.Wait() && token.Error() != nil { 84 | fmt.Println(token.Error()) 85 | os.Exit(1) 86 | } 87 | 88 | } 89 | func main() { 90 | host := flag.String("host", "localhost", "host") 91 | port := flag.Int("port", 1883, "server port") 92 | p := flag.Int("p", 1, "count process") 93 | flag.Parse() 94 | 95 | var i int 96 | defer func() { 97 | recover() 98 | mqtt.DEBUG.Printf("connected :%d", i) 99 | }() 100 | go func() { 101 | for { 102 | select { 103 | case c := <-newClientChan: 104 | clients = append(clients, c) 105 | case <-closeAll: 106 | for _, c := range clients { 107 | c.Disconnect(250) 108 | } 109 | close(closeAll) 110 | return 111 | } 112 | } 113 | }() 114 | // go push(*host,*port, 0) 115 | for i = 1; i <= *p; i++ { 116 | ch := make(chan bool) 117 | go subscribe(*host, *port, i, ch) 118 | <-ch 119 | } 120 | signals := make(chan os.Signal, 1) 121 | 122 | signal.Notify(signals, os.Interrupt) 123 | select { 124 | case <-signals: 125 | //关闭服务器的端口监听,以退出 126 | closeAll <- true 127 | } 128 | <-closeAll 129 | } 130 | -------------------------------------------------------------------------------- /benchmark/pub/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "os" 9 | "os/signal" 10 | "strings" 11 | 12 | log "github.com/Sirupsen/logrus" 13 | "github.com/iotalking/mqtt-broker/client" 14 | "github.com/iotalking/mqtt-broker/session" 15 | "github.com/iotalking/mqtt-broker/utils" 16 | ) 17 | 18 | var host = flag.String("h", "mqtt://localhost:1883", "mqtt server address.protocal://[username][:password]@ip[:port],protocal:mqtt,mqtts,ws,wss") 19 | var topic = flag.String("t", "", "topic") 20 | var qos = flag.Int("q", 0, "the QoS of the message") 21 | var body = flag.String("m", "", "the message body") 22 | var loglevel = flag.String("log", "error", "set log level.") 23 | var id = flag.String("i", "", "the id of the client") 24 | var times = flag.Int("c", 1, "publish message times") 25 | var retain = flag.Bool("r", false, "retain message") 26 | 27 | func main() { 28 | log.SetOutput(os.Stdout) 29 | 30 | flag.Parse() 31 | 32 | level, err := log.ParseLevel(*loglevel) 33 | if err != nil { 34 | flag.Usage() 35 | return 36 | } 37 | log.SetLevel(level) 38 | if len(*host) <= 0 { 39 | flag.Usage() 40 | return 41 | } 42 | if len(*topic) == 0 { 43 | *topic = flag.Arg(0) 44 | } 45 | if len(*topic) == 0 { 46 | flag.Usage() 47 | return 48 | } 49 | go http.ListenAndServe(":6061", nil) 50 | 51 | var protocal = "tcp" 52 | t := strings.Split(*host, "://") 53 | if len(t) < 2 { 54 | protocal = "mqtt" 55 | } else { 56 | protocal = t[0] 57 | *host = t[1] 58 | } 59 | if len(*id) == 0 { 60 | *id = utils.NewId() 61 | } 62 | c := client.NewClient(*id, session.GetMgr()) 63 | token, err := c.Connect(protocal, *host) 64 | if err != nil { 65 | fmt.Fprintf(os.Stderr, err.Error()) 66 | return 67 | } 68 | token.Wait() 69 | c.SetOnMessage(func(topic string, body []byte, qos byte) { 70 | fmt.Printf("qos:%d topic:%s payload:%s\n", qos, topic, body) 71 | }) 72 | signals := make(chan os.Signal, 1) 73 | c.SetOnDisconnected(func() { 74 | log.Debug("client disconnected") 75 | signals <- os.Interrupt 76 | }) 77 | go func() { 78 | for *times > 0 { 79 | token, err := c.Publish(*topic, []byte(*body), byte(*qos), *retain) 80 | if err != nil { 81 | log.Error("publish error:", err) 82 | break 83 | } 84 | token.Wait() 85 | *times-- 86 | } 87 | signals <- os.Interrupt 88 | }() 89 | 90 | signal.Notify(signals, os.Interrupt) 91 | 92 | select { 93 | case <-signals: 94 | c.Disconnect() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /benchmark/sub/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "os" 9 | "os/signal" 10 | "strings" 11 | 12 | log "github.com/Sirupsen/logrus" 13 | "github.com/iotalking/mqtt-broker/client" 14 | "github.com/iotalking/mqtt-broker/session" 15 | "github.com/iotalking/mqtt-broker/utils" 16 | ) 17 | 18 | var host = flag.String("h", "localhost", "mqtt server address.protocal://ip:port,protocal:mqtt,mqtts,ws,wss") 19 | var topic = flag.String("t", "", "topic") 20 | var qos = flag.Int("q", 0, "the QoS of the message") 21 | var port = flag.Int("p", 1883, "the server port") 22 | var loglevel = flag.String("log", "error", "set log level.") 23 | var id = flag.String("i", "", "the id of the client") 24 | var index = flag.Bool("s", false, "show the message sequence number") 25 | var showHeader = flag.Bool("sh", true, "show the qos and topicname") 26 | var payloadstring = flag.Bool("ps", true, "show message payload as string") 27 | 28 | func main() { 29 | log.SetOutput(os.Stdout) 30 | 31 | flag.Parse() 32 | 33 | level, err := log.ParseLevel(*loglevel) 34 | if err != nil { 35 | flag.Usage() 36 | return 37 | } 38 | log.SetLevel(level) 39 | if len(*host) <= 0 { 40 | flag.Usage() 41 | return 42 | } 43 | if len(*topic) == 0 { 44 | *topic = flag.Arg(0) 45 | } 46 | if len(*topic) == 0 { 47 | flag.Usage() 48 | return 49 | } 50 | go http.ListenAndServe(":6061", nil) 51 | 52 | var protocal = "tcp" 53 | t := strings.Split(*host, "://") 54 | if len(t) < 2 { 55 | protocal = "mqtt" 56 | } else { 57 | protocal = t[0] 58 | } 59 | if len(*id) == 0 { 60 | *id = utils.NewId() 61 | } 62 | c := client.NewClient(*id, session.GetMgr()) 63 | token, err := c.Connect(protocal, fmt.Sprintf("%s:%d", *host, *port)) 64 | if err != nil { 65 | fmt.Fprintf(os.Stderr, err.Error()) 66 | return 67 | } 68 | token.Wait() 69 | i := 0 70 | c.SetOnMessage(func(topic string, body []byte, qos byte) { 71 | i++ 72 | if *index { 73 | fmt.Printf("[%d]", i) 74 | } 75 | if *showHeader { 76 | fmt.Printf("qos:%d topic:%s ", qos, topic) 77 | } 78 | if *payloadstring { 79 | fmt.Println(string(body)) 80 | } else { 81 | fmt.Println(body) 82 | } 83 | 84 | }) 85 | signals := make(chan os.Signal, 1) 86 | c.SetOnDisconnected(func() { 87 | log.Debug("client disconnected") 88 | signals <- os.Interrupt 89 | }) 90 | c.Subcribe(map[string]byte{ 91 | *topic: byte(*qos), 92 | }) 93 | 94 | signal.Notify(signals, os.Interrupt) 95 | 96 | select { 97 | case <-signals: 98 | c.Disconnect() 99 | } 100 | } 101 | 102 | func pubmqtt() { 103 | 104 | } 105 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "crypto/tls" 5 | "io" 6 | "net" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | log "github.com/Sirupsen/logrus" 12 | "github.com/eclipse/paho.mqtt.golang/packets" 13 | "github.com/iotalking/mqtt-broker/config" 14 | "github.com/iotalking/mqtt-broker/safe-runtine" 15 | "github.com/iotalking/mqtt-broker/session" 16 | 17 | _ "github.com/iotalking/mqtt-broker/store/mem-provider" 18 | ) 19 | 20 | var mgrOnce sync.Once 21 | 22 | type Client struct { 23 | sessionMgr session.SessionMgr 24 | session *session.Session 25 | proto string 26 | addr string 27 | user string 28 | password []byte 29 | clientId string 30 | WillTopic string 31 | WillMessage []byte 32 | WillQos byte 33 | WillRetain bool 34 | Keepalive uint16 //second 35 | 36 | mainRuntine *runtine.SafeRuntine 37 | } 38 | 39 | func NewClient(id string, mgr session.SessionMgr) *Client { 40 | c := &Client{ 41 | sessionMgr: mgr, 42 | clientId: id, 43 | } 44 | return c 45 | } 46 | 47 | //连接服务器 48 | //proto:mqtt,mqtts,ws,wss 49 | //mqtt:tcp 50 | //mqtts:tcp tls 51 | //ws:websocket 52 | //wss:websocket tls 53 | //addr格式: 54 | //[username][:password]@ip[:port] 55 | func (this *Client) Connect(proto, addr string) (token session.Token, err error) { 56 | this.proto = proto 57 | this.addr = addr 58 | //解析username和password 59 | tmps := strings.Split(addr, "@") 60 | if len(tmps) > 1 { 61 | addr = tmps[1] 62 | //包含用户名和密码段 63 | tmps := strings.Split(tmps[0], ":") 64 | this.user = tmps[0] 65 | if len(tmps) > 1 { 66 | this.password = []byte(tmps[1]) 67 | } 68 | 69 | } 70 | var c io.ReadWriteCloser 71 | switch proto { 72 | case "mqtt": 73 | c, err = this.newTcpConn(addr) 74 | case "mqtts": 75 | c, err = this.newTcpTlsConn(addr) 76 | default: 77 | c, err = this.newTcpConn(addr) 78 | } 79 | 80 | if err == nil { 81 | this.session = session.NewSession(this.sessionMgr, c, false) 82 | this.session.SetClientId(this.clientId) 83 | this.sessionMgr.HandleConnection(this.session) 84 | 85 | connectMsg := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket) 86 | connectMsg.ProtocolName = "MQTT" 87 | connectMsg.ProtocolVersion = 4 88 | connectMsg.UsernameFlag = true 89 | connectMsg.Username = this.user 90 | connectMsg.PasswordFlag = true 91 | connectMsg.Password = this.password 92 | connectMsg.ClientIdentifier = this.clientId 93 | if this.Keepalive == 0 { 94 | connectMsg.Keepalive = uint16(this.session.GetKeepalive() / int64(time.Second)) 95 | } else { 96 | connectMsg.Keepalive = this.Keepalive 97 | } 98 | 99 | token, err = this.session.Send(connectMsg) 100 | } else { 101 | log.Error("connect error:", err) 102 | defer func() { 103 | recover() 104 | }() 105 | 106 | } 107 | return 108 | } 109 | 110 | func (this *Client) newTcpConn(addr string) (c io.ReadWriteCloser, err error) { 111 | c, err = net.DialTimeout("tcp", addr, time.Duration(config.ConnectTimeout)) 112 | return 113 | } 114 | 115 | func (this *Client) newTcpTlsConn(addr string) (c io.ReadWriteCloser, err error) { 116 | var config tls.Config 117 | config.InsecureSkipVerify = true 118 | c, err = tls.Dial("tcp", addr, &config) 119 | return 120 | } 121 | 122 | func (this *Client) Disconnect() (err error) { 123 | if this.session == nil || this.session.IsClosed() { 124 | return 125 | } 126 | disconnectMsg := packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket) 127 | token, err := this.session.Send(disconnectMsg) 128 | if err == nil { 129 | token.Wait() 130 | log.Debug("disconnect token.Wait return") 131 | } 132 | return 133 | } 134 | 135 | //发布消息 136 | func (this *Client) Publish(topic string, body []byte, qos byte, retain bool) (token session.Token, err error) { 137 | msg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) 138 | msg.TopicName = topic 139 | msg.Payload = body 140 | msg.Qos = qos 141 | msg.Retain = retain 142 | token, err = this.session.Publish(msg) 143 | return 144 | } 145 | 146 | //订阅主题,可以一次订阅多条 147 | //submap 148 | //key:subscription 149 | //value:qos 150 | func (this *Client) Subcribe(submap map[string]byte) (token session.Token, err error) { 151 | subMsg := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket) 152 | for sub, qos := range submap { 153 | subMsg.Topics = append(subMsg.Topics, sub) 154 | subMsg.Qoss = append(subMsg.Qoss, qos) 155 | } 156 | token, err = this.session.Send(subMsg) 157 | return 158 | } 159 | 160 | //不订阅主题 161 | func (this *Client) Unsubcribe(subs ...string) (token session.Token, err error) { 162 | unsubMsg := packets.NewControlPacket(packets.Unsubscribe).(*packets.UnsubscribePacket) 163 | unsubMsg.Topics = subs 164 | token, err = this.session.Send(unsubMsg) 165 | return 166 | } 167 | func (this *Client) SetOnMessage(cb func(topic string, body []byte, qos byte)) { 168 | this.session.SetOnMessage(func(msg *packets.PublishPacket) { 169 | if cb != nil { 170 | cb(msg.TopicName, msg.Payload, msg.Qos) 171 | } 172 | 173 | }) 174 | } 175 | func (this *Client) SetOnDisconnected(cb func()) { 176 | this.session.SetOnDisconnected(cb) 177 | } 178 | 179 | func (this *Client) SetKeepalive(keepalive uint16) { 180 | this.Keepalive = keepalive 181 | } 182 | func (this *Client) GetKeepalive() uint16 { 183 | return this.Keepalive 184 | } 185 | func (this *Client) GetID() string { 186 | return this.clientId 187 | } 188 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var TcpPort = 1883 8 | 9 | //tcp ssl 加密端口 10 | var TcpTLSPort = 8883 11 | 12 | //websocket端口,url ip:port/mqtt 13 | var WSPort = 8083 14 | 15 | //websocket ssl加密商品,url ip:port/mqtts 16 | var WSSPort = 8084 17 | 18 | var CertFileName = "./mqttdebug.crt" 19 | var KeyFileName = "./mqttdebug.key" 20 | 21 | //pprof查看端口,url ip:port/debug/pprof 22 | var PprofPort = 6060 23 | 24 | //web server for some html page 25 | var WebPort = 8080 26 | 27 | //restful api port,url base: ip:port/dashboard 28 | var RestfulPort = 8081 29 | 30 | var DashboordUrl = "/dashboard" 31 | var DashboordApiUrl = DashboordUrl + "/api" 32 | var RestfullApiUrl = "/api" 33 | 34 | //发送心跳包的周期(秒) 35 | var PingTimeout = 30 * int64(time.Second) 36 | 37 | //客户端用户检测PINGRESP有没有超时接收 38 | var PingrespTimeout = 45 * int64(time.Second) //1.5 PingTimeout 39 | 40 | //连接超时时间,秒 41 | var ConnectTimeout = 10 * int64(time.Second) 42 | 43 | //消息发送超时时间 44 | var SentTimeout = 5 * int64(time.Second) 45 | 46 | //看板的数据更新时间 47 | var DashboardUpdateTime = 3 * time.Second 48 | 49 | //发送窗口的大小 50 | var MaxSizeOfInflight int = 100 51 | 52 | //sessionMgr处理新连接的chan缓冲大小 53 | var MaxSizeOfNewConnectionChan int = 10000 54 | 55 | //重发消息的最大次数 56 | var MaxRetryTimes int = 5 57 | 58 | //每个session 最大pedding消息数 59 | var MaxSizeOfPublishMsg = 10 * 1024 60 | 61 | //是否给支持自己给自己发消息 62 | var SendToSelf = true 63 | -------------------------------------------------------------------------------- /config/topics.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var OverviewTopic = "$sys/overview" 8 | 9 | func SessionInfoTopic(sessionId string) string { 10 | return fmt.Sprintf("$sys/session/%s/info", sessionId) 11 | } 12 | -------------------------------------------------------------------------------- /dashboard/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "time" 9 | 10 | log "github.com/Sirupsen/logrus" 11 | 12 | "github.com/iotalking/mqtt-broker/dashboard" 13 | ) 14 | 15 | func GetOverviewData(w http.ResponseWriter, r *http.Request) { 16 | 17 | tm := time.Now().Sub(startTime) 18 | dashboard.Overview.RunTimeString.Set(tm.String()) 19 | dashboard.Overview.RunNanoSeconds.Set(tm.Nanoseconds()) 20 | log.Debug("OverviewData.Get") 21 | 22 | name := r.FormValue("name") 23 | 24 | d := dashboard.Overview.Copy() 25 | if len(name) > 0 { 26 | log.Debug("name = ", name) 27 | 28 | value := reflect.ValueOf(d).Elem().FieldByName(r.FormValue("name")) 29 | if value.IsValid() { 30 | v := value.Addr().Interface().(dashboard.AtmType).Get() 31 | sv := fmt.Sprintf("%s", v) 32 | log.Info("OverviewData.Get name=", sv) 33 | w.Write([]byte(sv)) 34 | return 35 | } else { 36 | log.Debug("no field of ", name) 37 | } 38 | } else { 39 | log.Debug("name is empty") 40 | } 41 | 42 | // bsjson, _ = json.MarshalIndent(&d, "", "\t") 43 | encoder := json.NewEncoder(w) 44 | encoder.SetIndent("", "\t") 45 | encoder.Encode(&d) 46 | } 47 | 48 | func SetLogLevel(w http.ResponseWriter, r *http.Request) { 49 | var err error 50 | defer func() { 51 | if err != nil { 52 | http.Error(w, "level=", http.StatusBadRequest) 53 | } 54 | }() 55 | err = r.ParseForm() 56 | if err != nil { 57 | log.Error("dashboard.SetLogLevel error:", err) 58 | return 59 | } 60 | v, err := log.ParseLevel(r.FormValue("level")) 61 | if err != nil { 62 | log.Error("dashboard.ParseLevel error:", err) 63 | return 64 | } 65 | log.SetLevel(v) 66 | } 67 | 68 | func GetSessions(w http.ResponseWriter, r *http.Request) { 69 | log.Debug("dashboard.GetActiveSessions") 70 | 71 | list := sessionMgr.GetSessions() 72 | encoder := json.NewEncoder(w) 73 | encoder.SetIndent("", "\t") 74 | encoder.Encode(list) 75 | } 76 | -------------------------------------------------------------------------------- /dashboard/api/run.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/iotalking/mqtt-broker/dashboard" 7 | "github.com/iotalking/mqtt-broker/session" 8 | ) 9 | 10 | type SessionMgr interface { 11 | GetSessions() session.SessionList 12 | } 13 | 14 | var sessionMgr SessionMgr 15 | var startTime time.Time 16 | var getChan chan byte 17 | var outChan chan dashboard.OverviewData 18 | 19 | func Start(mgr SessionMgr) { 20 | startTime = time.Now() 21 | 22 | sessionMgr = mgr 23 | getChan = make(chan byte) 24 | outChan = make(chan dashboard.OverviewData) 25 | run() 26 | } 27 | 28 | func run() { 29 | 30 | duration := time.Second 31 | var _secondsTimer = time.NewTimer(duration) 32 | var lastSentMsgCnt = dashboard.Overview.SentMsgCnt 33 | var lastRecvMsgCnt = dashboard.Overview.RecvMsgCnt 34 | var lastPerTime = time.Now() 35 | var perCnt dashboard.AtmI64 36 | var spsTotall dashboard.AtmI64 37 | var rpsTotall dashboard.AtmI64 38 | 39 | var lastSentBytes = dashboard.Overview.SentBytes 40 | var lastRecvBytes = dashboard.Overview.RecvBytes 41 | //平均发送速率总和 42 | var sbpsTotall dashboard.AtmI64 43 | //平均接收速率总和 44 | var rbpsTotall dashboard.AtmI64 45 | for { 46 | select { 47 | case <-_secondsTimer.C: 48 | //每秒计算一次平均数 49 | tm := time.Now().Sub(lastPerTime).Seconds() 50 | 51 | sps := (dashboard.Overview.SentMsgCnt - lastSentMsgCnt) / dashboard.AtmI64(tm) 52 | rps := (dashboard.Overview.RecvMsgCnt - lastRecvMsgCnt) / dashboard.AtmI64(tm) 53 | 54 | sbps := (dashboard.Overview.SentBytes - lastSentBytes) / dashboard.AtmI64(tm) 55 | rbps := (dashboard.Overview.RecvBytes - lastRecvBytes) / dashboard.AtmI64(tm) 56 | 57 | perCnt++ 58 | if sps > 0 { 59 | spsTotall += sps 60 | dashboard.Overview.SentMsgPerSeconds.Set(int64(spsTotall / perCnt)) 61 | } 62 | if rps > 0 { 63 | rpsTotall += rps 64 | dashboard.Overview.RecvMsgPerSeconds.Set(int64(rpsTotall / perCnt)) 65 | } 66 | if sbps > 0 { 67 | sbpsTotall += sbps 68 | dashboard.Overview.SentBytesPerSeconds.Set(int64(rbpsTotall / perCnt)) 69 | } 70 | if rbps > 0 { 71 | rbpsTotall += rbps 72 | dashboard.Overview.RecvBytesPerSeconds.Set(int64(rbpsTotall / perCnt)) 73 | } 74 | lastPerTime = time.Now() 75 | lastSentMsgCnt = dashboard.Overview.SentMsgCnt 76 | lastRecvMsgCnt = dashboard.Overview.RecvMsgCnt 77 | 78 | lastSentBytes = dashboard.Overview.SentBytes 79 | lastRecvBytes = dashboard.Overview.RecvBytes 80 | 81 | _secondsTimer.Reset(duration) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /dashboard/dashboard_test.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | ) 10 | 11 | func init() { 12 | log.SetLevel(log.DebugLevel) 13 | } 14 | func TestAtmAdd(t *testing.T) { 15 | var a AtmI64 16 | a.Add(1) 17 | a.Add(1) 18 | if a != 2 { 19 | t.Fatalf("RecvMsgCnt != 2,RecvMsgCnt=%d", a) 20 | } 21 | } 22 | func TestAtmI642AtmType(t *testing.T) { 23 | var ati AtmType = &Overview.ActiveClients 24 | 25 | t.Log(ati.Get()) 26 | } 27 | func TestCopy(t *testing.T) { 28 | Overview.RunTimeString.Set("hello") 29 | Overview.ActiveClients.Set(int64(100)) 30 | ov := Overview.Copy() 31 | t.Logf("ActiveClients :%#v", ov.ActiveClients.Get()) 32 | if ov.ActiveClients.Get().(int64) != 100 { 33 | t.FailNow() 34 | } 35 | if ov.RunTimeString.Get().(string) != "hello" { 36 | t.Fatalf(`RunTimeString != "hello"`) 37 | } 38 | } 39 | 40 | func TestOverviewToJson(t *testing.T) { 41 | Overview.RecvBytesPerSeconds.Set(int64(1000)) 42 | Overview.RunTimeString.Set("test time") 43 | bsjson, err := json.MarshalIndent(&Overview, "", "\t") 44 | if err != nil { 45 | t.Fatal("overview data json.MarshalIndent error:", err) 46 | t.FailNow() 47 | } 48 | var ov OverviewData 49 | err = json.Unmarshal(bsjson, &ov) 50 | if err != nil { 51 | t.Fatal("overview data json.Unmarshal error:", err) 52 | t.FailNow() 53 | } 54 | if ov.RunTimeString.Get().(string) != "test time" { 55 | t.Fatal(`RunTimeString != "test time"`) 56 | t.FailNow() 57 | } 58 | if ov.RecvBytesPerSeconds.Get().(int64) != int64(1000) { 59 | t.Fatal(`RecvBytesPerSeconds != 1000:`, ov.RecvBytesPerSeconds) 60 | t.FailNow() 61 | } 62 | 63 | w := bytes.NewBuffer(nil) 64 | encoder := json.NewEncoder(w) 65 | encoder.SetIndent("", "\t") 66 | encoder.Encode(&Overview) 67 | 68 | if err != nil { 69 | t.Fatal("overview data encoder.Encode error:", err) 70 | t.FailNow() 71 | } 72 | r := bytes.NewReader(w.Bytes()) 73 | decoder := json.NewDecoder(r) 74 | var ov2 OverviewData 75 | err = decoder.Decode(&ov2) 76 | if err != nil { 77 | t.Fatal("overview data decoder.Decode error:", err) 78 | t.FailNow() 79 | } 80 | if ov2.RecvBytesPerSeconds.Get().(int64) != int64(1000) { 81 | t.Fatal(`RecvBytesPerSeconds != 1000:`, ov.RecvBytesPerSeconds) 82 | t.FailNow() 83 | } 84 | if ov2.RunTimeString.Get().(string) != "test time" { 85 | t.Fatalf(`ov2.RunTimeString != "test time":%#v`, ov2) 86 | t.FailNow() 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /dashboard/dashbord.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | type AtmType interface { 10 | Set(v interface{}) 11 | Get() interface{} 12 | } 13 | 14 | //线程安全64位整数 15 | type AtmI64 int64 16 | 17 | func (o *AtmI64) Add(v int64) { 18 | atomic.AddInt64((*int64)(o), v) 19 | } 20 | func (o *AtmI64) Set(v interface{}) { 21 | atomic.StoreInt64((*int64)(o), v.(int64)) 22 | } 23 | 24 | func (o *AtmI64) Get() (v interface{}) { 25 | return atomic.LoadInt64((*int64)(o)) 26 | } 27 | 28 | type AtmString struct { 29 | v string 30 | mtx sync.Mutex 31 | } 32 | 33 | func (o *AtmString) String() string { 34 | return o.Get().(string) 35 | } 36 | func (o *AtmString) Add(v interface{}) { 37 | } 38 | func (o *AtmString) Set(v interface{}) { 39 | o.mtx.Lock() 40 | o.v = v.(string) 41 | o.mtx.Unlock() 42 | } 43 | func (o *AtmString) Get() (v interface{}) { 44 | o.mtx.Lock() 45 | v = o.v 46 | o.mtx.Unlock() 47 | return 48 | } 49 | func (o *AtmString) MarshalJSON() ([]byte, error) { 50 | v := o.Get() 51 | return []byte(fmt.Sprintf(`"%s"`, v.(string))), nil 52 | } 53 | func (o *AtmString) UnmarshalJSON(v []byte) (err error) { 54 | var s string 55 | 56 | l := len(v) 57 | if l > 1 { 58 | s = string(v[1 : l-1]) 59 | } 60 | o.Set(s) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /dashboard/var.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/iotalking/mqtt-broker/utils" 7 | ) 8 | 9 | type OverviewData struct { 10 | //总运行时间(纳秒) 11 | RunNanoSeconds AtmI64 12 | RunTimeString AtmString 13 | 14 | //服务器的唯一标识 15 | BrokerId AtmString 16 | //服务器的网络地址 17 | BrokerAddress AtmString 18 | 19 | //总接入消息数 20 | RecvMsgCnt AtmI64 21 | //总发送消息数 22 | SentMsgCnt AtmI64 23 | 24 | //总接入字节 25 | RecvBytes AtmI64 26 | 27 | //总发送字节 28 | SentBytes AtmI64 29 | 30 | //每秒接入消息数 31 | RecvBytesPerSeconds AtmI64 32 | //每秒发送消息数 33 | SentBytesPerSeconds AtmI64 34 | //每秒接入消息数 35 | RecvMsgPerSeconds AtmI64 36 | //每秒发送消息数 37 | SentMsgPerSeconds AtmI64 38 | 39 | //在线客户端 40 | ActiveClients AtmI64 41 | 42 | //未验证的客户端数 43 | InactiveClients AtmI64 44 | 45 | //最大在线客户端数 46 | MaxActiveClinets AtmI64 47 | 48 | //总主题数 49 | TipicCnt AtmI64 50 | 51 | //总订阅数 52 | Subscritions AtmI64 53 | 54 | //保存的总消息数 55 | RetainedMsgCnt AtmI64 56 | 57 | //未发送消息数 58 | //由于session端的发达缓冲满而丢的包 59 | DropMsgCnt AtmI64 60 | 61 | //未处理消息数 62 | //从网络接入到,但由于接入缓冲满而求能处理的包数 63 | UnrecvMsgCnt AtmI64 64 | 65 | //消息缓冲峰值 66 | PeekPublishBufferCnt AtmI64 67 | 68 | //消息缓冲当前值 69 | CurPUblishBufferCnt AtmI64 70 | 71 | //发送超时的客户端数 72 | SentErrClientCnt AtmI64 73 | 74 | //ticker map中的ticker总数 75 | TickerMapCnt AtmI64 76 | //添加的ticker总数 77 | AddTickerCnt AtmI64 78 | 79 | //打开的文件句柄数 80 | OpenedFiles AtmI64 81 | //打开文件峰值 82 | MaxOpenedFiles AtmI64 83 | 84 | //正在关闭的文件句柄数 85 | ClosingFiles AtmI64 86 | 87 | //各类消息数 88 | ConnectSentCnt AtmI64 89 | ConnackSentCnt AtmI64 90 | 91 | PublishSentCnt AtmI64 92 | PubackRecvCnt AtmI64 93 | 94 | PubrecRecvCnt AtmI64 95 | PubrelSentCnt AtmI64 96 | PubcompRecvCnt AtmI64 97 | 98 | SubscribeSentCnt AtmI64 99 | SubackSentCnt AtmI64 100 | UnsubscribeSentCnt AtmI64 101 | UnsubackSentCnt AtmI64 102 | PingreqSentCnt AtmI64 103 | PingrespSentCnt AtmI64 104 | DisconectSentCnt AtmI64 105 | 106 | ConnectRecvCnt AtmI64 107 | ConnackRecvCnt AtmI64 108 | 109 | PublishRecvCnt AtmI64 110 | PubackSentCnt AtmI64 111 | 112 | PubrecSentCnt AtmI64 113 | PubrelRecvCnt AtmI64 114 | PubcompSentCnt AtmI64 115 | 116 | SubscribeRecvCnt AtmI64 117 | SubackRecvCnt AtmI64 118 | UnsubscribeRecvCnt AtmI64 119 | UnsubackRecvCnt AtmI64 120 | PingreqRecvCnt AtmI64 121 | PingrespRecvCnt AtmI64 122 | DisconectRecvCnt AtmI64 123 | 124 | //Ticker最后一个耗时(ns) 125 | LastTickerBusyTime AtmI64 126 | //定局定时器轮询最大耗时(ns) 127 | MaxTickerBusyTime AtmI64 128 | } 129 | 130 | var Overview = &OverviewData{} 131 | 132 | func init() { 133 | Overview.BrokerId.Set(utils.LocalId()) 134 | } 135 | func (this *OverviewData) Copy() *OverviewData { 136 | var ov OverviewData 137 | 138 | _fromv := reflect.ValueOf(this) 139 | _tov := reflect.ValueOf(&ov) 140 | _t := _fromv.Type().Elem() 141 | fnum := _t.NumField() 142 | for i := 0; i < fnum; i++ { 143 | if tv, ok := _tov.Elem().Field(i).Addr().Interface().(AtmType); ok { 144 | tv.Set(_fromv.Elem().Field(i).Addr().Interface().(AtmType).Get()) 145 | } 146 | 147 | } 148 | return &ov 149 | } 150 | -------------------------------------------------------------------------------- /dashboard/www/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/dashboard/www/index.html -------------------------------------------------------------------------------- /mac.sh: -------------------------------------------------------------------------------- 1 | sudo sysctl -w kern.maxfiles=1048600 2 | sudo sysctl -w kern.maxfilesperproc=1048500 3 | ulimit -n 204800 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | pprof "runtime/pprof" 10 | "runtime/trace" 11 | "sync" 12 | 13 | log "github.com/Sirupsen/logrus" 14 | 15 | "github.com/iotalking/mqtt-broker/config" 16 | "github.com/iotalking/mqtt-broker/network/tcp" 17 | "github.com/iotalking/mqtt-broker/network/websocket" 18 | "github.com/iotalking/mqtt-broker/restful_api" 19 | "github.com/iotalking/mqtt-broker/session" 20 | "github.com/iotalking/mqtt-broker/www" 21 | 22 | "net/http" 23 | _ "net/http/pprof" 24 | 25 | _ "github.com/iotalking/mqtt-broker/store/mem-provider" 26 | ) 27 | 28 | type timeFormater struct { 29 | log.TextFormatter 30 | } 31 | 32 | func (this timeFormater) Format(e *log.Entry) ([]byte, error) { 33 | bs, err := this.TextFormatter.Format(e) 34 | bs = []byte(fmt.Sprintf("%d: %s", e.Time.UnixNano(), bs)) 35 | return bs, err 36 | } 37 | 38 | func init() { 39 | // os.OpenFile("./log.txt", os.O_CREATE|os.O_WRONLY, 666) 40 | log.SetOutput(os.Stdout) 41 | // log.SetLevel(log.WarnLevel) 42 | log.SetLevel(log.DebugLevel) 43 | log.SetFormatter(timeFormater{}) 44 | } 45 | 46 | var listenNewChan = make(chan net.Listener) 47 | 48 | var basePort = 1883 49 | 50 | var sessionMgr = session.GetMgr() 51 | var level = flag.String("log", "debug", "log level string") 52 | var traceFileName = flag.String("trace", "", "out trace to file") 53 | var cpuprofile = flag.String("cpuprofile", "", "CPU Profile Filename") 54 | 55 | func main() { 56 | flag.Parse() 57 | 58 | if *cpuprofile != "" { 59 | f, err := os.Create(*cpuprofile) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | pprof.StartCPUProfile(f) 65 | defer func() { 66 | pprof.StopCPUProfile() 67 | f.Close() 68 | }() 69 | } 70 | 71 | if len(*traceFileName) > 0 { 72 | f, err := os.OpenFile(*traceFileName, os.O_CREATE|os.O_WRONLY, 0666) 73 | if err != nil { 74 | panic(err) 75 | } 76 | trace.Start(f) 77 | defer trace.Stop() 78 | } 79 | 80 | loglevel, err := log.ParseLevel(*level) 81 | if err != nil { 82 | panic(err) 83 | } 84 | log.SetLevel(loglevel) 85 | 86 | log.Debugln("server starting") 87 | var wg sync.WaitGroup 88 | 89 | wg.Add(1) 90 | go func() { 91 | wg.Done() 92 | log.Debug("starting tcp server...") 93 | tcp.Start(fmt.Sprintf(":%d", config.TcpPort)) 94 | }() 95 | wg.Add(1) 96 | go func() { 97 | wg.Done() 98 | log.Debug("starting tcp tls server") 99 | tcp.StartTLS(fmt.Sprintf(":%d", config.TcpTLSPort), config.CertFileName, config.KeyFileName) 100 | }() 101 | wg.Add(1) 102 | 103 | wg.Add(1) 104 | go func() { 105 | wg.Done() 106 | log.Debug("starting websocket server...") 107 | websocket.Start(fmt.Sprintf(":%d", config.WSPort)) 108 | }() 109 | 110 | wg.Add(1) 111 | go func() { 112 | wg.Done() 113 | log.Debug("starting websocket tls server...") 114 | websocket.StartTLS(fmt.Sprintf(":%d", config.WSSPort), config.CertFileName, config.KeyFileName) 115 | }() 116 | 117 | go func() { 118 | wg.Done() 119 | log.Debug("starting restful api server...") 120 | api.Start(fmt.Sprintf(":%d", config.RestfulPort), sessionMgr) 121 | }() 122 | 123 | wg.Add(1) 124 | go func() { 125 | wg.Done() 126 | log.Debug("starting debug pprof server...") 127 | err = http.ListenAndServe(fmt.Sprintf(":%d", config.PprofPort), nil) 128 | if err != nil { 129 | panic(err) 130 | } 131 | }() 132 | 133 | wg.Add(1) 134 | go func() { 135 | wg.Done() 136 | log.Debugf("starting www server on:%d ...", config.WebPort) 137 | err = www.Start(fmt.Sprintf(":%d", config.WebPort)) 138 | if err != nil { 139 | panic(err) 140 | } 141 | }() 142 | 143 | wg.Wait() 144 | log.Debugln("server running") 145 | // Trap SIGINT to trigger a shutdown. 146 | signals := make(chan os.Signal, 1) 147 | 148 | signal.Notify(signals, os.Interrupt) 149 | 150 | select { 151 | case <-signals: 152 | //关闭服务器的端口监听,以退出 153 | tcp.Stop() 154 | tcp.StopTLS() 155 | } 156 | 157 | log.Debugf("server stoped:%#v", sessionMgr) 158 | } 159 | -------------------------------------------------------------------------------- /mqttdebug.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkDCCAngCCQCSROgjWmOJ7zANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMC 3 | Q04xEjAQBgNVBAgTCWd1YW5nZG9uZzERMA8GA1UEBxMIc2hlbnpoZW4xEjAQBgNV 4 | BAoTCWlvdGFsa2luZzELMAkGA1UECxMCaXQxEjAQBgNVBAMTCWlvdGFsa2luZzEe 5 | MBwGCSqGSIb3DQEJARYPZnVubnl3d2hAcXEuY29tMB4XDTE3MDUxMTAyMjcyN1oX 6 | DTE4MDUxMTAyMjcyN1owgYkxCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlndWFuZ2Rv 7 | bmcxETAPBgNVBAcTCHNoZW56aGVuMRIwEAYDVQQKEwlpb3RhbGtpbmcxCzAJBgNV 8 | BAsTAml0MRIwEAYDVQQDEwlpb3RhbGtpbmcxHjAcBgkqhkiG9w0BCQEWD2Z1bm55 9 | d3doQHFxLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbWdfGk 10 | htecOH3MhaohfdVeORcOIzyPLl8vjyqxeXmLcswzgieSb92qvdm5hAjsklYeVTpC 11 | YoUwF4+EdJen9kpydoJeOiQHitJj1WXOl+XjGQuCwC3MVRykBGSK9eicOxA4mr4q 12 | pDBvsGzeSSkwkWCSkDypwajSB/WYph7Qc8k6mCfXl7qs3mPifYmrQzuoXMV+aHmr 13 | JGAq5sM89H5BGu9cCGpLX/qtoGiib5wsZ4bqoTUodn0N9Ypsu/7Adz8qvTNg2Wq4 14 | IK+R+M3JDfTddIUneKSxWd59RjYYmp5LaAUCNFrSnFC68gBTpHcfrbsnXON5GTb2 15 | WAwsOMjLC51p2SECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAWjooiBhQp2ujD06s 16 | u4jC3Ks/vninendEs9pZgy57p+Ir8gORI0kgC9wkGC6ET85Up7FoMSfJh+OTWr4P 17 | 3iZQ4u9a0WLIB19GZe2JTUa7c0lmroycW2xYQhu0z4EMOA3XnCP1GanaA19eZwjF 18 | PIrOPqzE7ZyXAjQNQaFNeTNU4E79Pdg2qaOTgffT6A4qNLQGBL5bYwPv/rYbbh+f 19 | gH7qzYTMtQHLeVAMZk0qg+x/s6u5gqCzVqIFO0Hemd0Nihv8b0KehDWMl2tSzY1J 20 | 3F8HqVIBIvKUO9+UCeVZOAPoaKD3C2/In9ydYWtL2en/nGW7eEeBZS9/oeTF/AH5 21 | XBUlaQ== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /mqttdebug.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICzzCCAbcCAQAwgYkxCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlndWFuZ2Rvbmcx 3 | ETAPBgNVBAcTCHNoZW56aGVuMRIwEAYDVQQKEwlpb3RhbGtpbmcxCzAJBgNVBAsT 4 | Aml0MRIwEAYDVQQDEwlpb3RhbGtpbmcxHjAcBgkqhkiG9w0BCQEWD2Z1bm55d3do 5 | QHFxLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbWdfGkhtec 6 | OH3MhaohfdVeORcOIzyPLl8vjyqxeXmLcswzgieSb92qvdm5hAjsklYeVTpCYoUw 7 | F4+EdJen9kpydoJeOiQHitJj1WXOl+XjGQuCwC3MVRykBGSK9eicOxA4mr4qpDBv 8 | sGzeSSkwkWCSkDypwajSB/WYph7Qc8k6mCfXl7qs3mPifYmrQzuoXMV+aHmrJGAq 9 | 5sM89H5BGu9cCGpLX/qtoGiib5wsZ4bqoTUodn0N9Ypsu/7Adz8qvTNg2Wq4IK+R 10 | +M3JDfTddIUneKSxWd59RjYYmp5LaAUCNFrSnFC68gBTpHcfrbsnXON5GTb2WAws 11 | OMjLC51p2SECAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQASOB9ms2ZFNBJkgbvU 12 | 7cUzMUph2yDTyqnGCvsyVJpoeCLiPOmcbZ/+8yLUMZfZJZ25MVJLc5TD0IQfFg9W 13 | IQoBhJuaAypsPCjEdrnw5sPV16vtfodLrFBqJJczQcyPqf/cEbdZ7d5KHd4uulRF 14 | +M0TvhhGYogDg8mZcjKrQHPiH+9zdO7rgYGNXegpITO0YGTeYanH6tRq1TeEq1+k 15 | vEKXHujt9Ah5+D+7kkxNRRRmvL40pnKpCHeZy9oTnpDD+9MSFKwznRI5lTqvWiSB 16 | RVRVsdHncHLbi8IWNk1eGZzILXFMskkqOy0Vn5y/0XNVD3v0lO3/OTJFq1Knjm3M 17 | eZjd 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /mqttdebug.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA1tZ18aSG15w4fcyFqiF91V45Fw4jPI8uXy+PKrF5eYtyzDOC 3 | J5Jv3aq92bmECOySVh5VOkJihTAXj4R0l6f2SnJ2gl46JAeK0mPVZc6X5eMZC4LA 4 | LcxVHKQEZIr16Jw7EDiaviqkMG+wbN5JKTCRYJKQPKnBqNIH9ZimHtBzyTqYJ9eX 5 | uqzeY+J9iatDO6hcxX5oeaskYCrmwzz0fkEa71wIaktf+q2gaKJvnCxnhuqhNSh2 6 | fQ31imy7/sB3Pyq9M2DZarggr5H4zckN9N10hSd4pLFZ3n1GNhianktoBQI0WtKc 7 | ULryAFOkdx+tuydc43kZNvZYDCw4yMsLnWnZIQIDAQABAoIBAA7PfhYIh5s5k9No 8 | VBakdG5PLpReTyOPo1zAhJgm3/73FBTrwB9GQxv+91OZLHcJCBWTxcBcbzpLVHR6 9 | ZydFVv9O4MigAlYVhwZ2B3IbSfF5NHLMsr71hTCSRhNAWS+OBaxamqBdhDAqovj+ 10 | YgDK7lq//92OvmELCa1utpUNPc4zcX6qljwZ4xxsfX2Mr++Mlxu/tBKU++Q10GAC 11 | bQnkBVp5sGd80bOLOK5o5QdiU8W3gMlGbudqeHVj7OEEaf7PPBINK8R6lmkEx3sR 12 | ZG+SYZs+S4Y21OV6ElhmhAbXZmuLkS2SBTwnWGFIN+3DuObep8dJs3XrnvV4pUkt 13 | WBnV9hkCgYEA+kEVLZmCekFKZdQgrrb/0IXKCq68XXa+JsuV/TdJh5Z/uzQDGUPw 14 | uRxOkNOF5mu6nDuGlrZA0SroJ/aku3iNXy434HxF26CPMCKQnSGtk5XCVorlkMUj 15 | 5eWE+brzD1kmcM84AvGvWwrlAcv2J3Dvxxv53L/97Q2uQZ+6Bm0bRm8CgYEA28U1 16 | 9FiwC5xwxhz/G1xl/TbRuj/S31J+SDGEAKpK6XISpcPeXSGp9y86LWpJoESXkcsZ 17 | peBVtIec7YEtztu8mE80M6dCK3g4z7Qfppl1ZKOzK5dW4pRdWkgkUdLyDmPl7A0Q 18 | 3T4l9sux4a2P0Z39L61ZOuZOm6loZBNFwc6eIW8CgYBvu9FF281q4m3iSzUQbPqI 19 | 0sNjw6KspFDAJ5PfS+kTlbmQDuf3RuNa8u2NMjQNrjnSea1c6yDDg0HGXhQs8+VD 20 | AvLF0xapueVUm4ov7hJ+25W6aCeZXLvrcG/PxI2zY52LLqoWfmxJ3PJku/k6oO0d 21 | 3DrSOhKY3cvY1wUKy3orFwKBgCYhmpHxzWHGW/7TyyOJLZz4pP0G0+SxoH2QMdIX 22 | ufW1OC0QxgUssRvVUufMWEf5fr7qGWIhgV1YZk8DKdqJX7ihjXIk5CM8sca//vne 23 | CHNUv+KhWI7ppSTj/YFA84SxqpQBVFq+zMF8aDm67og1PFfIYQIKMbtqclVizqio 24 | 2+D7AoGBAJPkBGRLqjxw1aGKfyzGZ9eLEledamA2prDUySyx6rWSZ3HxBJLJ41sF 25 | CyhdFD7UzoE/MUzjUvEeWAWSi3IVs7ex+UOXmAT5OZiQK6Vmy7BJ7VLS3wA1+K+x 26 | g+m0w3JM0tdApvt7xCRG8jfojzVqimPEvhINvyfC8NRHUY+F1BLT 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /mqttdebug.txt: -------------------------------------------------------------------------------- 1 | password:mqttdebug 2 | -------------------------------------------------------------------------------- /network/tcp/tcp.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "net" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | "github.com/iotalking/mqtt-broker/dashboard" 8 | "github.com/iotalking/mqtt-broker/session" 9 | ) 10 | 11 | var listener net.Listener 12 | 13 | func Start(addr string) { 14 | var err error 15 | listener, err = net.Listen("tcp", addr) 16 | if err != nil { 17 | panic(err) 18 | } 19 | if len(dashboard.Overview.BrokerAddress.String()) == 0 { 20 | dashboard.Overview.BrokerAddress.Set(listener.Addr().String()) 21 | log.Info("brokerAddress:", dashboard.Overview.BrokerAddress) 22 | } 23 | var sessionMgr = session.GetMgr() 24 | for { 25 | c, err := listener.Accept() 26 | 27 | if err != nil { 28 | log.Error("server accept error.", err) 29 | break 30 | } else { 31 | if c == nil { 32 | panic("tcp accept a nil conn") 33 | } 34 | if dashboard.Overview.OpenedFiles > dashboard.Overview.MaxOpenedFiles { 35 | dashboard.Overview.MaxOpenedFiles.Set(int64(dashboard.Overview.OpenedFiles)) 36 | } 37 | sessionMgr.HandleConnection(session.NewSession(sessionMgr, c, true)) 38 | } 39 | 40 | } 41 | return 42 | } 43 | 44 | func Stop() { 45 | listener.Close() 46 | } 47 | -------------------------------------------------------------------------------- /network/tcp/tls.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | "github.com/iotalking/mqtt-broker/dashboard" 9 | "github.com/iotalking/mqtt-broker/session" 10 | ) 11 | 12 | var tlsListener net.Listener 13 | 14 | func StartTLS(addr string, certFile, keyFile string) { 15 | var config tls.Config 16 | var err error 17 | config.Certificates = make([]tls.Certificate, 1) 18 | config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 19 | if err != nil { 20 | panic(err) 21 | } 22 | l, err := net.Listen("tcp", addr) 23 | if err != nil { 24 | panic(err) 25 | } 26 | tlsListener = tls.NewListener(l, &config) 27 | 28 | log.Infof("tcp tls started on :%s", addr) 29 | var sessionMgr = session.GetMgr() 30 | for { 31 | c, err := tlsListener.Accept() 32 | 33 | if err != nil { 34 | log.Error("server accept error.", err) 35 | break 36 | } else { 37 | if c == nil { 38 | panic("tls accept nil conn") 39 | } 40 | log.Error("tls accept a conn") 41 | if dashboard.Overview.OpenedFiles > dashboard.Overview.MaxOpenedFiles { 42 | dashboard.Overview.MaxOpenedFiles.Set(int64(dashboard.Overview.OpenedFiles)) 43 | } 44 | sessionMgr.HandleConnection(session.NewSession(sessionMgr, c, true)) 45 | } 46 | 47 | } 48 | return 49 | } 50 | 51 | func StopTLS() { 52 | if tlsListener == nil { 53 | panic(nil) 54 | } 55 | tlsListener.Close() 56 | tlsListener = nil 57 | } 58 | -------------------------------------------------------------------------------- /network/websocket/tls.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "net/http" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | ) 8 | 9 | func StartTLS(addr string, certFile, keyFile string) { 10 | log.Infof("starting websocket tls on :%s", addr) 11 | mux.HandleFunc("/mqtts", serverWS) 12 | s := http.Server{} 13 | s.Addr = addr 14 | s.Handler = mux 15 | err := s.ListenAndServeTLS(certFile, keyFile) 16 | if err != nil { 17 | panic(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /network/websocket/websocket.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/gorilla/websocket" 8 | 9 | "github.com/iotalking/mqtt-broker/session" 10 | 11 | log "github.com/Sirupsen/logrus" 12 | ) 13 | 14 | var mux = http.NewServeMux() 15 | var upgrader = websocket.Upgrader{ 16 | ReadBufferSize: 1024, 17 | WriteBufferSize: 1024, 18 | CheckOrigin: func(r *http.Request) bool { 19 | return true 20 | }, 21 | Subprotocols: []string{"mqtt", "mqttv3.1"}, 22 | } 23 | 24 | func Start(addr string) { 25 | log.Infof("starting websocket on :%s", addr) 26 | mux.HandleFunc("/mqtt", serverWS) 27 | s := http.Server{} 28 | s.Addr = addr 29 | s.Handler = mux 30 | err := s.ListenAndServe() 31 | if err != nil { 32 | panic(err) 33 | } 34 | } 35 | 36 | type readData struct { 37 | buf []byte 38 | n int 39 | err error 40 | } 41 | type writeData struct { 42 | buf []byte 43 | n int 44 | err error 45 | } 46 | type wsReadWriteCloser struct { 47 | reader io.Reader 48 | conn *websocket.Conn 49 | } 50 | 51 | func (this *wsReadWriteCloser) Read(p []byte) (n int, err error) { 52 | 53 | next: 54 | if this.reader == nil { 55 | _, this.reader, err = this.conn.NextReader() 56 | if err != nil { 57 | return 58 | } 59 | } 60 | n, err = this.reader.Read(p) 61 | if err == io.EOF { 62 | this.reader = nil 63 | goto next 64 | } 65 | return 66 | } 67 | func (this *wsReadWriteCloser) Write(p []byte) (int, error) { 68 | writer, err := this.conn.NextWriter(websocket.BinaryMessage) 69 | if err != nil { 70 | return 0, err 71 | } 72 | defer writer.Close() 73 | return writer.Write(p) 74 | } 75 | func (this *wsReadWriteCloser) Close() error { 76 | err := this.conn.Close() 77 | return err 78 | } 79 | 80 | func serverWS(resp http.ResponseWriter, req *http.Request) { 81 | log.Debug("serverWS comming") 82 | resp.Header().Add("Access-Control-Allow-Origin", "*") 83 | swp := req.Header.Get("Sec-Websocket-Protocol") 84 | log.Debug("Sec-Websocket-Protocol:", swp) 85 | 86 | conn, err := upgrader.Upgrade(resp, req, nil) 87 | // _, err := upgrader.Upgrade(resp, req, nil) 88 | if err != nil { 89 | log.Errorf("serverWS error:%s", err) 90 | return 91 | } 92 | 93 | sessionMgr := session.GetMgr() 94 | rwc := wsReadWriteCloser{ 95 | conn: conn, 96 | } 97 | sessionMgr.HandleConnection(session.NewSession(sessionMgr, &rwc, true)) 98 | log.Debug("ws mqtt exited") 99 | } 100 | -------------------------------------------------------------------------------- /restful_api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/iotalking/mqtt-broker/session" 8 | ) 9 | 10 | func Pub(w http.ResponseWriter, r *http.Request) { 11 | var topic string 12 | var msg string 13 | var qos string 14 | var q byte 15 | err := r.ParseForm() 16 | if err != nil { 17 | w.WriteHeader(401) 18 | return 19 | } 20 | topic = r.Form.Get("topic") 21 | msg = r.Form.Get("msg") 22 | qos = r.Form.Get("qos") 23 | if len(qos) > 0 { 24 | fmt.Sscan(qos, &q) 25 | } 26 | err = session.GetMgr().Publish(topic, []byte(msg), q) 27 | if err != nil { 28 | w.WriteHeader(403) 29 | return 30 | } 31 | w.WriteHeader(200) 32 | } 33 | -------------------------------------------------------------------------------- /restful_api/router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/iotalking/mqtt-broker/config" 7 | dashboardapi "github.com/iotalking/mqtt-broker/dashboard/api" 8 | ) 9 | 10 | var mux = http.NewServeMux() 11 | 12 | func router() { 13 | mux.HandleFunc(config.DashboordApiUrl+"/overview", dashboardapi.GetOverviewData) 14 | mux.HandleFunc(config.DashboordApiUrl+"/log", dashboardapi.SetLogLevel) 15 | mux.HandleFunc(config.DashboordApiUrl+"/activeSessions", dashboardapi.GetSessions) 16 | mux.HandleFunc(config.RestfullApiUrl+"/pub", Pub) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /restful_api/run.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | "github.com/iotalking/mqtt-broker/dashboard/api" 8 | "github.com/iotalking/mqtt-broker/session" 9 | ) 10 | 11 | func Start(addr string, mgr session.SessionMgr) { 12 | s := http.Server{} 13 | s.Addr = addr 14 | s.Handler = mux 15 | router() 16 | go func() { 17 | api.Start(mgr) 18 | }() 19 | log.Debugf("restfull http started on :%s", addr) 20 | err := s.ListenAndServe() 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /safe-runtine/safe-runtine.go: -------------------------------------------------------------------------------- 1 | package runtine 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | ) 9 | 10 | type SafeRuntine struct { 11 | startedWG sync.WaitGroup 12 | 13 | stopingChan chan bool 14 | 15 | //SafeRuntine的运行函数里可以通过接收IsInterrupt chan来判断是否要安全退出 16 | //调用都可以调用Stop便可以安全退出 17 | //例如: 18 | //runtine.Go(func(r *runtine.SafeRuntine){ 19 | // select{ 20 | // case <-r.IsInterrupt: 21 | // } 22 | //) 23 | // 24 | IsInterrupt chan bool 25 | 26 | //stoped == 0 表示runtine没有运行 27 | stoped int32 28 | } 29 | 30 | //同步启动runtine,直到runtine被执行 31 | func (o *SafeRuntine) Go(fn func(args ...interface{}), args ...interface{}) { 32 | o.startedWG.Add(1) 33 | o.IsInterrupt = make(chan bool) 34 | o.stopingChan = make(chan bool) 35 | go func(args ...interface{}) { 36 | atomic.AddInt32(&o.stoped, 1) 37 | o.startedWG.Done() 38 | 39 | fn(args...) 40 | 41 | close(o.stopingChan) 42 | 43 | atomic.StoreInt32(&o.stoped, 0) 44 | }(args...) 45 | 46 | //直到runtine执行 47 | o.startedWG.Wait() 48 | 49 | return 50 | } 51 | 52 | func (this *SafeRuntine) Stop() { 53 | 54 | if atomic.LoadInt32(&this.stoped) == 0 { 55 | log.Println("runtine has exit") 56 | return 57 | } 58 | atomic.StoreInt32(&this.stoped, 0) 59 | 60 | log.Debugln("runtine want to stop") 61 | close(this.IsInterrupt) 62 | 63 | <-this.stopingChan 64 | 65 | log.Debugln("runtine want to stoped") 66 | return 67 | } 68 | 69 | func (this *SafeRuntine) IsStoped() bool { 70 | return atomic.LoadInt32(&this.stoped) == 0 71 | } 72 | -------------------------------------------------------------------------------- /session/channel.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync/atomic" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "github.com/eclipse/paho.mqtt.golang/packets" 10 | "github.com/iotalking/mqtt-broker/dashboard" 11 | "github.com/iotalking/mqtt-broker/safe-runtine" 12 | "github.com/iotalking/mqtt-broker/utils" 13 | ) 14 | 15 | type SentChan chan *SendData 16 | 17 | type SendData struct { 18 | Msg packets.ControlPacket 19 | 20 | Result chan<- error 21 | } 22 | 23 | type Channel struct { 24 | //>0为已经退出 25 | isStoped int32 26 | //下层数据通讯接口 27 | conn io.ReadWriteCloser 28 | 29 | //内部发送的消息队列,所有还在流程中的消息 30 | 31 | iSendList *utils.List 32 | //写入从net.Conn里接入到的消息 33 | session *Session 34 | recvRuntine runtine.SafeRuntine 35 | sendRuntine runtine.SafeRuntine 36 | 37 | //取后通讯的时间戳,即最后发包,收包时间 38 | lastStamp int64 39 | } 40 | 41 | //New 创建通道 42 | //通过网络层接口进行数据通讯 43 | func NewChannel(c io.ReadWriteCloser, session *Session) *Channel { 44 | if c == nil { 45 | panic("NewChannel c == nil") 46 | } 47 | var channel = &Channel{ 48 | conn: c, 49 | session: session, 50 | iSendList: utils.NewList(), 51 | } 52 | session.channel = channel 53 | 54 | return channel 55 | } 56 | func (this *Channel) Start() { 57 | this.recvRuntine.Go(func(args ...interface{}) { 58 | log.Debug("channel recv running") 59 | this.recvRun() 60 | }) 61 | this.sendRuntine.Go(func(args ...interface{}) { 62 | log.Debug("channel send running") 63 | this.sendRun() 64 | }) 65 | } 66 | func (this *Channel) Read(p []byte) (n int, err error) { 67 | n, err = this.conn.Read(p) 68 | dashboard.Overview.RecvBytes.Add(int64(n)) 69 | return 70 | } 71 | func (this *Channel) Write(p []byte) (n int, err error) { 72 | n, err = this.conn.Write(p) 73 | dashboard.Overview.SentBytes.Add(int64(n)) 74 | return 75 | } 76 | 77 | //从底层接入消息循环 78 | //从net.Conn里流式解包消息 79 | func (this *Channel) recvRun() { 80 | for { 81 | msg, err := packets.ReadPacket(this) 82 | 83 | //回调session,可用于检查有没有connect连时,或者ping超时 84 | this.session.onChannelReaded(msg, err) 85 | if err == nil { 86 | this.session.RecvMsg(msg) 87 | 88 | log.Debug("channel recv a msg:", msg.String()) 89 | } else { 90 | this.session.OnChannelError(err) 91 | log.Error("recvRun error:", err) 92 | break 93 | } 94 | 95 | } 96 | log.Debug("channel recvRun exited") 97 | } 98 | 99 | func (this *Channel) sendRun() { 100 | 101 | defer func() { 102 | log.Debug("Channel.sendRun exited") 103 | atomic.StoreInt32(&this.isStoped, 1) 104 | }() 105 | for !this.sendRuntine.IsStoped() { 106 | select { 107 | case <-this.sendRuntine.IsInterrupt: 108 | case <-this.iSendList.Wait(): 109 | for !this.sendRuntine.IsStoped() { 110 | v := this.iSendList.Pop() 111 | if v == nil { 112 | break 113 | } 114 | err := this.writeMsg(v.(packets.ControlPacket)) 115 | if err != nil { 116 | log.Errorf("channel.writeMsg error:%s,msg:%#v", err, v) 117 | return 118 | } 119 | } 120 | // this.session.checkInflightList() 121 | } 122 | } 123 | 124 | } 125 | func (this *Channel) writeMsg(msg packets.ControlPacket) (err error) { 126 | log.Debug("Channel.Write :", msg.String()) 127 | //处理上层的消息 128 | err = msg.Write(this) 129 | //回调session,用于检查有没有要重发的消息,ping超时等 130 | this.session.onChannelWrited(msg, err) 131 | //如果写失败,则退出runtine 132 | if err != nil { 133 | if neterr, ok := err.(net.Error); ok && neterr.Timeout() { 134 | log.Debug("channel write timeout") 135 | return 136 | } 137 | log.Error("sendRun write msg to conn error", err) 138 | dashboard.Overview.SentErrClientCnt.Add(1) 139 | return 140 | } 141 | 142 | return 143 | } 144 | 145 | //Send 将消息写入发送channel 146 | //如果channel的buffer满后,会阻塞 147 | func (this *Channel) Send(msg packets.ControlPacket) (err error) { 148 | if this.IsStop() { 149 | log.Debug("channel is closed") 150 | return 151 | } 152 | this.iSendList.Push(msg) 153 | return 154 | } 155 | 156 | //安全退出 157 | func (this *Channel) Close() { 158 | if atomic.LoadInt32(&this.isStoped) > 0 { 159 | log.Debug("Channel has closed") 160 | return 161 | } 162 | //把下层的net.Conn关闭,让recvChn从net.Conn的接入中退出 163 | atomic.StoreInt32(&this.isStoped, 1) 164 | this.conn.Close() 165 | dashboard.Overview.OpenedFiles.Add(-1) 166 | this.sendRuntine.Stop() 167 | this.recvRuntine.Stop() 168 | } 169 | 170 | func (this *Channel) IsStop() bool { 171 | return atomic.LoadInt32(&this.isStoped) > 0 172 | } 173 | -------------------------------------------------------------------------------- /session/msg-handler.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "encoding/json" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/eclipse/paho.mqtt.golang/packets" 9 | 10 | log "github.com/Sirupsen/logrus" 11 | 12 | "github.com/iotalking/mqtt-broker/config" 13 | "github.com/iotalking/mqtt-broker/dashboard" 14 | "github.com/iotalking/mqtt-broker/store" 15 | ) 16 | 17 | //处理 CONNECT 消息 18 | //连接消息 19 | func (this *Session) onConnect(msg *packets.ConnectPacket) (err error) { 20 | //如果已经连接,则是违规,应该断开 21 | if this.IsConnected() { 22 | log.Error("connected client dup CONNECT") 23 | return packets.ConnErrors[packets.ErrProtocolViolation] 24 | } 25 | //判断保留标志位是否为0,如果不为0,返回协议错误 26 | if msg.ReservedBit&0x1 == 0x1 { 27 | log.Error("client ReservedBit != 0") 28 | return packets.ConnErrors[packets.ErrProtocolViolation] 29 | } 30 | //检查是否有相同clientId的连接,如果有,则要断开原有连接 31 | this.clientId = msg.ClientIdentifier 32 | this.mgr.DisconectSessionByClientId(this.clientId) 33 | 34 | conack := packets.NewControlPacket(packets.Connack).(*packets.ConnackPacket) 35 | conack.ReturnCode = packets.Accepted 36 | if msg.CleanSession { 37 | conack.SessionPresent = false 38 | } else { 39 | //如果服务器有保存session,则 SessionPresent设成true 40 | //如果服务器没有保存session,则SessionPresent设成false 41 | conack.SessionPresent = false 42 | } 43 | 44 | log.Debug("sessionMgr.OnConnected") 45 | this.mgr.OnConnected(this) 46 | 47 | log.Debug("session.Send") 48 | 49 | err = this.channel.Send(conack) 50 | atomic.StoreInt32(&this.connected, 1) 51 | 52 | if msg.WillFlag { 53 | this.willFlag = true 54 | this.willTopic = msg.WillTopic 55 | this.willQos = msg.WillQos 56 | this.willMessage = msg.WillMessage 57 | } 58 | 59 | //save keep alive (seconds) 60 | this.keepalive = int64(float64(msg.Keepalive) * 1.5 * float64(time.Second)) 61 | this.resetTimeout() 62 | return 63 | } 64 | 65 | //处理 CONNACK 消息 66 | //连接应答消息 67 | //此消息只有作为客户端时才会收到 68 | func (this *Session) onConnack(msg *packets.ConnackPacket) error { 69 | 70 | if msg.ReturnCode > 0 { 71 | log.Debug("server return conack code is:", msg.ReturnCode) 72 | atomic.StoreInt32(&this.connected, -int32(msg.ReturnCode)) 73 | } else { 74 | log.Debug("Session was connected") 75 | atomic.StoreInt32(&this.connected, 1) 76 | } 77 | this.mgr.OnConnected(this) 78 | if this.connectToken != nil { 79 | if msg.ReturnCode != packets.Accepted { 80 | //验证失败 81 | this.connectToken.err = packets.ConnErrors[msg.ReturnCode] 82 | } 83 | this.connectToken.flowComplete() 84 | } 85 | 86 | return nil 87 | } 88 | 89 | //由独立的协程调用,注意共享数据访问 90 | func (this *Session) BroadcastSessionInfo() { 91 | 92 | subMgr := this.mgr.GetSubscriptionMgr() 93 | infoTopic := config.SessionInfoTopic(this.clientId) 94 | sessionQosMap, err := subMgr.GetSessions(infoTopic) 95 | if err != nil { 96 | log.Error("subMgr.GetSessions error:", err) 97 | return 98 | } 99 | if len(sessionQosMap) == 0 { 100 | log.Debug("sessionQosMap is empty for topic:", infoTopic) 101 | return 102 | } 103 | nmsg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) 104 | var info SessionInfo 105 | info.Id = this.clientId 106 | info.InflightMsgCnt = this.inflightingList.Len() 107 | info.PeddingMsgCnt = this.peddingMsgList.Len() 108 | info.SendingMsgCnt = this.channel.iSendList.Len() 109 | bs, err := json.MarshalIndent(info, "", "\t") 110 | log.Debug("broadcastSessionInfo ", info) 111 | 112 | if err != nil { 113 | log.Error("broadcastSessionInfo json.MarshalIndent :", err) 114 | return 115 | } 116 | nmsg.Payload = bs 117 | //qos取订阅和原消息的最小值 118 | nmsg.Qos = 0 119 | 120 | for v, _ := range sessionQosMap { 121 | s := v.(*Session) 122 | if s.IsConnected() && !s.IsClosed() { 123 | _, err = s.Publish(nmsg) 124 | if err != nil { 125 | log.Errorf("session[%s].Publish Send error:", s.clientId, err) 126 | break 127 | } 128 | } else { 129 | log.Debug("session is disconnected or not connect") 130 | } 131 | 132 | } 133 | 134 | return 135 | } 136 | 137 | //要除自己之外的所有订阅者 138 | func (this *Session) sendToSubcriber(msg *packets.PublishPacket) error { 139 | log.Debug("sendToSubcriber") 140 | subMgr := this.mgr.GetSubscriptionMgr() 141 | 142 | sessionQosMap, err := subMgr.GetSessions(msg.TopicName) 143 | if err != nil { 144 | log.Error("subMgr.GetSessions error:", err) 145 | return err 146 | } 147 | if len(sessionQosMap) == 0 { 148 | log.Debug("sessionQosMap is empty for topic:", msg.TopicName) 149 | } 150 | log.Debug("sessionQosMap len:", len(sessionQosMap)) 151 | msg.Retain = false 152 | for v, qos := range sessionQosMap { 153 | s := v.(*Session) 154 | if config.SendToSelf == false { 155 | //不给自己发消息 156 | if s == this { 157 | continue 158 | } 159 | } 160 | if s.IsConnected() && !s.IsClosed() { 161 | nmsg := msg.Copy() 162 | //qos取订阅和原消息的最小值 163 | if qos < msg.Qos { 164 | nmsg.Qos = qos 165 | } 166 | _, err = s.Publish(msg) 167 | if err != nil { 168 | log.Errorf("session[%s].Publish Send error:", s.clientId, err) 169 | break 170 | } 171 | } else { 172 | log.Debug("session is disconnected or not connect") 173 | } 174 | 175 | } 176 | 177 | return nil 178 | } 179 | 180 | //处理 PUBLISH 消息 181 | //发布消息 182 | //retain消息的处理方式: 183 | //1.服务端只保存payload不为空的消息 184 | //2.服务端收到payload为空的消息清空以前相关主题的消息 185 | func (this *Session) onPublish(msg *packets.PublishPacket) (err error) { 186 | log.Debug("session onpublish") 187 | //从mgr获取和tipic匹配的sessions 188 | //TODO:获取匹配的sessions 189 | payloadLen := len(msg.Payload) 190 | if payloadLen == 0 { 191 | if msg.Retain { 192 | err = this.mgr.GetStoreMgr().RemoveMsgByTopic(msg.TopicName) 193 | if err != nil { 194 | log.Errorf("this.mgr.storeMgr.RemoveMsgByTopic error:", err) 195 | if err == store.ErrNoExsit { 196 | err = nil 197 | } else { 198 | return 199 | } 200 | } 201 | } 202 | 203 | } 204 | switch msg.Qos { 205 | case 0: 206 | if msg.Retain { 207 | //清空离线消息 208 | if payloadLen > 0 { 209 | err = this.mgr.GetStoreMgr().RemoveMsgByTopic(msg.TopicName) 210 | if err != nil { 211 | log.Errorf("this.mgr.storeMgr.RemoveMsgByTopic error:", err) 212 | if err == store.ErrNoExsit { 213 | err = nil 214 | } 215 | } 216 | log.Debugf("retain msg has clear for client:%s", this.clientId) 217 | 218 | err = this.mgr.GetStoreMgr().SaveRetainMsg(msg.TopicName, msg.Payload, msg.Qos) 219 | if err != nil { 220 | log.Errorf("SaveRetainMsg error:%s", err.Error()) 221 | } 222 | } 223 | 224 | } 225 | dashboard.Overview.RecvMsgCnt.Add(1) 226 | if this.isServer { 227 | this.sendToSubcriber(msg) 228 | } else { 229 | //回调接入到消息的函数 230 | this.callbackOnMessage(msg) 231 | } 232 | case 1: 233 | ack := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket) 234 | ack.MessageID = msg.MessageID 235 | this.channel.Send(ack) 236 | dashboard.Overview.RecvMsgCnt.Add(1) 237 | smsg := &store.Msg{ 238 | ClientId: this.clientId, 239 | MsgId: msg.MessageID, 240 | Topic: msg.TopicName, 241 | Qos: msg.Qos, 242 | Body: msg.Payload, 243 | } 244 | 245 | if msg.Retain { 246 | if payloadLen > 0 { 247 | err = this.mgr.GetStoreMgr().SaveRetainMsg(smsg.Topic, smsg.Body, smsg.Qos) 248 | if err != nil { 249 | log.Error(err) 250 | err = packets.ConnErrors[packets.ErrRefusedServerUnavailable] 251 | return 252 | } 253 | } 254 | 255 | } 256 | 257 | if this.isServer { 258 | this.sendToSubcriber(msg) 259 | } else { 260 | //回调接入到消息的函数 261 | this.callbackOnMessage(msg) 262 | } 263 | case 2: 264 | ack := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket) 265 | ack.MessageID = msg.MessageID 266 | this.channel.Send(ack) 267 | 268 | storeMgr := this.mgr.GetStoreMgr() 269 | if _, err = storeMgr.GetMsgByClientIdMsgId(this.clientId, msg.MessageID); err == store.ErrNoExsit { 270 | //保存消息,等待PUBREL后再发送 271 | smsg := &store.Msg{ 272 | ClientId: this.clientId, 273 | MsgId: msg.MessageID, 274 | Topic: msg.TopicName, 275 | Qos: msg.Qos, 276 | Body: msg.Payload, 277 | } 278 | err = storeMgr.SaveByClientIdMsgId(smsg) 279 | if err != nil { 280 | log.Error(err) 281 | err = packets.ConnErrors[packets.ErrRefusedServerUnavailable] 282 | return 283 | } 284 | if msg.Retain { 285 | if payloadLen > 0 { 286 | err = this.mgr.GetStoreMgr().SaveRetainMsg(smsg.Topic, smsg.Body, smsg.Qos) 287 | if err != nil { 288 | log.Error(err) 289 | err = packets.ConnErrors[packets.ErrRefusedServerUnavailable] 290 | return 291 | } 292 | } 293 | 294 | } 295 | } 296 | 297 | } 298 | return err 299 | } 300 | 301 | //处理 PUBACK 消息 302 | //向qos=1的主题消息发布者应答 303 | func (this *Session) onPuback(msg *packets.PubackPacket) error { 304 | this.onInflightDone(msg) 305 | return nil 306 | } 307 | 308 | //处理 PUBREC 消息 309 | func (this *Session) onPubrec(msg *packets.PubrecPacket) error { 310 | 311 | relmsg := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket) 312 | relmsg.MessageID = msg.MessageID 313 | this.channel.Send(relmsg) 314 | 315 | this.updateInflightMsg(relmsg) 316 | return nil 317 | } 318 | 319 | //处理 PUBREL 消息 320 | //向qos=2的主题消息发布者应答服务器第二步 321 | //对PUBREC的响应 322 | func (this *Session) onPubrel(msg *packets.PubrelPacket) error { 323 | compmsg := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket) 324 | compmsg.MessageID = msg.MessageID 325 | this.channel.Send(compmsg) 326 | 327 | storeMgr := this.mgr.GetStoreMgr() 328 | smsg, err := storeMgr.GetMsgByClientIdMsgId(this.clientId, msg.MessageID) 329 | if err != nil { 330 | log.Debugf("onPubrel error:%s,clientId:%s,msgId:%d", err, this.clientId, msg.MessageID) 331 | return nil 332 | } 333 | dashboard.Overview.RecvMsgCnt.Add(1) 334 | 335 | storeMgr.RemoveMsgById(smsg.Id) 336 | 337 | pubMsg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) 338 | pubMsg.Qos = smsg.Qos 339 | pubMsg.Payload = smsg.Body 340 | pubMsg.TopicName = smsg.Topic 341 | 342 | if this.isServer { 343 | this.sendToSubcriber(pubMsg) 344 | } else { 345 | //回调接入到消息的函数 346 | this.callbackOnMessage(pubMsg) 347 | } 348 | return nil 349 | } 350 | 351 | //处理 PUBCOMP 消息 352 | //向qos=2的主题消息发布者应答服务器第三步(发布完成) 353 | //对PUBREL的响应 354 | //它是QoS 2等级协议交换的第四个也是最后一个报文 355 | func (this *Session) onPubcomp(msg *packets.PubcompPacket) error { 356 | this.onInflightDone(msg) 357 | return nil 358 | } 359 | 360 | //处理 SUBSCRIBE - 订阅主题消息 361 | //客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。 362 | //每个订阅注册客户端关心的一个或多个主题。为了将应用消息转发给与那些订阅匹配的主题,服务端发送PUBLISH报文给客户端。 363 | //SUBSCRIBE报文也(为每个订阅)指定了最大的QoS等级,服务端根据这个发送应用消息给客户端。 364 | func (this *Session) onSubscribe(msg *packets.SubscribePacket) error { 365 | log.Debug("session.onSubscribe") 366 | submgr := this.mgr.GetSubscriptionMgr() 367 | 368 | err := submgr.Add(msg.Topics, msg.Qoss, this) 369 | 370 | suback := packets.NewControlPacket(packets.Suback).(*packets.SubackPacket) 371 | suback.MessageID = msg.MessageID 372 | 373 | suback.ReturnCodes = msg.Qoss 374 | for i, _ := range msg.Qoss { 375 | if err != nil { 376 | suback.ReturnCodes[i] = 0x80 377 | } 378 | 379 | } 380 | //向客户端发送匹配主题的retain消息 381 | go this.procRetainMessages(msg) 382 | 383 | return this.channel.Send(suback) 384 | } 385 | func (this *Session) procRetainMessages(msg *packets.SubscribePacket) { 386 | subMgr := this.mgr.GetSubscriptionMgr() 387 | storeMgr := this.mgr.GetStoreMgr() 388 | msgs, err := storeMgr.GetAllRetainMsgs() 389 | if err != nil { 390 | log.Debug("no retain messages") 391 | return 392 | } 393 | 394 | log.Debug("retain len:", len(msgs)) 395 | for _, msg := range msgs { 396 | pubMsg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) 397 | log.Debugf("%#v", msg) 398 | pubMsg.Retain = true 399 | pubMsg.TopicName = msg.Topic 400 | pubMsg.Qos = msg.Qos 401 | pubMsg.Payload = msg.Body 402 | if subMgr.IsTopicMatch(msg.Topic, msg.Topic) { 403 | //匹配主题,发送给session 404 | this.Publish(pubMsg) 405 | } 406 | } 407 | } 408 | 409 | //处理 SUBACK – 订阅确认 410 | //服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。 411 | //SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。 412 | func (this *Session) onSuback(msg *packets.SubackPacket) error { 413 | this.onInflightDone(msg) 414 | return nil 415 | } 416 | 417 | //处理 UNSUBACK –取消订阅 418 | //客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。 419 | func (this *Session) onUnsubscribe(msg *packets.UnsubscribePacket) error { 420 | submgr := this.mgr.GetSubscriptionMgr() 421 | for _, s := range msg.Topics { 422 | submgr.Remove(s, this) 423 | } 424 | ack := &packets.UnsubackPacket{ 425 | MessageID: msg.MessageID, 426 | } 427 | this.channel.Send(ack) 428 | return nil 429 | } 430 | 431 | //处理 UNSUBSCRIBE –取消订阅确认 432 | //服务端发送UNSUBACK报文给客户端。 433 | func (this *Session) onUnsuback(msg *packets.UnsubackPacket) error { 434 | this.onInflightDone(msg) 435 | return nil 436 | } 437 | 438 | //处理 PINGREQ 439 | func (this *Session) onPingreq(msg *packets.PingreqPacket) error { 440 | pingresp := packets.NewControlPacket(packets.Pingresp).(*packets.PingrespPacket) 441 | this.channel.Send(pingresp) 442 | return nil 443 | } 444 | 445 | //处理 PINGRESP 446 | //服务端发送PINGRESP报文响应客户端的PINGREQ报文。表示服务端还活着。 447 | //保持连接(Keep Alive)处理中用到这个报文 448 | func (this *Session) onPingresp(msg *packets.PingrespPacket) error { 449 | //TODO 450 | return nil 451 | } 452 | 453 | //处理 DISCONNECT 454 | //从sessionMgr中删除session 455 | //断开网络 456 | func (this *Session) onDisconnect(msg *packets.DisconnectPacket) error { 457 | log.Debugf("session(%s) onDisconnect", this.clientId, this.sentMsgChan) 458 | this.mgrOnDisconnected() 459 | 460 | log.Debugf("session(%s) onDisconnect end", this.clientId) 461 | return nil 462 | } 463 | 464 | //消息发送完成。走完所有流程 465 | func (this *Session) onInflightDone(msg packets.ControlPacket) { 466 | switch msg.Type() { 467 | case packets.Publish: 468 | if msg.Details().Qos > 0 { 469 | break 470 | } 471 | fallthrough 472 | case packets.Puback, packets.Pubcomp, packets.Suback, packets.Unsuback: 473 | imsg := this.removeInflightMsg(msg.Details().MessageID) 474 | if imsg != nil && imsg.pt != nil && imsg.pt.t != nil { 475 | switch msg.Type() { 476 | case packets.Suback: 477 | var subToken *SubscribeToken = imsg.pt.t.(*SubscribeToken) 478 | subackMsg := msg.(*packets.SubackPacket) 479 | for i, sub := range subToken.subs { 480 | subToken.subResult[sub] = subackMsg.ReturnCodes[i] 481 | } 482 | } 483 | log.Debug("flowComplete for msgId:", imsg.pt.p.Details().MessageID) 484 | imsg.pt.t.flowComplete() 485 | } 486 | } 487 | 488 | if this.inflightingList.Len() < config.MaxSizeOfInflight { 489 | select { 490 | case <-this.peddingChan: 491 | default: 492 | } 493 | v := this.peddingMsgList.Pop() 494 | if v != nil { 495 | this.insert2Inflight(v.(*PacketAndToken)) 496 | } 497 | } 498 | dashboard.Overview.SentMsgCnt.Add(1) 499 | } 500 | -------------------------------------------------------------------------------- /session/session-info.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | type SessionInfo struct { 4 | Id string 5 | //等待发送的消息数 6 | PeddingMsgCnt int 7 | //正在发送中的消息数 8 | InflightMsgCnt int 9 | //在Channel发送队列中的包数 10 | SendingMsgCnt int 11 | } 12 | -------------------------------------------------------------------------------- /session/session-mgr.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "sync" 7 | "time" 8 | 9 | log "github.com/Sirupsen/logrus" 10 | 11 | "github.com/iotalking/mqtt-broker/safe-runtine" 12 | "github.com/iotalking/mqtt-broker/topic" 13 | 14 | "github.com/eclipse/paho.mqtt.golang/packets" 15 | 16 | "github.com/iotalking/mqtt-broker/config" 17 | "github.com/iotalking/mqtt-broker/dashboard" 18 | "github.com/iotalking/mqtt-broker/store" 19 | "github.com/iotalking/mqtt-broker/utils" 20 | ) 21 | 22 | type SessionMgr interface { 23 | HandleConnection(session *Session) 24 | OnConnected(session *Session) 25 | OnConnectTimeout(session *Session) 26 | OnPingTimeout(session *Session) 27 | OnDisconnected(session *Session) 28 | DisconectSessionByClientId(clientId string) 29 | GetSubscriptionMgr() topic.SubscriptionMgr 30 | GetSessions() SessionList 31 | GetStoreMgr() store.StoreMgr 32 | BroadcastSessionInfo(session *Session) 33 | 34 | //给包外部调用 35 | Publish(topic string, msg []byte, qos byte) error 36 | } 37 | 38 | type SessionList struct { 39 | Active []string 40 | Inactive []string 41 | } 42 | 43 | type sessionMgr struct { 44 | //主runtine 45 | runtine runtine.SafeRuntine 46 | 47 | //用于处理新连接 48 | insertSessionChan chan *Session 49 | //连接验证超时 50 | connectTimeoutChan chan *Session 51 | //连接验证通过 52 | connectedChan chan *Session 53 | 54 | //接入pingreq超时 55 | pingTimeoutChan chan *Session 56 | 57 | //已经连接难的session主动断开连接 58 | disconnectChan chan *Session 59 | 60 | //断开指定clientId的session 61 | disconnectSessionByClientIdChan chan string 62 | 63 | //保存未验证连接的session 64 | waitingConnectSessionMap map[io.ReadWriteCloser]*Session 65 | 66 | //保存已经验证连接的session 67 | connectedSessionMap map[string]*Session 68 | 69 | subscriptionMgr topic.SubscriptionMgr 70 | 71 | getSessionsChan chan byte 72 | //返回json的active session和inactive session的clientId列表 73 | getSessionsResultChan chan SessionList 74 | 75 | getActiveSessionsChan chan byte 76 | getActiveSessionsResultChan chan []*Session 77 | 78 | getAllSessionChan chan byte 79 | getAllSessionResultChan chan []*Session 80 | 81 | closeSessionRuntine runtine.SafeRuntine 82 | closeSessionChan chan *Session 83 | 84 | tickerRuntine runtine.SafeRuntine 85 | //处理看板广播的协程 86 | overviewRuntine runtine.SafeRuntine 87 | //处理sessionInfo的广播 88 | sessionInfoRuntine runtine.SafeRuntine 89 | 90 | sessionInfoList *utils.List 91 | 92 | storeMgr store.StoreMgr 93 | } 94 | 95 | var gSessionMgr *sessionMgr 96 | var sessionMgrOnce sync.Once 97 | 98 | func GetMgr() SessionMgr { 99 | sessionMgrOnce.Do(func() { 100 | mgr := &sessionMgr{ 101 | connectedSessionMap: make(map[string]*Session), 102 | insertSessionChan: make(chan *Session), 103 | subscriptionMgr: topic.NewSubscriptionMgr(), 104 | connectTimeoutChan: make(chan *Session), 105 | connectedChan: make(chan *Session), 106 | disconnectSessionByClientIdChan: make(chan string), 107 | disconnectChan: make(chan *Session), 108 | pingTimeoutChan: make(chan *Session), 109 | waitingConnectSessionMap: make(map[io.ReadWriteCloser]*Session), 110 | getSessionsChan: make(chan byte), 111 | getSessionsResultChan: make(chan SessionList), 112 | getActiveSessionsChan: make(chan byte), 113 | getActiveSessionsResultChan: make(chan []*Session), 114 | getAllSessionChan: make(chan byte), 115 | getAllSessionResultChan: make(chan []*Session), 116 | closeSessionChan: make(chan *Session), 117 | sessionInfoList: utils.NewList(), 118 | storeMgr: store.NewStoreMgr(), 119 | } 120 | mgr.runtine.Go(func(args ...interface{}) { 121 | mgr.run() 122 | }) 123 | mgr.closeSessionRuntine.Go(func(args ...interface{}) { 124 | for { 125 | select { 126 | case <-mgr.closeSessionRuntine.IsInterrupt: 127 | 128 | return 129 | case s := <-mgr.closeSessionChan: 130 | log.Info("sessionMgr closing session") 131 | 132 | s.Close() 133 | dashboard.Overview.ClosingFiles.Add(-1) 134 | log.Infof("sessionMgr has closed session:%s", s.clientId) 135 | 136 | } 137 | } 138 | log.Info("closeSessionRuntine is closed") 139 | }) 140 | mgr.tickerRuntine.Go(func(args ...interface{}) { 141 | mgr.tickerRun() 142 | }) 143 | mgr.overviewRuntine.Go(func(args ...interface{}) { 144 | mgr.overviewRun() 145 | }) 146 | mgr.sessionInfoRuntine.Go(func(args ...interface{}) { 147 | mgr.sessionInfoRun() 148 | }) 149 | gSessionMgr = mgr 150 | 151 | }) 152 | 153 | return gSessionMgr 154 | } 155 | 156 | func (this *sessionMgr) Close() { 157 | if this.runtine.IsStoped() { 158 | log.Warnf("Close:sessionMgr istoped") 159 | return 160 | } 161 | this.sessionInfoRuntine.Stop() 162 | this.overviewRuntine.Stop() 163 | this.tickerRuntine.Stop() 164 | this.closeSessionRuntine.Stop() 165 | this.runtine.Stop() 166 | } 167 | 168 | func (this *sessionMgr) CloseSession(s *Session) { 169 | dashboard.Overview.ClosingFiles.Add(1) 170 | this.closeSessionChan <- s 171 | } 172 | func (this *sessionMgr) HandleConnection(session *Session) { 173 | if this.runtine.IsStoped() { 174 | log.Warnf("sessionMgr istoped") 175 | return 176 | } 177 | dashboard.Overview.InactiveClients.Add(1) 178 | dashboard.Overview.OpenedFiles.Add(1) 179 | this.insertSessionChan <- session 180 | return 181 | } 182 | func (this *sessionMgr) tickerRun() { 183 | var secondTicker = time.NewTimer(time.Second) 184 | for { 185 | select { 186 | case <-secondTicker.C: 187 | preTime := time.Now() 188 | asessions := this.getAllSesssions() 189 | 190 | for _, s := range asessions { 191 | s.OnTick() 192 | } 193 | 194 | usedNs := time.Now().Sub(preTime).Nanoseconds() 195 | d := time.Second - time.Duration(usedNs) 196 | log.Debug("ontick used:", usedNs) 197 | if usedNs > dashboard.Overview.MaxTickerBusyTime.Get().(int64) { 198 | dashboard.Overview.MaxTickerBusyTime.Set(usedNs) 199 | } 200 | dashboard.Overview.LastTickerBusyTime.Set(usedNs) 201 | secondTicker.Reset(d) 202 | } 203 | 204 | } 205 | } 206 | func (this *sessionMgr) overviewRun() { 207 | var startTime = time.Now() 208 | for { 209 | select { 210 | case <-this.overviewRuntine.IsInterrupt: 211 | return 212 | case <-time.After(config.DashboardUpdateTime): 213 | tm := time.Now().Sub(startTime) 214 | dashboard.Overview.RunTimeString.Set(tm.String()) 215 | dashboard.Overview.RunNanoSeconds.Set(tm.Nanoseconds()) 216 | ov := dashboard.Overview.Copy() 217 | bsjson, err := json.Marshal(&ov) 218 | if err != nil { 219 | panic(err) 220 | } 221 | pubmsg := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) 222 | pubmsg.TopicName = config.OverviewTopic 223 | pubmsg.Payload = bsjson 224 | pubmsg.Retain = true 225 | sessionMaps, _ := this.GetSubscriptionMgr().GetSessions(config.OverviewTopic) 226 | for k, _ := range sessionMaps { 227 | session := k.(*Session) 228 | session.Publish(pubmsg) 229 | } 230 | } 231 | } 232 | } 233 | 234 | func (this *sessionMgr) BroadcastSessionInfo(session *Session) { 235 | this.sessionInfoList.Push(session) 236 | } 237 | func (this *sessionMgr) sessionInfoRun() { 238 | for { 239 | select { 240 | case <-this.overviewRuntine.IsInterrupt: 241 | return 242 | case <-this.sessionInfoList.Wait(): 243 | v := this.sessionInfoList.Pop() 244 | if v != nil { 245 | s := v.(*Session) 246 | s.BroadcastSessionInfo() 247 | } 248 | } 249 | } 250 | } 251 | 252 | func (this *sessionMgr) run() { 253 | log.Debug("sessionMgr running...") 254 | for { 255 | select { 256 | case <-this.runtine.IsInterrupt: 257 | break 258 | 259 | case session := <-this.insertSessionChan: 260 | log.Debug("insertSessionChan") 261 | this.waitingConnectSessionMap[session.channel.conn] = session 262 | session.channel.Start() 263 | case s := <-this.connectTimeoutChan: 264 | delete(this.waitingConnectSessionMap, s.channel.conn) 265 | dashboard.Overview.InactiveClients.Add(-1) 266 | this.CloseSession(s) 267 | case s := <-this.connectedChan: 268 | log.Debugf("session %s connected,%#v", s.clientId, s.channel) 269 | delete(this.waitingConnectSessionMap, s.channel.conn) 270 | if _, ok := this.waitingConnectSessionMap[s.channel.conn]; ok { 271 | panic("not delete") 272 | } 273 | this.connectedSessionMap[s.clientId] = s 274 | dashboard.Overview.ActiveClients.Add(1) 275 | dashboard.Overview.InactiveClients.Add(-1) 276 | if dashboard.Overview.ActiveClients.Get().(int64) > dashboard.Overview.MaxActiveClinets.Get().(int64) { 277 | dashboard.Overview.MaxActiveClinets.Set(dashboard.Overview.ActiveClients.Get()) 278 | } 279 | case clientId := <-this.disconnectSessionByClientIdChan: 280 | log.Debug("disconnectSessionByClientIdChan id:", clientId) 281 | if s, ok := this.connectedSessionMap[clientId]; ok { 282 | log.Info("sessionMgr disconnectSessionByClientId:", clientId) 283 | delete(this.connectedSessionMap, clientId) 284 | //由session mgr来安全退出session,因为是由mgr创建的 285 | dashboard.Overview.ActiveClients.Add(-1) 286 | this.CloseSession(s) 287 | } 288 | 289 | case s := <-this.disconnectChan: 290 | log.Debug("disconnectChan id:", s.clientId) 291 | if _, ok := this.connectedSessionMap[s.clientId]; ok { 292 | log.Info("sessionMgr disconnet client:", s.clientId) 293 | delete(this.connectedSessionMap, s.clientId) 294 | //由session mgr来安全退出session,因为是由mgr创建的 295 | dashboard.Overview.ActiveClients.Set(int64(len(this.connectedSessionMap))) 296 | this.CloseSession(s) 297 | } 298 | case s := <-this.pingTimeoutChan: 299 | log.Debug("ping timeout id:", s.clientId) 300 | if _, ok := this.connectedSessionMap[s.clientId]; ok { 301 | log.Info("sessionMgr disconnet client:", s.clientId) 302 | delete(this.connectedSessionMap, s.clientId) 303 | //由session mgr来安全退出session,因为是由mgr创建的 304 | dashboard.Overview.ActiveClients.Set(int64(len(this.connectedSessionMap))) 305 | this.CloseSession(s) 306 | } 307 | case <-this.getSessionsChan: 308 | log.Debug("sessionMgr.getSessionsChan") 309 | list := SessionList{} 310 | for _, s := range this.connectedSessionMap { 311 | list.Active = append(list.Active, s.clientId) 312 | } 313 | for _, s := range this.waitingConnectSessionMap { 314 | list.Inactive = append(list.Inactive, s.clientId) 315 | } 316 | select { 317 | case this.getSessionsResultChan <- list: 318 | default: 319 | 320 | } 321 | case <-this.getActiveSessionsChan: 322 | log.Debug("sessionMgr.getSessionsChan") 323 | sessions := make([]*Session, 0, len(this.connectedSessionMap)) 324 | for _, s := range this.connectedSessionMap { 325 | 326 | sessions = append(sessions, s) 327 | } 328 | this.getActiveSessionsResultChan <- sessions 329 | case <-this.getAllSessionChan: 330 | sessions := make([]*Session, 0, len(this.connectedSessionMap)) 331 | for _, s := range this.waitingConnectSessionMap { 332 | sessions = append(sessions, s) 333 | } 334 | for _, s := range this.connectedSessionMap { 335 | sessions = append(sessions, s) 336 | } 337 | this.getAllSessionResultChan <- sessions 338 | } 339 | } 340 | } 341 | 342 | type mgrPublishData struct { 343 | msg *packets.PublishPacket 344 | session *Session 345 | } 346 | 347 | func (this *sessionMgr) OnConnected(session *Session) { 348 | //不能阻塞session,不然会死锁 349 | this.connectedChan <- session 350 | 351 | } 352 | 353 | //断开指定clientId的session 354 | func (this *sessionMgr) DisconectSessionByClientId(clientId string) { 355 | this.disconnectSessionByClientIdChan <- clientId 356 | } 357 | func (this *sessionMgr) OnDisconnected(session *Session) { 358 | //不能阻塞session,不然会死锁 359 | this.disconnectChan <- session 360 | } 361 | 362 | //连接验证超时 363 | func (this *sessionMgr) OnConnectTimeout(session *Session) { 364 | //不能阻塞session,不然会死锁 365 | this.connectTimeoutChan <- session 366 | } 367 | 368 | func (this *sessionMgr) OnPingTimeout(session *Session) { 369 | //不能阻塞session,不然会死锁 370 | this.pingTimeoutChan <- session 371 | } 372 | 373 | func (this *sessionMgr) GetSessions() SessionList { 374 | log.Debug("sessionMgr.GetSessions") 375 | this.getSessionsChan <- 1 376 | return <-this.getSessionsResultChan 377 | } 378 | 379 | func (this *sessionMgr) getActiveSessions() []*Session { 380 | this.getActiveSessionsChan <- 1 381 | return <-this.getActiveSessionsResultChan 382 | } 383 | 384 | func (this *sessionMgr) getAllSesssions() []*Session { 385 | this.getAllSessionChan <- 1 386 | return <-this.getAllSessionResultChan 387 | } 388 | 389 | func (this *sessionMgr) GetSubscriptionMgr() topic.SubscriptionMgr { 390 | return this.subscriptionMgr 391 | } 392 | func (this *sessionMgr) GetStoreMgr() store.StoreMgr { 393 | return this.storeMgr 394 | } 395 | 396 | func (this *sessionMgr) Publish(topic string, msg []byte, qos byte) (err error) { 397 | 398 | sessinMap, err := this.GetSubscriptionMgr().GetSessions(topic) 399 | if err != nil { 400 | return 401 | } 402 | for key, _ := range sessinMap { 403 | if s, ok := key.(*Session); ok { 404 | var pkg = packets.NewControlPacket(packets.Publish).(*packets.PublishPacket) 405 | pkg.TopicName = topic 406 | pkg.Payload = msg 407 | pkg.Qos = qos 408 | s.Publish(pkg) 409 | } 410 | } 411 | return 412 | } 413 | -------------------------------------------------------------------------------- /session/session_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestCloseSendingChan(t *testing.T) { 9 | c := make(chan byte, 1) 10 | defer func() { 11 | err := recover() 12 | if err == nil { 13 | t.Fail() 14 | } 15 | }() 16 | go func() { 17 | time.Sleep(2 * time.Second) 18 | close(c) 19 | }() 20 | c <- 1 21 | c <- 1 22 | } 23 | -------------------------------------------------------------------------------- /session/token.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | * 9 | * Contributors: 10 | * Allan Stockdill-Mander 11 | */ 12 | 13 | package session 14 | 15 | import ( 16 | "sync" 17 | "time" 18 | 19 | "github.com/eclipse/paho.mqtt.golang/packets" 20 | ) 21 | 22 | //PacketAndToken is a struct that contains both a ControlPacket and a 23 | //Token. This struct is passed via channels between the client interface 24 | //code and the underlying code responsible for sending and receiving 25 | //MQTT messages. 26 | type PacketAndToken struct { 27 | p packets.ControlPacket 28 | t Token 29 | } 30 | 31 | //Token defines the interface for the tokens used to indicate when 32 | //actions have completed. 33 | type Token interface { 34 | Wait() bool 35 | WaitTimeout(time.Duration) bool 36 | flowComplete() 37 | Error() error 38 | } 39 | 40 | type baseToken struct { 41 | m sync.RWMutex 42 | complete chan struct{} 43 | ready bool 44 | err error 45 | } 46 | 47 | // Wait will wait indefinitely for the Token to complete, ie the Publish 48 | // to be sent and confirmed receipt from the broker 49 | func (b *baseToken) Wait() bool { 50 | b.m.Lock() 51 | defer b.m.Unlock() 52 | if !b.ready { 53 | <-b.complete 54 | b.ready = true 55 | } 56 | return b.ready 57 | } 58 | 59 | // WaitTimeout takes a time in ms to wait for the flow associated with the 60 | // Token to complete, returns true if it returned before the timeout or 61 | // returns false if the timeout occurred. In the case of a timeout the Token 62 | // does not have an error set in case the caller wishes to wait again 63 | func (b *baseToken) WaitTimeout(d time.Duration) bool { 64 | b.m.Lock() 65 | defer b.m.Unlock() 66 | if !b.ready { 67 | select { 68 | case <-b.complete: 69 | b.ready = true 70 | case <-time.After(d): 71 | } 72 | } 73 | return b.ready 74 | } 75 | 76 | func (b *baseToken) flowComplete() { 77 | close(b.complete) 78 | } 79 | 80 | func (b *baseToken) Error() error { 81 | b.m.RLock() 82 | defer b.m.RUnlock() 83 | return b.err 84 | } 85 | 86 | func newToken(tType byte) Token { 87 | switch tType { 88 | case packets.Connect: 89 | return &ConnectToken{baseToken: baseToken{complete: make(chan struct{})}} 90 | case packets.Subscribe: 91 | return &SubscribeToken{baseToken: baseToken{complete: make(chan struct{})}, subResult: make(map[string]byte)} 92 | case packets.Publish: 93 | return &PublishToken{baseToken: baseToken{complete: make(chan struct{})}} 94 | case packets.Unsubscribe: 95 | return &UnsubscribeToken{baseToken: baseToken{complete: make(chan struct{})}} 96 | case packets.Disconnect: 97 | return &DisconnectToken{baseToken: baseToken{complete: make(chan struct{})}} 98 | } 99 | return nil 100 | } 101 | 102 | //ConnectToken is an extension of Token containing the extra fields 103 | //required to provide information about calls to Connect() 104 | type ConnectToken struct { 105 | baseToken 106 | returnCode byte 107 | } 108 | 109 | //ReturnCode returns the acknowlegement code in the connack sent 110 | //in response to a Connect() 111 | func (c *ConnectToken) ReturnCode() byte { 112 | c.m.RLock() 113 | defer c.m.RUnlock() 114 | return c.returnCode 115 | } 116 | 117 | //PublishToken is an extension of Token containing the extra fields 118 | //required to provide information about calls to Publish() 119 | type PublishToken struct { 120 | baseToken 121 | messageID uint16 122 | } 123 | 124 | //MessageID returns the MQTT message ID that was assigned to the 125 | //Publish packet when it was sent to the broker 126 | func (p *PublishToken) MessageID() uint16 { 127 | return p.messageID 128 | } 129 | 130 | //SubscribeToken is an extension of Token containing the extra fields 131 | //required to provide information about calls to Subscribe() 132 | type SubscribeToken struct { 133 | baseToken 134 | subs []string 135 | subResult map[string]byte 136 | } 137 | 138 | //Result returns a map of topics that were subscribed to along with 139 | //the matching return code from the broker. This is either the Qos 140 | //value of the subscription or an error code. 141 | func (s *SubscribeToken) Result() map[string]byte { 142 | s.m.RLock() 143 | defer s.m.RUnlock() 144 | return s.subResult 145 | } 146 | 147 | //UnsubscribeToken is an extension of Token containing the extra fields 148 | //required to provide information about calls to Unsubscribe() 149 | type UnsubscribeToken struct { 150 | baseToken 151 | } 152 | 153 | //DisconnectToken is an extension of Token containing the extra fields 154 | //required to provide information about calls to Disconnect() 155 | type DisconnectToken struct { 156 | baseToken 157 | } 158 | -------------------------------------------------------------------------------- /store/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/store/.DS_Store -------------------------------------------------------------------------------- /store/mem-provider/mem-store.go: -------------------------------------------------------------------------------- 1 | package memstore 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/iotalking/mqtt-broker/store" 8 | ) 9 | 10 | type memStore struct { 11 | 12 | //Id索引 13 | indexId map[int]*store.Msg 14 | 15 | //clientId&MsgId索引 16 | //key:clientId拼接MsgId的字串 17 | //如:clientId="client123" MsgId=100 那么key="client123100" 18 | indexClientIdMsgId map[string]*store.Msg 19 | 20 | //以topic为索引存储离线消息 21 | retainMsgs map[string]*store.Msg 22 | 23 | mtx sync.Mutex 24 | retainMtx sync.Mutex 25 | } 26 | 27 | func (this *memStore) ClientIdMsgIdAsKey(clientId string, msgId uint16) string { 28 | return fmt.Sprintf("%s%d", clientId, msgId) 29 | } 30 | func (this *memStore) SaveByClientIdMsgId(msg *store.Msg) error { 31 | this.mtx.Lock() 32 | defer this.mtx.Unlock() 33 | key := this.ClientIdMsgIdAsKey(msg.ClientId, msg.MsgId) 34 | if _, ok := this.indexClientIdMsgId[key]; ok { 35 | return store.ErrDupMsg 36 | } 37 | msg.Id = len(this.indexId) 38 | 39 | this.indexId[msg.Id] = msg 40 | this.indexClientIdMsgId[key] = msg 41 | 42 | return nil 43 | } 44 | 45 | //以Topic作为key 46 | func (this *memStore) SaveRetainMsg(topic string, body []byte, qos byte) error { 47 | this.mtx.Lock() 48 | defer this.mtx.Unlock() 49 | msg := &store.Msg{ 50 | Topic: topic, 51 | Body: body, 52 | Qos: qos, 53 | } 54 | if len(msg.Body) == 0 { 55 | //不能存储内容为空的消息,反而会删除相同主题的retain消息 56 | delete(this.retainMsgs, msg.Topic) 57 | } else { 58 | msg.ClientId = "" 59 | msg.MsgId = 0 60 | this.retainMsgs[msg.Topic] = msg 61 | } 62 | 63 | return nil 64 | } 65 | func (this *memStore) GetMsgByClientIdMsgId(clientId string, msgId uint16) (msg *store.Msg, err error) { 66 | this.mtx.Lock() 67 | defer this.mtx.Unlock() 68 | msg, ok := this.indexClientIdMsgId[this.ClientIdMsgIdAsKey(clientId, msgId)] 69 | if !ok { 70 | return nil, store.ErrNoExsit 71 | } 72 | return msg, nil 73 | } 74 | 75 | //根据消息Id删除消息 76 | func (this *memStore) RemoveMsgById(id int) error { 77 | this.mtx.Lock() 78 | defer this.mtx.Unlock() 79 | msg, ok := this.indexId[id] 80 | if ok { 81 | delete(this.indexId, id) 82 | delete(this.indexClientIdMsgId, this.ClientIdMsgIdAsKey(msg.ClientId, msg.MsgId)) 83 | } else { 84 | return store.ErrNoExsit 85 | } 86 | return nil 87 | } 88 | 89 | //获取所有离线消息 90 | func (this *memStore) GetAllRetainMsgs() (msgs []*store.Msg, err error) { 91 | this.retainMtx.Lock() 92 | defer this.retainMtx.Unlock() 93 | for _, msg := range this.retainMsgs { 94 | msgs = append(msgs, msg) 95 | } 96 | return 97 | } 98 | func (this *memStore) RemoveMsgByTopic(topic string) error { 99 | this.retainMtx.Lock() 100 | defer this.retainMtx.Unlock() 101 | if _, ok := this.retainMsgs[topic]; !ok { 102 | return store.ErrNoExsit 103 | } 104 | delete(this.retainMsgs, topic) 105 | return nil 106 | } 107 | 108 | func NewStoreMgr() store.StoreMgr { 109 | return &memStore{ 110 | indexId: make(map[int]*store.Msg), 111 | indexClientIdMsgId: make(map[string]*store.Msg), 112 | retainMsgs: make(map[string]*store.Msg), 113 | } 114 | } 115 | 116 | func init() { 117 | store.RegisterProvider(NewStoreMgr) 118 | } 119 | -------------------------------------------------------------------------------- /store/mem-store_test.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestError(t *testing.T) { 9 | e := ErrDupMsg 10 | 11 | t.Log(e) 12 | } 13 | -------------------------------------------------------------------------------- /store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | type Error int 4 | 5 | const ( 6 | //消息重复 7 | ErrDupMsg Error = 1 8 | ErrNoExsit Error = 2 9 | ) 10 | 11 | type Msg struct { 12 | //Store里唯一的消息标识 13 | Id int 14 | ClientId string 15 | //消息作者定义的id 16 | MsgId uint16 17 | Topic string 18 | Qos byte 19 | Body []byte 20 | } 21 | 22 | //发送中的消息存储 23 | type InflightStore interface { 24 | //以ClientId&MsgId作key 25 | SaveByClientIdMsgId(msg *Msg) error 26 | //以Topic作为key 27 | SaveRetainMsg(topic string, body []byte, qos byte) error 28 | 29 | GetMsgByClientIdMsgId(clientId string, msgId uint16) (msg *Msg, err error) 30 | //根据消息Id删除消息 31 | RemoveMsgById(id int) error 32 | } 33 | 34 | //相关题的最后一条离线消息存储 35 | type RetainStore interface { 36 | //Retain消息 37 | //获取所有离线消息 38 | GetAllRetainMsgs() (msgs []*Msg, err error) 39 | 40 | //删除topic的离线消息 41 | RemoveMsgByTopic(topic string) error 42 | } 43 | type StoreMgr interface { 44 | InflightStore 45 | RetainStore 46 | } 47 | 48 | func (this Error) Error() string { 49 | switch this { 50 | case ErrDupMsg: 51 | return "ErrDupMsg" 52 | case ErrNoExsit: 53 | return "ErrNoExsit" 54 | } 55 | return "Unknown" 56 | } 57 | 58 | type ProviderFactory func() StoreMgr 59 | 60 | var factory ProviderFactory 61 | 62 | func NewStoreMgr() StoreMgr { 63 | if factory == nil { 64 | panic("register provider first") 65 | } 66 | return factory() 67 | } 68 | 69 | func RegisterProvider(f func() StoreMgr) { 70 | if factory != nil { 71 | panic("dup factory") 72 | } 73 | factory = f 74 | } 75 | -------------------------------------------------------------------------------- /ticker/second-ticker.go: -------------------------------------------------------------------------------- 1 | package ticker 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/iotalking/mqtt-broker/dashboard" 8 | "github.com/iotalking/mqtt-broker/utils" 9 | ) 10 | 11 | type Ticker interface { 12 | Tick() 13 | } 14 | 15 | var ( 16 | secondsOnce sync.Once 17 | //保存未到基的ticker 18 | secondsTickMap = make(map[time.Time]*tickData) 19 | 20 | afterList = utils.NewList() 21 | cancelList = utils.NewList() 22 | 23 | dataPool = sync.Pool{ 24 | New: func() interface{} { 25 | return &tickData{} 26 | }, 27 | } 28 | ) 29 | 30 | type tickData struct { 31 | id time.Time 32 | ticker Ticker 33 | } 34 | 35 | type Timer struct { 36 | ch chan interface{} 37 | id time.Time 38 | param interface{} 39 | mux sync.Mutex 40 | } 41 | 42 | func NewTimer(ch chan interface{}) *Timer { 43 | secondsOnce.Do(func() { 44 | go run() 45 | }) 46 | if ch == nil { 47 | ch = make(chan interface{}) 48 | } 49 | t := &Timer{ 50 | ch: ch, 51 | } 52 | 53 | return t 54 | } 55 | func (this *Timer) Reset(duration time.Duration, param interface{}) { 56 | cancelTicker(this.id) 57 | this.mux.Lock() 58 | this.param = param 59 | this.mux.Unlock() 60 | this.id = after(duration, this) 61 | } 62 | func (this *Timer) Stop() { 63 | cancelTicker(this.id) 64 | 65 | this.mux.Lock() 66 | this.param = nil 67 | this.ch = nil 68 | this.mux.Unlock() 69 | } 70 | 71 | func (this *Timer) Wait() <-chan interface{} { 72 | return this.ch 73 | } 74 | func (this *Timer) Tick() { 75 | this.mux.Lock() 76 | select { 77 | case this.ch <- this.param: 78 | default: 79 | } 80 | this.mux.Unlock() 81 | } 82 | 83 | //延时duration后,调用tk的Tick 84 | //运行一个常驻的runntine,再runtine中启动一个一秒的定时器,每秒循环比较每个ticker到期时间戳, 85 | //不用要time.AfterFunc一样每次的都启动一个runtine 86 | //time.After又每次都产生一个新的chan,不好select 87 | func after(duration time.Duration, tk Ticker) time.Time { 88 | 89 | tm := time.Now() 90 | id := tm.Add(duration) 91 | var data *tickData 92 | 93 | data = dataPool.Get().(*tickData) 94 | data.id = id 95 | data.ticker = tk 96 | 97 | afterList.Push(data) 98 | return id 99 | } 100 | 101 | func cancelTicker(tm time.Time) { 102 | cancelList.Push(tm) 103 | } 104 | 105 | func run() { 106 | //用time来做延时,每隔一秒检查一下定时器列表 107 | 108 | var d time.Duration = time.Second 109 | for { 110 | select { 111 | case <-time.After(d): 112 | last := time.Now() 113 | //遍历tickers 114 | //如果超时则调用Tick 115 | dashboard.Overview.TickerMapCnt.Set(int64(len(secondsTickMap))) 116 | 117 | for tm, data := range secondsTickMap { 118 | if tm.Sub(last) < 0 { 119 | //ticker小于当前时间,表示超时了 120 | data.ticker.Tick() 121 | delete(secondsTickMap, tm) 122 | dataPool.Put(data) 123 | } 124 | 125 | } 126 | //下一次定时器要减去遍历使用的时间 127 | d = time.Second - time.Since(last) 128 | if d < 0 { 129 | d = 0 130 | } 131 | case <-afterList.Wait(): 132 | dashboard.Overview.AddTickerCnt.Add(1) 133 | data := afterList.Pop().(*tickData) 134 | secondsTickMap[data.id] = data 135 | 136 | case <-cancelList.Wait(): 137 | id := cancelList.Pop().(time.Time) 138 | if data, ok := secondsTickMap[id]; ok { 139 | delete(secondsTickMap, id) 140 | dataPool.Put(data) 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /topic/topic-mgr.go: -------------------------------------------------------------------------------- 1 | package topic 2 | 3 | type SessoinQosMap map[interface{}]byte 4 | 5 | //订阅管理器 6 | type SubscriptionMgr interface { 7 | //添加订阅 8 | //subs和qoss根据索引位置关联 9 | Add(subs []string, qoss []byte, session interface{}) error 10 | //删除订阅 11 | //如果session!=nil,那么删除指定订阅下的session 12 | //如果session==nil,那么删除所以指定订阅 13 | Remove(filter string, session interface{}) error 14 | 15 | //将session从所有关联订阅中删除 16 | RemoveSession(session interface{}) 17 | //返回匹配主题的session列表 18 | GetSessions(topic string) (SessoinQosMap, error) 19 | 20 | //判断主题和订阅是否匹配 21 | IsTopicMatch(topic string, subscription string) bool 22 | } 23 | 24 | func NewSubscriptionMgr() SubscriptionMgr { 25 | return newNode() 26 | } 27 | -------------------------------------------------------------------------------- /topic/topic_test.go: -------------------------------------------------------------------------------- 1 | package topic 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func equalSlice(a, b []interface{}) bool { 8 | if len(a) != len(b) { 9 | return false 10 | } 11 | for i, v := range a { 12 | if v != b[i] { 13 | return false 14 | } 15 | } 16 | return true 17 | } 18 | func checkSessions(t *testing.T, m SessoinQosMap, s []int) { 19 | if len(m) != len(s) { 20 | t.FailNow() 21 | } 22 | for _, c := range s { 23 | if _, ok := m[c]; !ok { 24 | t.FailNow() 25 | } 26 | 27 | } 28 | } 29 | func TestNode(t *testing.T) { 30 | root := newNode() 31 | root.add("a/b/+", 1, 1) 32 | root.add("a/b/d", 0, 2) 33 | root.add("#", 1, 3) 34 | root.add("a/+/d", 2, 4) 35 | root.add("a/b/#", 1, 5) 36 | root.add("a/+/#", 1, 6) 37 | root.add("+/+/#", 0, 7) 38 | root.add("+/b/+", 2, 8) 39 | root.add("aaaaa/bbbbb/ccccc/ddddd/eeeee/fffff/ggggg/hhhhh/iiiii/jjjjj", 0, 9) 40 | 41 | ss := root.getSessions("a/b/d") 42 | 43 | ss = root.getSessions("a/b/c") 44 | checkSessions(t, ss, []int{1, 3, 5, 6, 7, 8}) 45 | 46 | ss = root.getSessions("f") 47 | checkSessions(t, ss, []int{3}) 48 | ss = root.getSessions("g/h") 49 | checkSessions(t, ss, []int{3}) 50 | 51 | ss = root.getSessions("a/2/d") 52 | checkSessions(t, ss, []int{3, 4, 6, 7}) 53 | 54 | } 55 | 56 | func BenchmarkNodeAdd(b *testing.B) { 57 | root := newNode() 58 | for i := 0; i < b.N; i++ { 59 | root.add("aaaaa/bbbbb/ccccc/ddddd/eeeee/fffff/ggggg/hhhhh/iiiii/jjjjj", 0, i) 60 | } 61 | 62 | } 63 | func BenchmarkNodeGetSessions(b *testing.B) { 64 | b.StopTimer() 65 | root := newNode() 66 | for i := 0; i < b.N; i++ { 67 | root.add("aaaaa/bbbbb/ccccc/ddddd/eeeee/fffff/ggggg/hhhhh/iiiii/jjjjj", 0, i) 68 | } 69 | b.StartTimer() 70 | for i := 0; i < b.N; i++ { 71 | root.getSessions("aaaaa/bbbbb/ccccc/ddddd/eeeee/fffff/ggggg/hhhhh/iiiii/jjjjj") 72 | } 73 | } 74 | 75 | func TestIsTopicMatch(t *testing.T) { 76 | root := newNode() 77 | 78 | topic := "a/b/c/d/e" 79 | sub := "a/b/c/d/e" 80 | if root.IsTopicMatch(topic, sub) != true { 81 | t.Fatal("a/b/c/d/e not match a/b/c/d/e") 82 | } 83 | sub = "a/b/+/d/e" 84 | if root.IsTopicMatch(topic, sub) != true { 85 | t.Fatal("a/b/c/d/e not match a/b/+/d/e") 86 | } 87 | sub = "a/#" 88 | if root.IsTopicMatch(topic, sub) != true { 89 | t.Fatal("a/b/c/d/e not match a/#") 90 | } 91 | sub = "a/b/+/#" 92 | if root.IsTopicMatch(topic, sub) != true { 93 | t.Fatal("a/b/c/d/e not match a/b/+/#") 94 | } 95 | topic = "a/b/c/d/e/d" 96 | sub = "a/b/c/d/e" 97 | if root.IsTopicMatch(topic, sub) == true { 98 | t.Fatal("a/b/c/d/e/d not match a/b/c/d/e") 99 | } 100 | topic = "a/b/c/d" 101 | sub = "a/b/c/d/e" 102 | if root.IsTopicMatch(topic, sub) == true { 103 | t.Fatal("a/b/c/d not match a/b/c/d/e") 104 | } 105 | topic = "a/b/c/d/" 106 | sub = "a/b/c/d/+" 107 | if root.IsTopicMatch(topic, sub) == false { 108 | t.Fatal("a/b/c/d/ not match a/b/c/d/+") 109 | } 110 | topic = "a/b/c/d" 111 | sub = "a/b/c/d/+" 112 | if root.IsTopicMatch(topic, sub) == false { 113 | t.Fatal("a/b/c/d not match a/b/c/d/+") 114 | } 115 | topic = "a/b/c/d/" 116 | sub = "a/b/c/d/#" 117 | if root.IsTopicMatch(topic, sub) == false { 118 | t.Fatal("a/b/c/d/ not match a/b/c/d/+") 119 | } 120 | topic = "a/b/c/d" 121 | sub = "a/b/c/d/#" 122 | if root.IsTopicMatch(topic, sub) == false { 123 | t.Fatal("a/b/c/d/ not match a/b/c/d/+") 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /topic/tree.go: -------------------------------------------------------------------------------- 1 | package topic 2 | 3 | import ( 4 | "sync" 5 | // "fmt" 6 | "strings" 7 | ) 8 | 9 | type treeSubsMgr struct { 10 | //如果没有子节点时,存储session和对应的qos 11 | sessionsMap SessoinQosMap 12 | 13 | //存储此级所有字节的name及对应的子节点 14 | subsMap map[string]*treeSubsMgr 15 | 16 | mux sync.Mutex 17 | } 18 | 19 | func newNode() *treeSubsMgr { 20 | return &treeSubsMgr{ 21 | sessionsMap: make(SessoinQosMap), 22 | subsMap: make(map[string]*treeSubsMgr), 23 | } 24 | } 25 | 26 | //将一个订阅添加到树中,并关联qos和session 27 | func (this *treeSubsMgr) add(filter string, qos byte, session interface{}) { 28 | this.insert(strings.Split(filter, "/"), 0, qos, session) 29 | } 30 | 31 | //将分好级的段递归插入 32 | //cur :当前级别,< fields的长度 33 | func (this *treeSubsMgr) insert(fields []string, cur int, qos byte, session interface{}) { 34 | var treeSubsMgr *treeSubsMgr 35 | var ok bool 36 | if treeSubsMgr, ok = this.subsMap[fields[cur]]; !ok { 37 | treeSubsMgr = newNode() 38 | this.subsMap[fields[cur]] = treeSubsMgr 39 | } 40 | if cur == (len(fields) - 1) { 41 | //叶节点 42 | treeSubsMgr.sessionsMap[session] = qos 43 | return 44 | } else { 45 | treeSubsMgr.insert(fields, cur+1, qos, session) 46 | } 47 | 48 | } 49 | 50 | //将订阅从树中删除 51 | //如果session != nil时,只删除filter下的这个session 52 | //如果session == nil,则将订阅从树中完全删除,包括子节点 53 | func (this *treeSubsMgr) del(filter string, session interface{}) { 54 | this.remove(strings.Split(filter, "/"), 0, session) 55 | } 56 | 57 | func (this *treeSubsMgr) remove(fields []string, cur int, session interface{}) { 58 | var treeSubsMgr *treeSubsMgr 59 | var ok bool 60 | if treeSubsMgr, ok = this.subsMap[fields[cur]]; !ok { 61 | return 62 | } 63 | if cur == (len(fields) - 1) { 64 | //叶节点 65 | if session == nil { 66 | //全部删除节点 67 | delete(treeSubsMgr.subsMap, fields[cur]) 68 | } else { 69 | //删除节点中的session 70 | delete(treeSubsMgr.sessionsMap, session) 71 | } 72 | 73 | return 74 | } else { 75 | treeSubsMgr.remove(fields, cur+1, session) 76 | } 77 | } 78 | 79 | //通过主题返回所有匹配的session 80 | func (this *treeSubsMgr) getSessions(topic string) SessoinQosMap { 81 | rm := make(SessoinQosMap) 82 | this._getSessions([]byte(topic), func(sm SessoinQosMap) { 83 | for k, v := range sm { 84 | rm[k] = v 85 | } 86 | }) 87 | return rm 88 | } 89 | 90 | func (this *treeSubsMgr) _getSessions(fields []byte, cb func(SessoinQosMap)) { 91 | var treeSubsMgr *treeSubsMgr 92 | var ok bool 93 | var l = len(fields) 94 | var field = string(fields) 95 | var i int 96 | if l <= 0 { 97 | return 98 | } 99 | for i = 0; i < l; i++ { 100 | if fields[i] == '/' { 101 | field = string(fields[:i]) 102 | fields = fields[i+1:] 103 | break 104 | } 105 | } 106 | 107 | if treeSubsMgr, ok = this.subsMap["#"]; ok { 108 | //如果有"#",则直接匹配 109 | cb(treeSubsMgr.sessionsMap) 110 | 111 | } 112 | if treeSubsMgr, ok = this.subsMap["+"]; ok { 113 | 114 | if i == l { 115 | //叶节点 116 | cb(treeSubsMgr.sessionsMap) 117 | } else { 118 | treeSubsMgr._getSessions(fields, cb) 119 | } 120 | } 121 | 122 | if treeSubsMgr, ok = this.subsMap[field]; ok { 123 | 124 | if i == l { 125 | //叶节点 126 | cb(treeSubsMgr.sessionsMap) 127 | } else { 128 | treeSubsMgr._getSessions(fields, cb) 129 | } 130 | } 131 | 132 | return 133 | } 134 | 135 | //添加订阅 136 | //subs和qoss根据索引位置关联 137 | func (this *treeSubsMgr) Add(subs []string, qoss []byte, session interface{}) error { 138 | this.mux.Lock() 139 | defer this.mux.Unlock() 140 | for i, s := range subs { 141 | this.add(string(s), qoss[i], session) 142 | } 143 | return nil 144 | } 145 | 146 | //删除订阅 147 | //如果session!=nil,那么删除指定订阅下的session 148 | //如果session==nil,那么删除所以指定订阅 149 | func (this *treeSubsMgr) Remove(filter string, session interface{}) error { 150 | this.mux.Lock() 151 | defer this.mux.Unlock() 152 | this.del(filter, session) 153 | return nil 154 | } 155 | 156 | //将session从所有关联订阅中删除 157 | func (this *treeSubsMgr) RemoveSession(session interface{}) { 158 | this.mux.Lock() 159 | defer this.mux.Unlock() 160 | if session == nil { 161 | panic("session is nil") 162 | } 163 | delete(this.sessionsMap, session) 164 | for _, n := range this.subsMap { 165 | n.RemoveSession(session) 166 | } 167 | return 168 | } 169 | 170 | //返回匹配主题的session列表 171 | func (this *treeSubsMgr) GetSessions(topic string) (SessoinQosMap, error) { 172 | this.mux.Lock() 173 | defer this.mux.Unlock() 174 | sqm := this.getSessions(topic) 175 | return sqm, nil 176 | } 177 | 178 | //判断主题和订阅是否匹配 179 | func (this *treeSubsMgr) IsTopicMatch(topic string, subscription string) bool { 180 | //TODO 181 | //要做subscription合法性校验 182 | topicLen := len(topic) 183 | subLen := len(subscription) 184 | i := 0 185 | j := 0 186 | for i < subLen && j < topicLen { 187 | //如果字体相等则比较下一个, 188 | //如果不等: 189 | //如果subscription[i]是“+”,那么topic直接查找'/‘或者结尾,之间的字体都认为是匹配的 190 | //如果subscription[i]是"#",那么topic后面的字符可以忽略,认为匹配 191 | //其它不匹配 192 | c := subscription[i] 193 | if c == topic[i] { 194 | i++ 195 | j++ 196 | continue 197 | } 198 | switch c { 199 | case '+': 200 | for j < topicLen { 201 | if topic[j] == '/' { 202 | break 203 | } 204 | j++ 205 | } 206 | i++ 207 | case '#': 208 | return true 209 | default: 210 | return false 211 | } 212 | } 213 | if i != subLen || j != topicLen { 214 | //处理topic比subscription短的情况 215 | if j == topicLen { 216 | tail := string(subscription[i:]) 217 | switch tail { 218 | case "+", "#", "/+", "/#": 219 | return true 220 | } 221 | } 222 | return false 223 | } 224 | return true 225 | } 226 | func (this *treeSubsMgr) IsSubscriptionValid(sub string) { 227 | 228 | } 229 | -------------------------------------------------------------------------------- /ubuntu.sh: -------------------------------------------------------------------------------- 1 | # 2 millions system-wide 2 | sysctl -w fs.file-max=10485760 3 | sysctl -w fs.nr_open=10485760 4 | echo 2097152 > /proc/sys/fs/nr_open 5 | 6 | ulimit -n 1048575 7 | 8 | 9 | 10 | sysctl -w net.core.somaxconn=32768 11 | #表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。 12 | sysctl -w net.ipv4.tcp_max_syn_backlog=16384 13 | # increase the length of the processor input queue 14 | sysctl -w net.core.netdev_max_backlog=16384 15 | #表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1000 - 65535。 16 | #(注意:这里不要将最低值设的太低,否则可能会占用掉正常的端口!) 17 | sysctl -w net.ipv4.ip_local_port_range='1024 65535' 18 | 19 | #sysctl -w net.ipv4.tcp_tw_recycle=1 #快速回收time_wait的连接 20 | #sysctl -w net.ipv4.tcp_tw_reuse=1 21 | #sysctl -w net.ipv4.tcp_timestamps=1 22 | 23 | sysctl -w net.core.rmem_default=262144 24 | sysctl -w net.core.wmem_default=262144 25 | sysctl -w net.core.rmem_max=16777216 26 | sysctl -w net.core.wmem_max=16777216 27 | sysctl -w net.core.optmem_max=16777216 28 | 29 | #sysctl -w net.ipv4.tcp_mem='16777216 16777216 16777216' 30 | sysctl -w net.ipv4.tcp_rmem=1024 31 | sysctl -w net.ipv4.tcp_wmem=1024 32 | #表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭; 33 | sysctl -w net.ipv4.tcp_syncookies=1 34 | #修改系統默认的 TIMEOUT 时间。 35 | sysctl -w net.ipv4.tcp_fin_timeout=30 36 | #表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。 37 | sysctl -w net.ipv4.tcp_keepalive_time=1200 38 | 39 | 40 | #额外的,对于内核版本新于**3.7.1**的,我们可以开启tcp_fastopen: 41 | sysctl -w net.ipv4.tcp_fastopen=3 42 | 43 | # recommended for hosts with jumbo frames enabled 44 | sysctl -w net.ipv4.tcp_mtu_probing=1 45 | 46 | #sysctl -w net.nf_conntrack_max=1000000 47 | #sysctl -w net.netfilter.nf_conntrack_max=1000000 48 | #sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | scp ubuntu.sh root@$1:~/ 2 | scp benchmark/benchmark root@$1:~/ 3 | ssh root@$1 4 | 5 | 6 | -------------------------------------------------------------------------------- /utils/event.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type Event struct { 4 | ch chan bool 5 | } 6 | 7 | func NewEvent() *Event { 8 | ev := &Event{ 9 | ch: make(chan bool, 1), 10 | } 11 | 12 | return ev 13 | } 14 | func (this *Event) Signal() { 15 | select { 16 | case this.ch <- true: 17 | default: 18 | } 19 | } 20 | 21 | func (this *Event) Wait() { 22 | <-this.ch 23 | } 24 | -------------------------------------------------------------------------------- /utils/event_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEvent(t *testing.T) { 8 | ev := NewEvent() 9 | ev.Signal() 10 | ev.Signal() 11 | ev.Wait() 12 | } 13 | -------------------------------------------------------------------------------- /utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | ) 10 | 11 | type Mux struct { 12 | *http.ServeMux 13 | } 14 | 15 | func NewHttpMux() *Mux { 16 | return &Mux{ 17 | ServeMux: http.NewServeMux(), 18 | } 19 | } 20 | 21 | func (this *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 22 | this.ServeMux.ServeHTTP(w, r) 23 | } 24 | 25 | func DirHandler(pattern, dir string) http.HandlerFunc { 26 | return func(w http.ResponseWriter, r *http.Request) { 27 | var _url = r.URL.Path 28 | 29 | var file = strings.Replace(_url, pattern, dir, 1) 30 | if file == _url { 31 | log.Errorf("not math dir") 32 | //不是相对于dir的url,在程序当前目录找 33 | file = fmt.Sprintf(".%s", _url) 34 | } 35 | 36 | log.Infof("HandleDir:r.RequestURI:%s,%#v,file:%s", r.RequestURI, r.URL, file) 37 | http.ServeFile(w, r, file) 38 | } 39 | } 40 | func (this *Mux) HandleDir(pattern, dir string) { 41 | this.ServeMux.HandleFunc(pattern, DirHandler(pattern, dir)) 42 | } 43 | -------------------------------------------------------------------------------- /utils/id.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "net" 8 | "time" 9 | 10 | log "github.com/Sirupsen/logrus" 11 | ) 12 | 13 | var localId string 14 | 15 | func init() { 16 | netifs, err := net.Interfaces() 17 | md5hash := md5.New() 18 | if err != nil { 19 | panic(err) 20 | } 21 | for _, nif := range netifs { 22 | mac := nif.HardwareAddr.String() 23 | log.Debug("mac:%s", mac) 24 | md5hash.Write([]byte(mac)) 25 | } 26 | localId = hex.EncodeToString(md5hash.Sum(nil)) 27 | log.Infof("localId:%s", localId) 28 | } 29 | func NewId() string { 30 | sum := md5.Sum([]byte(fmt.Sprintf("%s%d", localId, time.Now().UnixNano()))) 31 | return hex.EncodeToString(sum[:]) 32 | 33 | } 34 | 35 | func LocalId() string { 36 | return localId 37 | } 38 | -------------------------------------------------------------------------------- /utils/list.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | ) 7 | 8 | type List struct { 9 | l *list.List 10 | len int 11 | mux sync.Mutex 12 | ch chan byte 13 | } 14 | 15 | func NewList() *List { 16 | return &List{ 17 | l: list.New(), 18 | ch: make(chan byte, 1), 19 | } 20 | } 21 | 22 | func (this *List) Push(v interface{}) { 23 | if v == nil { 24 | panic(nil) 25 | } 26 | this.mux.Lock() 27 | this.len++ 28 | this.l.PushBack(v) 29 | this.mux.Unlock() 30 | 31 | select { 32 | case this.ch <- 0: 33 | default: 34 | } 35 | } 36 | func (this *List) Pop() interface{} { 37 | this.mux.Lock() 38 | defer this.mux.Unlock() 39 | 40 | if this.l.Len() > 0 { 41 | v := this.l.Remove(this.l.Front()) 42 | if this.l.Len() > 0 { 43 | select { 44 | case this.ch <- 0: 45 | default: 46 | } 47 | } 48 | 49 | return v 50 | } 51 | return nil 52 | } 53 | func (this *List) Len() int { 54 | this.mux.Lock() 55 | defer this.mux.Unlock() 56 | return this.l.Len() 57 | } 58 | 59 | //遍历列表 60 | //cb返回true时停止遍历,false继续遍历 61 | func (this *List) Each(cb func(v interface{}) (stop bool)) { 62 | this.mux.Lock() 63 | defer this.mux.Unlock() 64 | for f := this.l.Front(); f != nil; f = f.Next() { 65 | stop := cb(f.Value) 66 | if stop { 67 | break 68 | } 69 | } 70 | } 71 | 72 | //删除元素 73 | //cb返回true时删除当前元素 74 | func (this *List) Remove(cb func(v interface{}) (del, c bool)) { 75 | this.mux.Lock() 76 | defer this.mux.Unlock() 77 | for f := this.l.Front(); f != nil; { 78 | del, _continue := cb(f.Value) 79 | if del { 80 | n := f.Next() 81 | this.l.Remove(f) 82 | f = n 83 | } else { 84 | f = f.Next() 85 | } 86 | if !_continue { 87 | break 88 | } 89 | } 90 | } 91 | 92 | func (this *List) Wait() <-chan byte { 93 | return this.ch 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *.msg 25 | *.lok 26 | 27 | samples/trivial 28 | samples/trivial2 29 | samples/sample 30 | samples/reconnect 31 | samples/ssl 32 | samples/custom_store 33 | samples/simple 34 | samples/stdinpub 35 | samples/stdoutsub 36 | samples/routing -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/connack.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //ConnackPacket is an internal representation of the fields of the 10 | //Connack MQTT packet 11 | type ConnackPacket struct { 12 | FixedHeader 13 | SessionPresent bool 14 | ReturnCode byte 15 | } 16 | 17 | func (ca *ConnackPacket) Type() byte { 18 | return ca.FixedHeader.MessageType 19 | } 20 | func (ca *ConnackPacket) String() string { 21 | str := fmt.Sprintf("%s\n", ca.FixedHeader) 22 | str += fmt.Sprintf("sessionpresent: %t returncode: %d", ca.SessionPresent, ca.ReturnCode) 23 | return str 24 | } 25 | 26 | func (ca *ConnackPacket) Write(w io.Writer) error { 27 | var body bytes.Buffer 28 | var err error 29 | 30 | body.WriteByte(boolToByte(ca.SessionPresent)) 31 | body.WriteByte(ca.ReturnCode) 32 | ca.FixedHeader.RemainingLength = 2 33 | packet := ca.FixedHeader.pack() 34 | packet.Write(body.Bytes()) 35 | _, err = packet.WriteTo(w) 36 | 37 | return err 38 | } 39 | 40 | //Unpack decodes the details of a ControlPacket after the fixed 41 | //header has been read 42 | func (ca *ConnackPacket) Unpack(b io.Reader) error { 43 | ca.SessionPresent = 1&decodeByte(b) > 0 44 | ca.ReturnCode = decodeByte(b) 45 | 46 | return nil 47 | } 48 | 49 | //Details returns a Details struct containing the Qos and 50 | //MessageID of this ControlPacket 51 | func (ca *ConnackPacket) Details() Details { 52 | return Details{Qos: 0, MessageID: 0} 53 | } 54 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/connect.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //ConnectPacket is an internal representation of the fields of the 10 | //Connect MQTT packet 11 | type ConnectPacket struct { 12 | FixedHeader 13 | ProtocolName string 14 | ProtocolVersion byte 15 | CleanSession bool 16 | WillFlag bool 17 | WillQos byte 18 | WillRetain bool 19 | UsernameFlag bool 20 | PasswordFlag bool 21 | ReservedBit byte 22 | Keepalive uint16 23 | 24 | ClientIdentifier string 25 | WillTopic string 26 | WillMessage []byte 27 | Username string 28 | Password []byte 29 | } 30 | 31 | func (c *ConnectPacket) Type() byte { 32 | return c.FixedHeader.MessageType 33 | } 34 | func (c *ConnectPacket) String() string { 35 | str := fmt.Sprintf("%s\n", c.FixedHeader) 36 | str += fmt.Sprintf("protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d\nclientId: %s\nwilltopic: %s\nwillmessage: %s\nUsername: %s\nPassword: %s\n", c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, c.Password) 37 | return str 38 | } 39 | 40 | func (c *ConnectPacket) Write(w io.Writer) error { 41 | var body bytes.Buffer 42 | var err error 43 | 44 | body.Write(encodeString(c.ProtocolName)) 45 | body.WriteByte(c.ProtocolVersion) 46 | body.WriteByte(boolToByte(c.CleanSession)<<1 | boolToByte(c.WillFlag)<<2 | c.WillQos<<3 | boolToByte(c.WillRetain)<<5 | boolToByte(c.PasswordFlag)<<6 | boolToByte(c.UsernameFlag)<<7) 47 | body.Write(encodeUint16(c.Keepalive)) 48 | body.Write(encodeString(c.ClientIdentifier)) 49 | if c.WillFlag { 50 | body.Write(encodeString(c.WillTopic)) 51 | body.Write(encodeBytes(c.WillMessage)) 52 | } 53 | if c.UsernameFlag { 54 | body.Write(encodeString(c.Username)) 55 | } 56 | if c.PasswordFlag { 57 | body.Write(encodeBytes(c.Password)) 58 | } 59 | c.FixedHeader.RemainingLength = body.Len() 60 | packet := c.FixedHeader.pack() 61 | packet.Write(body.Bytes()) 62 | _, err = packet.WriteTo(w) 63 | 64 | return err 65 | } 66 | 67 | //Unpack decodes the details of a ControlPacket after the fixed 68 | //header has been read 69 | func (c *ConnectPacket) Unpack(b io.Reader) error { 70 | c.ProtocolName = decodeString(b) 71 | c.ProtocolVersion = decodeByte(b) 72 | options := decodeByte(b) 73 | c.ReservedBit = 1 & options 74 | c.CleanSession = 1&(options>>1) > 0 75 | c.WillFlag = 1&(options>>2) > 0 76 | c.WillQos = 3 & (options >> 3) 77 | c.WillRetain = 1&(options>>5) > 0 78 | c.PasswordFlag = 1&(options>>6) > 0 79 | c.UsernameFlag = 1&(options>>7) > 0 80 | c.Keepalive = decodeUint16(b) 81 | c.ClientIdentifier = decodeString(b) 82 | if c.WillFlag { 83 | c.WillTopic = decodeString(b) 84 | c.WillMessage = decodeBytes(b) 85 | } 86 | if c.UsernameFlag { 87 | c.Username = decodeString(b) 88 | } 89 | if c.PasswordFlag { 90 | c.Password = decodeBytes(b) 91 | } 92 | 93 | return nil 94 | } 95 | 96 | //Validate performs validation of the fields of a Connect packet 97 | func (c *ConnectPacket) Validate() byte { 98 | if c.PasswordFlag && !c.UsernameFlag { 99 | return ErrRefusedBadUsernameOrPassword 100 | } 101 | if c.ReservedBit != 0 { 102 | //Bad reserved bit 103 | return ErrProtocolViolation 104 | } 105 | if (c.ProtocolName == "MQIsdp" && c.ProtocolVersion != 3) || (c.ProtocolName == "MQTT" && c.ProtocolVersion != 4) { 106 | //Mismatched or unsupported protocol version 107 | return ErrRefusedBadProtocolVersion 108 | } 109 | if c.ProtocolName != "MQIsdp" && c.ProtocolName != "MQTT" { 110 | //Bad protocol name 111 | return ErrProtocolViolation 112 | } 113 | if len(c.ClientIdentifier) > 65535 || len(c.Username) > 65535 || len(c.Password) > 65535 { 114 | //Bad size field 115 | return ErrProtocolViolation 116 | } 117 | return Accepted 118 | } 119 | 120 | //Details returns a Details struct containing the Qos and 121 | //MessageID of this ControlPacket 122 | func (c *ConnectPacket) Details() Details { 123 | return Details{Qos: 0, MessageID: 0} 124 | } 125 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/disconnect.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //DisconnectPacket is an internal representation of the fields of the 9 | //Disconnect MQTT packet 10 | type DisconnectPacket struct { 11 | FixedHeader 12 | } 13 | 14 | func (d *DisconnectPacket) Type() byte { 15 | return d.FixedHeader.MessageType 16 | } 17 | func (d *DisconnectPacket) String() string { 18 | str := fmt.Sprintf("%s\n", d.FixedHeader) 19 | return str 20 | } 21 | 22 | func (d *DisconnectPacket) Write(w io.Writer) error { 23 | packet := d.FixedHeader.pack() 24 | _, err := packet.WriteTo(w) 25 | 26 | return err 27 | } 28 | 29 | //Unpack decodes the details of a ControlPacket after the fixed 30 | //header has been read 31 | func (d *DisconnectPacket) Unpack(b io.Reader) error { 32 | return nil 33 | } 34 | 35 | //Details returns a Details struct containing the Qos and 36 | //MessageID of this ControlPacket 37 | func (d *DisconnectPacket) Details() Details { 38 | return Details{Qos: 0, MessageID: 0} 39 | } 40 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/packets.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | //ControlPacket defines the interface for structs intended to hold 12 | //decoded MQTT packets, either from being read or before being 13 | //written 14 | type ControlPacket interface { 15 | Write(io.Writer) error 16 | Unpack(io.Reader) error 17 | String() string 18 | Details() Details 19 | Type() byte 20 | } 21 | 22 | //PacketNames maps the constants for each of the MQTT packet types 23 | //to a string representation of their name. 24 | var PacketNames = map[uint8]string{ 25 | 1: "CONNECT", 26 | 2: "CONNACK", 27 | 3: "PUBLISH", 28 | 4: "PUBACK", 29 | 5: "PUBREC", 30 | 6: "PUBREL", 31 | 7: "PUBCOMP", 32 | 8: "SUBSCRIBE", 33 | 9: "SUBACK", 34 | 10: "UNSUBSCRIBE", 35 | 11: "UNSUBACK", 36 | 12: "PINGREQ", 37 | 13: "PINGRESP", 38 | 14: "DISCONNECT", 39 | } 40 | 41 | //Below are the constants assigned to each of the MQTT packet types 42 | const ( 43 | Connect = 1 44 | Connack = 2 45 | Publish = 3 46 | Puback = 4 47 | Pubrec = 5 48 | Pubrel = 6 49 | Pubcomp = 7 50 | Subscribe = 8 51 | Suback = 9 52 | Unsubscribe = 10 53 | Unsuback = 11 54 | Pingreq = 12 55 | Pingresp = 13 56 | Disconnect = 14 57 | ) 58 | 59 | //Below are the const definitions for error codes returned by 60 | //Connect() 61 | const ( 62 | Accepted = 0x00 63 | ErrRefusedBadProtocolVersion = 0x01 64 | ErrRefusedIDRejected = 0x02 65 | ErrRefusedServerUnavailable = 0x03 66 | ErrRefusedBadUsernameOrPassword = 0x04 67 | ErrRefusedNotAuthorised = 0x05 68 | ErrNetworkError = 0xFE 69 | ErrProtocolViolation = 0xFF 70 | ) 71 | 72 | //ConnackReturnCodes is a map of the error codes constants for Connect() 73 | //to a string representation of the error 74 | var ConnackReturnCodes = map[uint8]string{ 75 | 0: "Connection Accepted", 76 | 1: "Connection Refused: Bad Protocol Version", 77 | 2: "Connection Refused: Client Identifier Rejected", 78 | 3: "Connection Refused: Server Unavailable", 79 | 4: "Connection Refused: Username or Password in unknown format", 80 | 5: "Connection Refused: Not Authorised", 81 | 254: "Connection Error", 82 | 255: "Connection Refused: Protocol Violation", 83 | } 84 | 85 | //ConnErrors is a map of the errors codes constants for Connect() 86 | //to a Go error 87 | var ConnErrors = map[byte]error{ 88 | Accepted: nil, 89 | ErrRefusedBadProtocolVersion: errors.New("Unnacceptable protocol version"), 90 | ErrRefusedIDRejected: errors.New("Identifier rejected"), 91 | ErrRefusedServerUnavailable: errors.New("Server Unavailable"), 92 | ErrRefusedBadUsernameOrPassword: errors.New("Bad user name or password"), 93 | ErrRefusedNotAuthorised: errors.New("Not Authorized"), 94 | ErrNetworkError: errors.New("Network Error"), 95 | ErrProtocolViolation: errors.New("Protocol Violation"), 96 | } 97 | 98 | //ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts 99 | //to read an MQTT packet from the stream. It returns a ControlPacket 100 | //representing the decoded MQTT packet and an error. One of these returns will 101 | //always be nil, a nil ControlPacket indicating an error occurred. 102 | func ReadPacket(r io.Reader) (cp ControlPacket, err error) { 103 | var fh FixedHeader 104 | b := make([]byte, 1) 105 | 106 | _, err = io.ReadFull(r, b) 107 | if err != nil { 108 | return nil, err 109 | } 110 | fh.unpack(b[0], r) 111 | cp = NewControlPacketWithHeader(fh) 112 | if cp == nil { 113 | return nil, errors.New("Bad data from client") 114 | } 115 | packetBytes := make([]byte, fh.RemainingLength) 116 | _, err = io.ReadFull(r, packetBytes) 117 | if err != nil { 118 | return nil, err 119 | } 120 | err = cp.Unpack(bytes.NewBuffer(packetBytes)) 121 | return cp, err 122 | } 123 | 124 | //NewControlPacket is used to create a new ControlPacket of the type specified 125 | //by packetType, this is usually done by reference to the packet type constants 126 | //defined in packets.go. The newly created ControlPacket is empty and a pointer 127 | //is returned. 128 | func NewControlPacket(packetType byte) (cp ControlPacket) { 129 | switch packetType { 130 | case Connect: 131 | cp = &ConnectPacket{FixedHeader: FixedHeader{MessageType: Connect}} 132 | case Connack: 133 | cp = &ConnackPacket{FixedHeader: FixedHeader{MessageType: Connack}} 134 | case Disconnect: 135 | cp = &DisconnectPacket{FixedHeader: FixedHeader{MessageType: Disconnect}} 136 | case Publish: 137 | cp = &PublishPacket{FixedHeader: FixedHeader{MessageType: Publish}} 138 | case Puback: 139 | cp = &PubackPacket{FixedHeader: FixedHeader{MessageType: Puback}} 140 | case Pubrec: 141 | cp = &PubrecPacket{FixedHeader: FixedHeader{MessageType: Pubrec}} 142 | case Pubrel: 143 | cp = &PubrelPacket{FixedHeader: FixedHeader{MessageType: Pubrel, Qos: 1}} 144 | case Pubcomp: 145 | cp = &PubcompPacket{FixedHeader: FixedHeader{MessageType: Pubcomp}} 146 | case Subscribe: 147 | cp = &SubscribePacket{FixedHeader: FixedHeader{MessageType: Subscribe, Qos: 1}} 148 | case Suback: 149 | cp = &SubackPacket{FixedHeader: FixedHeader{MessageType: Suback}} 150 | case Unsubscribe: 151 | cp = &UnsubscribePacket{FixedHeader: FixedHeader{MessageType: Unsubscribe, Qos: 1}} 152 | case Unsuback: 153 | cp = &UnsubackPacket{FixedHeader: FixedHeader{MessageType: Unsuback}} 154 | case Pingreq: 155 | cp = &PingreqPacket{FixedHeader: FixedHeader{MessageType: Pingreq}} 156 | case Pingresp: 157 | cp = &PingrespPacket{FixedHeader: FixedHeader{MessageType: Pingresp}} 158 | default: 159 | return nil 160 | } 161 | return cp 162 | } 163 | 164 | //NewControlPacketWithHeader is used to create a new ControlPacket of the type 165 | //specified within the FixedHeader that is passed to the function. 166 | //The newly created ControlPacket is empty and a pointer is returned. 167 | func NewControlPacketWithHeader(fh FixedHeader) (cp ControlPacket) { 168 | switch fh.MessageType { 169 | case Connect: 170 | cp = &ConnectPacket{FixedHeader: fh} 171 | case Connack: 172 | cp = &ConnackPacket{FixedHeader: fh} 173 | case Disconnect: 174 | cp = &DisconnectPacket{FixedHeader: fh} 175 | case Publish: 176 | cp = &PublishPacket{FixedHeader: fh} 177 | case Puback: 178 | cp = &PubackPacket{FixedHeader: fh} 179 | case Pubrec: 180 | cp = &PubrecPacket{FixedHeader: fh} 181 | case Pubrel: 182 | cp = &PubrelPacket{FixedHeader: fh} 183 | case Pubcomp: 184 | cp = &PubcompPacket{FixedHeader: fh} 185 | case Subscribe: 186 | cp = &SubscribePacket{FixedHeader: fh} 187 | case Suback: 188 | cp = &SubackPacket{FixedHeader: fh} 189 | case Unsubscribe: 190 | cp = &UnsubscribePacket{FixedHeader: fh} 191 | case Unsuback: 192 | cp = &UnsubackPacket{FixedHeader: fh} 193 | case Pingreq: 194 | cp = &PingreqPacket{FixedHeader: fh} 195 | case Pingresp: 196 | cp = &PingrespPacket{FixedHeader: fh} 197 | default: 198 | return nil 199 | } 200 | return cp 201 | } 202 | 203 | //Details struct returned by the Details() function called on 204 | //ControlPackets to present details of the Qos and MessageID 205 | //of the ControlPacket 206 | type Details struct { 207 | Qos byte 208 | MessageID uint16 209 | } 210 | 211 | //FixedHeader is a struct to hold the decoded information from 212 | //the fixed header of an MQTT ControlPacket 213 | type FixedHeader struct { 214 | MessageType byte 215 | Dup bool 216 | Qos byte 217 | Retain bool 218 | RemainingLength int 219 | } 220 | 221 | func (fh FixedHeader) String() string { 222 | return fmt.Sprintf("%s: dup: %t qos: %d retain: %t rLength: %d", PacketNames[fh.MessageType], fh.Dup, fh.Qos, fh.Retain, fh.RemainingLength) 223 | } 224 | 225 | func boolToByte(b bool) byte { 226 | switch b { 227 | case true: 228 | return 1 229 | default: 230 | return 0 231 | } 232 | } 233 | 234 | func (fh *FixedHeader) pack() bytes.Buffer { 235 | var header bytes.Buffer 236 | header.WriteByte(fh.MessageType<<4 | boolToByte(fh.Dup)<<3 | fh.Qos<<1 | boolToByte(fh.Retain)) 237 | header.Write(encodeLength(fh.RemainingLength)) 238 | return header 239 | } 240 | 241 | func (fh *FixedHeader) unpack(typeAndFlags byte, r io.Reader) { 242 | fh.MessageType = typeAndFlags >> 4 243 | fh.Dup = (typeAndFlags>>3)&0x01 > 0 244 | fh.Qos = (typeAndFlags >> 1) & 0x03 245 | fh.Retain = typeAndFlags&0x01 > 0 246 | fh.RemainingLength = decodeLength(r) 247 | } 248 | 249 | func decodeByte(b io.Reader) byte { 250 | num := make([]byte, 1) 251 | b.Read(num) 252 | return num[0] 253 | } 254 | 255 | func decodeUint16(b io.Reader) uint16 { 256 | num := make([]byte, 2) 257 | b.Read(num) 258 | return binary.BigEndian.Uint16(num) 259 | } 260 | 261 | func encodeUint16(num uint16) []byte { 262 | bytes := make([]byte, 2) 263 | binary.BigEndian.PutUint16(bytes, num) 264 | return bytes 265 | } 266 | 267 | func encodeString(field string) []byte { 268 | fieldLength := make([]byte, 2) 269 | binary.BigEndian.PutUint16(fieldLength, uint16(len(field))) 270 | return append(fieldLength, []byte(field)...) 271 | } 272 | 273 | func decodeString(b io.Reader) string { 274 | fieldLength := decodeUint16(b) 275 | field := make([]byte, fieldLength) 276 | b.Read(field) 277 | return string(field) 278 | } 279 | 280 | func decodeBytes(b io.Reader) []byte { 281 | fieldLength := decodeUint16(b) 282 | field := make([]byte, fieldLength) 283 | b.Read(field) 284 | return field 285 | } 286 | 287 | func encodeBytes(field []byte) []byte { 288 | fieldLength := make([]byte, 2) 289 | binary.BigEndian.PutUint16(fieldLength, uint16(len(field))) 290 | return append(fieldLength, field...) 291 | } 292 | 293 | func encodeLength(length int) []byte { 294 | var encLength []byte 295 | for { 296 | digit := byte(length % 128) 297 | length /= 128 298 | if length > 0 { 299 | digit |= 0x80 300 | } 301 | encLength = append(encLength, digit) 302 | if length == 0 { 303 | break 304 | } 305 | } 306 | return encLength 307 | } 308 | 309 | func decodeLength(r io.Reader) int { 310 | var rLength uint32 311 | var multiplier uint32 312 | b := make([]byte, 1) 313 | for multiplier < 27 { //fix: Infinite '(digit & 128) == 1' will cause the dead loop 314 | io.ReadFull(r, b) 315 | digit := b[0] 316 | rLength |= uint32(digit&127) << multiplier 317 | if (digit & 128) == 0 { 318 | break 319 | } 320 | multiplier += 7 321 | } 322 | return int(rLength) 323 | } 324 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/packets_test.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestPacketNames(t *testing.T) { 9 | if PacketNames[1] != "CONNECT" { 10 | t.Errorf("PacketNames[1] is %s, should be %s", PacketNames[1], "CONNECT") 11 | } 12 | if PacketNames[2] != "CONNACK" { 13 | t.Errorf("PacketNames[2] is %s, should be %s", PacketNames[2], "CONNACK") 14 | } 15 | if PacketNames[3] != "PUBLISH" { 16 | t.Errorf("PacketNames[3] is %s, should be %s", PacketNames[3], "PUBLISH") 17 | } 18 | if PacketNames[4] != "PUBACK" { 19 | t.Errorf("PacketNames[4] is %s, should be %s", PacketNames[4], "PUBACK") 20 | } 21 | if PacketNames[5] != "PUBREC" { 22 | t.Errorf("PacketNames[5] is %s, should be %s", PacketNames[5], "PUBREC") 23 | } 24 | if PacketNames[6] != "PUBREL" { 25 | t.Errorf("PacketNames[6] is %s, should be %s", PacketNames[6], "PUBREL") 26 | } 27 | if PacketNames[7] != "PUBCOMP" { 28 | t.Errorf("PacketNames[7] is %s, should be %s", PacketNames[7], "PUBCOMP") 29 | } 30 | if PacketNames[8] != "SUBSCRIBE" { 31 | t.Errorf("PacketNames[8] is %s, should be %s", PacketNames[8], "SUBSCRIBE") 32 | } 33 | if PacketNames[9] != "SUBACK" { 34 | t.Errorf("PacketNames[9] is %s, should be %s", PacketNames[9], "SUBACK") 35 | } 36 | if PacketNames[10] != "UNSUBSCRIBE" { 37 | t.Errorf("PacketNames[10] is %s, should be %s", PacketNames[10], "UNSUBSCRIBE") 38 | } 39 | if PacketNames[11] != "UNSUBACK" { 40 | t.Errorf("PacketNames[11] is %s, should be %s", PacketNames[11], "UNSUBACK") 41 | } 42 | if PacketNames[12] != "PINGREQ" { 43 | t.Errorf("PacketNames[12] is %s, should be %s", PacketNames[12], "PINGREQ") 44 | } 45 | if PacketNames[13] != "PINGRESP" { 46 | t.Errorf("PacketNames[13] is %s, should be %s", PacketNames[13], "PINGRESP") 47 | } 48 | if PacketNames[14] != "DISCONNECT" { 49 | t.Errorf("PacketNames[14] is %s, should be %s", PacketNames[14], "DISCONNECT") 50 | } 51 | } 52 | 53 | func TestPacketConsts(t *testing.T) { 54 | if Connect != 1 { 55 | t.Errorf("Const for Connect is %d, should be %d", Connect, 1) 56 | } 57 | if Connack != 2 { 58 | t.Errorf("Const for Connack is %d, should be %d", Connack, 2) 59 | } 60 | if Publish != 3 { 61 | t.Errorf("Const for Publish is %d, should be %d", Publish, 3) 62 | } 63 | if Puback != 4 { 64 | t.Errorf("Const for Puback is %d, should be %d", Puback, 4) 65 | } 66 | if Pubrec != 5 { 67 | t.Errorf("Const for Pubrec is %d, should be %d", Pubrec, 5) 68 | } 69 | if Pubrel != 6 { 70 | t.Errorf("Const for Pubrel is %d, should be %d", Pubrel, 6) 71 | } 72 | if Pubcomp != 7 { 73 | t.Errorf("Const for Pubcomp is %d, should be %d", Pubcomp, 7) 74 | } 75 | if Subscribe != 8 { 76 | t.Errorf("Const for Subscribe is %d, should be %d", Subscribe, 8) 77 | } 78 | if Suback != 9 { 79 | t.Errorf("Const for Suback is %d, should be %d", Suback, 9) 80 | } 81 | if Unsubscribe != 10 { 82 | t.Errorf("Const for Unsubscribe is %d, should be %d", Unsubscribe, 10) 83 | } 84 | if Unsuback != 11 { 85 | t.Errorf("Const for Unsuback is %d, should be %d", Unsuback, 11) 86 | } 87 | if Pingreq != 12 { 88 | t.Errorf("Const for Pingreq is %d, should be %d", Pingreq, 12) 89 | } 90 | if Pingresp != 13 { 91 | t.Errorf("Const for Pingresp is %d, should be %d", Pingresp, 13) 92 | } 93 | if Disconnect != 14 { 94 | t.Errorf("Const for Disconnect is %d, should be %d", Disconnect, 14) 95 | } 96 | } 97 | 98 | func TestConnackConsts(t *testing.T) { 99 | if Accepted != 0x00 { 100 | t.Errorf("Const for Accepted is %d, should be %d", Accepted, 0) 101 | } 102 | if ErrRefusedBadProtocolVersion != 0x01 { 103 | t.Errorf("Const for RefusedBadProtocolVersion is %d, should be %d", ErrRefusedBadProtocolVersion, 1) 104 | } 105 | if ErrRefusedIDRejected != 0x02 { 106 | t.Errorf("Const for RefusedIDRejected is %d, should be %d", ErrRefusedIDRejected, 2) 107 | } 108 | if ErrRefusedServerUnavailable != 0x03 { 109 | t.Errorf("Const for RefusedServerUnavailable is %d, should be %d", ErrRefusedServerUnavailable, 3) 110 | } 111 | if ErrRefusedBadUsernameOrPassword != 0x04 { 112 | t.Errorf("Const for RefusedBadUsernameOrPassword is %d, should be %d", ErrRefusedBadUsernameOrPassword, 4) 113 | } 114 | if ErrRefusedNotAuthorised != 0x05 { 115 | t.Errorf("Const for RefusedNotAuthorised is %d, should be %d", ErrRefusedNotAuthorised, 5) 116 | } 117 | } 118 | 119 | func TestConnectPacket(t *testing.T) { 120 | connectPacketBytes := bytes.NewBuffer([]byte{16, 52, 0, 4, 77, 81, 84, 84, 4, 204, 0, 0, 0, 0, 0, 4, 116, 101, 115, 116, 0, 12, 84, 101, 115, 116, 32, 80, 97, 121, 108, 111, 97, 100, 0, 8, 116, 101, 115, 116, 117, 115, 101, 114, 0, 8, 116, 101, 115, 116, 112, 97, 115, 115}) 121 | packet, err := ReadPacket(connectPacketBytes) 122 | if err != nil { 123 | t.Fatalf("Error reading packet: %s", err.Error()) 124 | } 125 | cp := packet.(*ConnectPacket) 126 | if cp.ProtocolName != "MQTT" { 127 | t.Errorf("Connect Packet ProtocolName is %s, should be %s", cp.ProtocolName, "MQTT") 128 | } 129 | if cp.ProtocolVersion != 4 { 130 | t.Errorf("Connect Packet ProtocolVersion is %d, should be %d", cp.ProtocolVersion, 4) 131 | } 132 | if cp.UsernameFlag != true { 133 | t.Errorf("Connect Packet UsernameFlag is %t, should be %t", cp.UsernameFlag, true) 134 | } 135 | if cp.Username != "testuser" { 136 | t.Errorf("Connect Packet Username is %s, should be %s", cp.Username, "testuser") 137 | } 138 | if cp.PasswordFlag != true { 139 | t.Errorf("Connect Packet PasswordFlag is %t, should be %t", cp.PasswordFlag, true) 140 | } 141 | if string(cp.Password) != "testpass" { 142 | t.Errorf("Connect Packet Password is %s, should be %s", string(cp.Password), "testpass") 143 | } 144 | if cp.WillFlag != true { 145 | t.Errorf("Connect Packet WillFlag is %t, should be %t", cp.WillFlag, true) 146 | } 147 | if cp.WillTopic != "test" { 148 | t.Errorf("Connect Packet WillTopic is %s, should be %s", cp.WillTopic, "test") 149 | } 150 | if cp.WillQos != 1 { 151 | t.Errorf("Connect Packet WillQos is %d, should be %d", cp.WillQos, 1) 152 | } 153 | if cp.WillRetain != false { 154 | t.Errorf("Connect Packet WillRetain is %t, should be %t", cp.WillRetain, false) 155 | } 156 | if string(cp.WillMessage) != "Test Payload" { 157 | t.Errorf("Connect Packet WillMessage is %s, should be %s", string(cp.WillMessage), "Test Payload") 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/pingreq.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //PingreqPacket is an internal representation of the fields of the 9 | //Pingreq MQTT packet 10 | type PingreqPacket struct { 11 | FixedHeader 12 | } 13 | 14 | func (pr *PingreqPacket) Type() byte { 15 | return pr.FixedHeader.MessageType 16 | } 17 | func (pr *PingreqPacket) String() string { 18 | str := fmt.Sprintf("%s", pr.FixedHeader) 19 | return str 20 | } 21 | 22 | func (pr *PingreqPacket) Write(w io.Writer) error { 23 | packet := pr.FixedHeader.pack() 24 | _, err := packet.WriteTo(w) 25 | 26 | return err 27 | } 28 | 29 | //Unpack decodes the details of a ControlPacket after the fixed 30 | //header has been read 31 | func (pr *PingreqPacket) Unpack(b io.Reader) error { 32 | return nil 33 | } 34 | 35 | //Details returns a Details struct containing the Qos and 36 | //MessageID of this ControlPacket 37 | func (pr *PingreqPacket) Details() Details { 38 | return Details{Qos: 0, MessageID: 0} 39 | } 40 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/pingresp.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //PingrespPacket is an internal representation of the fields of the 9 | //Pingresp MQTT packet 10 | type PingrespPacket struct { 11 | FixedHeader 12 | } 13 | 14 | func (pr *PingrespPacket) Type() byte { 15 | return pr.FixedHeader.MessageType 16 | } 17 | func (pr *PingrespPacket) String() string { 18 | str := fmt.Sprintf("%s", pr.FixedHeader) 19 | return str 20 | } 21 | 22 | func (pr *PingrespPacket) Write(w io.Writer) error { 23 | packet := pr.FixedHeader.pack() 24 | _, err := packet.WriteTo(w) 25 | 26 | return err 27 | } 28 | 29 | //Unpack decodes the details of a ControlPacket after the fixed 30 | //header has been read 31 | func (pr *PingrespPacket) Unpack(b io.Reader) error { 32 | return nil 33 | } 34 | 35 | //Details returns a Details struct containing the Qos and 36 | //MessageID of this ControlPacket 37 | func (pr *PingrespPacket) Details() Details { 38 | return Details{Qos: 0, MessageID: 0} 39 | } 40 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/puback.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //PubackPacket is an internal representation of the fields of the 9 | //Puback MQTT packet 10 | type PubackPacket struct { 11 | FixedHeader 12 | MessageID uint16 13 | } 14 | 15 | func (pa *PubackPacket) Type() byte { 16 | return pa.FixedHeader.MessageType 17 | } 18 | func (pa *PubackPacket) String() string { 19 | str := fmt.Sprintf("%s\n", pa.FixedHeader) 20 | str += fmt.Sprintf("messageID: %d", pa.MessageID) 21 | return str 22 | } 23 | 24 | func (pa *PubackPacket) Write(w io.Writer) error { 25 | var err error 26 | pa.FixedHeader.RemainingLength = 2 27 | packet := pa.FixedHeader.pack() 28 | packet.Write(encodeUint16(pa.MessageID)) 29 | _, err = packet.WriteTo(w) 30 | 31 | return err 32 | } 33 | 34 | //Unpack decodes the details of a ControlPacket after the fixed 35 | //header has been read 36 | func (pa *PubackPacket) Unpack(b io.Reader) error { 37 | pa.MessageID = decodeUint16(b) 38 | 39 | return nil 40 | } 41 | 42 | //Details returns a Details struct containing the Qos and 43 | //MessageID of this ControlPacket 44 | func (pa *PubackPacket) Details() Details { 45 | return Details{Qos: pa.Qos, MessageID: pa.MessageID} 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/pubcomp.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //PubcompPacket is an internal representation of the fields of the 9 | //Pubcomp MQTT packet 10 | type PubcompPacket struct { 11 | FixedHeader 12 | MessageID uint16 13 | } 14 | 15 | func (pc *PubcompPacket) Type() byte { 16 | return pc.FixedHeader.MessageType 17 | } 18 | func (pc *PubcompPacket) String() string { 19 | str := fmt.Sprintf("%s\n", pc.FixedHeader) 20 | str += fmt.Sprintf("MessageID: %d", pc.MessageID) 21 | return str 22 | } 23 | 24 | func (pc *PubcompPacket) Write(w io.Writer) error { 25 | var err error 26 | pc.FixedHeader.RemainingLength = 2 27 | packet := pc.FixedHeader.pack() 28 | packet.Write(encodeUint16(pc.MessageID)) 29 | _, err = packet.WriteTo(w) 30 | 31 | return err 32 | } 33 | 34 | //Unpack decodes the details of a ControlPacket after the fixed 35 | //header has been read 36 | func (pc *PubcompPacket) Unpack(b io.Reader) error { 37 | pc.MessageID = decodeUint16(b) 38 | 39 | return nil 40 | } 41 | 42 | //Details returns a Details struct containing the Qos and 43 | //MessageID of this ControlPacket 44 | func (pc *PubcompPacket) Details() Details { 45 | return Details{Qos: pc.Qos, MessageID: pc.MessageID} 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/publish.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //PublishPacket is an internal representation of the fields of the 10 | //Publish MQTT packet 11 | type PublishPacket struct { 12 | FixedHeader 13 | TopicName string 14 | MessageID uint16 15 | Payload []byte 16 | } 17 | 18 | func (p *PublishPacket) Type() byte { 19 | return p.FixedHeader.MessageType 20 | } 21 | func (p *PublishPacket) String() string { 22 | str := fmt.Sprintf("%s\n", p.FixedHeader) 23 | str += fmt.Sprintf("topicName: %s MessageID: %d\n", p.TopicName, p.MessageID) 24 | str += fmt.Sprintf("payload: %s\n", string(p.Payload)) 25 | return str 26 | } 27 | 28 | func (p *PublishPacket) Write(w io.Writer) error { 29 | var body bytes.Buffer 30 | var err error 31 | 32 | body.Write(encodeString(p.TopicName)) 33 | if p.Qos > 0 { 34 | body.Write(encodeUint16(p.MessageID)) 35 | } 36 | p.FixedHeader.RemainingLength = body.Len() + len(p.Payload) 37 | packet := p.FixedHeader.pack() 38 | packet.Write(body.Bytes()) 39 | packet.Write(p.Payload) 40 | _, err = w.Write(packet.Bytes()) 41 | 42 | return err 43 | } 44 | 45 | //Unpack decodes the details of a ControlPacket after the fixed 46 | //header has been read 47 | func (p *PublishPacket) Unpack(b io.Reader) error { 48 | var payloadLength = p.FixedHeader.RemainingLength 49 | p.TopicName = decodeString(b) 50 | if p.Qos > 0 { 51 | p.MessageID = decodeUint16(b) 52 | payloadLength -= len(p.TopicName) + 4 53 | } else { 54 | payloadLength -= len(p.TopicName) + 2 55 | } 56 | if payloadLength < 0 { 57 | return fmt.Errorf("Error upacking publish, payload length < 0") 58 | } 59 | p.Payload = make([]byte, payloadLength) 60 | _, err := b.Read(p.Payload) 61 | 62 | return err 63 | } 64 | 65 | //Copy creates a new PublishPacket with the same topic and payload 66 | //but an empty fixed header, useful for when you want to deliver 67 | //a message with different properties such as Qos but the same 68 | //content 69 | func (p *PublishPacket) Copy() *PublishPacket { 70 | newP := NewControlPacket(Publish).(*PublishPacket) 71 | newP.TopicName = p.TopicName 72 | newP.Payload = p.Payload 73 | 74 | return newP 75 | } 76 | 77 | //Details returns a Details struct containing the Qos and 78 | //MessageID of this ControlPacket 79 | func (p *PublishPacket) Details() Details { 80 | return Details{Qos: p.Qos, MessageID: p.MessageID} 81 | } 82 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrec.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //PubrecPacket is an internal representation of the fields of the 9 | //Pubrec MQTT packet 10 | type PubrecPacket struct { 11 | FixedHeader 12 | MessageID uint16 13 | } 14 | 15 | func (pr *PubrecPacket) Type() byte { 16 | return pr.FixedHeader.MessageType 17 | } 18 | func (pr *PubrecPacket) String() string { 19 | str := fmt.Sprintf("%s\n", pr.FixedHeader) 20 | str += fmt.Sprintf("MessageID: %d", pr.MessageID) 21 | return str 22 | } 23 | 24 | func (pr *PubrecPacket) Write(w io.Writer) error { 25 | var err error 26 | pr.FixedHeader.RemainingLength = 2 27 | packet := pr.FixedHeader.pack() 28 | packet.Write(encodeUint16(pr.MessageID)) 29 | _, err = packet.WriteTo(w) 30 | 31 | return err 32 | } 33 | 34 | //Unpack decodes the details of a ControlPacket after the fixed 35 | //header has been read 36 | func (pr *PubrecPacket) Unpack(b io.Reader) error { 37 | pr.MessageID = decodeUint16(b) 38 | 39 | return nil 40 | } 41 | 42 | //Details returns a Details struct containing the Qos and 43 | //MessageID of this ControlPacket 44 | func (pr *PubrecPacket) Details() Details { 45 | return Details{Qos: pr.Qos, MessageID: pr.MessageID} 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/pubrel.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //PubrelPacket is an internal representation of the fields of the 9 | //Pubrel MQTT packet 10 | type PubrelPacket struct { 11 | FixedHeader 12 | MessageID uint16 13 | } 14 | 15 | func (pr *PubrelPacket) Type() byte { 16 | return pr.FixedHeader.MessageType 17 | } 18 | func (pr *PubrelPacket) String() string { 19 | str := fmt.Sprintf("%s\n", pr.FixedHeader) 20 | str += fmt.Sprintf("MessageID: %d", pr.MessageID) 21 | return str 22 | } 23 | 24 | func (pr *PubrelPacket) Write(w io.Writer) error { 25 | var err error 26 | pr.FixedHeader.RemainingLength = 2 27 | packet := pr.FixedHeader.pack() 28 | packet.Write(encodeUint16(pr.MessageID)) 29 | _, err = packet.WriteTo(w) 30 | 31 | return err 32 | } 33 | 34 | //Unpack decodes the details of a ControlPacket after the fixed 35 | //header has been read 36 | func (pr *PubrelPacket) Unpack(b io.Reader) error { 37 | pr.MessageID = decodeUint16(b) 38 | 39 | return nil 40 | } 41 | 42 | //Details returns a Details struct containing the Qos and 43 | //MessageID of this ControlPacket 44 | func (pr *PubrelPacket) Details() Details { 45 | return Details{Qos: pr.Qos, MessageID: pr.MessageID} 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/suback.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //SubackPacket is an internal representation of the fields of the 10 | //Suback MQTT packet 11 | type SubackPacket struct { 12 | FixedHeader 13 | MessageID uint16 14 | ReturnCodes []byte 15 | } 16 | 17 | func (sa *SubackPacket) Type() byte { 18 | return sa.FixedHeader.MessageType 19 | } 20 | func (sa *SubackPacket) String() string { 21 | str := fmt.Sprintf("%s\n", sa.FixedHeader) 22 | str += fmt.Sprintf("MessageID: %d", sa.MessageID) 23 | return str 24 | } 25 | 26 | func (sa *SubackPacket) Write(w io.Writer) error { 27 | var body bytes.Buffer 28 | var err error 29 | body.Write(encodeUint16(sa.MessageID)) 30 | body.Write(sa.ReturnCodes) 31 | sa.FixedHeader.RemainingLength = body.Len() 32 | packet := sa.FixedHeader.pack() 33 | packet.Write(body.Bytes()) 34 | _, err = packet.WriteTo(w) 35 | 36 | return err 37 | } 38 | 39 | //Unpack decodes the details of a ControlPacket after the fixed 40 | //header has been read 41 | func (sa *SubackPacket) Unpack(b io.Reader) error { 42 | var qosBuffer bytes.Buffer 43 | sa.MessageID = decodeUint16(b) 44 | qosBuffer.ReadFrom(b) 45 | sa.ReturnCodes = qosBuffer.Bytes() 46 | 47 | return nil 48 | } 49 | 50 | //Details returns a Details struct containing the Qos and 51 | //MessageID of this ControlPacket 52 | func (sa *SubackPacket) Details() Details { 53 | return Details{Qos: 0, MessageID: sa.MessageID} 54 | } 55 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/subscribe.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //SubscribePacket is an internal representation of the fields of the 10 | //Subscribe MQTT packet 11 | type SubscribePacket struct { 12 | FixedHeader 13 | MessageID uint16 14 | Topics []string 15 | Qoss []byte 16 | } 17 | 18 | func (s *SubscribePacket) Type() byte { 19 | return s.FixedHeader.MessageType 20 | } 21 | func (s *SubscribePacket) String() string { 22 | str := fmt.Sprintf("%s", s.FixedHeader) 23 | str += fmt.Sprintf("MessageID: %d topics: %s", s.MessageID, s.Topics) 24 | return str 25 | } 26 | 27 | func (s *SubscribePacket) Write(w io.Writer) error { 28 | var body bytes.Buffer 29 | var err error 30 | 31 | body.Write(encodeUint16(s.MessageID)) 32 | for i, topic := range s.Topics { 33 | body.Write(encodeString(topic)) 34 | body.WriteByte(s.Qoss[i]) 35 | } 36 | s.FixedHeader.RemainingLength = body.Len() 37 | packet := s.FixedHeader.pack() 38 | packet.Write(body.Bytes()) 39 | _, err = packet.WriteTo(w) 40 | 41 | return err 42 | } 43 | 44 | //Unpack decodes the details of a ControlPacket after the fixed 45 | //header has been read 46 | func (s *SubscribePacket) Unpack(b io.Reader) error { 47 | s.MessageID = decodeUint16(b) 48 | payloadLength := s.FixedHeader.RemainingLength - 2 49 | for payloadLength > 0 { 50 | topic := decodeString(b) 51 | s.Topics = append(s.Topics, topic) 52 | qos := decodeByte(b) 53 | s.Qoss = append(s.Qoss, qos) 54 | payloadLength -= 2 + len(topic) + 1 //2 bytes of string length, plus string, plus 1 byte for Qos 55 | } 56 | 57 | return nil 58 | } 59 | 60 | //Details returns a Details struct containing the Qos and 61 | //MessageID of this ControlPacket 62 | func (s *SubscribePacket) Details() Details { 63 | return Details{Qos: 1, MessageID: s.MessageID} 64 | } 65 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/unsuback.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | //UnsubackPacket is an internal representation of the fields of the 9 | //Unsuback MQTT packet 10 | type UnsubackPacket struct { 11 | FixedHeader 12 | MessageID uint16 13 | } 14 | 15 | func (ua *UnsubackPacket) Type() byte { 16 | return ua.FixedHeader.MessageType 17 | } 18 | func (ua *UnsubackPacket) String() string { 19 | str := fmt.Sprintf("%s\n", ua.FixedHeader) 20 | str += fmt.Sprintf("MessageID: %d", ua.MessageID) 21 | return str 22 | } 23 | 24 | func (ua *UnsubackPacket) Write(w io.Writer) error { 25 | var err error 26 | ua.FixedHeader.RemainingLength = 2 27 | packet := ua.FixedHeader.pack() 28 | packet.Write(encodeUint16(ua.MessageID)) 29 | _, err = packet.WriteTo(w) 30 | 31 | return err 32 | } 33 | 34 | //Unpack decodes the details of a ControlPacket after the fixed 35 | //header has been read 36 | func (ua *UnsubackPacket) Unpack(b io.Reader) error { 37 | ua.MessageID = decodeUint16(b) 38 | 39 | return nil 40 | } 41 | 42 | //Details returns a Details struct containing the Qos and 43 | //MessageID of this ControlPacket 44 | func (ua *UnsubackPacket) Details() Details { 45 | return Details{Qos: 0, MessageID: ua.MessageID} 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/eclipse/paho.mqtt.golang/packets/unsubscribe.go: -------------------------------------------------------------------------------- 1 | package packets 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //UnsubscribePacket is an internal representation of the fields of the 10 | //Unsubscribe MQTT packet 11 | type UnsubscribePacket struct { 12 | FixedHeader 13 | MessageID uint16 14 | Topics []string 15 | } 16 | 17 | func (u *UnsubscribePacket) Type() byte { 18 | return u.FixedHeader.MessageType 19 | } 20 | func (u *UnsubscribePacket) String() string { 21 | str := fmt.Sprintf("%s\n", u.FixedHeader) 22 | str += fmt.Sprintf("MessageID: %d", u.MessageID) 23 | return str 24 | } 25 | 26 | func (u *UnsubscribePacket) Write(w io.Writer) error { 27 | var body bytes.Buffer 28 | var err error 29 | body.Write(encodeUint16(u.MessageID)) 30 | for _, topic := range u.Topics { 31 | body.Write(encodeString(topic)) 32 | } 33 | u.FixedHeader.RemainingLength = body.Len() 34 | packet := u.FixedHeader.pack() 35 | packet.Write(body.Bytes()) 36 | _, err = packet.WriteTo(w) 37 | 38 | return err 39 | } 40 | 41 | //Unpack decodes the details of a ControlPacket after the fixed 42 | //header has been read 43 | func (u *UnsubscribePacket) Unpack(b io.Reader) error { 44 | u.MessageID = decodeUint16(b) 45 | var topic string 46 | for topic = decodeString(b); topic != ""; topic = decodeString(b) { 47 | u.Topics = append(u.Topics, topic) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | //Details returns a Details struct containing the Qos and 54 | //MessageID of this ControlPacket 55 | func (u *UnsubscribePacket) Details() Details { 56 | return Details{Qos: 1, MessageID: u.MessageID} 57 | } 58 | -------------------------------------------------------------------------------- /www/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | mqtt-www 4 | 5 | 6 | 7 | 8 | 9 | com.aptana.ide.core.unifiedBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.aptana.projects.webnature 16 | 17 | 18 | 19 | 1495678551838 20 | 21 | 26 22 | 23 | org.eclipse.ui.ide.multiFilter 24 | 1.0-name-matches-false-false-node_modules 25 | 26 | 27 | 28 | 1495861487119 29 | 30 | 26 31 | 32 | org.eclipse.ui.ide.multiFilter 33 | 1.0-name-matches-false-false-node_modules 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /www/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | test Ws mqtt.js 4 | 5 | 6 | 7 |
8 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /www/client/tls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | test Ws mqtt.js 4 | 5 | 6 | 7 |
8 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /www/dashboard/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { 4 | "modules": false 5 | }] 6 | ] 7 | } -------------------------------------------------------------------------------- /www/dashboard/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /www/dashboard/.eslintignore: -------------------------------------------------------------------------------- 1 | src/router.js 2 | src/libs/util.js 3 | src/vendors.js -------------------------------------------------------------------------------- /www/dashboard/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "browser": true 9 | }, 10 | "extends": "eslint:recommended", 11 | "plugins": ["html"], 12 | "rules": { 13 | "indent": ["error", 4, { 14 | "SwitchCase": 1 15 | }], 16 | "quotes": ["error", "single"], 17 | "semi": ["error", "always"], 18 | "no-console": ["error"] 19 | } 20 | } -------------------------------------------------------------------------------- /www/dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/ 3 | .DS_Store 4 | node_modules/ 5 | .project 6 | dist 7 | dist/* 8 | src/config/*.tmp 9 | src/config/env.js 10 | npm-debug.log -------------------------------------------------------------------------------- /www/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /www/dashboard/index_prod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iView project 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /www/dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-dashboard", 3 | "version": "1.0.0", 4 | "description": "mqtt-broker的看板页面", 5 | "main": "index.js", 6 | "scripts": { 7 | "init": "webpack --progress --config webpack.dev.config.js", 8 | "dev": "webpack-dev-server --content-base ./ --open --inline --hot --compress --history-api-fallback --config webpack.dev.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.dev.config.js", 10 | "lint": "eslint --fix --ext .js,.vue src" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "" 15 | }, 16 | "author": "", 17 | "license": "MIT", 18 | "dependencies": { 19 | "axios": "^0.15.3", 20 | "echarts": "^3.4.0", 21 | "iview": "^2.0.0-rc.13", 22 | "mqtt": "^2.8.0", 23 | "vue": "^2.2.6", 24 | "vue-router": "^2.3.1" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer-loader": "^2.0.0", 28 | "babel": "^6.23.0", 29 | "babel-core": "^6.23.1", 30 | "babel-loader": "^6.2.4", 31 | "babel-plugin-transform-runtime": "^6.12.0", 32 | "babel-preset-es2015": "^6.9.0", 33 | "babel-runtime": "^6.11.6", 34 | "css-loader": "^0.23.1", 35 | "extract-text-webpack-plugin": "^2.0.0", 36 | "file-loader": "^0.8.5", 37 | "html-loader": "^0.3.0", 38 | "html-webpack-plugin": "^2.28.0", 39 | "style-loader": "^0.13.1", 40 | "url-loader": "^0.5.7", 41 | "vue-hot-reload-api": "^1.3.3", 42 | "vue-html-loader": "^1.2.3", 43 | "vue-loader": "^11.0.0", 44 | "vue-style-loader": "^1.0.0", 45 | "vue-template-compiler": "^2.2.1", 46 | "webpack": "^2.2.1", 47 | "webpack-dev-server": "^2.4.1", 48 | "webpack-merge": "^3.0.0", 49 | "eslint": "^3.12.2", 50 | "eslint-plugin-html": "^1.7.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /www/dashboard/src/app.vue: -------------------------------------------------------------------------------- 1 | 53 | 75 | -------------------------------------------------------------------------------- /www/dashboard/src/config/config.js: -------------------------------------------------------------------------------- 1 | import Env from './env'; 2 | 3 | let config = { 4 | env: Env 5 | }; 6 | export default config; -------------------------------------------------------------------------------- /www/dashboard/src/font/demo.css: -------------------------------------------------------------------------------- 1 | *{margin: 0;padding: 0;list-style: none;} 2 | /* 3 | KISSY CSS Reset 4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。 5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。 6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。 7 | 特色:1. 适应中文;2. 基于最新主流浏览器。 8 | 维护:玉伯, 正淳 9 | */ 10 | 11 | /** 清除内外边距 **/ 12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 14 | pre, /* text formatting elements 文本格式元素 */ 15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */ 16 | th, td /* table elements 表格元素 */ { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | /** 设置默认字体 **/ 22 | body, 23 | button, input, select, textarea /* for ie */ { 24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; 25 | } 26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 30 | 31 | /** 重置列表元素 **/ 32 | ul, ol { list-style: none; } 33 | 34 | /** 重置文本格式元素 **/ 35 | a { text-decoration: none; } 36 | a:hover { text-decoration: underline; } 37 | 38 | 39 | /** 重置表单元素 **/ 40 | legend { color: #000; } /* for ie6 */ 41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */ 43 | /* 注:optgroup 无法扶正 */ 44 | 45 | /** 重置表格元素 **/ 46 | table { border-collapse: collapse; border-spacing: 0; } 47 | 48 | /* 清除浮动 */ 49 | .ks-clear:after, .clear:after { 50 | content: '\20'; 51 | display: block; 52 | height: 0; 53 | clear: both; 54 | } 55 | .ks-clear, .clear { 56 | *zoom: 1; 57 | } 58 | 59 | .main { 60 | padding: 30px 100px; 61 | width: 960px; 62 | margin: 0 auto; 63 | } 64 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;} 65 | 66 | .helps{margin-top:40px;} 67 | .helps pre{ 68 | padding:20px; 69 | margin:10px 0; 70 | border:solid 1px #e7e1cd; 71 | background-color: #fffdef; 72 | overflow: auto; 73 | } 74 | 75 | .icon_lists{ 76 | width: 100% !important; 77 | 78 | } 79 | 80 | .icon_lists li{ 81 | float:left; 82 | width: 100px; 83 | height:180px; 84 | text-align: center; 85 | list-style: none !important; 86 | } 87 | .icon_lists .icon{ 88 | font-size: 42px; 89 | line-height: 100px; 90 | margin: 10px 0; 91 | color:#333; 92 | -webkit-transition: font-size 0.25s ease-out 0s; 93 | -moz-transition: font-size 0.25s ease-out 0s; 94 | transition: font-size 0.25s ease-out 0s; 95 | 96 | } 97 | .icon_lists .icon:hover{ 98 | font-size: 100px; 99 | } 100 | 101 | 102 | 103 | .markdown { 104 | color: #666; 105 | font-size: 14px; 106 | line-height: 1.8; 107 | } 108 | 109 | .highlight { 110 | line-height: 1.5; 111 | } 112 | 113 | .markdown img { 114 | vertical-align: middle; 115 | max-width: 100%; 116 | } 117 | 118 | .markdown h1 { 119 | color: #404040; 120 | font-weight: 500; 121 | line-height: 40px; 122 | margin-bottom: 24px; 123 | } 124 | 125 | .markdown h2, 126 | .markdown h3, 127 | .markdown h4, 128 | .markdown h5, 129 | .markdown h6 { 130 | color: #404040; 131 | margin: 1.6em 0 0.6em 0; 132 | font-weight: 500; 133 | clear: both; 134 | } 135 | 136 | .markdown h1 { 137 | font-size: 28px; 138 | } 139 | 140 | .markdown h2 { 141 | font-size: 22px; 142 | } 143 | 144 | .markdown h3 { 145 | font-size: 16px; 146 | } 147 | 148 | .markdown h4 { 149 | font-size: 14px; 150 | } 151 | 152 | .markdown h5 { 153 | font-size: 12px; 154 | } 155 | 156 | .markdown h6 { 157 | font-size: 12px; 158 | } 159 | 160 | .markdown hr { 161 | height: 1px; 162 | border: 0; 163 | background: #e9e9e9; 164 | margin: 16px 0; 165 | clear: both; 166 | } 167 | 168 | .markdown p, 169 | .markdown pre { 170 | margin: 1em 0; 171 | } 172 | 173 | .markdown > p, 174 | .markdown > blockquote, 175 | .markdown > .highlight, 176 | .markdown > ol, 177 | .markdown > ul { 178 | width: 80%; 179 | } 180 | 181 | .markdown ul > li { 182 | list-style: circle; 183 | } 184 | 185 | .markdown > ul li, 186 | .markdown blockquote ul > li { 187 | margin-left: 20px; 188 | padding-left: 4px; 189 | } 190 | 191 | .markdown > ul li p, 192 | .markdown > ol li p { 193 | margin: 0.6em 0; 194 | } 195 | 196 | .markdown ol > li { 197 | list-style: decimal; 198 | } 199 | 200 | .markdown > ol li, 201 | .markdown blockquote ol > li { 202 | margin-left: 20px; 203 | padding-left: 4px; 204 | } 205 | 206 | .markdown code { 207 | margin: 0 3px; 208 | padding: 0 5px; 209 | background: #eee; 210 | border-radius: 3px; 211 | } 212 | 213 | .markdown pre { 214 | border-radius: 6px; 215 | background: #f7f7f7; 216 | padding: 20px; 217 | } 218 | 219 | .markdown pre code { 220 | border: none; 221 | background: #f7f7f7; 222 | margin: 0; 223 | } 224 | 225 | .markdown strong, 226 | .markdown b { 227 | font-weight: 600; 228 | } 229 | 230 | .markdown > table { 231 | border-collapse: collapse; 232 | border-spacing: 0px; 233 | empty-cells: show; 234 | border: 1px solid #e9e9e9; 235 | width: 95%; 236 | margin-bottom: 24px; 237 | } 238 | 239 | .markdown > table th { 240 | white-space: nowrap; 241 | color: #333; 242 | font-weight: 600; 243 | 244 | } 245 | 246 | .markdown > table th, 247 | .markdown > table td { 248 | border: 1px solid #e9e9e9; 249 | padding: 8px 16px; 250 | text-align: left; 251 | } 252 | 253 | .markdown > table th { 254 | background: #F7F7F7; 255 | } 256 | 257 | .markdown blockquote { 258 | font-size: 90%; 259 | color: #999; 260 | border-left: 4px solid #e9e9e9; 261 | padding-left: 0.8em; 262 | margin: 1em 0; 263 | font-style: italic; 264 | } 265 | 266 | .markdown blockquote p { 267 | margin: 0; 268 | } 269 | 270 | .markdown .anchor { 271 | opacity: 0; 272 | transition: opacity 0.3s ease; 273 | margin-left: 8px; 274 | } 275 | 276 | .markdown .waiting { 277 | color: #ccc; 278 | } 279 | 280 | .markdown h1:hover .anchor, 281 | .markdown h2:hover .anchor, 282 | .markdown h3:hover .anchor, 283 | .markdown h4:hover .anchor, 284 | .markdown h5:hover .anchor, 285 | .markdown h6:hover .anchor { 286 | opacity: 1; 287 | display: inline-block; 288 | } 289 | 290 | .markdown > br, 291 | .markdown > p > br { 292 | clear: both; 293 | } 294 | 295 | 296 | .hljs { 297 | display: block; 298 | background: white; 299 | padding: 0.5em; 300 | color: #333333; 301 | overflow-x: auto; 302 | } 303 | 304 | .hljs-comment, 305 | .hljs-meta { 306 | color: #969896; 307 | } 308 | 309 | .hljs-string, 310 | .hljs-variable, 311 | .hljs-template-variable, 312 | .hljs-strong, 313 | .hljs-emphasis, 314 | .hljs-quote { 315 | color: #df5000; 316 | } 317 | 318 | .hljs-keyword, 319 | .hljs-selector-tag, 320 | .hljs-type { 321 | color: #a71d5d; 322 | } 323 | 324 | .hljs-literal, 325 | .hljs-symbol, 326 | .hljs-bullet, 327 | .hljs-attribute { 328 | color: #0086b3; 329 | } 330 | 331 | .hljs-section, 332 | .hljs-name { 333 | color: #63a35c; 334 | } 335 | 336 | .hljs-tag { 337 | color: #333333; 338 | } 339 | 340 | .hljs-title, 341 | .hljs-attr, 342 | .hljs-selector-id, 343 | .hljs-selector-class, 344 | .hljs-selector-attr, 345 | .hljs-selector-pseudo { 346 | color: #795da3; 347 | } 348 | 349 | .hljs-addition { 350 | color: #55a532; 351 | background-color: #eaffea; 352 | } 353 | 354 | .hljs-deletion { 355 | color: #bd2c00; 356 | background-color: #ffecec; 357 | } 358 | 359 | .hljs-link { 360 | text-decoration: underline; 361 | } 362 | 363 | pre{ 364 | background: #fff; 365 | } 366 | 367 | 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /www/dashboard/src/font/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1496207840404'); /* IE9*/ 4 | src: url('iconfont.eot?t=1496207840404#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('iconfont.woff?t=1496207840404') format('woff'), /* chrome, firefox */ 6 | url('iconfont.ttf?t=1496207840404') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1496207840404#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-home:before { content: "\e603"; } 19 | 20 | .icon-chart:before { content: "\e79e"; } 21 | 22 | .icon-about:before { content: "\e602"; } 23 | 24 | -------------------------------------------------------------------------------- /www/dashboard/src/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/dashboard/src/font/iconfont.eot -------------------------------------------------------------------------------- /www/dashboard/src/font/iconfont.js: -------------------------------------------------------------------------------- 1 | (function(window){var svgSprite=""+""+''+""+''+""+""+""+''+""+''+""+''+""+""+""+''+""+''+""+''+""+''+""+""+""+"";var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window) -------------------------------------------------------------------------------- /www/dashboard/src/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Created by FontForge 20120731 at Wed May 31 13:17:20 2017 6 | By admin 7 | 8 | 9 | 10 | 24 | 26 | 28 | 30 | 32 | 34 | 38 | 40 | 42 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /www/dashboard/src/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/dashboard/src/font/iconfont.ttf -------------------------------------------------------------------------------- /www/dashboard/src/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/dashboard/src/font/iconfont.woff -------------------------------------------------------------------------------- /www/dashboard/src/imgs/iotalking.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/dashboard/src/imgs/iotalking.ico -------------------------------------------------------------------------------- /www/dashboard/src/imgs/iotalking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/dashboard/src/imgs/iotalking.png -------------------------------------------------------------------------------- /www/dashboard/src/libs/util.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import env from '../config/env'; 3 | 4 | let util = { 5 | 6 | }; 7 | util.title = function(title) { 8 | title = title ? title + ' - Home' : 'Iotalking'; 9 | window.document.title = title; 10 | }; 11 | 12 | const ajaxUrl = env === 'development' ? 13 | 'http://127.0.0.1:8888' : 14 | env === 'production' ? 15 | 'https://www.url.com' : 16 | 'https://debug.url.com'; 17 | 18 | util.ajax = axios.create({ 19 | baseURL: ajaxUrl, 20 | timeout: 30000 21 | }); 22 | 23 | export default util; -------------------------------------------------------------------------------- /www/dashboard/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import VueRouter from 'vue-router'; 4 | import Routers from './router'; 5 | 6 | import Util from './libs/util'; 7 | import App from './app.vue'; 8 | import 'iview/dist/styles/iview.css'; 9 | 10 | 11 | Vue.use(VueRouter); 12 | 13 | 14 | Vue.use(iView); 15 | 16 | 17 | 18 | // 路由配置 19 | const RouterConfig = { 20 | mode: 'history', 21 | routes: Routers 22 | }; 23 | const router = new VueRouter(RouterConfig); 24 | 25 | router.beforeEach((to, from, next) => { 26 | iView.LoadingBar.start(); 27 | Util.title(to.meta.title); 28 | next(); 29 | }); 30 | 31 | router.afterEach(() => { 32 | iView.LoadingBar.finish(); 33 | window.scrollTo(0, 0); 34 | }); 35 | 36 | 37 | 38 | new Vue({ 39 | el: '#app', 40 | router: router, 41 | render: h => h(App) 42 | }); -------------------------------------------------------------------------------- /www/dashboard/src/router.js: -------------------------------------------------------------------------------- 1 | import Menu from './views/menu.vue' 2 | import Home from './views/home.vue' 3 | import Charts from './views/charts.vue' 4 | import About from './views/about.vue' 5 | 6 | const routers = [ 7 | { 8 | path: '/', 9 | name:'home', 10 | meta: { 11 | title: '' 12 | }, 13 | components: { 14 | 'menu':Menu, 15 | 'main':Home, 16 | } 17 | }, 18 | { 19 | path: '/charts', 20 | name:'charts', 21 | meta: { 22 | title: '' 23 | }, 24 | components: { 25 | 'menu':Menu, 26 | 'main':Charts, 27 | } 28 | }, 29 | { 30 | path: '/about', 31 | name:'about', 32 | meta: { 33 | title: '' 34 | }, 35 | components: { 36 | 'menu':Menu, 37 | 'main':About, 38 | } 39 | }, 40 | ]; 41 | export default routers; -------------------------------------------------------------------------------- /www/dashboard/src/template/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /www/dashboard/src/vendors.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import VueRouter from 'vue-router'; 4 | import axios from 'axios'; 5 | 6 | import echarts from 'echarts'; 7 | 8 | import mqtt from 'mqtt'; 9 | -------------------------------------------------------------------------------- /www/dashboard/src/views/about.vue: -------------------------------------------------------------------------------- 1 | 22 | 27 | -------------------------------------------------------------------------------- /www/dashboard/src/views/charts.vue: -------------------------------------------------------------------------------- 1 | 49 | 73 | -------------------------------------------------------------------------------- /www/dashboard/src/views/home.vue: -------------------------------------------------------------------------------- 1 | 49 | 132 | -------------------------------------------------------------------------------- /www/dashboard/src/views/menu.vue: -------------------------------------------------------------------------------- 1 | 19 | 35 | -------------------------------------------------------------------------------- /www/dashboard/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | main: './src/main', 8 | vendors: './src/vendors' 9 | }, 10 | output: { 11 | path: path.join(__dirname, './dist') 12 | }, 13 | module: { 14 | rules: [{ 15 | test: /\.vue$/, 16 | loader: 'vue-loader', 17 | options: { 18 | loaders: { 19 | 20 | css: ExtractTextPlugin.extract({ 21 | use: ['css-loader', 'autoprefixer-loader'], 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /iview\/.*?js$/, 29 | loader: 'babel-loader' 30 | }, 31 | { 32 | test: /\.js$/, 33 | loader: 'babel-loader', 34 | exclude: /node_modules/ 35 | }, 36 | { 37 | test: /\.css$/, 38 | use: ExtractTextPlugin.extract({ 39 | use: ['css-loader?minimize', 'autoprefixer-loader'], 40 | fallback: 'style-loader' 41 | }) 42 | }, 43 | 44 | { 45 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 46 | loader: 'url-loader?limit=1024' 47 | }, 48 | { 49 | test: /\.(html|tpl)$/, 50 | loader: 'html-loader' 51 | } 52 | ] 53 | }, 54 | resolve: { 55 | extensions: ['.js', '.vue'], 56 | alias: { 57 | 'vue': 'vue/dist/vue.esm.js' 58 | } 59 | } 60 | }; -------------------------------------------------------------------------------- /www/dashboard/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const merge = require('webpack-merge'); 5 | const webpackBaseConfig = require('./webpack.base.config.js'); 6 | const fs = require('fs'); 7 | 8 | fs.open('./src/config/env.js', 'w', function(err, fd) { 9 | const buf = 'export default "development";'; 10 | fs.write(fd, buf, 0, buf.length, 0, function(err, written, buffer) {}); 11 | }); 12 | 13 | module.exports = merge(webpackBaseConfig, { 14 | devtool: '#source-map', 15 | output: { 16 | publicPath: '/www/dashboard/dist/', 17 | filename: '[name].js', 18 | chunkFilename: '[name].chunk.js' 19 | }, 20 | plugins: [ 21 | new ExtractTextPlugin({ 22 | filename: '[name].css', 23 | allChunks: true 24 | }), 25 | new webpack.optimize.CommonsChunkPlugin({ 26 | name: 'vendors', 27 | filename: 'vendors.js' 28 | }), 29 | new HtmlWebpackPlugin({ 30 | title:'Iotalking MQTT broker', 31 | favicon:'./src//imgs/iotalking.ico', 32 | filename: '../index.html', 33 | template: './src/template/index.ejs', 34 | inject: true, 35 | }) 36 | ] 37 | }); -------------------------------------------------------------------------------- /www/dashboard/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const merge = require('webpack-merge'); 5 | const webpackBaseConfig = require('./webpack.base.config.js'); 6 | const fs = require('fs'); 7 | 8 | fs.open('./src/config/env.js', 'w', function(err, fd) { 9 | const buf = 'export default "production";'; 10 | fs.write(fd, buf, 0, buf.length, 0, function(err, written, buffer) {}); 11 | }); 12 | 13 | module.exports = merge(webpackBaseConfig, { 14 | output: { 15 | publicPath: './dist/', 16 | filename: '[name].[hash].js', 17 | chunkFilename: '[name].[hash].chunk.js' 18 | }, 19 | plugins: [ 20 | new ExtractTextPlugin({ 21 | filename: '[name].[hash].css', 22 | allChunks: true 23 | }), 24 | new webpack.optimize.CommonsChunkPlugin({ 25 | name: 'vendors', 26 | filename: 'vendors.[hash].js' 27 | }), 28 | new webpack.DefinePlugin({ 29 | 'process.env': { 30 | NODE_ENV: '"production"' 31 | } 32 | }), 33 | new webpack.optimize.UglifyJsPlugin({ 34 | compress: { 35 | warnings: false 36 | } 37 | }), 38 | new HtmlWebpackPlugin({ 39 | title:'Iotalking MQTT broker', 40 | favicon:'./src//imgs/iotalking.ico', 41 | filename: '../index.html', 42 | template: './src/template/index.ejs', 43 | inject: true, 44 | }) 45 | ] 46 | }); -------------------------------------------------------------------------------- /www/locker/img/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/locker/img/close.png -------------------------------------------------------------------------------- /www/locker/img/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/locker/img/open.png -------------------------------------------------------------------------------- /www/locker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 智能锁 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 | 63 | 64 | 149 | 150 | -------------------------------------------------------------------------------- /www/router.go: -------------------------------------------------------------------------------- 1 | package www 2 | 3 | import ( 4 | "mime" 5 | "net/http" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | "github.com/iotalking/mqtt-broker/utils" 9 | ) 10 | 11 | func runter() { 12 | mime.AddExtensionType(".js", "application/javascript") 13 | mime.AddExtensionType(".html", "text/html") 14 | mime.AddExtensionType(".png", "image/png") 15 | 16 | mux.HandleDir("/www/client/", "./www/client/") 17 | mux.HandleDir("/www/libs/", "./www/libs/") 18 | mux.HandleDir("/www/dashboard/", "./www/dashboard/") 19 | mux.HandleDir("/dist/", "./www/dashboard/dist/") 20 | mux.HandleDir("/www/locker/", "./www/locker/") 21 | HandleWhiteBoard("/www/whiteboard/", "./www/whiteboard/") 22 | } 23 | func HandleWhiteBoard(pattern, dir string) { 24 | mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { 25 | 26 | var _url = r.URL.Path 27 | if _url == pattern { 28 | //创建白板name 29 | ck := http.Cookie{ 30 | Name: "name", 31 | Value: utils.NewId(), 32 | } 33 | log.Info("HandleWhiteBoard") 34 | http.SetCookie(w, &ck) 35 | } 36 | utils.DirHandler(pattern, dir)(w, r) 37 | }) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /www/run.go: -------------------------------------------------------------------------------- 1 | package www 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/iotalking/mqtt-broker/utils" 7 | ) 8 | 9 | var mux *utils.Mux 10 | 11 | func Start(addr string) error { 12 | mux = utils.NewHttpMux() 13 | s := http.Server{} 14 | s.Addr = addr 15 | s.Handler = mux 16 | runter() 17 | err := s.ListenAndServe() 18 | if err != nil { 19 | return err 20 | } 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /www/whiteboard/img/Clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/whiteboard/img/Clear.png -------------------------------------------------------------------------------- /www/whiteboard/img/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotalking/mqtt-broker/a01428dd5778392049380612a458ffa8b22eb2dc/www/whiteboard/img/exit.png -------------------------------------------------------------------------------- /www/whiteboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 白板 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 |
20 | 28 | 29 | 382 | 383 | --------------------------------------------------------------------------------