├── .DS_Store ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── README_ENGLISH.md ├── admin ├── admin.go └── html │ └── index.html ├── business_client ├── business_client.go ├── business_client_conf.go └── gateway_client.go ├── discovery ├── .DS_Store ├── discover.go ├── etcd_discovery │ └── etcd_discovery.go └── redis_discovery │ └── redis_discovery.go ├── dns └── dns.go ├── docs └── performance_test.md ├── go.mod ├── go.sum ├── grpcs ├── main.go └── socket_cluster_gateway │ ├── config │ └── config.go │ ├── handler │ └── gateway.go │ ├── logic │ └── gateway_logic │ │ ├── is_online.go │ │ ├── send_to_client_id.go │ │ └── send_to_client_ids.go │ ├── proto │ └── gateway │ │ ├── gateway.pb.go │ │ ├── gateway.proto │ │ └── gateway_grpc.pb.go │ └── socket_cluster_gateway.go ├── logx ├── fileLogger.go ├── filelogger_test.go ├── log.go ├── log │ └── 2023-08-19.log ├── logx.go ├── stdlogger.go └── stdlogger_test.go ├── node ├── .DS_Store ├── common.go ├── conf.pb.go ├── conf.proto ├── const.go ├── msg.pb.go ├── msg.proto ├── node.go ├── node_conf.go ├── plugin.go └── session.go ├── pic ├── business_client.png └── socket_cluster.png ├── protocol ├── .DS_Store ├── flow_proto.go ├── protocol.go ├── quic_protocol │ ├── quic_connect.go │ ├── quic_protocol.go │ └── quic_test.go ├── session.go ├── tcp_protocol │ ├── tcp_connection.go │ └── tcp_protocol.go ├── tls.go └── ws_protocol │ ├── ws_connection.go │ └── ws_protocol.go ├── server └── server.go ├── session_storage ├── redis_storage │ └── redis_storage.go └── session_storage.go └── unsafehash └── segment_hash.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | .idea 3 | .vscode -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /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 2020-2030 Weblazy 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 | # socket-cluster 2 | 即时通讯框架,如果大家觉得好用,右上角帮忙点个star吧。(^_^) 3 | > 欢迎感兴趣的小伙伴一同开发,解决日常消息推送,长连接需求。 4 | - 后端(golang): https://github.com/weblazy/socket-cluster 5 | - 使用案例: 6 | - 在线体验: http://web.xiaoyuantongbbs.cn:3333/login.html#/ 7 | - 后端源码: https://github.com/weblazy/socket-cluster-examples 8 | - 前端源码: https://github.com/weblazy/socket-cluster-web 9 | - 使用文档: http://doc.xiaoyuantongbbs.cn:4000/ 10 | - 后端好用的工具库(golang): https://github.com/weblazy/easy 11 | # 框架库依赖 12 | - redis 13 | 14 | # 框架使用方式 15 | ``` 16 | // 客户端协议组件 17 | protocolHandler := &ws_protocol.WsProtocol{} 18 | // 在线状态存储组件 19 | sessionStorageHandler := redis_storage.NewRedisStorage([]*redis_storage.RedisNode{&redis_storage.RedisNode{ 20 | RedisConf: &redis.Options{Addr: redisHost, Password: redisPassword, DB: 0}, 21 | Position: 10000, 22 | }}) 23 | // 服务发现组件 24 | discoveryHandler := redis_discovery.NewRedisDiscovery(&redis.Options{Addr: redisHost, Password: redisPassword, DB: 0}) 25 | // 启动服务 26 | common.NodeInfo, err = node.NewNode(node.NewNodeConf(*host, protocolHandler, sessionStorageHandler, discoveryHandler, onMsg).WithPort(*port)) 27 | if err != nil { 28 | logx.Info(err) 29 | } 30 | ``` 31 | ``` 32 | func onMsg(context *node.Context) { 33 | logx.Info("msg:", string(context.Msg)) 34 | } 35 | ``` 36 | 37 | # 架构框图 38 | ![scheme 1](pic/socket_cluster.png) 39 | 40 | - 使用business_client将业务和节点分离部署 41 | ![scheme 1](pic/business_client.png) 42 | 43 | # 联系我们 44 | - 技术支持/合作/咨询请联系作者QQ: 2276282419 45 | - 作者邮箱: 2276282419@qq.com 46 | - 即时通讯技术交流QQ群: 33280853 47 | -------------------------------------------------------------------------------- /README_ENGLISH.md: -------------------------------------------------------------------------------- 1 | # socket-cluster 2 | 即时通讯框架 3 | # Contact Us 4 | 5 | Users Mail Group: join in pika_users@groups.163.com 6 | 7 | Developers Mail Group: join in pika_developers@groups.163.com 8 | 9 | QQ group: 294254078 10 | -------------------------------------------------------------------------------- /admin/admin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "text/template" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func main() { 11 | e := echo.New() 12 | // Initialize the template engine 13 | temp := &Template{ 14 | // The purpose of the precompilation process is to optimize the speed of later rendering of the template file 15 | templates: template.Must(template.ParseGlob("html/*.html")), 16 | } 17 | e.Renderer = temp 18 | e.GET("/index", Index) 19 | e.Start(":9529") 20 | } 21 | 22 | // Custom template engine 23 | type Template struct { 24 | templates *template.Template 25 | } 26 | 27 | // Render Implement interface, Render function 28 | func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 29 | // Invokes the template engine to render the template 30 | return t.templates.ExecuteTemplate(w, name, data) 31 | } 32 | 33 | // Index 34 | func Index(c echo.Context) error { 35 | params := make(map[string]interface{}) 36 | params["list"] = []map[string]interface{}{map[string]interface{}{ 37 | "ip": "127.0.0.1", 38 | "uuid": "9527", 39 | "count": "100", 40 | }, map[string]interface{}{ 41 | "ip": "127.0.0.2", 42 | "uuid": "9528", 43 | "count": "101", 44 | }} 45 | return c.Render(200, "index.html", params) 46 | } 47 | -------------------------------------------------------------------------------- /admin/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Socket-Cluster 7 | 43 | 44 | 45 | 46 |
Socket-Cluster
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {{ range $k, $v := .list }} 55 | 56 | 57 | 58 | 59 | 60 | {{ end }} 61 |
ipuuidcount
{{ $v.ip }}{{ $v.uuid }}{{ $v.count }}
62 | 63 | 64 | -------------------------------------------------------------------------------- /business_client/business_client.go: -------------------------------------------------------------------------------- 1 | package business_client 2 | 3 | import ( 4 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 5 | ) 6 | 7 | type ( 8 | BusinessClient interface { 9 | // IsOnline gets the online status of the clientId 10 | IsOnline(clientId string) (bool, error) 11 | // SendToClientId sends a message to a clientId 12 | SendToClientId(req *gateway.SendToClientIdRequest) (*gateway.SendToClientIdResponse, error) 13 | // SendToClientId sends a message to multiple clientIds 14 | SendToClientIds(req *gateway.SendToClientIdsRequest) (*gateway.SendToClientIdsResponse, error) 15 | } 16 | 17 | // Node communication node 18 | // businessClient struct { 19 | // BusinessClient 20 | // businessClientConf *BusinessClientConf 21 | // // Key: socket address 22 | // // Value: protocol.Connection 23 | // nodeIpMap goutil.Map // Receive messages forwarded by other nodes 24 | // nodeIdMap goutil.Map // Receive messages forwarded by other nodes 25 | // nodeTimeout int64 // Node heartbeat timeout time 26 | // clientTimeout int64 // Client heartbeat timeout time 27 | 28 | // internalProtocolHandler protocol.Protocol // Direct protocol between node and node 29 | // } 30 | ) 31 | 32 | // // NewNode return a Node 33 | // func NewBusinessClient(cfg *BusinessClientConf) (BusinessClient, error) { 34 | // businessClientObj := &businessClient{ 35 | // businessClientConf: cfg, 36 | // nodeIpMap: goutil.AtomicMap(), 37 | // nodeIdMap: goutil.AtomicMap(), 38 | // nodeTimeout: cfg.nodePingInterval * 3, 39 | // } 40 | // go businessClientObj.watchService() 41 | // return businessClientObj, nil 42 | // } 43 | 44 | // // SendToClientId Send message to a clientId 45 | // func (this *businessClient) SendToClientId(clientId string, req []byte) error { 46 | // if req == nil { 47 | // return fmt.Errorf("message is nil") 48 | // } 49 | // ipArr, err := this.businessClientConf.sessionStorageHandler.GetIps(clientId) 50 | // if err == nil { 51 | // mapreduce.MapVoid(func(source chan<- interface{}) { 52 | // for key := range ipArr { 53 | // source <- ipArr[key] 54 | // } 55 | // }, func(item interface{}) { 56 | // ip := item.(string) 57 | 58 | // connect, ok := this.nodeIdMap.Load(ip) 59 | // if ok { 60 | // conn, ok := connect.(protocol.Connection) 61 | // if !ok { 62 | // return 63 | // } 64 | // clientsMsg := node.ClientsMsg{ 65 | // ReceiveClientIds: []string{clientId}, 66 | // Data: req, 67 | // } 68 | // clientsMsgBytes, err := proto.Marshal(&clientsMsg) 69 | // transReq := node.Msg{ 70 | // MsgType: node.ClientMsgType, 71 | // Data: clientsMsgBytes, 72 | // } 73 | // reqBytes, err := proto.Marshal(&transReq) 74 | // err = conn.WriteMsg(reqBytes) 75 | // if err != nil { 76 | // logx.LogHandler.Error(err) 77 | // } 78 | // } else { 79 | // logx.LogHandler.Errorf("node:%s not online", ip) 80 | // return 81 | // } 82 | 83 | // }) 84 | // } 85 | // return nil 86 | // } 87 | 88 | // type BatchData struct { 89 | // ip string 90 | // clientIds []string 91 | // } 92 | 93 | // // SendToClientIds Sending messages to multiple clients 94 | // func (this *businessClient) SendToClientIds(clientIds []string, req []byte) error { 95 | // if req == nil { 96 | // return fmt.Errorf("message is nil") 97 | // } 98 | // clientMap, err := this.businessClientConf.sessionStorageHandler.GetClientsIps(clientIds) 99 | // if err != nil { 100 | // return err 101 | // } 102 | // // Concurrent sends to other nodes 103 | // mapreduce.MapVoid(func(source chan<- interface{}) { 104 | // for k1 := range clientMap { 105 | // source <- &BatchData{ip: k1, clientIds: clientMap[k1]} 106 | // } 107 | // }, func(item interface{}) { 108 | // batchData := item.(*BatchData) 109 | // connect, ok := this.nodeIdMap.Load(batchData.ip) 110 | // if ok { 111 | // conn, ok := connect.(protocol.Connection) 112 | // if !ok { 113 | // return 114 | // } 115 | // ids := make([]string, 0) 116 | // for k1 := range batchData.clientIds { 117 | // ids = append(ids, batchData.clientIds[k1]) 118 | // } 119 | // clientsMsg := node.ClientsMsg{ 120 | // ReceiveClientIds: ids, 121 | // Data: req, 122 | // } 123 | // clientsMsgBytes, err := proto.Marshal(&clientsMsg) 124 | // if err != nil { 125 | // logx.LogHandler.Error(err) 126 | // } 127 | // msg := node.Msg{ 128 | // MsgType: node.ClientMsgType, 129 | // Data: clientsMsgBytes, 130 | // } 131 | // msgBytes, err := proto.Marshal(&msg) 132 | // if err != nil { 133 | // logx.LogHandler.Error(err) 134 | // } 135 | // err = conn.WriteMsg(msgBytes) 136 | // if err != nil { 137 | // logx.LogHandler.Error(err) 138 | // } 139 | // } else { 140 | // logx.LogHandler.Error("node:%s not online", batchData.ip) 141 | // return 142 | // } 143 | 144 | // }) 145 | 146 | // return nil 147 | // } 148 | 149 | // // IsOnline determine if a clientId is online 150 | // func (this *businessClient) IsOnline(clientId string) bool { 151 | // addrArr, err := this.businessClientConf.sessionStorageHandler.GetIps(clientId) 152 | // if err != nil { 153 | // logx.LogHandler.Error(err) 154 | // return false 155 | // } 156 | // if len(addrArr) > 0 { 157 | // return true 158 | // } 159 | // return false 160 | // } 161 | 162 | // // watchService 163 | // func (this *businessClient) watchService() { 164 | // watchChan := make(chan discovery.EventType, 1) 165 | // go this.businessClientConf.discoveryHandler.WatchService(watchChan) 166 | // go func() { 167 | // for { 168 | // select { 169 | // case _, ok := <-watchChan: 170 | // if !ok { 171 | // logx.LogHandler.Infof("channel close\n") 172 | // return 173 | // } 174 | // err := this.updateNodeList() 175 | // if err != nil { 176 | // logx.LogHandler.Error(err) 177 | // } 178 | // } 179 | // } 180 | // }() 181 | // // init 182 | // err := this.updateNodeList() 183 | // if err != nil { 184 | // logx.LogHandler.Error(err) 185 | // } 186 | // } 187 | 188 | // // UpdateNodeList Add handles addition request 189 | // func (this *businessClient) updateNodeList() error { 190 | // nodeMap := make(map[string]int) 191 | // for k1 := range this.businessClientConf.hostList { 192 | // nodeList, port, err := dns.DnsParse(this.businessClientConf.hostList[k1]) 193 | // if err != nil { 194 | // logx.LogHandler.Error(err) 195 | // return err 196 | // } 197 | // for k2 := range nodeList { 198 | // nodeMap[nodeList[k2]+":"+port] = 1 199 | // } 200 | // } 201 | 202 | // for k1 := range nodeMap { 203 | // addr := k1 204 | // // Connection already exists 205 | // _, ok := this.nodeIpMap.LoadOrStore(addr, "") 206 | // if ok { 207 | // continue 208 | // } 209 | // conn, err := this.businessClientConf.internalProtocolHandler.Dial(addr) 210 | // if err != nil { 211 | // logx.LogHandler.Errorf("dial:%s", err.Error()) 212 | // this.nodeIpMap.Delete(addr) 213 | // continue 214 | // } 215 | 216 | // auth := node.AuthMsg{ 217 | // Password: this.businessClientConf.password, 218 | // } 219 | // authBytes, err := proto.Marshal(&auth) 220 | // if err != nil { 221 | // logx.LogHandler.Error(err) 222 | // } 223 | // data := node.Msg{ 224 | // MsgType: node.AuthBusinessClientMsgType, 225 | // Data: authBytes, 226 | // } 227 | // reqBytes, err := proto.Marshal(&data) 228 | // if err != nil { 229 | // logx.LogHandler.Error(err) 230 | // } 231 | // err = conn.WriteMsg(reqBytes) 232 | // if err != nil { 233 | // logx.LogHandler.Info(err) 234 | // } 235 | // this.nodeIpMap.Store(addr, conn) 236 | // go func(addr string, conn protocol.Connection) { 237 | // defer func(addr string, conn protocol.Connection) { 238 | // nodeId, _ := this.nodeIpMap.Load(addr) 239 | // if nodeId != "" { 240 | // this.nodeIdMap.Delete(nodeId) 241 | // } 242 | // this.nodeIpMap.Delete(addr) 243 | // if conn != nil { 244 | // conn.Close() 245 | // } 246 | // }(addr, conn) 247 | // for { 248 | // msg, err := conn.ReadMsg() 249 | // if err != nil { 250 | // break 251 | // } 252 | // this.onTransServerMsg(addr, conn, msg) 253 | // } 254 | // }(addr, conn) 255 | // } 256 | // return nil 257 | // } 258 | 259 | // // OnTransMsg handle internal communication node messages 260 | // func (this *businessClient) onTransServerMsg(addr string, conn protocol.Connection, msg []byte) { 261 | // var transMsg node.Msg 262 | // err := proto.Unmarshal(msg, &transMsg) 263 | // if err != nil { 264 | // logx.LogHandler.Error(err) 265 | // return 266 | // } 267 | // switch transMsg.MsgType { 268 | 269 | // case node.PingMsgType: // The heartbeat message 270 | // case node.BindNodeIdMsgType: 271 | // var bindNodeIdMsg node.BindNodeIdMsg 272 | // err = proto.Unmarshal(transMsg.Data, &bindNodeIdMsg) 273 | // if err != nil { 274 | // logx.LogHandler.Error(err) 275 | // } 276 | // this.nodeIdMap.Store(bindNodeIdMsg.NodeId, conn) 277 | // this.nodeIpMap.Store(addr, bindNodeIdMsg.NodeId) 278 | // default: // The unknow message 279 | // logx.LogHandler.Info(transMsg) 280 | // } 281 | 282 | // } 283 | 284 | // // SendPing send node Heartbeat 285 | // func (this *businessClient) sendPing() { 286 | // go func() { 287 | // for { 288 | // time.Sleep(time.Duration(this.businessClientConf.nodePingInterval) * time.Second) 289 | 290 | // // send to other node 291 | // this.nodeIpMap.Range(func(k, v interface{}) bool { 292 | // conn, ok := v.(protocol.Connection) 293 | // if !ok { 294 | // return true 295 | // } 296 | 297 | // err := conn.WriteMsg(node.PingMsg) 298 | // if err != nil { 299 | // logx.LogHandler.Error(err) 300 | // } 301 | // return true 302 | // }) 303 | // } 304 | // }() 305 | // } 306 | -------------------------------------------------------------------------------- /business_client/business_client_conf.go: -------------------------------------------------------------------------------- 1 | package business_client 2 | 3 | import ( 4 | "github.com/weblazy/socket-cluster/discovery" 5 | "github.com/weblazy/socket-cluster/node" 6 | "github.com/weblazy/socket-cluster/session_storage" 7 | ) 8 | 9 | type ( 10 | // NodeConf node config 11 | BusinessClientConf struct { 12 | password string // Password for auth when connect to other node 13 | discoveryHandler discovery.ServiceDiscovery // Discover service 14 | sessionStorageHandler session_storage.SessionStorage // On-line state storage components 15 | } 16 | ) 17 | 18 | // NewNodeConf creates a new NodeConf. 19 | func NewBusinessClientConf(discoveryHandler discovery.ServiceDiscovery, sessionStorageHandler session_storage.SessionStorage) *BusinessClientConf { 20 | return &BusinessClientConf{ 21 | password: node.DefaultPassword, 22 | discoveryHandler: discoveryHandler, 23 | sessionStorageHandler: sessionStorageHandler, 24 | } 25 | 26 | } 27 | 28 | // WithPassword sets the password for transport node 29 | func (conf *BusinessClientConf) WithPassword(password string) *BusinessClientConf { 30 | conf.password = password 31 | return conf 32 | } 33 | -------------------------------------------------------------------------------- /business_client/gateway_client.go: -------------------------------------------------------------------------------- 1 | package business_client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/weblazy/core/mapreduce" 8 | "github.com/weblazy/easy/econfig" 9 | "github.com/weblazy/easy/elog" 10 | "github.com/weblazy/easy/grpc/grpc_client" 11 | "github.com/weblazy/easy/grpc/grpc_client/grpc_client_config" 12 | "github.com/weblazy/goutil" 13 | "github.com/weblazy/socket-cluster/discovery" 14 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 15 | "github.com/weblazy/socket-cluster/logx" 16 | "go.uber.org/zap" 17 | ) 18 | 19 | type GatewayClient struct { 20 | nodeIpMap goutil.Map // Receive messages forwarded by other nodes 21 | nodeIdMap goutil.Map // Receive messages forwarded by other nodes 22 | businessClientConf *BusinessClientConf 23 | } 24 | 25 | func NewGatewayClient(cfg *BusinessClientConf) *GatewayClient { 26 | gatewayClient := GatewayClient{ 27 | businessClientConf: cfg, 28 | nodeIpMap: goutil.AtomicMap(), 29 | nodeIdMap: goutil.AtomicMap(), 30 | } 31 | watchChan := make(chan discovery.EventType, 1) 32 | go gatewayClient.businessClientConf.discoveryHandler.WatchService(watchChan) 33 | go func() { 34 | for { 35 | select { 36 | case _, ok := <-watchChan: 37 | if !ok { 38 | logx.LogHandler.Infof("channel close\n") 39 | return 40 | } 41 | err := gatewayClient.updateNodeList() 42 | if err != nil { 43 | logx.LogHandler.Error(err) 44 | } 45 | } 46 | } 47 | }() 48 | err := gatewayClient.updateNodeList() 49 | if err != nil { 50 | elog.ErrorCtx(context.Background(), "updateNodeList", zap.Error(err)) 51 | } 52 | return &gatewayClient 53 | } 54 | 55 | func (g *GatewayClient) updateNodeList() error { 56 | nodeMap, err := g.businessClientConf.discoveryHandler.GetServerList() 57 | if err != nil { 58 | return err 59 | } 60 | for k1 := range nodeMap { 61 | addr := k1 62 | // Connection already exists 63 | _, ok := g.nodeIpMap.LoadOrStore(addr, "") 64 | if ok { 65 | continue 66 | } 67 | cfg := grpc_client_config.DefaultConfig() 68 | cfg.Addr = addr 69 | client := grpc_client.NewGrpcClient(cfg) 70 | gatewayClient := gateway.NewGatewayServiceClient(client) 71 | g.nodeIpMap.Store(addr, gatewayClient) 72 | } 73 | return nil 74 | } 75 | 76 | func (g *GatewayClient) SendToClientId(req *gateway.SendToClientIdRequest) (*gateway.SendToClientIdResponse, error) { 77 | if req == nil { 78 | return nil, fmt.Errorf("message is nil") 79 | } 80 | ipArr, err := g.businessClientConf.sessionStorageHandler.GetIps(req.ClientId) 81 | if err == nil { 82 | mapreduce.MapVoid(func(source chan<- interface{}) { 83 | for key := range ipArr { 84 | source <- ipArr[key] 85 | } 86 | }, func(item interface{}) { 87 | ip := item.(string) 88 | connect, ok := g.nodeIpMap.Load(ip) 89 | if ok { 90 | conn, ok := connect.(gateway.GatewayServiceClient) 91 | if !ok { 92 | return 93 | } 94 | clientsMsg := &gateway.SendToClientIdRequest{ 95 | ClientId: req.ClientId, 96 | Data: req.Data, 97 | } 98 | 99 | resp, err := conn.SendToClientId(context.Background(), clientsMsg) 100 | if econfig.GlobalViper.GetBool("BaseConfig.Debug") { 101 | elog.InfoCtx(context.Background(), "testlogmsg", zap.Any("clientsMsg", clientsMsg), zap.String("ip", ip), zap.Any("resp", resp)) 102 | } 103 | if err != nil { 104 | elog.InfoCtx(context.Background(), "msg", zap.Any("req", req), zap.Error(err)) 105 | } 106 | } else { 107 | elog.InfoCtx(context.Background(), "node not online", zap.Any("req", req), zap.String("ip", ip)) 108 | return 109 | } 110 | 111 | }) 112 | } 113 | return nil, nil 114 | } 115 | 116 | type BatchData struct { 117 | ip string 118 | clientIds []string 119 | } 120 | 121 | func (g *GatewayClient) SendToClientIds(req *gateway.SendToClientIdsRequest) (*gateway.SendToClientIdsResponse, error) { 122 | if req == nil { 123 | return &gateway.SendToClientIdsResponse{ 124 | Code: -1, 125 | Msg: "message is nil", 126 | }, nil 127 | } 128 | clientMap, err := g.businessClientConf.sessionStorageHandler.GetClientsIps(req.ClientIds) 129 | if err != nil { 130 | return &gateway.SendToClientIdsResponse{ 131 | Code: -1, 132 | Msg: err.Error(), 133 | }, nil 134 | } 135 | // Concurrent sends to other nodes 136 | mapreduce.MapVoid(func(source chan<- interface{}) { 137 | for k1 := range clientMap { 138 | source <- &BatchData{ip: k1, clientIds: clientMap[k1]} 139 | } 140 | }, func(item interface{}) { 141 | batchData := item.(*BatchData) 142 | connect, ok := g.nodeIpMap.Load(batchData.ip) 143 | if ok { 144 | conn, ok := connect.(gateway.GatewayServiceClient) 145 | if !ok { 146 | return 147 | } 148 | ids := make([]string, 0) 149 | for k1 := range batchData.clientIds { 150 | ids = append(ids, batchData.clientIds[k1]) 151 | } 152 | 153 | _, err = conn.SendToClientIds(context.Background(), &gateway.SendToClientIdsRequest{ 154 | ClientIds: ids, 155 | Data: req.Data, 156 | }) 157 | if err != nil { 158 | elog.InfoCtx(context.Background(), "msg", zap.Any("req", req), zap.Error(err)) 159 | } 160 | } else { 161 | logx.LogHandler.Error("node:%s not online", batchData.ip) 162 | return 163 | } 164 | 165 | }) 166 | 167 | return &gateway.SendToClientIdsResponse{}, nil 168 | } 169 | func (g *GatewayClient) IsOnline(clientId string) (bool, error) { 170 | return g.businessClientConf.sessionStorageHandler.IsOnline(clientId), nil 171 | } 172 | -------------------------------------------------------------------------------- /discovery/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/discovery/.DS_Store -------------------------------------------------------------------------------- /discovery/discover.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | type EventType int32 4 | 5 | const ( 6 | PUT EventType = 0 7 | DELETE EventType = 1 8 | ) 9 | 10 | type WatchChan chan EventType 11 | 12 | // ServiceDiscovery 13 | type ServiceDiscovery interface { 14 | // SetNodeAddr sets nodeAddr 15 | SetNodeAddr(nodeAddr string) 16 | // WatchService Listens for a new node to start 17 | WatchService(watchChan WatchChan) 18 | // UpdateInfo Updates the information for this node 19 | UpdateInfo([]byte) error 20 | // Register registers the NodeID and notify other nodes 21 | Register() error 22 | // WatchService Listens for a new node to start 23 | GetServerList() (map[string]string, error) 24 | } 25 | -------------------------------------------------------------------------------- /discovery/etcd_discovery/etcd_discovery.go: -------------------------------------------------------------------------------- 1 | package etcd_discovery 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/weblazy/socket-cluster/discovery" 10 | "github.com/weblazy/socket-cluster/logx" 11 | "github.com/weblazy/socket-cluster/node" 12 | "go.etcd.io/etcd/api/v3/mvccpb" 13 | clientv3 "go.etcd.io/etcd/client/v3" 14 | ) 15 | 16 | const defaultDialTimeout = 5 * time.Second 17 | 18 | // EtcdDiscovery 19 | type EtcdDiscovery struct { 20 | discovery.ServiceDiscovery 21 | cli *clientv3.Client // etcd client 22 | serverList map[string]string // service list 23 | lock sync.Mutex 24 | lease int64 25 | nodeAddr string 26 | key string 27 | val string // value 28 | leaseID clientv3.LeaseID 29 | keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse // chan for renewal of lease 30 | } 31 | 32 | // NewEtcdDiscovery return a EtcdDiscovery 33 | func NewEtcdDiscovery(conf clientv3.Config) *EtcdDiscovery { 34 | cli, err := clientv3.New(conf) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | return &EtcdDiscovery{ 39 | cli: cli, 40 | serverList: make(map[string]string), 41 | key: node.NodeAddress, 42 | } 43 | } 44 | 45 | // SetNodeAddr sets a nodeAddr 46 | func (this *EtcdDiscovery) SetNodeAddr(nodeAddr string) { 47 | this.nodeAddr = nodeAddr 48 | } 49 | 50 | // WatchService Listens for a new node to start 51 | func (s *EtcdDiscovery) WatchService(watchChan discovery.WatchChan) { 52 | prefix := s.key 53 | rch := s.cli.Watch(context.Background(), prefix, clientv3.WithPrefix()) 54 | for wresp := range rch { 55 | for _, ev := range wresp.Events { 56 | switch ev.Type { 57 | case mvccpb.PUT: // Modify or add 58 | watchChan <- discovery.PUT 59 | case mvccpb.DELETE: // delete 60 | 61 | } 62 | } 63 | } 64 | } 65 | 66 | // Close closes the etcd client 67 | func (s *EtcdDiscovery) Close() error { 68 | return s.cli.Close() 69 | } 70 | 71 | // Register registers the nodeId and notify other nodes 72 | func (s *EtcdDiscovery) Register() error { 73 | //sets the lease time 74 | resp, err := s.cli.Grant(context.Background(), s.lease) 75 | if err != nil { 76 | return err 77 | } 78 | // register and bind lease 79 | _, err = s.cli.Put(context.Background(), s.key, "string(value)", clientv3.WithLease(resp.ID)) 80 | if err != nil { 81 | return err 82 | } 83 | // Set up renewal time to send renewal request 84 | leaseRespChan, err := s.cli.KeepAlive(context.Background(), resp.ID) 85 | 86 | if err != nil { 87 | return err 88 | } 89 | s.leaseID = resp.ID 90 | s.keepAliveChan = leaseRespChan 91 | go s.ListenLeaseRespChan() 92 | return nil 93 | } 94 | 95 | func (s *EtcdDiscovery) GetServerList() (map[string]string, error) { 96 | resp, err := s.cli.Get(context.Background(), s.key) 97 | if err != nil { 98 | return nil, err 99 | } 100 | for _, obj := range resp.Kvs { 101 | s.serverList[string(obj.Key)] = string(obj.Value) 102 | } 103 | return s.serverList, nil 104 | } 105 | 106 | // ListenLeaseRespChan Monitor lease renewals 107 | func (s *EtcdDiscovery) ListenLeaseRespChan() { 108 | for leaseKeepResp := range s.keepAliveChan { 109 | logx.LogHandler.Info("续约成功", leaseKeepResp) 110 | } 111 | logx.LogHandler.Infof("关闭续租") 112 | } 113 | -------------------------------------------------------------------------------- /discovery/redis_discovery/redis_discovery.go: -------------------------------------------------------------------------------- 1 | package redis_discovery 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "sort" 7 | "time" 8 | 9 | "github.com/spf13/cast" 10 | "github.com/weblazy/easy/db/eredis" 11 | "github.com/weblazy/easy/sortx" 12 | "github.com/weblazy/socket-cluster/discovery" 13 | "github.com/weblazy/socket-cluster/logx" 14 | "github.com/weblazy/socket-cluster/node" 15 | ) 16 | 17 | // RedisDiscovery 18 | type RedisDiscovery struct { 19 | discovery.ServiceDiscovery 20 | nodeAddr string 21 | adminRedis *eredis.RedisClient 22 | key string 23 | timeout int64 24 | } 25 | 26 | // NewRedisDiscovery return a RedisDiscovery 27 | func NewRedisDiscovery(adminRedis *eredis.RedisClient) *RedisDiscovery { 28 | return &RedisDiscovery{ 29 | adminRedis: adminRedis, 30 | timeout: 120, 31 | key: node.NodeAddress, 32 | } 33 | } 34 | 35 | // SetNodeAddr sets a addr 36 | func (this *RedisDiscovery) SetNodeAddr(addr string) { 37 | this.nodeAddr = addr 38 | } 39 | 40 | // WatchService Listens for a new node to start 41 | func (this *RedisDiscovery) WatchService(watchChan discovery.WatchChan) { 42 | go func() { 43 | pb := this.adminRedis.Subscribe(context.Background(), this.key) 44 | for mg := range pb.Channel() { 45 | data := make(map[string]interface{}) 46 | err := json.Unmarshal([]byte(mg.Payload), &data) 47 | if err != nil { 48 | logx.LogHandler.Error(err) 49 | return 50 | } 51 | watchChan <- discovery.PUT 52 | } 53 | }() 54 | } 55 | 56 | // Close closes the redis 57 | func (s *RedisDiscovery) Close() error { 58 | return s.adminRedis.Close() 59 | } 60 | 61 | // Register registers the NodeID and notify other nodes 62 | func (this *RedisDiscovery) Register() error { 63 | err := this.adminRedis.Publish(context.Background(), this.key, this.nodeAddr).Err() 64 | if err != nil { 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | // UpdateInfo Update the information for this node 71 | func (this *RedisDiscovery) UpdateInfo(nodeInfoByte []byte) error { 72 | err := this.adminRedis.HSet(context.Background(), node.NodeAddress, this.nodeAddr, string(nodeInfoByte)).Err() 73 | if err != nil { 74 | return err 75 | } 76 | err = this.adminRedis.Expire(context.Background(), node.NodeAddress, time.Duration(this.timeout*int64(time.Second))).Err() 77 | if err != nil { 78 | return err 79 | } 80 | return nil 81 | } 82 | 83 | // GetInfo get node information 84 | func (this *RedisDiscovery) GetInfo() ([]string, error) { 85 | list := make([]string, 0) 86 | now := time.Now().Unix() 87 | addrMap, err := this.adminRedis.HGetAll(context.Background(), node.NodeAddress).Result() 88 | if err != nil { 89 | return nil, err 90 | } 91 | sortList := sortx.NewSortList(sortx.DESC) 92 | expire := now - this.timeout 93 | for k1 := range addrMap { 94 | addrObj := make(map[string]interface{}) 95 | err := json.Unmarshal([]byte(addrMap[k1]), &addrObj) 96 | if err != nil { 97 | return nil, err 98 | } 99 | if cast.ToInt64(addrObj["timestamp"]) > expire { 100 | sortList.List = append(sortList.List, sortx.Sort{ 101 | Obj: k1, 102 | Sort: cast.ToFloat64(addrObj["client_count"]), 103 | }) 104 | } 105 | } 106 | sort.Sort(sortList) 107 | for k1 := range sortList.List { 108 | list = append(list, sortList.List[k1].Obj.(string)) 109 | } 110 | return list, nil 111 | } 112 | 113 | func (this *RedisDiscovery) GetServerList() (map[string]string, error) { 114 | addrMap, err := this.adminRedis.HGetAll(context.Background(), node.NodeAddress).Result() 115 | if err != nil { 116 | return nil, err 117 | } 118 | resp := map[string]string{} 119 | for k, v := range addrMap { 120 | resp[k] = v 121 | } 122 | return resp, nil 123 | } 124 | -------------------------------------------------------------------------------- /dns/dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "strings" 9 | ) 10 | 11 | type Target struct { 12 | Scheme string 13 | Authority string 14 | Endpoint string 15 | } 16 | 17 | // parseTarget takes the user input target string and default port, returns formatted host and port info. 18 | // If target doesn't specify a port, set the port to be the defaultPort. 19 | // If target is in IPv6 format and host-name is enclosed in square brackets, brackets 20 | // are stripped when setting the host. 21 | // examples: 22 | // target: "www.google.com" defaultPort: "443" returns host: "www.google.com", port: "443" 23 | // target: "ipv4-host:80" defaultPort: "443" returns host: "ipv4-host", port: "80" 24 | // target: "[ipv6-host]" defaultPort: "443" returns host: "ipv6-host", port: "443" 25 | // target: ":80" defaultPort: "443" returns host: "localhost", port: "80" 26 | func ParsePort(target, defaultPort string) (host, port string, err error) { 27 | if target == "" { 28 | return "", "", errors.New("") 29 | } 30 | if ip := net.ParseIP(target); ip != nil { 31 | // target is an IPv4 or IPv6(without brackets) address 32 | return target, defaultPort, nil 33 | } 34 | if host, port, err = net.SplitHostPort(target); err == nil { 35 | if port == "" { 36 | // If the port field is empty (target ends with colon), e.g. "[::1]:", this is an error. 37 | return "", "", errors.New("") 38 | } 39 | // target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port 40 | if host == "" { 41 | // Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed. 42 | host = "localhost" 43 | } 44 | return host, port, nil 45 | } 46 | if host, port, err = net.SplitHostPort(target + ":" + defaultPort); err == nil { 47 | // target doesn't have port 48 | return host, port, nil 49 | } 50 | return "", "", fmt.Errorf("invalid target address %v, error info: %v", target, err) 51 | } 52 | 53 | // split2 returns the values from strings.SplitN(s, sep, 2). 54 | // If sep is not found, it returns ("", "", false) instead. 55 | func split2(s, sep string) (string, string, bool) { 56 | spl := strings.SplitN(s, sep, 2) 57 | if len(spl) < 2 { 58 | return "", "", false 59 | } 60 | return spl[0], spl[1], true 61 | } 62 | 63 | // ParseTarget splits target into a resolver.Target struct containing scheme, 64 | // authority and endpoint. 65 | // 66 | // If target is not a valid scheme://authority/endpoint, it returns {Endpoint: 67 | // target}. 68 | func ParseScheme(target string) (ret Target) { 69 | var ok bool 70 | ret.Scheme, ret.Endpoint, ok = split2(target, "://") 71 | if !ok { 72 | return Target{Endpoint: target} 73 | } 74 | ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/") 75 | if !ok { 76 | return Target{Endpoint: target} 77 | } 78 | return ret 79 | } 80 | 81 | // Resolve the DNS domain name 82 | func DnsParse(domain string) ([]string, string, error) { 83 | target := ParseScheme(domain) 84 | host, port, _ := ParsePort(target.Endpoint, "80") 85 | ipList, err := net.DefaultResolver.LookupHost(context.Background(), host) 86 | return ipList, port, err 87 | } 88 | -------------------------------------------------------------------------------- /docs/performance_test.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/docs/performance_test.md -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/weblazy/socket-cluster 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-redis/redis/v8 v8.11.5 7 | github.com/golang/protobuf v1.5.3 8 | github.com/gorilla/websocket v1.5.0 9 | github.com/labstack/echo/v4 v4.9.0 10 | github.com/onsi/ginkgo/v2 v2.9.5 // indirect 11 | github.com/satori/go.uuid v1.2.0 12 | github.com/spf13/cast v1.5.0 13 | github.com/urfave/cli/v2 v2.25.7 14 | github.com/weblazy/core v1.1.1 15 | github.com/weblazy/easy v1.1.10-0.20230819085829-bbe510ae9fab 16 | github.com/weblazy/goutil v1.1.2 17 | go.etcd.io/etcd/api/v3 v3.5.9 18 | go.etcd.io/etcd/client/v3 v3.5.9 19 | go.uber.org/zap v1.21.0 20 | golang.org/x/crypto v0.4.0 // indirect 21 | google.golang.org/grpc v1.51.0 22 | google.golang.org/protobuf v1.28.1 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /grpcs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway" 8 | 9 | "github.com/urfave/cli/v2" 10 | "github.com/weblazy/easy/elog" 11 | "github.com/weblazy/easy/print" 12 | ) 13 | 14 | const ( 15 | ProjectName = "socket_cluster" 16 | ProjectVersion = "v1.0.0" 17 | ) 18 | 19 | func main() { 20 | // 打印Banner 21 | print.PrintBanner(ProjectName) 22 | // 配置cli参数 23 | cliApp := cli.NewApp() 24 | cliApp.Name = ProjectName 25 | cliApp.Version = ProjectVersion 26 | 27 | // 指定命令运行的函数 28 | cliApp.Commands = []*cli.Command{ 29 | socket_cluster_gateway.Cmd, 30 | } 31 | 32 | // 启动cli 33 | if err := cliApp.Run(os.Args); err != nil { 34 | elog.ErrorCtx(context.Background(), "Failed to start application", elog.FieldError(err)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/weblazy/easy/grpc/grpc_server/grpc_server_config" 5 | ) 6 | 7 | type Config struct { 8 | BaseConfig struct{} 9 | GrpcServerConfig *grpc_server_config.Config 10 | } 11 | 12 | var Conf = Config{ 13 | BaseConfig: struct{}{}, 14 | GrpcServerConfig: grpc_server_config.DefaultConfig(), 15 | } 16 | 17 | var LocalConfig = "" 18 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/handler/gateway.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/logic/gateway_logic" 7 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 8 | "github.com/weblazy/socket-cluster/node" 9 | "go.uber.org/zap" 10 | 11 | "github.com/weblazy/easy/code_err" 12 | "github.com/weblazy/easy/econfig" 13 | "github.com/weblazy/easy/elog" 14 | ) 15 | 16 | type GatewayService struct { 17 | gateway.UnimplementedGatewayServiceServer 18 | Node node.Node 19 | } 20 | 21 | func NewGatewayService(n node.Node) *GatewayService { 22 | return &GatewayService{ 23 | Node: n, 24 | } 25 | } 26 | 27 | func (h *GatewayService) IsOnline(ctx context.Context, req *gateway.IsOnlineRequest) (*gateway.IsOnlineResponse, error) { 28 | svcCtx := &gateway_logic.IsOnlineCtx{ 29 | Log: code_err.NewLog(ctx), 30 | Req: req, 31 | Res: new(gateway.IsOnlineResponse), 32 | } 33 | err := gateway_logic.IsOnline(svcCtx) 34 | if err != nil { 35 | svcCtx.Res.Code = err.Code 36 | svcCtx.Res.Msg = err.Msg 37 | } 38 | return svcCtx.Res, nil 39 | } 40 | 41 | func (h *GatewayService) SendToClientId(ctx context.Context, req *gateway.SendToClientIdRequest) (*gateway.SendToClientIdResponse, error) { 42 | if econfig.GlobalViper.GetBool("BaseConfig.Debug") { 43 | elog.InfoCtx(ctx, "SendToClientId", zap.Any("req", req)) 44 | } 45 | resp := gateway.SendToClientIdResponse{} 46 | err := h.Node.SendToClientId(req.ClientId, req.Data) 47 | if err != nil { 48 | resp.Code = -1 49 | resp.Msg = err.Error() 50 | } 51 | return &resp, nil 52 | } 53 | 54 | func (h *GatewayService) SendToClientIds(ctx context.Context, req *gateway.SendToClientIdsRequest) (*gateway.SendToClientIdsResponse, error) { 55 | resp := gateway.SendToClientIdsResponse{} 56 | err := h.Node.SendToClientIds(req.ClientIds, req.Data) 57 | if err != nil { 58 | resp.Code = -1 59 | resp.Msg = err.Error() 60 | } 61 | return &resp, nil 62 | } 63 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/logic/gateway_logic/is_online.go: -------------------------------------------------------------------------------- 1 | package gateway_logic 2 | 3 | import ( 4 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 5 | 6 | "github.com/weblazy/easy/code_err" 7 | ) 8 | 9 | type IsOnlineCtx struct { 10 | *code_err.Log 11 | Req *gateway.IsOnlineRequest 12 | Res *gateway.IsOnlineResponse 13 | } 14 | 15 | func IsOnline(ctx *IsOnlineCtx) *code_err.CodeErr { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/logic/gateway_logic/send_to_client_id.go: -------------------------------------------------------------------------------- 1 | package gateway_logic 2 | 3 | import ( 4 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 5 | 6 | "github.com/weblazy/easy/code_err" 7 | ) 8 | 9 | type SendToClientIdCtx struct { 10 | *code_err.Log 11 | Req *gateway.SendToClientIdRequest 12 | Res *gateway.SendToClientIdResponse 13 | } 14 | 15 | func SendToClientId(ctx *SendToClientIdCtx) *code_err.CodeErr { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/logic/gateway_logic/send_to_client_ids.go: -------------------------------------------------------------------------------- 1 | package gateway_logic 2 | 3 | import ( 4 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 5 | 6 | "github.com/weblazy/easy/code_err" 7 | ) 8 | 9 | type SendToClientIdsCtx struct { 10 | *code_err.Log 11 | Req *gateway.SendToClientIdsRequest 12 | Res *gateway.SendToClientIdsResponse 13 | } 14 | 15 | func SendToClientIds(ctx *SendToClientIdsCtx) *code_err.CodeErr { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/proto/gateway/gateway.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.14.0 5 | // source: grpcs/socket_cluster_gateway/proto/gateway/gateway.proto 6 | 7 | package gateway 8 | 9 | import ( 10 | reflect "reflect" 11 | sync "sync" 12 | 13 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 14 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type IsOnlineRequest struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` 30 | } 31 | 32 | func (x *IsOnlineRequest) Reset() { 33 | *x = IsOnlineRequest{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *IsOnlineRequest) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*IsOnlineRequest) ProtoMessage() {} 46 | 47 | func (x *IsOnlineRequest) ProtoReflect() protoreflect.Message { 48 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use IsOnlineRequest.ProtoReflect.Descriptor instead. 60 | func (*IsOnlineRequest) Descriptor() ([]byte, []int) { 61 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *IsOnlineRequest) GetClientId() string { 65 | if x != nil { 66 | return x.ClientId 67 | } 68 | return "" 69 | } 70 | 71 | type IsOnlineResponse struct { 72 | state protoimpl.MessageState 73 | sizeCache protoimpl.SizeCache 74 | unknownFields protoimpl.UnknownFields 75 | 76 | Code int64 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 77 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 78 | IsOnline bool `protobuf:"varint,3,opt,name=is_online,json=isOnline,proto3" json:"is_online,omitempty"` 79 | } 80 | 81 | func (x *IsOnlineResponse) Reset() { 82 | *x = IsOnlineResponse{} 83 | if protoimpl.UnsafeEnabled { 84 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[1] 85 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 86 | ms.StoreMessageInfo(mi) 87 | } 88 | } 89 | 90 | func (x *IsOnlineResponse) String() string { 91 | return protoimpl.X.MessageStringOf(x) 92 | } 93 | 94 | func (*IsOnlineResponse) ProtoMessage() {} 95 | 96 | func (x *IsOnlineResponse) ProtoReflect() protoreflect.Message { 97 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[1] 98 | if protoimpl.UnsafeEnabled && x != nil { 99 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 100 | if ms.LoadMessageInfo() == nil { 101 | ms.StoreMessageInfo(mi) 102 | } 103 | return ms 104 | } 105 | return mi.MessageOf(x) 106 | } 107 | 108 | // Deprecated: Use IsOnlineResponse.ProtoReflect.Descriptor instead. 109 | func (*IsOnlineResponse) Descriptor() ([]byte, []int) { 110 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP(), []int{1} 111 | } 112 | 113 | func (x *IsOnlineResponse) GetCode() int64 { 114 | if x != nil { 115 | return x.Code 116 | } 117 | return 0 118 | } 119 | 120 | func (x *IsOnlineResponse) GetMsg() string { 121 | if x != nil { 122 | return x.Msg 123 | } 124 | return "" 125 | } 126 | 127 | func (x *IsOnlineResponse) GetIsOnline() bool { 128 | if x != nil { 129 | return x.IsOnline 130 | } 131 | return false 132 | } 133 | 134 | type SendToClientIdRequest struct { 135 | state protoimpl.MessageState 136 | sizeCache protoimpl.SizeCache 137 | unknownFields protoimpl.UnknownFields 138 | 139 | ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` 140 | Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 141 | } 142 | 143 | func (x *SendToClientIdRequest) Reset() { 144 | *x = SendToClientIdRequest{} 145 | if protoimpl.UnsafeEnabled { 146 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[2] 147 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 148 | ms.StoreMessageInfo(mi) 149 | } 150 | } 151 | 152 | func (x *SendToClientIdRequest) String() string { 153 | return protoimpl.X.MessageStringOf(x) 154 | } 155 | 156 | func (*SendToClientIdRequest) ProtoMessage() {} 157 | 158 | func (x *SendToClientIdRequest) ProtoReflect() protoreflect.Message { 159 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[2] 160 | if protoimpl.UnsafeEnabled && x != nil { 161 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 162 | if ms.LoadMessageInfo() == nil { 163 | ms.StoreMessageInfo(mi) 164 | } 165 | return ms 166 | } 167 | return mi.MessageOf(x) 168 | } 169 | 170 | // Deprecated: Use SendToClientIdRequest.ProtoReflect.Descriptor instead. 171 | func (*SendToClientIdRequest) Descriptor() ([]byte, []int) { 172 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP(), []int{2} 173 | } 174 | 175 | func (x *SendToClientIdRequest) GetClientId() string { 176 | if x != nil { 177 | return x.ClientId 178 | } 179 | return "" 180 | } 181 | 182 | func (x *SendToClientIdRequest) GetData() []byte { 183 | if x != nil { 184 | return x.Data 185 | } 186 | return nil 187 | } 188 | 189 | type SendToClientIdResponse struct { 190 | state protoimpl.MessageState 191 | sizeCache protoimpl.SizeCache 192 | unknownFields protoimpl.UnknownFields 193 | 194 | Code int64 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 195 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 196 | } 197 | 198 | func (x *SendToClientIdResponse) Reset() { 199 | *x = SendToClientIdResponse{} 200 | if protoimpl.UnsafeEnabled { 201 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[3] 202 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 203 | ms.StoreMessageInfo(mi) 204 | } 205 | } 206 | 207 | func (x *SendToClientIdResponse) String() string { 208 | return protoimpl.X.MessageStringOf(x) 209 | } 210 | 211 | func (*SendToClientIdResponse) ProtoMessage() {} 212 | 213 | func (x *SendToClientIdResponse) ProtoReflect() protoreflect.Message { 214 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[3] 215 | if protoimpl.UnsafeEnabled && x != nil { 216 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 217 | if ms.LoadMessageInfo() == nil { 218 | ms.StoreMessageInfo(mi) 219 | } 220 | return ms 221 | } 222 | return mi.MessageOf(x) 223 | } 224 | 225 | // Deprecated: Use SendToClientIdResponse.ProtoReflect.Descriptor instead. 226 | func (*SendToClientIdResponse) Descriptor() ([]byte, []int) { 227 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP(), []int{3} 228 | } 229 | 230 | func (x *SendToClientIdResponse) GetCode() int64 { 231 | if x != nil { 232 | return x.Code 233 | } 234 | return 0 235 | } 236 | 237 | func (x *SendToClientIdResponse) GetMsg() string { 238 | if x != nil { 239 | return x.Msg 240 | } 241 | return "" 242 | } 243 | 244 | type SendToClientIdsRequest struct { 245 | state protoimpl.MessageState 246 | sizeCache protoimpl.SizeCache 247 | unknownFields protoimpl.UnknownFields 248 | 249 | ClientIds []string `protobuf:"bytes,1,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` 250 | Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 251 | } 252 | 253 | func (x *SendToClientIdsRequest) Reset() { 254 | *x = SendToClientIdsRequest{} 255 | if protoimpl.UnsafeEnabled { 256 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[4] 257 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 258 | ms.StoreMessageInfo(mi) 259 | } 260 | } 261 | 262 | func (x *SendToClientIdsRequest) String() string { 263 | return protoimpl.X.MessageStringOf(x) 264 | } 265 | 266 | func (*SendToClientIdsRequest) ProtoMessage() {} 267 | 268 | func (x *SendToClientIdsRequest) ProtoReflect() protoreflect.Message { 269 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[4] 270 | if protoimpl.UnsafeEnabled && x != nil { 271 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 272 | if ms.LoadMessageInfo() == nil { 273 | ms.StoreMessageInfo(mi) 274 | } 275 | return ms 276 | } 277 | return mi.MessageOf(x) 278 | } 279 | 280 | // Deprecated: Use SendToClientIdsRequest.ProtoReflect.Descriptor instead. 281 | func (*SendToClientIdsRequest) Descriptor() ([]byte, []int) { 282 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP(), []int{4} 283 | } 284 | 285 | func (x *SendToClientIdsRequest) GetClientIds() []string { 286 | if x != nil { 287 | return x.ClientIds 288 | } 289 | return nil 290 | } 291 | 292 | func (x *SendToClientIdsRequest) GetData() []byte { 293 | if x != nil { 294 | return x.Data 295 | } 296 | return nil 297 | } 298 | 299 | type SendToClientIdsResponse struct { 300 | state protoimpl.MessageState 301 | sizeCache protoimpl.SizeCache 302 | unknownFields protoimpl.UnknownFields 303 | 304 | Code int64 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 305 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 306 | } 307 | 308 | func (x *SendToClientIdsResponse) Reset() { 309 | *x = SendToClientIdsResponse{} 310 | if protoimpl.UnsafeEnabled { 311 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[5] 312 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 313 | ms.StoreMessageInfo(mi) 314 | } 315 | } 316 | 317 | func (x *SendToClientIdsResponse) String() string { 318 | return protoimpl.X.MessageStringOf(x) 319 | } 320 | 321 | func (*SendToClientIdsResponse) ProtoMessage() {} 322 | 323 | func (x *SendToClientIdsResponse) ProtoReflect() protoreflect.Message { 324 | mi := &file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[5] 325 | if protoimpl.UnsafeEnabled && x != nil { 326 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 327 | if ms.LoadMessageInfo() == nil { 328 | ms.StoreMessageInfo(mi) 329 | } 330 | return ms 331 | } 332 | return mi.MessageOf(x) 333 | } 334 | 335 | // Deprecated: Use SendToClientIdsResponse.ProtoReflect.Descriptor instead. 336 | func (*SendToClientIdsResponse) Descriptor() ([]byte, []int) { 337 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP(), []int{5} 338 | } 339 | 340 | func (x *SendToClientIdsResponse) GetCode() int64 { 341 | if x != nil { 342 | return x.Code 343 | } 344 | return 0 345 | } 346 | 347 | func (x *SendToClientIdsResponse) GetMsg() string { 348 | if x != nil { 349 | return x.Msg 350 | } 351 | return "" 352 | } 353 | 354 | var File_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto protoreflect.FileDescriptor 355 | 356 | var file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDesc = []byte{ 357 | 0x0a, 0x38, 0x67, 0x72, 0x70, 0x63, 0x73, 0x2f, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x63, 358 | 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2f, 0x70, 359 | 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2f, 0x67, 0x61, 0x74, 360 | 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x67, 0x61, 0x74, 0x65, 361 | 0x77, 0x61, 0x79, 0x22, 0x2e, 0x0a, 0x0f, 0x49, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 362 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 363 | 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 364 | 0x74, 0x49, 0x64, 0x22, 0x55, 0x0a, 0x10, 0x49, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 365 | 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 366 | 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 367 | 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1b, 0x0a, 368 | 0x09, 0x69, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 369 | 0x52, 0x08, 0x69, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x48, 0x0a, 0x15, 0x53, 0x65, 370 | 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 371 | 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 372 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 373 | 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 374 | 0x64, 0x61, 0x74, 0x61, 0x22, 0x3e, 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 375 | 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 376 | 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 377 | 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 378 | 0x03, 0x6d, 0x73, 0x67, 0x22, 0x4b, 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 379 | 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 380 | 0x0a, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 381 | 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x12, 0x12, 0x0a, 382 | 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 383 | 0x61, 0x22, 0x3f, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 384 | 0x74, 0x49, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 385 | 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 386 | 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 387 | 0x73, 0x67, 0x32, 0xfa, 0x01, 0x0a, 0x0e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 388 | 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x49, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 389 | 0x65, 0x12, 0x18, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x49, 0x73, 0x4f, 0x6e, 390 | 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x61, 391 | 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x49, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 392 | 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 393 | 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 394 | 0x61, 0x79, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 395 | 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 396 | 0x61, 0x79, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 397 | 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 398 | 0x64, 0x54, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x12, 0x1f, 0x2e, 0x67, 399 | 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 0x69, 400 | 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 401 | 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x43, 0x6c, 402 | 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 403 | 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x62, 0x06, 0x70, 0x72, 404 | 0x6f, 0x74, 0x6f, 0x33, 405 | } 406 | 407 | var ( 408 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescOnce sync.Once 409 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescData = file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDesc 410 | ) 411 | 412 | func file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescGZIP() []byte { 413 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescOnce.Do(func() { 414 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescData) 415 | }) 416 | return file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDescData 417 | } 418 | 419 | var file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 420 | var file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_goTypes = []interface{}{ 421 | (*IsOnlineRequest)(nil), // 0: gateway.IsOnlineRequest 422 | (*IsOnlineResponse)(nil), // 1: gateway.IsOnlineResponse 423 | (*SendToClientIdRequest)(nil), // 2: gateway.SendToClientIdRequest 424 | (*SendToClientIdResponse)(nil), // 3: gateway.SendToClientIdResponse 425 | (*SendToClientIdsRequest)(nil), // 4: gateway.SendToClientIdsRequest 426 | (*SendToClientIdsResponse)(nil), // 5: gateway.SendToClientIdsResponse 427 | } 428 | var file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_depIdxs = []int32{ 429 | 0, // 0: gateway.GatewayService.IsOnline:input_type -> gateway.IsOnlineRequest 430 | 2, // 1: gateway.GatewayService.SendToClientId:input_type -> gateway.SendToClientIdRequest 431 | 4, // 2: gateway.GatewayService.SendToClientIds:input_type -> gateway.SendToClientIdsRequest 432 | 1, // 3: gateway.GatewayService.IsOnline:output_type -> gateway.IsOnlineResponse 433 | 3, // 4: gateway.GatewayService.SendToClientId:output_type -> gateway.SendToClientIdResponse 434 | 5, // 5: gateway.GatewayService.SendToClientIds:output_type -> gateway.SendToClientIdsResponse 435 | 3, // [3:6] is the sub-list for method output_type 436 | 0, // [0:3] is the sub-list for method input_type 437 | 0, // [0:0] is the sub-list for extension type_name 438 | 0, // [0:0] is the sub-list for extension extendee 439 | 0, // [0:0] is the sub-list for field type_name 440 | } 441 | 442 | func init() { file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_init() } 443 | func file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_init() { 444 | if File_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto != nil { 445 | return 446 | } 447 | if !protoimpl.UnsafeEnabled { 448 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 449 | switch v := v.(*IsOnlineRequest); i { 450 | case 0: 451 | return &v.state 452 | case 1: 453 | return &v.sizeCache 454 | case 2: 455 | return &v.unknownFields 456 | default: 457 | return nil 458 | } 459 | } 460 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 461 | switch v := v.(*IsOnlineResponse); i { 462 | case 0: 463 | return &v.state 464 | case 1: 465 | return &v.sizeCache 466 | case 2: 467 | return &v.unknownFields 468 | default: 469 | return nil 470 | } 471 | } 472 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 473 | switch v := v.(*SendToClientIdRequest); i { 474 | case 0: 475 | return &v.state 476 | case 1: 477 | return &v.sizeCache 478 | case 2: 479 | return &v.unknownFields 480 | default: 481 | return nil 482 | } 483 | } 484 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 485 | switch v := v.(*SendToClientIdResponse); i { 486 | case 0: 487 | return &v.state 488 | case 1: 489 | return &v.sizeCache 490 | case 2: 491 | return &v.unknownFields 492 | default: 493 | return nil 494 | } 495 | } 496 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 497 | switch v := v.(*SendToClientIdsRequest); i { 498 | case 0: 499 | return &v.state 500 | case 1: 501 | return &v.sizeCache 502 | case 2: 503 | return &v.unknownFields 504 | default: 505 | return nil 506 | } 507 | } 508 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { 509 | switch v := v.(*SendToClientIdsResponse); i { 510 | case 0: 511 | return &v.state 512 | case 1: 513 | return &v.sizeCache 514 | case 2: 515 | return &v.unknownFields 516 | default: 517 | return nil 518 | } 519 | } 520 | } 521 | type x struct{} 522 | out := protoimpl.TypeBuilder{ 523 | File: protoimpl.DescBuilder{ 524 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 525 | RawDescriptor: file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDesc, 526 | NumEnums: 0, 527 | NumMessages: 6, 528 | NumExtensions: 0, 529 | NumServices: 1, 530 | }, 531 | GoTypes: file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_goTypes, 532 | DependencyIndexes: file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_depIdxs, 533 | MessageInfos: file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_msgTypes, 534 | }.Build() 535 | File_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto = out.File 536 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_rawDesc = nil 537 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_goTypes = nil 538 | file_grpcs_socket_cluster_gateway_proto_gateway_gateway_proto_depIdxs = nil 539 | } 540 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/proto/gateway/gateway.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package gateway; 5 | 6 | option go_package = "./gateway"; 7 | 8 | message IsOnlineRequest{ 9 | string client_id = 1; 10 | 11 | } 12 | message IsOnlineResponse{ 13 | int64 code = 1; 14 | string msg = 2; 15 | bool is_online = 3; 16 | 17 | } 18 | message SendToClientIdRequest{ 19 | string client_id = 1; 20 | bytes data = 2; 21 | 22 | } 23 | message SendToClientIdResponse{ 24 | int64 code = 1; 25 | string msg = 2; 26 | 27 | } 28 | message SendToClientIdsRequest{ 29 | repeated string client_ids = 1; 30 | bytes data = 2; 31 | 32 | } 33 | message SendToClientIdsResponse{ 34 | int64 code = 1; 35 | string msg = 2; 36 | 37 | } 38 | 39 | 40 | service GatewayService{ 41 | rpc IsOnline(IsOnlineRequest) returns (IsOnlineResponse); rpc SendToClientId(SendToClientIdRequest) returns (SendToClientIdResponse); rpc SendToClientIds(SendToClientIdsRequest) returns (SendToClientIdsResponse); 42 | } 43 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/proto/gateway/gateway_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v3.14.0 5 | // source: grpcs/socket_cluster_gateway/proto/gateway/gateway.proto 6 | 7 | package gateway 8 | 9 | import ( 10 | context "context" 11 | 12 | grpc "google.golang.org/grpc" 13 | codes "google.golang.org/grpc/codes" 14 | status "google.golang.org/grpc/status" 15 | ) 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the grpc package it is being compiled against. 19 | // Requires gRPC-Go v1.32.0 or later. 20 | const _ = grpc.SupportPackageIsVersion7 21 | 22 | const ( 23 | GatewayService_IsOnline_FullMethodName = "/gateway.GatewayService/IsOnline" 24 | GatewayService_SendToClientId_FullMethodName = "/gateway.GatewayService/SendToClientId" 25 | GatewayService_SendToClientIds_FullMethodName = "/gateway.GatewayService/SendToClientIds" 26 | ) 27 | 28 | // GatewayServiceClient is the client API for GatewayService service. 29 | // 30 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 31 | type GatewayServiceClient interface { 32 | IsOnline(ctx context.Context, in *IsOnlineRequest, opts ...grpc.CallOption) (*IsOnlineResponse, error) 33 | SendToClientId(ctx context.Context, in *SendToClientIdRequest, opts ...grpc.CallOption) (*SendToClientIdResponse, error) 34 | SendToClientIds(ctx context.Context, in *SendToClientIdsRequest, opts ...grpc.CallOption) (*SendToClientIdsResponse, error) 35 | } 36 | 37 | type gatewayServiceClient struct { 38 | cc grpc.ClientConnInterface 39 | } 40 | 41 | func NewGatewayServiceClient(cc grpc.ClientConnInterface) GatewayServiceClient { 42 | return &gatewayServiceClient{cc} 43 | } 44 | 45 | func (c *gatewayServiceClient) IsOnline(ctx context.Context, in *IsOnlineRequest, opts ...grpc.CallOption) (*IsOnlineResponse, error) { 46 | out := new(IsOnlineResponse) 47 | err := c.cc.Invoke(ctx, GatewayService_IsOnline_FullMethodName, in, out, opts...) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return out, nil 52 | } 53 | 54 | func (c *gatewayServiceClient) SendToClientId(ctx context.Context, in *SendToClientIdRequest, opts ...grpc.CallOption) (*SendToClientIdResponse, error) { 55 | out := new(SendToClientIdResponse) 56 | err := c.cc.Invoke(ctx, GatewayService_SendToClientId_FullMethodName, in, out, opts...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return out, nil 61 | } 62 | 63 | func (c *gatewayServiceClient) SendToClientIds(ctx context.Context, in *SendToClientIdsRequest, opts ...grpc.CallOption) (*SendToClientIdsResponse, error) { 64 | out := new(SendToClientIdsResponse) 65 | err := c.cc.Invoke(ctx, GatewayService_SendToClientIds_FullMethodName, in, out, opts...) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return out, nil 70 | } 71 | 72 | // GatewayServiceServer is the server API for GatewayService service. 73 | // All implementations must embed UnimplementedGatewayServiceServer 74 | // for forward compatibility 75 | type GatewayServiceServer interface { 76 | IsOnline(context.Context, *IsOnlineRequest) (*IsOnlineResponse, error) 77 | SendToClientId(context.Context, *SendToClientIdRequest) (*SendToClientIdResponse, error) 78 | SendToClientIds(context.Context, *SendToClientIdsRequest) (*SendToClientIdsResponse, error) 79 | mustEmbedUnimplementedGatewayServiceServer() 80 | } 81 | 82 | // UnimplementedGatewayServiceServer must be embedded to have forward compatible implementations. 83 | type UnimplementedGatewayServiceServer struct { 84 | } 85 | 86 | func (UnimplementedGatewayServiceServer) IsOnline(context.Context, *IsOnlineRequest) (*IsOnlineResponse, error) { 87 | return nil, status.Errorf(codes.Unimplemented, "method IsOnline not implemented") 88 | } 89 | func (UnimplementedGatewayServiceServer) SendToClientId(context.Context, *SendToClientIdRequest) (*SendToClientIdResponse, error) { 90 | return nil, status.Errorf(codes.Unimplemented, "method SendToClientId not implemented") 91 | } 92 | func (UnimplementedGatewayServiceServer) SendToClientIds(context.Context, *SendToClientIdsRequest) (*SendToClientIdsResponse, error) { 93 | return nil, status.Errorf(codes.Unimplemented, "method SendToClientIds not implemented") 94 | } 95 | func (UnimplementedGatewayServiceServer) mustEmbedUnimplementedGatewayServiceServer() {} 96 | 97 | // UnsafeGatewayServiceServer may be embedded to opt out of forward compatibility for this service. 98 | // Use of this interface is not recommended, as added methods to GatewayServiceServer will 99 | // result in compilation errors. 100 | type UnsafeGatewayServiceServer interface { 101 | mustEmbedUnimplementedGatewayServiceServer() 102 | } 103 | 104 | func RegisterGatewayServiceServer(s grpc.ServiceRegistrar, srv GatewayServiceServer) { 105 | s.RegisterService(&GatewayService_ServiceDesc, srv) 106 | } 107 | 108 | func _GatewayService_IsOnline_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 109 | in := new(IsOnlineRequest) 110 | if err := dec(in); err != nil { 111 | return nil, err 112 | } 113 | if interceptor == nil { 114 | return srv.(GatewayServiceServer).IsOnline(ctx, in) 115 | } 116 | info := &grpc.UnaryServerInfo{ 117 | Server: srv, 118 | FullMethod: GatewayService_IsOnline_FullMethodName, 119 | } 120 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 121 | return srv.(GatewayServiceServer).IsOnline(ctx, req.(*IsOnlineRequest)) 122 | } 123 | return interceptor(ctx, in, info, handler) 124 | } 125 | 126 | func _GatewayService_SendToClientId_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 127 | in := new(SendToClientIdRequest) 128 | if err := dec(in); err != nil { 129 | return nil, err 130 | } 131 | if interceptor == nil { 132 | return srv.(GatewayServiceServer).SendToClientId(ctx, in) 133 | } 134 | info := &grpc.UnaryServerInfo{ 135 | Server: srv, 136 | FullMethod: GatewayService_SendToClientId_FullMethodName, 137 | } 138 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 139 | return srv.(GatewayServiceServer).SendToClientId(ctx, req.(*SendToClientIdRequest)) 140 | } 141 | return interceptor(ctx, in, info, handler) 142 | } 143 | 144 | func _GatewayService_SendToClientIds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 145 | in := new(SendToClientIdsRequest) 146 | if err := dec(in); err != nil { 147 | return nil, err 148 | } 149 | if interceptor == nil { 150 | return srv.(GatewayServiceServer).SendToClientIds(ctx, in) 151 | } 152 | info := &grpc.UnaryServerInfo{ 153 | Server: srv, 154 | FullMethod: GatewayService_SendToClientIds_FullMethodName, 155 | } 156 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 157 | return srv.(GatewayServiceServer).SendToClientIds(ctx, req.(*SendToClientIdsRequest)) 158 | } 159 | return interceptor(ctx, in, info, handler) 160 | } 161 | 162 | // GatewayService_ServiceDesc is the grpc.ServiceDesc for GatewayService service. 163 | // It's only intended for direct use with grpc.RegisterService, 164 | // and not to be introspected or modified (even as a copy) 165 | var GatewayService_ServiceDesc = grpc.ServiceDesc{ 166 | ServiceName: "gateway.GatewayService", 167 | HandlerType: (*GatewayServiceServer)(nil), 168 | Methods: []grpc.MethodDesc{ 169 | { 170 | MethodName: "IsOnline", 171 | Handler: _GatewayService_IsOnline_Handler, 172 | }, 173 | { 174 | MethodName: "SendToClientId", 175 | Handler: _GatewayService_SendToClientId_Handler, 176 | }, 177 | { 178 | MethodName: "SendToClientIds", 179 | Handler: _GatewayService_SendToClientIds_Handler, 180 | }, 181 | }, 182 | Streams: []grpc.StreamDesc{}, 183 | Metadata: "grpcs/socket_cluster_gateway/proto/gateway/gateway.proto", 184 | } 185 | -------------------------------------------------------------------------------- /grpcs/socket_cluster_gateway/socket_cluster_gateway.go: -------------------------------------------------------------------------------- 1 | package socket_cluster_gateway 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/urfave/cli/v2" 8 | 9 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/handler" 10 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 11 | "github.com/weblazy/socket-cluster/node" 12 | 13 | "github.com/weblazy/easy/grpc/grpc_server" 14 | "github.com/weblazy/easy/grpc/grpc_server/grpc_server_config" 15 | ) 16 | 17 | var Cmd = &cli.Command{ 18 | // Name: "socket_cluster_gateway", 19 | // Aliases: []string{}, 20 | // Usage: "socket_cluster_gateway start", 21 | // Subcommands: []*cli.Command{ 22 | // { 23 | // Name: "start", 24 | // Usage: "start service", 25 | // Action: Run, 26 | // }, 27 | // }, 28 | } 29 | 30 | func Run(c context.Context, n node.Node, grpcConf *grpc_server_config.Config) error { 31 | // defer closes.Close() 32 | s := grpc_server.NewGrpcServer(grpcConf) 33 | gatewayService := handler.NewGatewayService(n) 34 | 35 | gateway.RegisterGatewayServiceServer(s, gatewayService) 36 | err := s.Init() 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | err = s.Start() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /logx/fileLogger.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | ) 9 | 10 | const defaultDir = "log" 11 | 12 | type fileLogger struct { 13 | dateFormat string // 时间格式 默认:2006-01-05 15:04:05.000 14 | flow string // 操作流 用于标记日志信息 不设置无输出 15 | level logLevel // 输出level 不设置默认全部输出 16 | output *os.File 17 | expiredTime int // 清理时间 <0 不清理 单位:天 默认:7 18 | } 19 | 20 | // 判断文件目录否存在 21 | func IsDirExists(path string) bool { 22 | fi, err := os.Stat(path) 23 | 24 | if err != nil { 25 | return os.IsExist(err) 26 | } else { 27 | return fi.IsDir() 28 | } 29 | } 30 | 31 | // 创建文件 32 | func MkdirFile(path string) error { 33 | err := os.Mkdir(path, os.ModePerm) //在当前目录下生成md目录 34 | if err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | 40 | func NewFileLogger() *fileLogger { 41 | if !IsDirExists(defaultDir) { 42 | if mkdirerr := MkdirFile(defaultDir); mkdirerr != nil { 43 | fmt.Println(mkdirerr) 44 | } 45 | } 46 | 47 | file, err := makeLogFile() 48 | if err != nil { 49 | file = os.Stdout 50 | fmt.Println("file logger create failed, redirect to stdlogger.") 51 | } 52 | return &fileLogger{ 53 | dateFormat: defaultTimeTemp, 54 | level: DEBUG | INFO | WARNING | ERROR, 55 | expiredTime: 7, 56 | output: file, 57 | } 58 | } 59 | func (l *fileLogger) SetDateFormat(temp string) *fileLogger { 60 | l.dateFormat = temp 61 | return l 62 | } 63 | 64 | func (l *fileLogger) SetFlow(flow string) *fileLogger { 65 | l.flow = flow 66 | return l 67 | } 68 | 69 | func (l *fileLogger) SetShowLevel(level logLevel) *fileLogger { 70 | l.level = level 71 | return l 72 | } 73 | 74 | func (l *fileLogger) SetExpiredTimeTime(expiredTime int) *fileLogger { 75 | l.expiredTime = expiredTime 76 | return l 77 | } 78 | 79 | func (l *fileLogger) Debug(v ...interface{}) { 80 | if l.level&DEBUG != 0 { 81 | content := makeContent(l.dateFormat, l.flow, DEBUG, v...) 82 | l.print(content) 83 | } 84 | } 85 | 86 | func (l *fileLogger) Debugf(format string, v ...interface{}) { 87 | if l.level&DEBUG != 0 { 88 | msg := makeMsg(l.dateFormat, l.flow, DEBUG, format, v...) 89 | l.print(msg) 90 | } 91 | } 92 | 93 | func (l *fileLogger) Info(v ...interface{}) { 94 | if l.level&INFO != 0 { 95 | content := makeContent(l.dateFormat, l.flow, INFO, v...) 96 | l.print(content) 97 | } 98 | } 99 | 100 | func (l *fileLogger) Infof(format string, v ...interface{}) { 101 | if l.level&INFO != 0 { 102 | msg := makeMsg(l.dateFormat, l.flow, INFO, format, v...) 103 | l.print(msg) 104 | } 105 | } 106 | 107 | func (l *fileLogger) Warning(v ...interface{}) { 108 | if l.level&WARNING != 0 { 109 | content := makeContent(l.dateFormat, l.flow, WARNING, v...) 110 | l.print(content) 111 | } 112 | } 113 | 114 | func (l *fileLogger) Warningf(format string, v ...interface{}) { 115 | if l.level&WARNING != 0 { 116 | msg := makeMsg(l.dateFormat, l.flow, WARNING, format, v...) 117 | l.print(msg) 118 | } 119 | } 120 | 121 | func (l *fileLogger) Error(v ...interface{}) { 122 | if l.level&ERROR != 0 { 123 | content := makeContent(l.dateFormat, l.flow, ERROR, v...) 124 | l.print(content) 125 | } 126 | } 127 | 128 | func (l *fileLogger) Errorf(format string, v ...interface{}) { 129 | if l.level&ERROR != 0 { 130 | msg := makeMsg(l.dateFormat, l.flow, ERROR, format, v...) 131 | l.print(msg) 132 | } 133 | } 134 | 135 | func makeLogFile() (*os.File, error) { 136 | now := time.Now().Format(defaultDateTemp) 137 | fileName := defaultDir + "/" + now + ".log" 138 | file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 139 | if err != nil { 140 | return nil, fmt.Errorf("log file open failed, err = %v", err) 141 | } 142 | return file, nil 143 | } 144 | 145 | func (s *fileLogger) print(log string) { 146 | if s.output == os.Stdout { 147 | fmt.Fprintln(s.output, log) 148 | } else { 149 | s.checkout() 150 | fmt.Fprintln(s.output, log) 151 | } 152 | } 153 | 154 | func (s *fileLogger) checkout() { 155 | now := time.Now().Format(defaultDateTemp) 156 | filename := s.output.Name() 157 | if filename[:10] != now { 158 | s.changeOutput() 159 | s.removeExpiredLogs() 160 | } 161 | } 162 | 163 | func (s *fileLogger) changeOutput() { 164 | s.output.Close() 165 | file, err := makeLogFile() 166 | if err == nil { 167 | s.output = file 168 | } else { 169 | fmt.Println("file logger create failed, redirect to stdlogger.") 170 | s.output = os.Stdout 171 | } 172 | } 173 | 174 | func (s *fileLogger) removeExpiredLogs() { 175 | files, err := filepath.Glob(defaultDir + "/*.log") 176 | if err != nil { 177 | fmt.Printf("failed to remove expired log files, error: %s", err) 178 | return 179 | } 180 | delTime := time.Now().AddDate(0, 0, -s.expiredTime).Format(defaultDateTemp) 181 | for _, file := range files { 182 | if file[:10] > delTime { 183 | continue 184 | } 185 | if err := os.Remove(file); err != nil { 186 | fmt.Printf("failed to remove expired log file: %s", file) 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /logx/filelogger_test.go: -------------------------------------------------------------------------------- 1 | package logx_test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFileLog(t *testing.T) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /logx/log.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | var LogHandler = NewFileLogger() 4 | -------------------------------------------------------------------------------- /logx/log/2023-08-19.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/logx/log/2023-08-19.log -------------------------------------------------------------------------------- /logx/logx.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "path" 7 | "runtime" 8 | "time" 9 | ) 10 | 11 | const ( 12 | DEBUG logLevel = 1 << iota 13 | INFO 14 | WARNING 15 | ERROR 16 | 17 | defaultTimeTemp = "2006-01-02 15:04:05" 18 | defaultDateTemp = "2006-01-02" 19 | ) 20 | 21 | type ( 22 | logLevel uint16 23 | Logx interface { 24 | Debug(v ...interface{}) 25 | Debugf(format string, v ...interface{}) 26 | Info(v ...interface{}) 27 | Infof(format string, v ...interface{}) 28 | Warning(v ...interface{}) 29 | Warningf(format string, v ...interface{}) 30 | Error(v ...interface{}) 31 | Errorf(format string, v ...interface{}) 32 | } 33 | 34 | record struct { 35 | TimeStamp string `json:"timestamp"` // 日志生成时间 36 | Level string `json:"level"` // 日志等级 37 | Flow string `json:"flow,omitempty"` // 操作流 38 | Loc string `json:"location"` // 发生位置 39 | Content []interface{} `json:"content,omitempty"` // 日志内容 40 | Msg string `json:"msg,omitempty"` // 日志内容 41 | } 42 | ) 43 | 44 | func makeContent(dateFormat, flow string, level logLevel, v ...interface{}) string { 45 | levelStr := levelToStr(level) 46 | funcName, fileName, lineNo := getCaseLineInfo(3) 47 | record := record{ 48 | TimeStamp: time.Now().Format(dateFormat), 49 | Flow: flow, 50 | Loc: fmt.Sprintf("%s:%d:%s", fileName, lineNo, funcName), 51 | Level: levelStr, 52 | Content: v, 53 | } 54 | bytes, _ := json.Marshal(record) 55 | return string(bytes) 56 | } 57 | 58 | func makeMsg(dateFormat, flow string, level logLevel, format string, v ...interface{}) string { 59 | levelStr := levelToStr(level) 60 | funcName, fileName, lineNo := getCaseLineInfo(3) 61 | record := record{ 62 | TimeStamp: time.Now().Format(dateFormat), 63 | Flow: flow, 64 | Loc: fmt.Sprintf("%s:%d:%s", fileName, lineNo, funcName), 65 | Level: levelStr, 66 | Msg: fmt.Sprintf(format, v...), 67 | } 68 | bytes, _ := json.Marshal(record) 69 | return string(bytes) 70 | } 71 | 72 | func getCaseLineInfo(skip int) (funcName, fileName string, lineNo int) { 73 | pc, file, lineNo, ok := runtime.Caller(skip) 74 | if !ok { 75 | fmt.Println("runtime.Caller() failed") 76 | return 77 | } 78 | funcName = path.Base(runtime.FuncForPC(pc).Name()) 79 | fileName = path.Base(file) 80 | return 81 | } 82 | 83 | func levelToStr(level logLevel) (levelStr string) { 84 | switch level { 85 | case DEBUG: 86 | levelStr = "debug" 87 | case INFO: 88 | levelStr = "info" 89 | case WARNING: 90 | levelStr = "warning" 91 | case ERROR: 92 | levelStr = "error" 93 | } 94 | return levelStr 95 | } 96 | -------------------------------------------------------------------------------- /logx/stdlogger.go: -------------------------------------------------------------------------------- 1 | package logx 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type stdLogger struct { 9 | dateFormat string // 时间格式 默认:2006-01-05 15:04:05.000 10 | flow string // 操作流 用于标记日志信息 不设置无输出 11 | level logLevel // 输出level 不设置默认全部输出 12 | } 13 | 14 | func NewStdLogger() *stdLogger { 15 | return &stdLogger{ 16 | dateFormat: defaultTimeTemp, 17 | level: DEBUG | INFO | WARNING | ERROR, 18 | } 19 | } 20 | 21 | func (l *stdLogger) SetDateFormat(temp string) *stdLogger { 22 | l.dateFormat = temp 23 | return l 24 | } 25 | 26 | func (l *stdLogger) SetFlow(flow string) *stdLogger { 27 | l.flow = flow 28 | return l 29 | } 30 | 31 | func (l *stdLogger) SetShowLevel(level logLevel) *stdLogger { 32 | l.level = level 33 | return l 34 | } 35 | 36 | 37 | func (l *stdLogger) Debug(v ...interface{}) { 38 | if l.level & DEBUG != 0 { 39 | content := makeContent(l.dateFormat, l.flow, DEBUG, v...) 40 | fmt.Fprintln(os.Stdout, content) 41 | } 42 | } 43 | 44 | func (l *stdLogger) Debugf(format string, v ...interface{}) { 45 | if l.level & DEBUG != 0 { 46 | msg := makeMsg(l.dateFormat, l.flow, DEBUG, format, v...) 47 | fmt.Fprintln(os.Stdout, msg) 48 | } 49 | } 50 | 51 | func (l *stdLogger) Info(v ...interface{}) { 52 | if l.level & INFO != 0 { 53 | content := makeContent(l.dateFormat, l.flow, INFO, v...) 54 | fmt.Fprintln(os.Stdout, content) 55 | } 56 | } 57 | 58 | func (l *stdLogger) Infof(format string, v ...interface{}) { 59 | if l.level & INFO != 0 { 60 | msg := makeMsg(l.dateFormat, l.flow, INFO, format, v...) 61 | fmt.Fprintln(os.Stdout, msg) 62 | 63 | } 64 | } 65 | 66 | func (l *stdLogger) Warning(v ...interface{}) { 67 | if l.level & WARNING != 0 { 68 | content := makeContent(l.dateFormat, l.flow, WARNING, v...) 69 | fmt.Fprintln(os.Stdout, content) 70 | } 71 | } 72 | 73 | func (l *stdLogger) Warningf(format string, v ...interface{}) { 74 | if l.level & WARNING != 0 { 75 | msg := makeMsg(l.dateFormat, l.flow, WARNING, format, v...) 76 | fmt.Fprintln(os.Stdout, msg) 77 | } 78 | } 79 | 80 | func (l *stdLogger) Error(v ...interface{}) { 81 | if l.level & ERROR != 0 { 82 | content := makeContent(l.dateFormat, l.flow, ERROR, v...) 83 | fmt.Fprintln(os.Stdout, content) 84 | } 85 | } 86 | 87 | func (l *stdLogger) Errorf(format string, v ...interface{}) { 88 | if l.level & ERROR != 0 { 89 | msg := makeMsg(l.dateFormat, l.flow, ERROR, format, v...) 90 | fmt.Fprintln(os.Stdout, msg) 91 | } 92 | } -------------------------------------------------------------------------------- /logx/stdlogger_test.go: -------------------------------------------------------------------------------- 1 | package logx_test 2 | 3 | import ( 4 | "github.com/weblazy/socket-cluster/logx" 5 | "testing" 6 | ) 7 | 8 | func TestStdLogger(t *testing.T) { 9 | logger := logx.NewStdLogger().SetFlow("std").SetShowLevel(logx.INFO | logx.ERROR) 10 | logger.Info("info") 11 | logger.Debug("debug") 12 | logger.Warning("warning") 13 | logger.Error("error") 14 | 15 | logger.Infof("infof") 16 | logger.Debugf("debugf") 17 | logger.Warningf("warningf") 18 | logger.Errorf("errorf") 19 | } 20 | -------------------------------------------------------------------------------- /node/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/node/.DS_Store -------------------------------------------------------------------------------- /node/common.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "strings" 5 | 6 | uuid "github.com/satori/go.uuid" 7 | ) 8 | 9 | // GetUUID return a uuid 10 | func GetUUID() string { 11 | uuId := uuid.NewV4().String() 12 | uuIdStr := strings.Replace(uuId, "-", "", -1) 13 | return uuIdStr 14 | } 15 | -------------------------------------------------------------------------------- /node/conf.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: conf.proto 3 | 4 | //包名,通过protoc生成时go文件时 5 | 6 | package node 7 | 8 | import ( 9 | fmt "fmt" 10 | proto "github.com/golang/protobuf/proto" 11 | math "math" 12 | ) 13 | 14 | // Reference imports to suppress errors if they are not otherwise used. 15 | var _ = proto.Marshal 16 | var _ = fmt.Errorf 17 | var _ = math.Inf 18 | 19 | // This is a compile-time assertion to ensure that this generated file 20 | // is compatible with the proto package it is being compiled against. 21 | // A compilation error at this line likely means your copy of the 22 | // proto package needs to be updated. 23 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 24 | 25 | type NodeInfo struct { 26 | ClientAddress string `protobuf:"bytes,1,opt,name=client_address,json=clientAddress,proto3" json:"client_address,omitempty"` 27 | ClientCount int64 `protobuf:"varint,2,opt,name=client_count,json=clientCount,proto3" json:"client_count,omitempty"` 28 | Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 29 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 30 | XXX_unrecognized []byte `json:"-"` 31 | XXX_sizecache int32 `json:"-"` 32 | } 33 | 34 | func (m *NodeInfo) Reset() { *m = NodeInfo{} } 35 | func (m *NodeInfo) String() string { return proto.CompactTextString(m) } 36 | func (*NodeInfo) ProtoMessage() {} 37 | func (*NodeInfo) Descriptor() ([]byte, []int) { 38 | return fileDescriptor_0b6ecbfc68e85c65, []int{0} 39 | } 40 | 41 | func (m *NodeInfo) XXX_Unmarshal(b []byte) error { 42 | return xxx_messageInfo_NodeInfo.Unmarshal(m, b) 43 | } 44 | func (m *NodeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 45 | return xxx_messageInfo_NodeInfo.Marshal(b, m, deterministic) 46 | } 47 | func (m *NodeInfo) XXX_Merge(src proto.Message) { 48 | xxx_messageInfo_NodeInfo.Merge(m, src) 49 | } 50 | func (m *NodeInfo) XXX_Size() int { 51 | return xxx_messageInfo_NodeInfo.Size(m) 52 | } 53 | func (m *NodeInfo) XXX_DiscardUnknown() { 54 | xxx_messageInfo_NodeInfo.DiscardUnknown(m) 55 | } 56 | 57 | var xxx_messageInfo_NodeInfo proto.InternalMessageInfo 58 | 59 | func (m *NodeInfo) GetClientAddress() string { 60 | if m != nil { 61 | return m.ClientAddress 62 | } 63 | return "" 64 | } 65 | 66 | func (m *NodeInfo) GetClientCount() int64 { 67 | if m != nil { 68 | return m.ClientCount 69 | } 70 | return 0 71 | } 72 | 73 | func (m *NodeInfo) GetTimestamp() int64 { 74 | if m != nil { 75 | return m.Timestamp 76 | } 77 | return 0 78 | } 79 | 80 | func init() { 81 | proto.RegisterType((*NodeInfo)(nil), "proto.NodeInfo") 82 | } 83 | 84 | func init() { proto.RegisterFile("conf.proto", fileDescriptor_0b6ecbfc68e85c65) } 85 | 86 | var fileDescriptor_0b6ecbfc68e85c65 = []byte{ 87 | // 130 bytes of a gzipped FileDescriptorProto 88 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4a, 0xce, 0xcf, 0x4b, 89 | 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x45, 0x5c, 0x1c, 0x7e, 0xf9, 90 | 0x29, 0xa9, 0x9e, 0x79, 0x69, 0xf9, 0x42, 0xaa, 0x5c, 0x7c, 0xc9, 0x39, 0x99, 0xa9, 0x79, 0x25, 91 | 0xf1, 0x89, 0x29, 0x29, 0x45, 0xa9, 0xc5, 0xc5, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0xbc, 92 | 0x10, 0x51, 0x47, 0x88, 0xa0, 0x90, 0x22, 0x17, 0x0f, 0x54, 0x59, 0x72, 0x7e, 0x69, 0x5e, 0x89, 93 | 0x04, 0x93, 0x02, 0xa3, 0x06, 0x73, 0x10, 0x37, 0x44, 0xcc, 0x19, 0x24, 0x24, 0x24, 0xc3, 0xc5, 94 | 0x59, 0x92, 0x99, 0x9b, 0x5a, 0x5c, 0x92, 0x98, 0x5b, 0x20, 0xc1, 0x0c, 0x96, 0x47, 0x08, 0x24, 95 | 0xb1, 0x81, 0xad, 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xe7, 0x42, 0x43, 0xa2, 0x8f, 0x00, 96 | 0x00, 0x00, 97 | } 98 | -------------------------------------------------------------------------------- /node/conf.proto: -------------------------------------------------------------------------------- 1 | // protoc --go_out=. msg.proto 2 | syntax = "proto3"; 3 | 4 | package node; 5 | // nodeInfo 6 | message NodeInfo { 7 | string client_address = 1; 8 | int64 client_count = 2; 9 | int64 timestamp = 3; 10 | } -------------------------------------------------------------------------------- /node/const.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "time" 5 | 6 | proto "github.com/golang/protobuf/proto" 7 | ) 8 | 9 | const ( 10 | clientPrefix = "client#" 11 | DefaultPassword = "password" 12 | defaultClientPingInterval = 120 13 | DefaultNodePingInterval = 10 14 | defaultInternalPort = 9528 15 | defaultPort = 9527 16 | defaultAddr = "127.0.0.1:9528" 17 | NodeAddress = "node_address" 18 | authTime = 10 * time.Second 19 | ) 20 | 21 | const ( 22 | AuthNodeMsgType int32 = 1 23 | ClientMsgType int32 = 2 24 | PingMsgType int32 = 3 25 | AuthBusinessClientMsgType int32 = 4 26 | BindNodeIdMsgType int32 = 5 27 | ) 28 | 29 | var PingMsg []byte 30 | 31 | func init() { 32 | msg := Msg{MsgType: PingMsgType} 33 | PingMsg, _ = proto.Marshal(&msg) 34 | } 35 | -------------------------------------------------------------------------------- /node/msg.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: msg.proto 3 | 4 | package node 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | 23 | type Msg struct { 24 | MsgType int32 `protobuf:"varint,1,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty"` 25 | Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 26 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 27 | XXX_unrecognized []byte `json:"-"` 28 | XXX_sizecache int32 `json:"-"` 29 | } 30 | 31 | func (m *Msg) Reset() { *m = Msg{} } 32 | func (m *Msg) String() string { return proto.CompactTextString(m) } 33 | func (*Msg) ProtoMessage() {} 34 | func (*Msg) Descriptor() ([]byte, []int) { 35 | return fileDescriptor_c06e4cca6c2cc899, []int{0} 36 | } 37 | 38 | func (m *Msg) XXX_Unmarshal(b []byte) error { 39 | return xxx_messageInfo_Msg.Unmarshal(m, b) 40 | } 41 | func (m *Msg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 42 | return xxx_messageInfo_Msg.Marshal(b, m, deterministic) 43 | } 44 | func (m *Msg) XXX_Merge(src proto.Message) { 45 | xxx_messageInfo_Msg.Merge(m, src) 46 | } 47 | func (m *Msg) XXX_Size() int { 48 | return xxx_messageInfo_Msg.Size(m) 49 | } 50 | func (m *Msg) XXX_DiscardUnknown() { 51 | xxx_messageInfo_Msg.DiscardUnknown(m) 52 | } 53 | 54 | var xxx_messageInfo_Msg proto.InternalMessageInfo 55 | 56 | func (m *Msg) GetMsgType() int32 { 57 | if m != nil { 58 | return m.MsgType 59 | } 60 | return 0 61 | } 62 | 63 | func (m *Msg) GetData() []byte { 64 | if m != nil { 65 | return m.Data 66 | } 67 | return nil 68 | } 69 | 70 | type ClientsMsg struct { 71 | ReceiveClientIds []string `protobuf:"bytes,1,rep,name=receive_client_ids,json=receiveClientIds,proto3" json:"receive_client_ids,omitempty"` 72 | Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 73 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 74 | XXX_unrecognized []byte `json:"-"` 75 | XXX_sizecache int32 `json:"-"` 76 | } 77 | 78 | func (m *ClientsMsg) Reset() { *m = ClientsMsg{} } 79 | func (m *ClientsMsg) String() string { return proto.CompactTextString(m) } 80 | func (*ClientsMsg) ProtoMessage() {} 81 | func (*ClientsMsg) Descriptor() ([]byte, []int) { 82 | return fileDescriptor_c06e4cca6c2cc899, []int{1} 83 | } 84 | 85 | func (m *ClientsMsg) XXX_Unmarshal(b []byte) error { 86 | return xxx_messageInfo_ClientsMsg.Unmarshal(m, b) 87 | } 88 | func (m *ClientsMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 89 | return xxx_messageInfo_ClientsMsg.Marshal(b, m, deterministic) 90 | } 91 | func (m *ClientsMsg) XXX_Merge(src proto.Message) { 92 | xxx_messageInfo_ClientsMsg.Merge(m, src) 93 | } 94 | func (m *ClientsMsg) XXX_Size() int { 95 | return xxx_messageInfo_ClientsMsg.Size(m) 96 | } 97 | func (m *ClientsMsg) XXX_DiscardUnknown() { 98 | xxx_messageInfo_ClientsMsg.DiscardUnknown(m) 99 | } 100 | 101 | var xxx_messageInfo_ClientsMsg proto.InternalMessageInfo 102 | 103 | func (m *ClientsMsg) GetReceiveClientIds() []string { 104 | if m != nil { 105 | return m.ReceiveClientIds 106 | } 107 | return nil 108 | } 109 | 110 | func (m *ClientsMsg) GetData() []byte { 111 | if m != nil { 112 | return m.Data 113 | } 114 | return nil 115 | } 116 | 117 | type AuthMsg struct { 118 | NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` 119 | Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` 120 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 121 | XXX_unrecognized []byte `json:"-"` 122 | XXX_sizecache int32 `json:"-"` 123 | } 124 | 125 | func (m *AuthMsg) Reset() { *m = AuthMsg{} } 126 | func (m *AuthMsg) String() string { return proto.CompactTextString(m) } 127 | func (*AuthMsg) ProtoMessage() {} 128 | func (*AuthMsg) Descriptor() ([]byte, []int) { 129 | return fileDescriptor_c06e4cca6c2cc899, []int{2} 130 | } 131 | 132 | func (m *AuthMsg) XXX_Unmarshal(b []byte) error { 133 | return xxx_messageInfo_AuthMsg.Unmarshal(m, b) 134 | } 135 | func (m *AuthMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 136 | return xxx_messageInfo_AuthMsg.Marshal(b, m, deterministic) 137 | } 138 | func (m *AuthMsg) XXX_Merge(src proto.Message) { 139 | xxx_messageInfo_AuthMsg.Merge(m, src) 140 | } 141 | func (m *AuthMsg) XXX_Size() int { 142 | return xxx_messageInfo_AuthMsg.Size(m) 143 | } 144 | func (m *AuthMsg) XXX_DiscardUnknown() { 145 | xxx_messageInfo_AuthMsg.DiscardUnknown(m) 146 | } 147 | 148 | var xxx_messageInfo_AuthMsg proto.InternalMessageInfo 149 | 150 | func (m *AuthMsg) GetNodeId() string { 151 | if m != nil { 152 | return m.NodeId 153 | } 154 | return "" 155 | } 156 | 157 | func (m *AuthMsg) GetPassword() string { 158 | if m != nil { 159 | return m.Password 160 | } 161 | return "" 162 | } 163 | 164 | type BindNodeIdMsg struct { 165 | NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` 166 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 167 | XXX_unrecognized []byte `json:"-"` 168 | XXX_sizecache int32 `json:"-"` 169 | } 170 | 171 | func (m *BindNodeIdMsg) Reset() { *m = BindNodeIdMsg{} } 172 | func (m *BindNodeIdMsg) String() string { return proto.CompactTextString(m) } 173 | func (*BindNodeIdMsg) ProtoMessage() {} 174 | func (*BindNodeIdMsg) Descriptor() ([]byte, []int) { 175 | return fileDescriptor_c06e4cca6c2cc899, []int{3} 176 | } 177 | 178 | func (m *BindNodeIdMsg) XXX_Unmarshal(b []byte) error { 179 | return xxx_messageInfo_BindNodeIdMsg.Unmarshal(m, b) 180 | } 181 | func (m *BindNodeIdMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 182 | return xxx_messageInfo_BindNodeIdMsg.Marshal(b, m, deterministic) 183 | } 184 | func (m *BindNodeIdMsg) XXX_Merge(src proto.Message) { 185 | xxx_messageInfo_BindNodeIdMsg.Merge(m, src) 186 | } 187 | func (m *BindNodeIdMsg) XXX_Size() int { 188 | return xxx_messageInfo_BindNodeIdMsg.Size(m) 189 | } 190 | func (m *BindNodeIdMsg) XXX_DiscardUnknown() { 191 | xxx_messageInfo_BindNodeIdMsg.DiscardUnknown(m) 192 | } 193 | 194 | var xxx_messageInfo_BindNodeIdMsg proto.InternalMessageInfo 195 | 196 | func (m *BindNodeIdMsg) GetNodeId() string { 197 | if m != nil { 198 | return m.NodeId 199 | } 200 | return "" 201 | } 202 | 203 | func init() { 204 | proto.RegisterType((*Msg)(nil), "node.Msg") 205 | proto.RegisterType((*ClientsMsg)(nil), "node.ClientsMsg") 206 | proto.RegisterType((*AuthMsg)(nil), "node.AuthMsg") 207 | proto.RegisterType((*BindNodeIdMsg)(nil), "node.BindNodeIdMsg") 208 | } 209 | 210 | func init() { proto.RegisterFile("msg.proto", fileDescriptor_c06e4cca6c2cc899) } 211 | 212 | var fileDescriptor_c06e4cca6c2cc899 = []byte{ 213 | // 202 bytes of a gzipped FileDescriptorProto 214 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x8f, 0xb1, 0x4b, 0xc6, 0x30, 215 | 0x10, 0xc5, 0x89, 0xad, 0x6d, 0x73, 0x28, 0x48, 0x16, 0xab, 0x53, 0xe9, 0x94, 0x41, 0x5c, 0x74, 216 | 0x16, 0xd4, 0xa9, 0x83, 0x1d, 0x82, 0x7b, 0xa8, 0xbd, 0x10, 0x03, 0xb6, 0x09, 0xbd, 0xa8, 0xf4, 217 | 0xbf, 0x97, 0x44, 0x71, 0xfa, 0xf8, 0xb6, 0x7b, 0x77, 0xef, 0xfd, 0x78, 0x07, 0x7c, 0x21, 0x7b, 218 | 0x1b, 0x36, 0x1f, 0xbd, 0x28, 0x57, 0x8f, 0xa6, 0xbf, 0x87, 0xe2, 0x85, 0xac, 0xb8, 0x82, 0x66, 219 | 0x21, 0xab, 0xe3, 0x1e, 0x4c, 0xcb, 0x3a, 0x26, 0x4f, 0x55, 0xbd, 0x90, 0x7d, 0xdd, 0x83, 0x11, 220 | 0x02, 0x4a, 0x9c, 0xe2, 0xd4, 0x9e, 0x74, 0x4c, 0x9e, 0xa9, 0x3c, 0xf7, 0x23, 0xc0, 0xf3, 0x87, 221 | 0x33, 0x6b, 0xa4, 0x14, 0xbe, 0x01, 0xb1, 0x99, 0xd9, 0xb8, 0x2f, 0xa3, 0xe7, 0xbc, 0xd5, 0x0e, 222 | 0xa9, 0x65, 0x5d, 0x21, 0xb9, 0xba, 0xf8, 0xbb, 0xfc, 0xda, 0x07, 0xa4, 0x83, 0xbc, 0x07, 0xa8, 223 | 0x1f, 0x3f, 0xe3, 0x7b, 0x82, 0x5d, 0x42, 0x9d, 0x8a, 0x69, 0x87, 0xb9, 0x08, 0x57, 0x55, 0x92, 224 | 0x03, 0x8a, 0x6b, 0x68, 0xc2, 0x44, 0xf4, 0xed, 0x37, 0xcc, 0x59, 0xae, 0xfe, 0x75, 0x2f, 0xe1, 225 | 0xfc, 0xc9, 0xad, 0x38, 0x66, 0xe7, 0x31, 0xca, 0x5b, 0x95, 0x9f, 0xbf, 0xfb, 0x09, 0x00, 0x00, 226 | 0xff, 0xff, 0x5a, 0x43, 0x07, 0x78, 0x09, 0x01, 0x00, 0x00, 227 | } 228 | -------------------------------------------------------------------------------- /node/msg.proto: -------------------------------------------------------------------------------- 1 | // protoc --go_out=. msg.proto 2 | syntax = "proto3"; 3 | 4 | package node; 5 | 6 | message Msg { 7 | int32 msg_type = 1; 8 | bytes data = 2; 9 | } 10 | 11 | message ClientsMsg { 12 | repeated string receive_client_ids = 1; 13 | bytes data = 2; 14 | } 15 | 16 | message AuthMsg { 17 | string node_id = 1; 18 | string password = 2; 19 | } 20 | 21 | message BindNodeIdMsg { 22 | string node_id = 1; 23 | } -------------------------------------------------------------------------------- /node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/weblazy/core/mapreduce" 10 | "github.com/weblazy/easy/econfig" 11 | "github.com/weblazy/easy/elog" 12 | "github.com/weblazy/easy/syncx" 13 | "github.com/weblazy/goutil" 14 | "github.com/weblazy/socket-cluster/logx" 15 | "github.com/weblazy/socket-cluster/protocol" 16 | "go.uber.org/zap" 17 | ) 18 | 19 | type ( 20 | Node interface { 21 | // SetClientIdOnline binds the client ID to the connection 22 | SetClientIdOnline(conn protocol.Connection, clientId string) error 23 | // OnClientPing updates the client heartbeat status 24 | OnClientPing(clientId string) error 25 | // IsOnline gets the online status of the clientId 26 | IsOnline(clientId string) bool 27 | // SendToClientId sends a message to a clientId 28 | SendToClientId(clientId string, req []byte) error 29 | // SendToClientId sends a message to multiple clientIds 30 | SendToClientIds(clientIds []string, req []byte) error 31 | } 32 | 33 | // Node communication node 34 | node struct { 35 | Node 36 | nodeConf *NodeConf 37 | // clientIdSessions map[key1]map[key2]value 38 | // Key1: clientId 39 | // Key2: socket address 40 | // Value: *Session 41 | clientIdSessions *syncx.ConcurrentDoubleMap 42 | // Key: socket address 43 | // Value: *Session 44 | clientConns goutil.Map 45 | // timer *timingwheel.TimingWheel // Timingwheel Close connects that timeout without authentication 46 | startTime time.Time // Start time 47 | // Key: socket address 48 | // Value: *Session 49 | businessClients goutil.Map // Send the message to client 50 | // Key: nodeId 51 | // Value: *Session 52 | // transClients goutil.Map // Forward the message to another node 53 | // Key: socket address 54 | // Value: protocol.Connection 55 | // transServices goutil.Map // Receive messages forwarded by other nodes 56 | nodeTimeout int64 // Node heartbeat timeout time 57 | clientTimeout int64 // Client heartbeat timeout time 58 | } 59 | ) 60 | 61 | // NewNode return a Node 62 | func NewNode(cfg *NodeConf) (Node, error) { 63 | // timer, err := timingwheel.NewTimingWheel(time.Second, 30, func(k, v interface{}) { 64 | // // TODO Count the number of unauthenticated connections 65 | // logx.LogHandler.Infof("%s auth timeout", k) 66 | // if v.(protocol.Connection) != nil { 67 | // err := v.(protocol.Connection).Close() 68 | // if err != nil { 69 | // logx.LogHandler.Error(err) 70 | // } 71 | // } 72 | // }) 73 | 74 | // if err != nil { 75 | // return nil, err 76 | // } 77 | nodeObj := &node{ 78 | nodeConf: cfg, 79 | clientIdSessions: syncx.NewConcurrentDoubleMap(32), 80 | startTime: time.Now(), 81 | // timer: timer, 82 | clientConns: goutil.AtomicMap(), 83 | businessClients: goutil.AtomicMap(), 84 | nodeTimeout: cfg.nodePingInterval * 3, // The timeout is three times as long as the interval between heartbeats 85 | clientTimeout: cfg.clientPingInterval * 3, // The timeout is three times as long as the interval between heartbeats 86 | } 87 | nodeObj.nodeConf.discoveryHandler.SetNodeAddr(cfg.addr) 88 | // cfg.internalProtocolHandler.ListenAndServe(cfg.internalPort, nodeObj.onTransConnect) 89 | 90 | cfg.protocolHandler.ListenAndServe(cfg.port, nodeObj.onClientConnect) 91 | nodeObj.sendPing() 92 | nodeObj.register() 93 | return nodeObj, nil 94 | } 95 | 96 | // SetOnline sets the clientId to online 97 | func (this *node) SetClientIdOnline(conn protocol.Connection, clientId string) error { 98 | addr := conn.Addr() 99 | // if conn.ConnProtocol() != ws_protocol.WsConnProtocol { 100 | // this.timer.RemoveTimer(addr) // Cancel timeingwheel task 101 | // } 102 | 103 | session := &Session{Conn: conn, ClientId: clientId} 104 | this.clientConns.Store(addr, session) 105 | this.clientIdSessions.StoreWithPlugin(clientId, addr, session, func() { 106 | oldClientId := session.CasClientId(clientId) 107 | if oldClientId != "" && oldClientId != clientId { 108 | this.clientIdSessions.DeleteWithoutLock(oldClientId, addr) 109 | } 110 | }) 111 | return this.nodeConf.sessionStorageHandler.BindClientId(this.nodeConf.addr, clientId) 112 | } 113 | 114 | // OnClientPing updates the client heartbeat status 115 | func (this *node) OnClientPing(clientId string) error { 116 | return this.nodeConf.sessionStorageHandler.OnClientPing(this.nodeConf.addr, clientId) 117 | } 118 | 119 | // IsOnline determine if a clientId is online 120 | func (this *node) IsOnline(clientId string) bool { 121 | addrArr, err := this.nodeConf.sessionStorageHandler.GetIps(clientId) 122 | if err != nil { 123 | logx.LogHandler.Error(err) 124 | return false 125 | } 126 | if len(addrArr) > 0 { 127 | return true 128 | } 129 | return false 130 | } 131 | 132 | // SendToClientId Send message to a clientId 133 | func (this *node) SendToClientId(clientId string, req []byte) error { 134 | if req == nil { 135 | return fmt.Errorf("message is nil") 136 | } 137 | ipArr, err := this.nodeConf.sessionStorageHandler.GetIps(clientId) 138 | if econfig.GlobalViper.GetBool("BaseConfig.Debug") { 139 | elog.InfoCtx(context.Background(), "SendToClientId", zap.String("clientId", clientId), zap.String("req", string(req)), zap.Any("ipArr", ipArr)) 140 | } 141 | if err == nil { 142 | mapreduce.MapVoid(func(source chan<- interface{}) { 143 | for key := range ipArr { 144 | source <- ipArr[key] 145 | } 146 | }, func(item interface{}) { 147 | // ip := item.(string) 148 | // if ip == this.nodeConf.nodeId { 149 | this.clientIdSessions.RangeNextMap(clientId, func(k1, k2 string, se interface{}) bool { 150 | err = se.(*Session).Conn.WriteMsg(req) 151 | if err != nil { 152 | elog.InfoCtx(context.Background(), "SendToClientId", zap.String("clientId", clientId), zap.String("req", string(req)), zap.Error(err)) 153 | } 154 | return true 155 | }) 156 | // } else { 157 | // connect, ok := this.transClients.Load(ip) 158 | // if ok { 159 | // conn, ok := connect.(protocol.Connection) 160 | // if !ok { 161 | // return 162 | // } 163 | // clientsMsg := ClientsMsg{ 164 | // ReceiveClientIds: []string{clientId}, 165 | // Data: req, 166 | // } 167 | // clientsMsgBytes, err := proto.Marshal(&clientsMsg) 168 | // if err != nil { 169 | // logx.LogHandler.Error(err) 170 | // } 171 | // transReq := Msg{ 172 | // MsgType: ClientMsgType, 173 | // Data: clientsMsgBytes, 174 | // } 175 | // reqBytes, err := proto.Marshal(&transReq) 176 | // err = conn.WriteMsg(reqBytes) 177 | // if err != nil { 178 | // logx.LogHandler.Error(err) 179 | // } 180 | // } else { 181 | // logx.LogHandler.Errorf("node:%s not online", ip) 182 | // return 183 | // } 184 | // } 185 | }) 186 | } 187 | return nil 188 | } 189 | 190 | type BatchData struct { 191 | ip string 192 | clientIds []string 193 | } 194 | 195 | // SendToClientIds Sending messages to multiple clients 196 | func (this *node) SendToClientIds(clientIds []string, req []byte) error { 197 | if req == nil { 198 | return fmt.Errorf("message is nil") 199 | } 200 | clientMap, err := this.nodeConf.sessionStorageHandler.GetClientsIps(clientIds) 201 | if err != nil { 202 | return err 203 | } 204 | localClientIds, _ := clientMap[this.nodeConf.addr] 205 | delete(clientMap, this.nodeConf.addr) 206 | // Concurrent sends to other nodes 207 | // mapreduce.MapVoid(func(source chan<- interface{}) { 208 | // for k1 := range clientMap { 209 | 210 | // source <- &BatchData{ip: k1, clientIds: clientMap[k1]} 211 | // } 212 | // }, func(item interface{}) { 213 | // batchData := item.(*BatchData) 214 | // connect, ok := this.transClients.Load(batchData.ip) 215 | // if ok { 216 | // conn, ok := connect.(protocol.Connection) 217 | // if !ok { 218 | // return 219 | // } 220 | // ids := make([]string, 0) 221 | // for k1 := range batchData.clientIds { 222 | // ids = append(ids, batchData.clientIds[k1]) 223 | // } 224 | // clientsMsg := ClientsMsg{ 225 | // ReceiveClientIds: ids, 226 | // Data: req, 227 | // } 228 | // clientsMsgBytes, err := proto.Marshal(&clientsMsg) 229 | // if err != nil { 230 | // logx.LogHandler.Error(err) 231 | // } 232 | // msg := Msg{ 233 | // MsgType: ClientMsgType, 234 | // Data: clientsMsgBytes, 235 | // } 236 | // msgBytes, err := proto.Marshal(&msg) 237 | // if err != nil { 238 | // logx.LogHandler.Error(err) 239 | // } 240 | // err = conn.WriteMsg(msgBytes) 241 | // if err != nil { 242 | // logx.LogHandler.Error(err) 243 | // } 244 | // } else { 245 | // logx.LogHandler.Error("node:%s not online", batchData.ip) 246 | // return 247 | // } 248 | 249 | // }) 250 | // Concurrent sends to clients 251 | mapreduce.MapVoid(func(source chan<- interface{}) { 252 | for k1 := range localClientIds { 253 | this.clientIdSessions.RangeNextMap(localClientIds[k1], func(key1 string, key2 string, value interface{}) bool { 254 | source <- value 255 | return true 256 | }) 257 | } 258 | }, func(item interface{}) { 259 | se := item.(*Session) 260 | err := se.Conn.WriteMsg(req) 261 | if err != nil { 262 | logx.LogHandler.Error(err) 263 | } 264 | }) 265 | return nil 266 | } 267 | 268 | func (this *node) onClientConnect(connect protocol.Connection) { 269 | defer func() { 270 | this.onClientClose(connect) 271 | if connect != nil { 272 | connect.Close() 273 | } 274 | }() 275 | this.nodeConf.plugin.OnConnect(connect) 276 | // if connect.ConnProtocol() != ws_protocol.WsConnProtocol { 277 | // // The timeout unbound clientId connect will be closed 278 | // this.timer.SetTimer(connect.Addr(), connect, authTime) 279 | // } 280 | for { 281 | msg, err := connect.ReadMsg() 282 | if err != nil { 283 | logx.LogHandler.Error(err) 284 | break 285 | } 286 | this.onClientMsg(connect, msg) 287 | } 288 | } 289 | 290 | // OnClientMsg deal client message 291 | func (this *node) onClientMsg(conn protocol.Connection, msg []byte) { 292 | addr := conn.Addr() 293 | session, ok := this.clientConns.Load(addr) 294 | clientId := "" 295 | if ok { 296 | clientId = session.(*Session).ClientId 297 | } 298 | this.nodeConf.onMsg(&Context{Conn: conn, Msg: msg, ClientId: clientId}) 299 | } 300 | 301 | func (this *node) onClientClose(connect protocol.Connection) { 302 | this.nodeConf.plugin.OnClose(connect) 303 | addr := connect.Addr() 304 | v1, ok := this.clientConns.Load(addr) 305 | if ok { 306 | clientId := v1.(*Session).ClientId 307 | if clientId != "" { 308 | this.clientIdSessions.Delete(clientId, addr) 309 | } 310 | } 311 | this.clientConns.Delete(addr) 312 | } 313 | 314 | // func (this *node) onTransConnect(connect protocol.Connection) { 315 | // defer func() { 316 | // if connect != nil { 317 | // connect.Close() 318 | // } 319 | // }() 320 | // this.timer.SetTimer(connect.Addr(), connect, authTime) 321 | // for { 322 | // msg, err := connect.ReadMsg() 323 | // if err != nil { 324 | // logx.LogHandler.Error(err) 325 | // break 326 | // } 327 | // this.onTransClientMsg(connect, msg) 328 | // } 329 | // } 330 | 331 | // OnTransMsg handle internal communication node messages 332 | // func (this *node) onTransServerMsg(conn protocol.Connection, msg []byte) { 333 | // var transMsg Msg 334 | // err := proto.Unmarshal(msg, &transMsg) 335 | // if err != nil { 336 | // logx.LogHandler.Error(err, string(msg)) 337 | // return 338 | // } 339 | // switch transMsg.MsgType { 340 | 341 | // case ClientMsgType: // Message forwarded to the client 342 | // var clientsMsg ClientsMsg 343 | // err = proto.Unmarshal(transMsg.Data, &clientsMsg) 344 | // if err != nil { 345 | // logx.LogHandler.Error(err) 346 | // } 347 | // for k1 := range clientsMsg.ReceiveClientIds { 348 | // receiveClientId := clientsMsg.ReceiveClientIds[k1] 349 | // this.clientIdSessions.RangeNextMap(receiveClientId, func(k1, k2 string, se interface{}) bool { 350 | // err = se.(*Session).Conn.WriteMsg(clientsMsg.Data) 351 | // if err != nil { 352 | // logx.LogHandler.Error(err) 353 | // } 354 | // return true 355 | // }) 356 | // } 357 | 358 | // case PingMsgType: // The heartbeat message 359 | 360 | // default: // The unknow message 361 | // logx.LogHandler.Info(transMsg) 362 | // } 363 | 364 | // } 365 | 366 | // OnTransMsg handle internal communication node messages 367 | // func (this *node) onTransClientMsg(conn protocol.Connection, msg []byte) { 368 | // var transMsg Msg 369 | // err := proto.Unmarshal(msg, &transMsg) 370 | // if err != nil { 371 | // logx.LogHandler.Error(err, string(msg)) 372 | // return 373 | // } 374 | // switch transMsg.MsgType { 375 | // case AuthNodeMsgType: // Authentication message 376 | // var authMsg AuthMsg 377 | // err = proto.Unmarshal(transMsg.Data, &authMsg) 378 | // if err != nil { 379 | // logx.LogHandler.Error(err) 380 | // } 381 | // err = this.authTrans(conn, &authMsg) 382 | // if err != nil { 383 | // logx.LogHandler.Error(err) 384 | // } 385 | 386 | // case PingMsgType: // The heartbeat message 387 | // case AuthBusinessClientMsgType: // Authentication message 388 | // var authMsg AuthMsg 389 | // err = proto.Unmarshal(transMsg.Data, &authMsg) 390 | // if err != nil { 391 | // logx.LogHandler.Error(err) 392 | // } 393 | // err = this.authBusinessClient(conn, &authMsg) 394 | // if err != nil { 395 | // logx.LogHandler.Error(err) 396 | // return 397 | // } 398 | // bindNodeIdMsg := BindNodeIdMsg{NodeId: this.nodeConf.nodeId} 399 | 400 | // bindNodeIdMsgBytes, err := proto.Marshal(&bindNodeIdMsg) 401 | // if err != nil { 402 | // logx.LogHandler.Error(err) 403 | // } 404 | // bindNodeIdReq := Msg{ 405 | // MsgType: BindNodeIdMsgType, 406 | // Data: bindNodeIdMsgBytes, 407 | // } 408 | // reqBytes, err := proto.Marshal(&bindNodeIdReq) 409 | 410 | // err = conn.WriteMsg(reqBytes) 411 | // if err != nil { 412 | // logx.LogHandler.Error(err) 413 | // } 414 | // case ClientMsgType: // Message forwarded to the client 415 | // addr := conn.Addr() 416 | // _, ok := this.businessClients.Load(addr) 417 | // if !ok { 418 | // logx.LogHandler.Error(errors.New("un auth connect")) 419 | // return 420 | // } 421 | // var clientsMsg ClientsMsg 422 | // err = proto.Unmarshal(transMsg.Data, &clientsMsg) 423 | // if err != nil { 424 | // logx.LogHandler.Error(err) 425 | // } 426 | // for k1 := range clientsMsg.ReceiveClientIds { 427 | // receiveClientId := clientsMsg.ReceiveClientIds[k1] 428 | // this.clientIdSessions.RangeNextMap(receiveClientId, func(k1, k2 string, se interface{}) bool { 429 | // err = se.(*Session).Conn.WriteMsg(clientsMsg.Data) 430 | // if err != nil { 431 | // logx.LogHandler.Error(err) 432 | // } 433 | // return true 434 | // }) 435 | // } 436 | 437 | // default: // The unknow message 438 | // logx.LogHandler.Info(transMsg) 439 | // } 440 | 441 | // } 442 | 443 | // AuthTrans Auth the node 444 | // func (this *node) authTrans(conn protocol.Connection, authMsg *AuthMsg) error { 445 | // nodeId := authMsg.NodeId 446 | // this.timer.RemoveTimer(conn.Addr()) // Cancel timeingwheel task 447 | // if authMsg.Password != this.nodeConf.password { 448 | // logx.LogHandler.Infof("Connect:%s,Wrong password:%s", nodeId, authMsg.Password) 449 | // conn.Close() 450 | // return fmt.Errorf("auth faild") 451 | // } 452 | // this.transClients.Store(nodeId, conn) 453 | // return nil 454 | // } 455 | 456 | // AuthTrans Auth the node 457 | // func (this *node) authBusinessClient(conn protocol.Connection, authMsg *AuthMsg) error { 458 | // addr := conn.Addr() 459 | // this.timer.RemoveTimer(addr) // Cancel timeingwheel task 460 | // if authMsg.Password != this.nodeConf.password { 461 | // logx.LogHandler.Infof("Connect:%s,Wrong password:%s", addr, authMsg.Password) 462 | // conn.Close() 463 | // return fmt.Errorf("auth faild") 464 | // } 465 | // this.businessClients.Store(addr, conn) 466 | // return nil 467 | // } 468 | 469 | // SendPing send node Heartbeat 470 | func (this *node) sendPing() { 471 | go func() { 472 | for { 473 | time.Sleep(time.Duration(this.nodeConf.nodePingInterval) * time.Second) 474 | // update node info 475 | nodeInfo := map[string]interface{}{ 476 | "node_addr": this.nodeConf.addr, 477 | "client_count": this.clientConns.Len(), 478 | "timestamp": time.Now().Unix(), 479 | } 480 | nodeInfoByte, err := json.Marshal(nodeInfo) 481 | if err != nil { 482 | logx.LogHandler.Error(err) 483 | } 484 | err = this.nodeConf.discoveryHandler.UpdateInfo(nodeInfoByte) 485 | if err != nil { 486 | logx.LogHandler.Error(err) 487 | } 488 | // err = this.updateNodeList() 489 | if err != nil { 490 | logx.LogHandler.Error(err) 491 | } 492 | // send to other node 493 | // this.transServices.Range(func(k, v interface{}) bool { 494 | // conn, ok := v.(protocol.Connection) 495 | // if !ok { 496 | // return true 497 | // } 498 | 499 | // err := conn.WriteMsg(PingMsg) 500 | // if err != nil { 501 | // logx.LogHandler.Error(err) 502 | // } 503 | // return true 504 | // }) 505 | } 506 | }() 507 | } 508 | 509 | // Register 510 | func (this *node) register() { 511 | this.nodeConf.discoveryHandler.Register() 512 | // watchChan := make(chan discovery.EventType, 1) 513 | // go this.nodeConf.discoveryHandler.WatchService(watchChan) 514 | // go func() { 515 | // for { 516 | // select { 517 | // case _, ok := <-watchChan: 518 | // if !ok { 519 | // logx.LogHandler.Infof("channel close\n") 520 | // return 521 | // } 522 | // err := this.updateNodeList() 523 | // if err != nil { 524 | // logx.LogHandler.Error(err) 525 | // } 526 | // } 527 | // } 528 | // }() 529 | // init 530 | // err := this.updateNodeList() 531 | // if err != nil { 532 | // logx.LogHandler.Error(err) 533 | // } 534 | } 535 | 536 | // UpdateNodeList Add handles addition request 537 | // func (this *node) updateNodeList() error { 538 | // nodeMap := make(map[string]int) 539 | // for k1 := range this.nodeConf.hostList { 540 | // nodeList, port, err := dns.DnsParse(this.nodeConf.hostList[k1]) 541 | // if err != nil { 542 | // logx.LogHandler.Error(err) 543 | // return err 544 | // } 545 | // for k2 := range nodeList { 546 | // nodeMap[nodeList[k2]+":"+port] = 1 547 | // } 548 | // } 549 | 550 | // for k1 := range nodeMap { 551 | // addr := k1 552 | // if addr == this.nodeConf.nodeId { 553 | // continue 554 | // } 555 | 556 | // // Connection already exists 557 | // _, ok := this.transServices.LoadOrStore(addr, "") 558 | // if ok { 559 | // continue 560 | // } 561 | // conn, err := this.nodeConf.internalProtocolHandler.Dial(addr) 562 | // if err != nil { 563 | // logx.LogHandler.Errorf("dial:%s", err.Error()) 564 | // this.transServices.Delete(addr) 565 | // continue 566 | // } 567 | 568 | // auth := AuthMsg{ 569 | // Password: this.nodeConf.password, 570 | // NodeId: this.nodeConf.nodeId, 571 | // } 572 | // authBytes, err := proto.Marshal(&auth) 573 | // if err != nil { 574 | // logx.LogHandler.Error(err) 575 | // } 576 | // data := Msg{ 577 | // MsgType: AuthNodeMsgType, 578 | // Data: authBytes, 579 | // } 580 | // reqBytes, err := proto.Marshal(&data) 581 | // if err != nil { 582 | // logx.LogHandler.Error(err) 583 | // } 584 | // err = conn.WriteMsg(reqBytes) 585 | // if err != nil { 586 | // logx.LogHandler.Info(err) 587 | // } 588 | // this.transServices.Store(addr, conn) 589 | // go func(addr string, conn protocol.Connection) { 590 | // defer func(addr string, conn protocol.Connection) { 591 | // this.transServices.Delete(addr) 592 | // if conn != nil { 593 | // conn.Close() 594 | // } 595 | // }(addr, conn) 596 | // for { 597 | // msg, err := conn.ReadMsg() 598 | // if err != nil { 599 | // break 600 | // } 601 | // this.onTransServerMsg(conn, msg) 602 | // } 603 | // }(addr, conn) 604 | // } 605 | // return nil 606 | // } 607 | -------------------------------------------------------------------------------- /node/node_conf.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/go-redis/redis/v8" 5 | "github.com/weblazy/socket-cluster/discovery" 6 | "github.com/weblazy/socket-cluster/protocol" 7 | "github.com/weblazy/socket-cluster/session_storage" 8 | ) 9 | 10 | type ( 11 | // RedisNode the hash redis node config 12 | RedisNode struct { 13 | RedisConf *redis.Options 14 | Position uint32 // The position of hash 15 | } 16 | // NodeConf node config 17 | NodeConf struct { 18 | addr string // The ip or domain of the node 19 | port int64 // Node clientPort 20 | internalPort int64 // Node internalPort 21 | password string // Password for auth when connect to other node 22 | clientPingInterval int64 // Node with client heartbeat interval 23 | nodePingInterval int64 // Node with node Heartbeat interval 24 | onMsg func(context *Context) // Callback function when receive client message 25 | discoveryHandler discovery.ServiceDiscovery // Discover service 26 | protocolHandler protocol.Protocol // Direct protocol between node and client 27 | // internalProtocolHandler protocol.Protocol // Direct protocol between node and node 28 | sessionStorageHandler session_storage.SessionStorage // On-line state storage components 29 | plugin Plugin // The interface that the client connects to or closes 30 | } 31 | // Params of onMsg 32 | Context struct { 33 | Conn protocol.Connection 34 | ClientId string 35 | Msg []byte 36 | } 37 | ) 38 | 39 | // NewNodeConf creates a new NodeConf. 40 | func NewNodeConf(addr string, protocolHandler protocol.Protocol, sessionStorageHandler session_storage.SessionStorage, discoveryHandler discovery.ServiceDiscovery, onMsg func(context *Context)) *NodeConf { 41 | return &NodeConf{ 42 | addr: defaultAddr, 43 | port: defaultPort, 44 | password: DefaultPassword, 45 | clientPingInterval: defaultClientPingInterval, 46 | nodePingInterval: DefaultNodePingInterval, 47 | protocolHandler: protocolHandler, 48 | // internalProtocolHandler: &tcp_protocol.TcpProtocol{}, 49 | internalPort: defaultInternalPort, 50 | sessionStorageHandler: sessionStorageHandler, 51 | discoveryHandler: discoveryHandler, 52 | onMsg: onMsg, 53 | plugin: defaultPlugin, 54 | } 55 | 56 | } 57 | 58 | // WithPassword sets the password for transport node 59 | func (conf *NodeConf) WithPassword(password string) *NodeConf { 60 | conf.password = password 61 | return conf 62 | } 63 | 64 | // WithPort sets the port 65 | func (conf *NodeConf) WithPort(port int64) *NodeConf { 66 | conf.port = port 67 | return conf 68 | } 69 | 70 | // WithInternalPort sets the port for internal protocol 71 | func (conf *NodeConf) WithInternalPort(port int64) *NodeConf { 72 | conf.internalPort = port 73 | return conf 74 | } 75 | 76 | // WithInternalProtocolHandler sets the internal protocol for node 77 | // func (conf *NodeConf) WithInternalProtocolHandler(internalProtocolHandler protocol.Protocol) *NodeConf { 78 | // conf.internalProtocolHandler = internalProtocolHandler 79 | // return conf 80 | // } 81 | 82 | // WithClientInterval sets the heartbeat interval 83 | func (conf *NodeConf) WithClientInterval(pingInterval int64) *NodeConf { 84 | conf.clientPingInterval = pingInterval 85 | return conf 86 | } 87 | 88 | // WithAddr sets the addr for node cluster 89 | func (conf *NodeConf) WithAddr(addr string) *NodeConf { 90 | conf.addr = addr 91 | return conf 92 | } 93 | 94 | // WithPlugin sets the plugin 95 | func (conf *NodeConf) WithPlugin(plugin Plugin) *NodeConf { 96 | conf.plugin = plugin 97 | return conf 98 | } 99 | -------------------------------------------------------------------------------- /node/plugin.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import "github.com/weblazy/socket-cluster/protocol" 4 | 5 | type ( 6 | Plugin interface { 7 | OnConnect(conn protocol.Connection) 8 | OnClose(conn protocol.Connection) 9 | } 10 | DefaultPlugin struct { 11 | } 12 | ) 13 | 14 | var defaultPlugin = &DefaultPlugin{} 15 | 16 | func (this *DefaultPlugin) OnConnect(conn protocol.Connection) { 17 | 18 | } 19 | func (this *DefaultPlugin) OnClose(conn protocol.Connection) { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /node/session.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | 7 | "github.com/weblazy/socket-cluster/protocol" 8 | ) 9 | 10 | type Session struct { 11 | Conn protocol.Connection 12 | ClientId string 13 | } 14 | 15 | // LoadClientId returns the session uid. 16 | func (s *Session) LoadClientId() string { 17 | pointer := unsafe.Pointer(&s.ClientId) 18 | return *(*string)(atomic.LoadPointer(&pointer)) 19 | } 20 | 21 | // StoreClientId sets the session uid. 22 | func (s *Session) StoreClientId(newClientId string) { 23 | pointer := unsafe.Pointer(&s.ClientId) 24 | atomic.StorePointer(&pointer, unsafe.Pointer(&newClientId)) 25 | } 26 | 27 | // CasClientId sets the session uid and return oldClientId 28 | func (s *Session) CasClientId(newClientId string) string { 29 | newValue := unsafe.Pointer(&newClientId) 30 | pointer := unsafe.Pointer(&s.ClientId) 31 | for { 32 | oldValue := atomic.LoadPointer(&pointer) 33 | if atomic.CompareAndSwapPointer(&pointer, oldValue, newValue) { 34 | return *(*string)(oldValue) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pic/business_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/pic/business_client.png -------------------------------------------------------------------------------- /pic/socket_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/pic/socket_cluster.png -------------------------------------------------------------------------------- /protocol/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weblazy/socket-cluster/63d8325ffa8a0ef84ba0f6bb7666a7fdc4cd66f1/protocol/.DS_Store -------------------------------------------------------------------------------- /protocol/flow_proto.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | ) 9 | 10 | const ( 11 | HEAD_SIZE = 4 12 | HEADER = "socket-cluster" 13 | // The maximum length of each message (including headers), which can be set to 4G 14 | MAX_LENGTH = 1024 * 70 15 | ) 16 | 17 | var ExceededErr = errors.New("The maximum length of the packet is exceeded") 18 | var HeaderErr = errors.New("The message header is incorrect") 19 | 20 | type FlowProtocol struct { 21 | Proto 22 | header string 23 | headLength int 24 | bufLength int 25 | start int 26 | end int 27 | buf []byte 28 | conn io.Reader 29 | } 30 | 31 | func NewFlowProtocol(header string, bufLength int, conn io.Reader) *FlowProtocol { 32 | return &FlowProtocol{ 33 | header: header, 34 | headLength: len(header) + HEAD_SIZE, 35 | bufLength: bufLength, 36 | buf: make([]byte, bufLength), 37 | conn: conn, 38 | } 39 | } 40 | 41 | // Read Read and parse the received message 42 | func (this *FlowProtocol) ReadMsg() ([]byte, error) { 43 | // read header 44 | for this.end-this.start < this.headLength { 45 | // reset start 46 | if this.start > 0 { 47 | copy(this.buf, this.buf[this.start:this.end]) 48 | this.end -= this.start 49 | this.start = 0 50 | } 51 | n, err := this.conn.Read(this.buf[this.end:]) 52 | if err != nil { 53 | return nil, err 54 | } 55 | this.end += n 56 | } 57 | 58 | headBuf := this.buf[this.start : this.start+this.headLength] 59 | // Verify that the message header is correct 60 | if string(headBuf[:len(this.header)]) != this.header { 61 | return nil, HeaderErr 62 | } 63 | 64 | // read content 65 | contentSize := int(binary.BigEndian.Uint32(headBuf[len(this.header):])) 66 | totalLenth := this.headLength + contentSize 67 | if totalLenth > this.bufLength { 68 | return nil, ExceededErr 69 | } 70 | for this.end-this.start < totalLenth { 71 | n, err := this.conn.Read(this.buf[this.end:]) 72 | if err != nil { 73 | return nil, err 74 | } 75 | this.end += n 76 | } 77 | this.start += this.headLength 78 | contentBuf := this.buf[this.start : this.start+contentSize] 79 | this.start += contentSize 80 | return contentBuf, nil 81 | } 82 | 83 | // Pack Package raw data 84 | func (this *FlowProtocol) Pack(data []byte) ([]byte, error) { 85 | headSize := len(data) 86 | if headSize+this.headLength > this.bufLength { 87 | return nil, ExceededErr 88 | } 89 | var headBytes = make([]byte, HEAD_SIZE) 90 | binary.BigEndian.PutUint32(headBytes, uint32(headSize)) 91 | var buf bytes.Buffer 92 | buf.Write([]byte(this.header)) 93 | buf.Write(headBytes) 94 | buf.Write(data) 95 | return buf.Bytes(), nil 96 | } 97 | -------------------------------------------------------------------------------- /protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type ( 4 | // Proto pack/read protocol scheme of socket message. 5 | Proto interface { 6 | // Pack writes the Message into the connection. 7 | // NOTE: Make sure to write only once or there will be package contamination! 8 | Pack([]byte) ([]byte, error) 9 | // Read bytes from the connection. 10 | ReadMsg() ([]byte, error) 11 | } 12 | ) 13 | 14 | type Protocol interface { 15 | // ListenAndServe turns on the listening service. 16 | ListenAndServe(port int64, onConnect func(conn Connection)) error 17 | // Dial connects with the socket of the destination address. 18 | Dial(addr string) (Connection, error) 19 | } 20 | -------------------------------------------------------------------------------- /protocol/quic_protocol/quic_connect.go: -------------------------------------------------------------------------------- 1 | package quic_protocol 2 | 3 | // import ( 4 | // "sync" 5 | 6 | // "github.com/quic-go/quic-go" 7 | // "github.com/weblazy/socket-cluster/protocol" 8 | // ) 9 | 10 | // type QuicConnection struct { 11 | // stream quic.Stream 12 | // session quic.Session 13 | // mutex sync.Mutex 14 | // flowProtocolHandler protocol.Proto 15 | // } 16 | 17 | // func NewQuicConnection(stream quic.Stream, session quic.Session) *QuicConnection { 18 | // return &QuicConnection{ 19 | // session: session, 20 | // stream: stream, 21 | // flowProtocolHandler: protocol.NewFlowProtocol(protocol.HEADER, protocol.MAX_LENGTH, stream), 22 | // } 23 | // } 24 | 25 | // // WriteMsg sends byte array message 26 | // func (this *QuicConnection) WriteMsg(data []byte) error { 27 | // data, err := this.flowProtocolHandler.Pack(data) 28 | // if err != nil { 29 | // return err 30 | // } 31 | // this.mutex.Lock() 32 | // defer this.mutex.Unlock() 33 | // _, err = this.stream.Write(data) 34 | // return err 35 | // } 36 | 37 | // // ReadMsg reads byte array message 38 | // func (this *QuicConnection) ReadMsg() ([]byte, error) { 39 | // msg, err := this.flowProtocolHandler.ReadMsg() 40 | // if err != nil { 41 | // return nil, err 42 | // } 43 | // return msg, err 44 | // } 45 | 46 | // func (this *QuicConnection) Addr() string { 47 | // return this.session.RemoteAddr().String() 48 | // } 49 | 50 | // func (this *QuicConnection) Close() error { 51 | // if this.stream == nil { 52 | // return nil 53 | // } 54 | // return this.stream.Close() 55 | // } 56 | 57 | // func (this *QuicConnection) SetFlowProtocolHandler(flowProtocolHandler protocol.Proto) { 58 | // this.flowProtocolHandler = flowProtocolHandler 59 | // } 60 | -------------------------------------------------------------------------------- /protocol/quic_protocol/quic_protocol.go: -------------------------------------------------------------------------------- 1 | package quic_protocol 2 | 3 | // import ( 4 | // "fmt" 5 | 6 | // "github.com/quic-go/quic-go" 7 | // "github.com/weblazy/socket-cluster/logx" 8 | // "github.com/weblazy/socket-cluster/protocol" 9 | // ) 10 | 11 | // type QuicProtocol struct { 12 | // } 13 | 14 | // func (this *QuicProtocol) ListenAndServe(port int64, onConnect func(conn protocol.Connection)) error { 15 | // // Setup a bare-bones TLS config for the server 16 | // tlsConf := protocol.GenerateTLSConfigForServer() 17 | // listener, err := quic.ListenAddr(fmt.Sprintf(":%d", port), tlsConf, nil) 18 | // if err != nil { 19 | // logx.LogHandler.Error(err) 20 | // } 21 | // for { 22 | // session, err := listener.Accept() 23 | // if err != nil { 24 | // logx.LogHandler.Error(err) 25 | // } else { 26 | // go func(session quic.Session) { 27 | // // Use only the first stream 28 | // stream, err := session.AcceptStream() 29 | // if err != nil { 30 | // panic(err) 31 | // } 32 | // conn := NewQuicConnection(stream, session) 33 | // onConnect(conn) 34 | // }(session) 35 | // } 36 | // } 37 | // } 38 | 39 | // func (this *QuicProtocol) Dial(addr string) (protocol.Connection, error) { 40 | // // Setup a bare-bones TLS config for the client 41 | // tlsConf := protocol.GenerateTLSConfigForClient() 42 | // session, err := quic.DialAddr(addr, tlsConf, nil) 43 | // if err != nil { 44 | // return nil, err 45 | // } 46 | // // Use only the first stream 47 | // stream, err := session.OpenStreamSync() 48 | // if err != nil { 49 | // return nil, err 50 | // } 51 | // return NewQuicConnection(stream, session), err 52 | // } 53 | -------------------------------------------------------------------------------- /protocol/quic_protocol/quic_test.go: -------------------------------------------------------------------------------- 1 | package quic_protocol 2 | 3 | // func TestQuic(t *testing.T) { 4 | // quic := &QuicProtocol{} 5 | // wait := sync.WaitGroup{} 6 | // wait.Add(1) 7 | // go quic.ListenAndServe(80, func(connection protocol.Connection) { 8 | // for { 9 | // msg, err := connection.ReadMsg() 10 | // if err != nil { 11 | // break 12 | // } 13 | // fmt.Printf("%s\n", string(msg)) 14 | // wait.Done() 15 | // } 16 | // }) 17 | // connection, err := quic.Dial("127.0.0.1:80") 18 | // if err != nil { 19 | // fmt.Printf("%#v\n", err.Error()) 20 | // } 21 | // err = connection.WriteMsg([]byte("你好")) 22 | // if err != nil { 23 | // fmt.Printf("%#v\n", err.Error()) 24 | // } 25 | // wait.Wait() 26 | // } 27 | -------------------------------------------------------------------------------- /protocol/session.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | ) 7 | 8 | type Connection interface { 9 | ReadMsg() ([]byte, error) 10 | WriteMsg(data []byte) error 11 | Close() error 12 | Addr() string 13 | ConnProtocol() string 14 | } 15 | 16 | type Session struct { 17 | Conn Connection 18 | ClientId string 19 | } 20 | 21 | // LoadClientId returns the session uid. 22 | func (s *Session) LoadClientId() string { 23 | pointer := unsafe.Pointer(&s.ClientId) 24 | return *(*string)(atomic.LoadPointer(&pointer)) 25 | } 26 | 27 | // StoreClientId sets the session uid. 28 | func (s *Session) StoreClientId(newClientId string) { 29 | pointer := unsafe.Pointer(&s.ClientId) 30 | atomic.StorePointer(&pointer, unsafe.Pointer(&newClientId)) 31 | } 32 | 33 | // CasClientId sets the session uid and return oldClientId 34 | func (s *Session) CasClientId(newClientId string) string { 35 | newValue := unsafe.Pointer(&newClientId) 36 | pointer := unsafe.Pointer(&s.ClientId) 37 | for { 38 | oldValue := atomic.LoadPointer(&pointer) 39 | if atomic.CompareAndSwapPointer(&pointer, oldValue, newValue) { 40 | return *(*string)(oldValue) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /protocol/tcp_protocol/tcp_connection.go: -------------------------------------------------------------------------------- 1 | package tcp_protocol 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | 7 | "github.com/weblazy/socket-cluster/protocol" 8 | ) 9 | 10 | type TcpConnection struct { 11 | Conn net.Conn 12 | Mutex sync.Mutex 13 | flowProtocolHandler protocol.Proto 14 | } 15 | 16 | const TcpConnProtocol = "tcp" 17 | 18 | func NewTcpConnection(conn net.Conn) *TcpConnection { 19 | return &TcpConnection{ 20 | Conn: conn, 21 | flowProtocolHandler: protocol.NewFlowProtocol(protocol.HEADER, protocol.MAX_LENGTH, conn), 22 | } 23 | } 24 | 25 | // WriteMsg send byte array message 26 | func (this *TcpConnection) WriteMsg(data []byte) error { 27 | data, err := this.flowProtocolHandler.Pack(data) 28 | if err != nil { 29 | return err 30 | } 31 | this.Mutex.Lock() 32 | defer this.Mutex.Unlock() 33 | _, err = this.Conn.Write(data) 34 | return err 35 | } 36 | 37 | func (this *TcpConnection) ReadMsg() ([]byte, error) { 38 | msg, err := this.flowProtocolHandler.ReadMsg() 39 | if err != nil { 40 | return nil, err 41 | } 42 | return msg, err 43 | } 44 | 45 | func (this *TcpConnection) Addr() string { 46 | return this.Conn.RemoteAddr().String() 47 | } 48 | 49 | func (this *TcpConnection) Close() error { 50 | return this.Conn.Close() 51 | } 52 | 53 | func (this *TcpConnection) SetFlowProtocolHandler(flowProtocolHandler protocol.Proto) { 54 | this.flowProtocolHandler = flowProtocolHandler 55 | } 56 | 57 | func (this *TcpConnection) ConnProtocol() string { 58 | return TcpConnProtocol 59 | } 60 | -------------------------------------------------------------------------------- /protocol/tcp_protocol/tcp_protocol.go: -------------------------------------------------------------------------------- 1 | package tcp_protocol 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/weblazy/socket-cluster/logx" 8 | "github.com/weblazy/socket-cluster/protocol" 9 | ) 10 | 11 | type TcpProtocol struct { 12 | } 13 | 14 | func (this *TcpProtocol) ListenAndServe(port int64, onConnect func(conn protocol.Connection)) error { 15 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 16 | if err != nil { 17 | return err 18 | } 19 | go func() { 20 | for { 21 | connect, err := listener.Accept() 22 | if err != nil { 23 | logx.LogHandler.Error(err) 24 | break 25 | } 26 | go func(connect net.Conn) { 27 | conn := NewTcpConnection(connect) 28 | onConnect(conn) 29 | }(connect) 30 | } 31 | }() 32 | return nil 33 | } 34 | 35 | func (this *TcpProtocol) Dial(addr string) (protocol.Connection, error) { 36 | tcpAddr, err := net.ResolveTCPAddr("tcp4", addr) 37 | if err != nil { 38 | return nil, err 39 | } 40 | conn, err := net.DialTCP("tcp", nil, tcpAddr) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return NewTcpConnection(conn), err 45 | } 46 | -------------------------------------------------------------------------------- /protocol/tls.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "math/big" 10 | ) 11 | 12 | // NewTLSConfigFromFile creates a new TLS config. 13 | func NewTLSConfigFromFile(tlsCertFile, tlsKeyFile string, insecureSkipVerifyForClient ...bool) (*tls.Config, error) { 14 | cert, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return newTLSConfig(cert, insecureSkipVerifyForClient...), nil 19 | } 20 | 21 | // GenerateTLSConfigForClient setup a bare-bones(skip verify) TLS config for client. 22 | func GenerateTLSConfigForClient() *tls.Config { 23 | return &tls.Config{ 24 | NextProtos: []string{"http/1.1", "h2"}, 25 | InsecureSkipVerify: true, 26 | } 27 | } 28 | 29 | // GenerateTLSConfigForServer setup a bare-bones TLS config for server. 30 | func GenerateTLSConfigForServer() *tls.Config { 31 | key, err := rsa.GenerateKey(rand.Reader, 1024) 32 | if err != nil { 33 | panic(err) 34 | } 35 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 36 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 37 | if err != nil { 38 | panic(err) 39 | } 40 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 41 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 42 | 43 | cert, err := tls.X509KeyPair(certPEM, keyPEM) 44 | if err != nil { 45 | panic(err) 46 | } 47 | return newTLSConfig(cert) 48 | } 49 | 50 | func newTLSConfig(cert tls.Certificate, insecureSkipVerifyForClient ...bool) *tls.Config { 51 | var insecureSkipVerify bool 52 | if len(insecureSkipVerifyForClient) > 0 { 53 | insecureSkipVerify = insecureSkipVerifyForClient[0] 54 | } 55 | return &tls.Config{ 56 | InsecureSkipVerify: insecureSkipVerify, 57 | Certificates: []tls.Certificate{cert}, 58 | NextProtos: []string{"http/1.1", "h2"}, 59 | PreferServerCipherSuites: true, 60 | CurvePreferences: []tls.CurveID{ 61 | tls.CurveP256, 62 | tls.X25519, 63 | }, 64 | MinVersion: tls.VersionTLS12, 65 | CipherSuites: []uint16{ 66 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 67 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 68 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 69 | tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 70 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 71 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 72 | tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 73 | tls.TLS_RSA_WITH_AES_128_GCM_SHA256, 74 | }, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /protocol/ws_protocol/ws_connection.go: -------------------------------------------------------------------------------- 1 | package ws_protocol 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/labstack/echo/v4" 8 | "github.com/weblazy/socket-cluster/protocol" 9 | ) 10 | 11 | const WsConnProtocol = "ws" 12 | 13 | type WsConnection struct { 14 | Conn *websocket.Conn 15 | Mutex sync.Mutex 16 | protocol.Connection 17 | } 18 | 19 | func NewWsConnection(conn *websocket.Conn) *WsConnection { 20 | return &WsConnection{ 21 | Conn: conn, 22 | } 23 | } 24 | 25 | // WriteMsg send byte array message 26 | func (this *WsConnection) WriteMsg(data []byte) error { 27 | this.Mutex.Lock() 28 | defer this.Mutex.Unlock() 29 | return this.Conn.WriteMessage(websocket.TextMessage, data) 30 | } 31 | 32 | func (this *WsConnection) ReadMsg() ([]byte, error) { 33 | _, msg, err := this.Conn.ReadMessage() 34 | if err != nil { 35 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 36 | 37 | } 38 | return nil, err 39 | } 40 | return msg, err 41 | } 42 | 43 | func (this *WsConnection) Addr() string { 44 | return this.Conn.RemoteAddr().String() 45 | } 46 | 47 | func (this *WsConnection) Close() error { 48 | if this.Conn == nil { 49 | return nil 50 | } 51 | return this.Conn.Close() 52 | } 53 | 54 | func OptionHandler(c echo.Context) error { 55 | c.Response().Header().Set("Access-Control-Allow-Origin", "*") 56 | c.Response().Header().Set("Access-Control-Allow-Headers", "*") 57 | return c.String(200, "") 58 | } 59 | 60 | func OriginMiddlewareFunc(next echo.HandlerFunc) echo.HandlerFunc { 61 | return func(c echo.Context) error { 62 | c.Response().Header().Set("Access-Control-Allow-Origin", "*") 63 | c.Response().Header().Set("Access-Control-Allow-Headers", "*") 64 | return next(c) 65 | } 66 | } 67 | 68 | func (this *WsConnection) ConnProtocol() string { 69 | return WsConnProtocol 70 | } 71 | -------------------------------------------------------------------------------- /protocol/ws_protocol/ws_protocol.go: -------------------------------------------------------------------------------- 1 | package ws_protocol 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gorilla/websocket" 8 | "github.com/labstack/echo/v4" 9 | "github.com/weblazy/socket-cluster/protocol" 10 | ) 11 | 12 | var ( 13 | upgrader = websocket.Upgrader{ 14 | ReadBufferSize: 4096, 15 | WriteBufferSize: 4096, 16 | EnableCompression: true, 17 | CheckOrigin: func(r *http.Request) bool { 18 | return true 19 | }, 20 | } 21 | ) 22 | 23 | type WsProtocol struct { 24 | ClientPath string 25 | HandlerFunc func(conn protocol.Connection) echo.HandlerFunc 26 | } 27 | 28 | func NewWsProtocol(clientPath string, h func(conn protocol.Connection) echo.HandlerFunc) *WsProtocol { 29 | return &WsProtocol{ 30 | ClientPath: clientPath, 31 | HandlerFunc: h, 32 | } 33 | } 34 | 35 | func (this *WsProtocol) ListenAndServe(port int64, onConnect func(conn protocol.Connection)) error { 36 | e := echo.New() 37 | e.GET(this.ClientPath, func(c echo.Context) error { 38 | connect, err := upgrader.Upgrade(c.Response(), c.Request(), nil) 39 | if err != nil { 40 | if _, ok := err.(websocket.HandshakeError); !ok { 41 | 42 | } 43 | return err 44 | } 45 | conn := NewWsConnection(connect) 46 | handler := this.HandlerFunc(conn) 47 | err = handler(c) 48 | if err != nil { 49 | return err 50 | } 51 | onConnect(conn) 52 | return nil 53 | }) 54 | go func() { 55 | err := e.Start(fmt.Sprintf(":%d", port)) 56 | if err != nil { 57 | panic(err) 58 | } 59 | }() 60 | return nil 61 | } 62 | 63 | func (this *WsProtocol) Dial(addr string) (protocol.Connection, error) { 64 | connect, _, err := websocket.DefaultDialer.Dial(addr, nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return NewWsConnection(connect), nil 69 | } 70 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/handler" 7 | "github.com/weblazy/socket-cluster/node" 8 | "go.uber.org/zap" 9 | 10 | "github.com/weblazy/easy/elog" 11 | "github.com/weblazy/easy/grpc/grpc_server" 12 | "github.com/weblazy/easy/grpc/grpc_server/grpc_server_config" 13 | "github.com/weblazy/socket-cluster/grpcs/socket_cluster_gateway/proto/gateway" 14 | ) 15 | 16 | type Server struct { 17 | Node node.Node 18 | } 19 | 20 | func (s *Server) Run(ctx context.Context, nodeConf *node.NodeConf, grpcConf *grpc_server_config.Config) { 21 | serverNode, err := node.NewNode(nodeConf) 22 | if err != nil { 23 | elog.ErrorCtx(ctx, "msg", zap.Error(err)) 24 | } 25 | s.Node = serverNode 26 | server := grpc_server.NewGrpcServer(grpcConf) 27 | gateway.RegisterGatewayServiceServer(server.Server, handler.NewGatewayService(serverNode)) 28 | err = server.Init() 29 | if err != nil { 30 | elog.ErrorCtx(ctx, "server.Init", zap.Error(err)) 31 | } 32 | err = server.Start() 33 | if err != nil { 34 | elog.ErrorCtx(ctx, "server.Start", zap.Error(err)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /session_storage/redis_storage/redis_storage.go: -------------------------------------------------------------------------------- 1 | package redis_storage 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/go-redis/redis/v8" 8 | "github.com/spf13/cast" 9 | "github.com/weblazy/easy/db/eredis" 10 | "github.com/weblazy/socket-cluster/logx" 11 | "github.com/weblazy/socket-cluster/session_storage" 12 | ) 13 | 14 | type RedisStorage struct { 15 | session_storage.SessionStorage 16 | clientTimeout int64 // client heartbeat timeout time 17 | redisClient *eredis.RedisClient 18 | } 19 | 20 | type RedisNode struct { 21 | RedisConf *redis.Options 22 | Position int64 //the position of hash ring 23 | } 24 | 25 | // NewRedisStorage return a RedisStorage 26 | func NewRedisStorage(redisClient *eredis.RedisClient) *RedisStorage { 27 | return &RedisStorage{redisClient: redisClient, clientTimeout: 360} 28 | } 29 | 30 | // GetIps get ip list by clientId 31 | func (this *RedisStorage) GetIps(clientId string) ([]string, error) { 32 | now := time.Now().Unix() 33 | ipArr, err := this.redisClient.ZRangeByScore(context.Background(), session_storage.ClientPrefix+clientId, &redis.ZRangeBy{Min: cast.ToString(now - this.clientTimeout), Max: "+inf"}).Result() 34 | return ipArr, err 35 | } 36 | 37 | // BindClientId set online with clientId 38 | func (this *RedisStorage) BindClientId(nodeAddr string, clientId string) error { 39 | now := time.Now().Unix() 40 | err := this.redisClient.ZAdd(context.Background(), session_storage.ClientPrefix+clientId, &redis.Z{Score: cast.ToFloat64(now), Member: nodeAddr}).Err() 41 | if err != nil { 42 | return err 43 | } 44 | 45 | err = this.redisClient.Expire(context.Background(), session_storage.ClientPrefix+clientId, time.Duration(this.clientTimeout)*time.Second).Err() 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // ClientIdsOnline Get online users in the group 54 | func (this *RedisStorage) ClientIdsOnline(clientIds []string) []string { 55 | onlineClientIds := make([]string, 0) 56 | rangeTime := cast.ToString(time.Now().Unix() - this.clientTimeout) 57 | 58 | pipe := this.redisClient.Pipeline() 59 | for k2 := range clientIds { 60 | pipe.ZRangeByScore(context.Background(), session_storage.ClientPrefix+clientIds[k2], &redis.ZRangeBy{Min: rangeTime, Max: "+inf"}).Result() 61 | } 62 | cmders, err := pipe.Exec(context.Background()) 63 | if err != nil { 64 | logx.LogHandler.Error(err) 65 | } 66 | for k3, cmder := range cmders { 67 | cmd := cmder.(*redis.StringSliceCmd) 68 | err := cmd.Err() 69 | if err != nil { 70 | logx.LogHandler.Error(err) 71 | } else { 72 | onlineClientIds = append(onlineClientIds, clientIds[k3]) 73 | } 74 | } 75 | 76 | return onlineClientIds 77 | } 78 | 79 | // ClientIdsOnlineWithLua Get online users in the group 80 | func (this *RedisStorage) ClientIdsOnlineWithLua(clientIds []string) []string { 81 | onlineClientIds := make([]string, 0) 82 | return onlineClientIds 83 | } 84 | 85 | // IsOnline determine if a clientId is online 86 | func (this *RedisStorage) IsOnline(clientId string) bool { 87 | now := time.Now().Unix() 88 | addrArr, err := this.redisClient.ZRangeByScore(context.Background(), session_storage.ClientPrefix+clientId, &redis.ZRangeBy{Min: cast.ToString(now - this.clientTimeout), Max: "+inf"}).Result() 89 | if err != nil { 90 | logx.LogHandler.Error(err) 91 | return false 92 | } 93 | if len(addrArr) > 0 { 94 | return true 95 | } 96 | return false 97 | } 98 | 99 | // OnClientPing receive client heartbeat 100 | func (this *RedisStorage) OnClientPing(nodeAddr string, clientId string) error { 101 | now := time.Now().Unix() 102 | err := this.redisClient.ZAdd(context.Background(), session_storage.ClientPrefix+clientId, &redis.Z{Score: cast.ToFloat64(now), Member: nodeAddr}).Err() 103 | if err != nil { 104 | return err 105 | } 106 | err = this.redisClient.Expire(context.Background(), session_storage.ClientPrefix+clientId, time.Duration(this.clientTimeout)*time.Second).Err() 107 | return err 108 | } 109 | 110 | // GetIps get ip list by clientId list 111 | func (this *RedisStorage) GetClientsIps(clientIds []string) (map[string][]string, error) { 112 | rangeTime := cast.ToString(time.Now().Unix() - this.clientTimeout) 113 | 114 | pipe := this.redisClient.Pipeline() 115 | for k2 := range clientIds { 116 | pipe.ZRangeByScore(context.Background(), session_storage.ClientPrefix+clientIds[k2], &redis.ZRangeBy{Min: rangeTime, Max: "+inf"}).Result() 117 | } 118 | cmders, err := pipe.Exec(context.Background()) 119 | if err != nil { 120 | logx.LogHandler.Error(cmders, err) 121 | } 122 | otherMap := make(map[string][]string) 123 | for k3, cmder := range cmders { 124 | cmd := cmder.(*redis.StringSliceCmd) 125 | strMap, err := cmd.Result() 126 | if err != nil { 127 | logx.LogHandler.Error(err) 128 | } else { 129 | for k4 := range strMap { 130 | if _, ok := otherMap[strMap[k4]]; ok { 131 | otherMap[strMap[k4]] = append(otherMap[strMap[k4]], clientIds[k3]) 132 | } else { 133 | otherMap[strMap[k4]] = []string{clientIds[k3]} 134 | } 135 | 136 | } 137 | 138 | } 139 | } 140 | 141 | return otherMap, nil 142 | } 143 | -------------------------------------------------------------------------------- /session_storage/session_storage.go: -------------------------------------------------------------------------------- 1 | package session_storage 2 | 3 | const ( 4 | ClientPrefix = "client#" 5 | ) 6 | 7 | type SessionStorage interface { 8 | IsOnline(clientId string) bool 9 | BindClientId(nodeAddr string, clientId string) error 10 | GetIps(clientId string) ([]string, error) 11 | GetClientsIps(clientIds []string) (map[string][]string, error) 12 | ClientIdsOnline(clientIds []string) []string 13 | OnClientPing(nodeAddr string, clientId string) error 14 | } 15 | -------------------------------------------------------------------------------- /unsafehash/segment_hash.go: -------------------------------------------------------------------------------- 1 | package unsafehash 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/spf13/cast" 7 | "github.com/weblazy/easy/sortx" 8 | ) 9 | 10 | type Segment struct { 11 | Position int64 12 | List []interface{} // client list 13 | } 14 | 15 | func NewSegment(position int64, list []interface{}) *Segment { 16 | return &Segment{ 17 | Position: position, 18 | List: list, 19 | } 20 | } 21 | 22 | type SegmentHash struct { 23 | segmentList *sortx.SortList 24 | } 25 | 26 | func NewSegmentHash(segmentList ...*Segment) *SegmentHash { 27 | list := sortx.NewSortList(sortx.ASC) 28 | for k1 := range segmentList { 29 | list.List = append(list.List, sortx.Sort{ 30 | Obj: segmentList[k1], 31 | Sort: cast.ToFloat64(segmentList[k1].Position), 32 | }) 33 | } 34 | sort.Sort(list) 35 | return &SegmentHash{ 36 | segmentList: list, 37 | } 38 | } 39 | 40 | func (c *SegmentHash) Append(segmentList ...*Segment) { 41 | for k1 := range segmentList { 42 | c.segmentList.List = append(c.segmentList.List, sortx.Sort{ 43 | Obj: segmentList[k1], 44 | Sort: cast.ToFloat64(segmentList[k1].Position), 45 | }) 46 | } 47 | sort.Sort(c.segmentList) 48 | } 49 | 50 | func (c *SegmentHash) Get(key int64) interface{} { 51 | if len(c.segmentList.List) == 0 || cast.ToFloat64(key) > c.segmentList.List[len(c.segmentList.List)-1].Sort { 52 | // out of range 53 | return nil 54 | } 55 | index := c.search(key) 56 | i := key % int64(len(c.segmentList.List[index].Obj.(*Segment).List)) 57 | return c.segmentList.List[index].Obj.(*Segment).List[i] 58 | } 59 | 60 | func (c *SegmentHash) search(key int64) int { 61 | n := len(c.segmentList.List) 62 | i := sort.Search(n, func(i int) bool { return c.segmentList.List[i].Sort >= cast.ToFloat64(key) }) 63 | if i < n { 64 | return i 65 | } else { 66 | return 0 67 | } 68 | } 69 | --------------------------------------------------------------------------------