├── .gitignore ├── LICENSE ├── LOAD_BALANCE.md ├── README.md ├── doc ├── dstat_pub.png ├── dstat_sub.png ├── project.png ├── pub.png ├── ss.png ├── status.png └── sub.png ├── pom.xml └── src ├── main └── java │ └── com │ └── yb │ └── socket │ ├── App.java │ ├── codec │ ├── JsonDecoder.java │ ├── JsonEncoder.java │ ├── MqttWebSocketCodec.java │ └── protocol │ │ ├── MessageTypeEnum.java │ │ ├── Protocol.java │ │ ├── ProtocolDecoder.java │ │ └── ProtocolEncoder.java │ ├── compression │ ├── Compression.java │ └── GzipCompression.java │ ├── count │ ├── CountHandler.java │ └── CountInfo.java │ ├── datasource │ └── HsqlDataSource.java │ ├── encrypt │ ├── AESEncrypt.java │ ├── BaseEncrypt.java │ ├── Encrypt.java │ └── RSAEncrypt.java │ ├── exception │ ├── SocketRuntimeException.java │ └── SocketTimeoutException.java │ ├── future │ ├── InvokeFuture.java │ └── InvokeFutureListener.java │ ├── listener │ ├── ChannelEventListener.java │ ├── DefaultExceptionListener.java │ ├── DefaultMessageEventListener.java │ ├── DefaultMqttMessageEventListener.java │ ├── EventBehavior.java │ ├── ExceptionEventListener.java │ └── MessageEventListener.java │ ├── pojo │ ├── BaseMessage.java │ ├── Heartbeat.java │ ├── MqttRequest.java │ ├── Request.java │ ├── Response.java │ └── protocol │ │ ├── ProtocolRequest.java │ │ ├── ProtocolResponse.java │ │ └── README.md │ ├── service │ ├── ChannelHandlerFunc.java │ ├── EventDispatcher.java │ ├── Service.java │ ├── SocketType.java │ ├── WrappedChannel.java │ ├── center │ │ └── ServerStateReportJob.java │ ├── client │ │ ├── BaseClient.java │ │ ├── Client.java │ │ ├── ClientDispatchHandler.java │ │ └── ClientHeartbeatHandler.java │ └── server │ │ ├── Server.java │ │ ├── ServerContext.java │ │ ├── ServerDispatchHandler.java │ │ └── ServerHeartbeatHandler.java │ ├── status │ ├── StatusMessageEventListener.java │ └── StatusServer.java │ └── util │ ├── AddressUtil.java │ └── Sequence.java └── test ├── java └── com │ └── yb │ └── socket │ ├── AppTest.java │ ├── center │ ├── CenterMock1.java │ ├── CenterMock2.java │ ├── CenterMockMessageEventListener.java │ ├── ClientTest.java │ ├── Server1.java │ └── Server2.java │ ├── push │ ├── database │ │ └── DatabaseTest.java │ └── server │ │ ├── ChannelDO.java │ │ ├── MqttClientTest.java │ │ ├── MqttServerTest.java │ │ ├── ServerContext.java │ │ ├── SubscribeServer.java │ │ └── TestMqttMessageEventListener.java │ └── service │ ├── mqtt │ ├── EchoMessageEventListener.java │ ├── MqttClientTest1.java │ ├── MqttClientTest2.java │ └── MqttServerTest.java │ ├── mqttws │ └── MqttWsServerTest.java │ ├── normal │ ├── ClientTest.java │ ├── JsonEchoMessageEventListener.java │ └── ServerTest.java │ └── protocol │ ├── ClientTest.java │ ├── ProtocolEchoMessageEventListener.java │ └── ServerTest.java └── resources ├── createTable.sql ├── logback.xml └── subscribe.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .settings/ 2 | .classpath 3 | .project 4 | tmp/ 5 | target/ 6 | logs/ 7 | .idea/ 8 | build/ 9 | out/ 10 | .svn/ 11 | .DS_Store 12 | *.iml 13 | *.ipr 14 | *.iws 15 | /bin/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LOAD_BALANCE.md: -------------------------------------------------------------------------------- 1 | 负载均衡方案 2 | 3 | **方案一:传统注册中心的客户端负载均衡方案** 4 | 5 | ![方案一](https://s2.loli.net/2022/04/29/tWgbUN4KQvXfYjy.png) 6 | 7 | 步骤: 8 | 9 | 1、tcp-server启动即向tcp-center注册中心上报自己的状态信息(IP、PORT、连接数等)。 10 | 11 | 2、tcp-center将节点状态信息存储到redis中。 12 | 13 | 3、Client通过GetTCPIP接口获取tcp-server列表信息(按最小连接数排好序),从第一个开始尝试建立连接。 14 | 15 | 备注:该方案适用于传统自建机房方案,可以避免SLB/CLB高性能、高可用要求的技术复杂度。 16 | 17 | **方案二:基于云厂商SLB/CLB负载均衡方案** 18 | 19 | ![方案二](https://s2.loli.net/2022/04/29/MGJoRlcNiSrIZjm.png) 20 | 21 | 说明: 22 | 23 | 1、客户端直连SLB/CLB,SLB/CLB通过最小连接数策略选择一个后端Server建立连接。 24 | 25 | 备注:该方案适用于云原生架构,可以节省web-server服务器成本,及客户端复杂度。 26 | 27 | 28 | ** 技术方案选型最终需要根据实际业务情况来决定 ** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/link-996.icu-red.svg) 2 | 3 | # socket-mqtt: Netty4.x + MQTT 4 | 5 | 这是一个基于[Netty4.x](https://netty.io/) + [MQTT](http://mqtt.org/)实现的Push推送基础框架。相比于原生Netty, 6 | socket-mqtt: 7 | 8 | - 为C/S模式开发封装简单统一的编程模式 9 | - 简单高性能的代码 10 | - 统一的连接管理方案 11 | - 统一的线程管理方案 12 | - 网络基础问题的解决与支持:如心跳保持、压缩解压缩、编码与解码、加密与解密等 13 | - 各种网络参数、连接池实现、监听器实现等可配置可替换 14 | - 可实现对等集群(见[负载均衡方案](LOAD_BALANCE.md)) 15 | - 提供数据统计/监控组件 16 | - 支持普通socket、MQTT、MQTT web socket协议及[自定义协议](src/main/java/com/yb/socket/pojo/protocol/README.md) 17 | 18 | # 项目结构 19 | 20 | - codec: 封装编码与解码 21 | - compression: 封装压缩与解压缩 22 | - count: 封装统计信息 23 | - database: 基于hsql的内存数据库 24 | - encrypt: 封装加密与解密 25 | - future: 封装同步和异步调用 26 | - listener: 封装事件监听,包括消息、通道、异常三类事件监听器 27 | - service: 封装C/S模型、通道、心跳管理、消息分发等核心模块 28 | 29 | # Linux内核参数配置 30 | 31 | ``` 32 | # 允许回收TCP连接 33 | net.ipv4.tcp_tw_reuse = 0 34 | net.ipv4.tcp_tw_recycle = 0 35 | 36 | # TCP 缓冲区内存 37 | net.ipv4.tcp_rmem = 4096 87380 8388608 38 | net.ipv4.tcp_wmem = 4096 87380 8388608 39 | net.ipv4.tcp_mem = 94500000 915000000 927000000 40 | # ulimits 优化 41 | fs.file-max = 1065353 42 | kernel.pid_max = 65536 43 | * soft nofile 655360 * hard nofile 655360 44 | ``` 45 | 46 | # 压测报告 47 | 48 | **MQTT协议-上下行消息处理能力压测** 49 | > 单Broker8核16G,支持44万连接(进程启动内存5G、开启心跳);1万客户端 单消息1024B 下行tps: 16万+; 4000客户端 Publish 单消息1024B 上行tps: 17万+,千兆网卡流量基本打满。 50 | 51 | **自定义协议-单机连接数支撑能力压测** 52 | 53 | > 单Broker8核16G开启心跳机制,客户端每30秒发送一次心跳,支持65万连接,稳定运行10分钟后,系统负载不到2个,服务端毫无压力。由于测试资源限制,理论上可以支持百万连接。 54 | 55 | 56 | 57 | #### MQTT协议-消息下行能力 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 70 | 71 |
1万Clients订阅的消息下行能力对应下行负载情况
65 | 66 | 68 | 69 |
72 | 73 | #### MQTT协议-消息上行能力 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 86 | 87 |
4000Clients订阅消息上行能力对应上行负载情况
81 | 82 | 84 | 85 |
88 | 89 | #### MQTT协议-查看连接数情况 90 | 91 | 92 | 93 | 94 | 95 | 96 | 99 | 102 | 103 |
查看连接数(telnet 10.43.204.61 8001; get status)查看连接数(ss -l)
97 | 98 | 100 | 101 |
104 | 105 | # 使用说明 106 | 107 | 各种测试类的源码在src/test/java/com/yb/socket包路径下: 108 | 包括: 109 | 110 | - 普通socket Server/Client 111 | - MQTT socket Server/Client 112 | - 带注册中心的普通socket/MQTT socket 113 | - 基于内存数据库的模拟订阅推送 114 | - 自定义协议 Server/Client 115 | 116 | ## 服务启动配置选项 117 | 118 | ```java 119 | Server server = new Server(); 120 | // 设置Broker端口 121 | server.setPort(8000); // 设置启动信息统计。默认true 122 | server.setOpenCount(true); 123 | // 设置启用心跳功能。默认true 124 | server.setCheckHeartbeat(true); 125 | // 设置启动服务状态,默认端口8001。通过telnet server_ip 8001; get status查看服务信息 126 | server.setOpenStatus(true); 127 | // 服务状态端口。默认8001 128 | server.setStatusPort(8001); 129 | // 设置服务名称 130 | server.setServiceName("Demo"); 131 | // 设置工作线程数量。默认CPU个数+1 132 | server.setWorkerCount(64); 133 | // 是否开户业务处理线程池。默认false 134 | server.setOpenExecutor(true); 135 | // 设置tcp no delay。默认true 136 | server.setTcpNoDelay(true); 137 | // 是否启用keepAlive。默认true 138 | server.setKeepAlive(true); 139 | // 自定义监听器,可处理相关事件 140 | server.addEventListener(new EchoMessageEventListener()); 141 | // 设置Broker启动协议。SocketType.MQTT - MQTT协议; SocketType.NORMAL - 普通Socket协议;SocketType.MQTT_WS - MQTT web socket协议; 142 | server.setSocketType(SocketType.MQTT); 143 | // 绑定端口启动服务 144 | server.bind(); 145 | ``` 146 | 147 | ## MQTT web socket server DEMO 148 | 149 | ```java 150 | Server server = new Server(); 151 | server.setPort(8000); 152 | server.addEventListener(new EchoMessageEventListener()); 153 | server.setSocketType(SocketType.MQTT_WS); 154 | server.bind(); 155 | 156 | //模拟推送 157 | String message = "this is a web socket message!"; 158 | MqttRequest mqttRequest = new MqttRequest((message.getBytes())); 159 | while (true) { 160 | if (server.getChannels().size() > 0) { 161 | logger.info("模拟推送消息"); 162 | for (WrappedChannel channel : server.getChannels().values()) { 163 | server.send(channel, "yb/notice/", mqttRequest); 164 | } 165 | } 166 | Thread.sleep(1000L); 167 | } 168 | ``` 169 | 170 | ## MQTT web socket client(浏览器) 171 | 172 | ``` 173 | 可用在线mqtt测试:http://www.tongxinmao.com/txm/webmqtt.php 174 | Topic Payload Time QoS 175 | yb/notice/ this is a web socket message! 2019-2-27 16:54:54 0 176 | ``` 177 | 178 | ## Normal socket server DEMO 179 | 180 | ```java 181 | Server server = new Server(); 182 | server.setPort(8000); 183 | server.addEventListener(new JsonEchoMessageEventListener()); 184 | server.addChannelHandler("decoder", JsonDecoder::new); 185 | server.addChannelHandler("encoder", JsonEncoder::new); 186 | server.bind(); 187 | 188 | //模拟推送 189 | JSONObject message = new JSONObject(); 190 | message.put("action", "echo"); 191 | message.put("message", "this is a normal socket message!"); 192 | 193 | Request request = new Request(); 194 | request.setSequence(0); 195 | request.setMessage(message); 196 | while (true) { 197 | if (server.getChannels().size() > 0) { 198 | logger.info("模拟推送消息"); 199 | for (WrappedChannel channel : server.getChannels().values()) { 200 | channel.send(request); 201 | Thread.sleep(5000L); 202 | } 203 | } 204 | } 205 | ``` 206 | 207 | ## Normal socket client DEMO 208 | 209 | ```java 210 | Client client = new Client(); 211 | client.setIp("127.0.0.1"); 212 | client.setPort(8000); 213 | client.setConnectTimeout(10000); 214 | client.addChannelHandler("decoder", JsonDecoder::new); 215 | client.addChannelHandler("encoder", JsonEncoder::new); 216 | client.connect(); 217 | 218 | for (int i = 0; i < 2; i++) { 219 | JSONObject message = new JSONObject(); 220 | message.put("action", "echo"); 221 | message.put("message", "hello world!"); 222 | Request request = new Request(); 223 | request.setSequence(i); 224 | request.setMessage(message); 225 | Response response = (Response) 226 | client.sendWithSync(request, 3000); 227 | logger.info("成功接收到同步的返回: '{}'.", response); 228 | } 229 | 230 | client.shutdown(); 231 | ``` 232 | 233 | ## 带注册中心 center DEMO 234 | 235 | ```java 236 | Server server = new Server(); 237 | server.setPort(9000); 238 | server.setCheckHeartbeat(false); 239 | server.addChannelHandler("decoder", JsonDecoder::new); 240 | server.addChannelHandler("encoder", JsonEncoder::new); 241 | server.addEventListener(new com.yb.socket.center.CenterMockMessageEventListener()); 242 | server.bind(); 243 | ``` 244 | 245 | ## 带注册中心 server DEMO 246 | 247 | ```java 248 | Server server = new Server(); 249 | server.setPort(8000); 250 | server.setCheckHeartbeat(false); 251 | server.setCenterAddr("127.0.0.1:9000,127.0.0.1:9010"); 252 | server.addEventListener(new JsonEchoMessageEventListener()); 253 | server.bind(); 254 | ``` 255 | 256 | ## 带注册中心 client DEMO 257 | 258 | ```java 259 | Client client = new Client(); 260 | client.setCheckHeartbeat(false); 261 | client.setCenterAddr("127.0.0.1:9000,127.0.0.1:9010"); 262 | client.addChannelHandler("decoder", JsonDecoder::new); 263 | client.addChannelHandler("encoder", JsonEncoder::new); 264 | client.connect(); 265 | 266 | JSONObject message = new JSONObject(); 267 | message.put("action", "echo"); 268 | message.put("message", "hello"); 269 | 270 | for (int i = 0; i < 5; i++) { 271 | Request request = new Request(); 272 | request.setSequence(i); 273 | request.setMessage(message); 274 | client.send(request); 275 | Thread.sleep(5000L); 276 | } 277 | ``` 278 | 279 | # 后续规划 280 | 281 | - 支持MQTT主题过滤机制 282 | - 支持SSL连接方式 283 | - 完整的QoS服务质量等级实现DEMO 284 | - 遗嘱消息, 保留消息及消息分发重试 285 | 286 | # 压测工具 287 | 288 | - https://github.com/daoshenzzg/mqtt-mock 289 | 290 | # 参考项目 291 | 292 | - https://github.com/netty/netty 293 | - https://github.com/1ssqq1lxr/iot_push 294 | - https://github.com/Wizzercn/MqttWk 295 | -------------------------------------------------------------------------------- /doc/dstat_pub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/dstat_pub.png -------------------------------------------------------------------------------- /doc/dstat_sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/dstat_sub.png -------------------------------------------------------------------------------- /doc/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/project.png -------------------------------------------------------------------------------- /doc/pub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/pub.png -------------------------------------------------------------------------------- /doc/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/ss.png -------------------------------------------------------------------------------- /doc/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/status.png -------------------------------------------------------------------------------- /doc/sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoshenzzg/socket-mqtt/b8a5ac68ed525b133ae3377259e640ebc88da751/doc/sub.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.yb.socket 5 | socket-mqtt 6 | jar 7 | 1.0-SNAPSHOT 8 | socket-mqtt 9 | https://www.yb.com 10 | 11 | 12 | junit 13 | junit 14 | 3.8.1 15 | test 16 | 17 | 18 | org.apache.commons 19 | commons-lang3 20 | 3.7 21 | 22 | 23 | ch.qos.logback 24 | logback-classic 25 | 1.3.12 26 | 27 | 28 | io.netty 29 | netty-all 30 | 4.1.68.Final 31 | 32 | 33 | com.alibaba 34 | fastjson 35 | 1.2.83 36 | 37 | 38 | org.hsqldb 39 | hsqldb 40 | 2.7.1 41 | test 42 | 43 | 44 | commons-dbutils 45 | commons-dbutils 46 | 1.7 47 | test 48 | 49 | 50 | org.springframework 51 | spring-context-support 52 | 5.3.21 53 | test 54 | 55 | 56 | org.eclipse.paho 57 | org.eclipse.paho.client.mqttv3 58 | 1.2.1 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-compiler-plugin 66 | 67 | 1.8 68 | 1.8 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/App.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2018/12/30 13:36 6 | */ 7 | public class App { 8 | public static void main(String[] args) { 9 | System.out.println("Hello World!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/JsonDecoder.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.yb.socket.pojo.Heartbeat; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.pojo.Response; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandler; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.handler.codec.CodecException; 12 | import io.netty.handler.codec.MessageToMessageDecoder; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Json 解码器 18 | * 19 | * @author daoshenzzg@163.com 20 | * @date 2018/12/30 16:55 21 | */ 22 | @ChannelHandler.Sharable 23 | public class JsonDecoder extends MessageToMessageDecoder { 24 | private static final String REQUEST = "request"; 25 | private static final String RESPONSE = "response"; 26 | private static final String HEARTBEAT = "heartbeat"; 27 | 28 | @Override 29 | protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception { 30 | try { 31 | byte[] tmp = new byte[buf.readableBytes()]; 32 | buf.readBytes(tmp); 33 | String jsonStr = new String(tmp); 34 | 35 | JSONObject json = JSON.parseObject(jsonStr); 36 | String type = json.getString("type"); 37 | if (type.equalsIgnoreCase(REQUEST)) { 38 | Request request = new Request(); 39 | request.setSequence(json.getIntValue("sequence")); 40 | request.setMessage(json.getString("message")); 41 | out.add(request); 42 | } else if (type.equalsIgnoreCase(RESPONSE)) { 43 | Response response = new Response(); 44 | response.setSequence(json.getIntValue("sequence")); 45 | response.setCode(json.getIntValue("code")); 46 | response.setResult(json.get("result")); 47 | out.add(response); 48 | } else if (type.equalsIgnoreCase(HEARTBEAT)) { 49 | out.add(Heartbeat.getSingleton()); 50 | } 51 | } catch (Exception ex) { 52 | throw new CodecException(ex); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/JsonEncoder.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.pojo.BaseMessage; 5 | import com.yb.socket.pojo.Heartbeat; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.pojo.Response; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.Unpooled; 10 | import io.netty.channel.ChannelHandler; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.handler.codec.CodecException; 13 | import io.netty.handler.codec.MessageToMessageEncoder; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Json 编码器 19 | * 20 | * @author daoshenzzg@163.com 21 | * @date 2018/12/30 16:56 22 | */ 23 | @ChannelHandler.Sharable 24 | public class JsonEncoder extends MessageToMessageEncoder { 25 | private static final String REQUEST = "request"; 26 | private static final String RESPONSE = "response"; 27 | private static final String HEARTBEAT = "heartbeat"; 28 | 29 | @Override 30 | protected void encode(ChannelHandlerContext ctx, BaseMessage msg, List out) throws Exception { 31 | if (msg instanceof Request) { 32 | Request request = (Request) msg; 33 | JSONObject json = new JSONObject(); 34 | json.put("type", REQUEST); 35 | json.put("message", request.getMessage()); 36 | json.put("sequence", request.getSequence()); 37 | ByteBuf buf = Unpooled.copiedBuffer(json.toString().getBytes()); 38 | out.add(buf); 39 | } else if (msg instanceof Response) { 40 | Response response = (Response) msg; 41 | JSONObject json = new JSONObject(); 42 | json.put("type", RESPONSE); 43 | json.put("code", response.getCode()); 44 | json.put("result", response.getResult()); 45 | json.put("sequence", response.getSequence()); 46 | ByteBuf buf = Unpooled.copiedBuffer(json.toString().getBytes()); 47 | out.add(buf); 48 | } else if (msg instanceof Heartbeat) { 49 | JSONObject json = new JSONObject(); 50 | json.put("type", HEARTBEAT); 51 | ByteBuf buf = Unpooled.copiedBuffer(json.toString().getBytes()); 52 | out.add(buf); 53 | } else { 54 | throw new CodecException("unknown message type: " + msg); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/MqttWebSocketCodec.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToMessageCodec; 6 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * WebSocket Mqtt消息编解码器 12 | * 13 | * @author daoshenzzg@163.com 14 | * @date 2019/1/3 19:04 15 | */ 16 | public class MqttWebSocketCodec extends MessageToMessageCodec { 17 | 18 | @Override 19 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 20 | out.add(new BinaryWebSocketFrame(msg.retain())); 21 | } 22 | 23 | @Override 24 | protected void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame msg, List out) throws Exception { 25 | out.add(msg.retain().content()); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/protocol/MessageTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec.protocol; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author daoshenzzg@163.com 8 | * @date 2022-04-29 15:41 9 | */ 10 | public enum MessageTypeEnum { 11 | // 消息类型。0-心跳;1-请求;2-响应。 12 | HEARTBEAT((byte) 0), 13 | REQUEST((byte) 1), 14 | RESPONSE((byte) 2); 15 | 16 | private byte type; 17 | 18 | MessageTypeEnum(byte type) { 19 | this.type = type; 20 | } 21 | 22 | private static Map map; 23 | 24 | private synchronized static void initMap() { 25 | map = new HashMap<>(); 26 | for (MessageTypeEnum e : MessageTypeEnum.values()) { 27 | map.put(e.type, e); 28 | } 29 | } 30 | 31 | public static MessageTypeEnum get(byte type) { 32 | if (map == null) { 33 | initMap(); 34 | } 35 | return map.get(type); 36 | } 37 | 38 | public byte getType() { 39 | return type; 40 | } 41 | 42 | public void setType(byte type) { 43 | this.type = type; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/protocol/Protocol.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec.protocol; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.yb.socket.pojo.Heartbeat; 6 | import com.yb.socket.pojo.protocol.ProtocolRequest; 7 | import com.yb.socket.pojo.protocol.ProtocolResponse; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.Unpooled; 10 | import org.apache.commons.lang3.ArrayUtils; 11 | 12 | import java.util.HashMap; 13 | import java.util.Iterator; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author daoshenzzg@163.com 18 | * @date 2022-04-29 15:36 19 | */ 20 | public class Protocol { 21 | 22 | /** 23 | * 编码 24 | * 25 | * @param msg 26 | * @return 27 | * @throws Exception 28 | */ 29 | public static ByteBuf encode(Object msg) throws Exception { 30 | ByteBuf totalBuf = Unpooled.buffer(); 31 | 32 | if (msg instanceof Heartbeat) { 33 | byte[] header = new byte[20]; 34 | totalBuf.writeBytes(header); 35 | return totalBuf; 36 | } else if (msg instanceof ProtocolRequest) { 37 | ProtocolRequest request = (ProtocolRequest) msg; 38 | byte[] body = packJson(request.getBody()); 39 | 40 | // 组织header 41 | ByteBuf headBuf = Unpooled.buffer(20); 42 | headBuf.writeByte(request.getVersion()); 43 | headBuf.writeByte(MessageTypeEnum.REQUEST.getType()); 44 | headBuf.writeInt(body != null ? body.length : 0); 45 | headBuf.writeInt(request.getSequence()); 46 | headBuf.writeBytes(request.getReserved()); 47 | 48 | totalBuf.writeBytes(headBuf); 49 | if (ArrayUtils.isNotEmpty(body)) { 50 | totalBuf.writeBytes(body); 51 | } 52 | 53 | return totalBuf; 54 | } else if (msg instanceof ProtocolResponse) { 55 | ProtocolResponse response = (ProtocolResponse) msg; 56 | byte[] body = packJson(response.getBody()); 57 | 58 | //组织header 59 | ByteBuf headBuf = Unpooled.buffer(20); 60 | headBuf.writeByte(response.getVersion()); 61 | headBuf.writeByte(MessageTypeEnum.RESPONSE.getType()); 62 | headBuf.writeInt(body != null ? body.length : 0); 63 | headBuf.writeInt(response.getSequence()); 64 | headBuf.writeBytes(response.getReserved()); 65 | 66 | totalBuf.writeBytes(headBuf); 67 | if (ArrayUtils.isNotEmpty(body)) { 68 | totalBuf.writeBytes(body); 69 | } 70 | 71 | return totalBuf; 72 | } 73 | 74 | return null; 75 | } 76 | 77 | /** 78 | * 解码 79 | * 80 | * @param buffer 81 | * @return 82 | * @throws Exception 83 | */ 84 | public static Object decode(ByteBuf buffer) throws Exception { 85 | //读取header 86 | byte version = buffer.readByte(); 87 | byte msgType = buffer.readByte(); 88 | int bodyLength = buffer.readInt(); 89 | int seqId = buffer.readInt(); 90 | byte[] reserved = new byte[10]; 91 | buffer.readBytes(reserved); 92 | 93 | MessageTypeEnum messageTypeEnum = MessageTypeEnum.get(msgType); 94 | if (messageTypeEnum == null) { 95 | throw new RuntimeException("unsupported messageType: " + msgType); 96 | } 97 | 98 | if (MessageTypeEnum.HEARTBEAT.equals(messageTypeEnum)) { 99 | return Heartbeat.getSingleton(); 100 | } 101 | 102 | byte[] bodyBytes = new byte[bodyLength]; 103 | buffer.readBytes(bodyBytes); 104 | 105 | Map body = unpackJson(bodyBytes); 106 | if (MessageTypeEnum.REQUEST.equals(messageTypeEnum)) { 107 | ProtocolRequest request = new ProtocolRequest(); 108 | request.setVersion(version); 109 | request.setBodyLength(bodyLength); 110 | request.setSequence(seqId); 111 | request.setReserved(reserved); 112 | request.setBody(body); 113 | 114 | return request; 115 | } else if (MessageTypeEnum.RESPONSE.equals(messageTypeEnum)) { 116 | ProtocolResponse response = new ProtocolResponse(); 117 | response.setVersion(version); 118 | response.setBodyLength(bodyLength); 119 | response.setSequence(seqId); 120 | response.setReserved(reserved); 121 | response.setBody(body); 122 | 123 | return response; 124 | } 125 | 126 | throw new RuntimeException("messageType must be [0,1,2]."); 127 | } 128 | 129 | 130 | public static byte[] packJson(Map body) throws Exception { 131 | if (body == null) { 132 | return null; 133 | } 134 | return JSON.toJSONString(body).getBytes(); 135 | } 136 | 137 | public static Map unpackJson(byte[] bodyBytes) throws Exception { 138 | String body = new String(bodyBytes); 139 | return jsonToMap(body); 140 | } 141 | 142 | private static Map jsonToMap(String str) { 143 | Map map = new HashMap<>(); 144 | JSONObject json = JSON.parseObject(str); 145 | @SuppressWarnings("rawtypes") 146 | Iterator keys = json.keySet().iterator(); 147 | while (keys.hasNext()) { 148 | String key = (String) keys.next(); 149 | String value = json.get(key).toString(); 150 | if (value.startsWith("{") && value.endsWith("}")) { 151 | map.put(key, jsonToMap(value)); 152 | } else { 153 | map.put(key, value); 154 | } 155 | } 156 | return map; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/protocol/ProtocolDecoder.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec.protocol; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageDecoder; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 自定义解码器粘包拆包处理 12 | * 13 | * @author daoshenzzg@163.com 14 | * @date 2022-04-29 15:46 15 | */ 16 | public class ProtocolDecoder extends ByteToMessageDecoder { 17 | 18 | private static final int HEADER_SIZE = 20; 19 | private static final int BODY_LENGTH_OFFSET = 2; 20 | 21 | @Override 22 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 23 | if (in == null || in.readableBytes() < HEADER_SIZE) { 24 | return; 25 | } 26 | in.markReaderIndex(); 27 | 28 | ByteBuf headerBuf = Unpooled.buffer(HEADER_SIZE); 29 | in.readBytes(headerBuf); 30 | 31 | int bodyLength = headerBuf.getInt(BODY_LENGTH_OFFSET); 32 | if (in.readableBytes() < bodyLength) { 33 | in.resetReaderIndex(); 34 | return; 35 | } 36 | in.resetReaderIndex(); 37 | 38 | out.add(Protocol.decode(in)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/codec/protocol/ProtocolEncoder.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.codec.protocol; 2 | 3 | import com.yb.socket.pojo.BaseMessage; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToByteEncoder; 8 | 9 | /** 10 | * @author daoshenzzg@163.com 11 | * @date 2022-04-29 15:46 12 | */ 13 | @ChannelHandler.Sharable 14 | public class ProtocolEncoder extends MessageToByteEncoder { 15 | 16 | @Override 17 | protected void encode(ChannelHandlerContext ctx, BaseMessage msg, ByteBuf out) throws Exception { 18 | out.writeBytes(Protocol.encode(msg)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/compression/Compression.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.compression; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * @author daoshenzzg@163.com 7 | * @date 2019/1/14 11:09 8 | */ 9 | public interface Compression { 10 | 11 | /** 12 | * compress 13 | * 14 | * @param buffer 15 | * @return 16 | * @throws IOException 17 | */ 18 | byte[] compress(byte[] buffer) throws IOException; 19 | 20 | /** 21 | * decompress 22 | * 23 | * @param buffer 24 | * @return 25 | * @throws IOException 26 | */ 27 | byte[] decompress(byte[] buffer) throws IOException; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/compression/GzipCompression.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.compression; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.util.zip.GZIPInputStream; 7 | import java.util.zip.GZIPOutputStream; 8 | 9 | /** 10 | * @author daoshenzzg@163.com 11 | * @date 2019/1/14 11:11 12 | */ 13 | public class GzipCompression implements Compression { 14 | 15 | private int unit = 2048; 16 | 17 | public int getUnit() { 18 | return unit; 19 | } 20 | 21 | public void setUnit(int unit) { 22 | this.unit = unit; 23 | } 24 | 25 | @Override 26 | public byte[] compress(byte[] buffer) throws IOException { 27 | ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); 28 | GZIPOutputStream gzip = new GZIPOutputStream(arrayOutputStream); 29 | ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); 30 | 31 | try { 32 | byte[] buf = new byte[unit]; 33 | int len = 0; 34 | while ((len = inputStream.read(buf)) != -1) { 35 | gzip.write(buf, 0, len); 36 | } 37 | 38 | gzip.finish(); 39 | 40 | byte[] result = arrayOutputStream.toByteArray(); 41 | 42 | return result; 43 | } catch (IOException e) { 44 | throw e; 45 | } finally { 46 | gzip.close(); 47 | arrayOutputStream.close(); 48 | inputStream.close(); 49 | } 50 | } 51 | 52 | @Override 53 | public byte[] decompress(byte[] buffer) throws IOException { 54 | ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); 55 | // Open the compressed stream 56 | GZIPInputStream gzip = new GZIPInputStream(inputStream); 57 | 58 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 59 | 60 | try { 61 | // Transfer bytes from the compressed stream to the output stream 62 | byte[] buf = new byte[unit]; 63 | int len; 64 | while ((len = gzip.read(buf)) > 0) { 65 | out.write(buf, 0, len); 66 | } 67 | 68 | return out.toByteArray(); 69 | } catch (IOException e) { 70 | throw e; 71 | } finally { 72 | // Close the file and stream 73 | gzip.close(); 74 | out.close(); 75 | inputStream.close(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/count/CountHandler.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.count; 2 | 3 | import com.yb.socket.service.server.Server; 4 | import com.yb.socket.service.server.ServerContext; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | 9 | /** 10 | * Server统计信息Handle 11 | * 放在pipeline的最后,因为channelInactive和channelActive事件处理中用到了Server里的最新channels size 12 | * 13 | * @author daoshenzzg@163.com 14 | * @date 2019/1/11 16:53 15 | */ 16 | @ChannelHandler.Sharable 17 | public class CountHandler extends ChannelInboundHandlerAdapter { 18 | 19 | @Override 20 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 21 | Server server = ServerContext.getContext().getServer(); 22 | server.getCountInfo().setCurChannelNum(server.getChannels().size()); 23 | super.channelActive(ctx); 24 | } 25 | 26 | @Override 27 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 28 | Server server = ServerContext.getContext().getServer(); 29 | server.getCountInfo().setCurChannelNum(server.getChannels().size()); 30 | super.channelInactive(ctx); 31 | } 32 | 33 | @Override 34 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | CountInfo countInfo = ServerContext.getContext().getServer().getCountInfo(); 36 | countInfo.getReceiveNum().incrementAndGet(); 37 | countInfo.setLastReceive(System.currentTimeMillis()); 38 | super.channelRead(ctx, msg); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/count/CountInfo.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.count; 2 | 3 | import java.io.Serializable; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | 6 | /** 7 | * @author daoshenzzg@163.com 8 | * @date 2019/1/11 16:55 9 | */ 10 | public class CountInfo implements Serializable { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | /** 15 | * 最后次接收消息 16 | */ 17 | private long lastReceive; 18 | /** 19 | * 最后次接收消息 20 | */ 21 | private long lastSent; 22 | /** 23 | * 最大连接数 24 | */ 25 | private long maxChannelNum; 26 | /** 27 | * 当前连接数 28 | */ 29 | private long curChannelNum; 30 | /** 31 | * 接收消息数 32 | */ 33 | private AtomicLong receiveNum = new AtomicLong(); 34 | /** 35 | * 发送消息数 36 | */ 37 | private AtomicLong sentNum = new AtomicLong(); 38 | /** 39 | * 收发心跳数 40 | */ 41 | private AtomicLong heartbeatNum = new AtomicLong(); 42 | 43 | public long getCurChannelNum() { 44 | return curChannelNum; 45 | } 46 | 47 | public void setCurChannelNum(long curChannelNum) { 48 | this.curChannelNum = curChannelNum; 49 | if (this.maxChannelNum < curChannelNum) { 50 | this.maxChannelNum = curChannelNum; 51 | } 52 | } 53 | 54 | public long getMaxChannelNum() { 55 | return maxChannelNum; 56 | } 57 | 58 | public AtomicLong getReceiveNum() { 59 | return receiveNum; 60 | } 61 | 62 | public AtomicLong getSentNum() { 63 | return sentNum; 64 | } 65 | 66 | public AtomicLong getHeartbeatNum() { 67 | return heartbeatNum; 68 | } 69 | 70 | public long getLastReceive() { 71 | return lastReceive; 72 | } 73 | 74 | public void setLastReceive(long lastReceive) { 75 | if (this.lastReceive < lastReceive) { 76 | this.lastReceive = lastReceive; 77 | } 78 | } 79 | 80 | public long getLastSent() { 81 | return lastSent; 82 | } 83 | 84 | public void setLastSent(long lastSent) { 85 | if (this.lastSent < lastSent) { 86 | this.lastSent = lastSent; 87 | } 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | StringBuilder sb = new StringBuilder(); 93 | sb.append("StatisticInfo [lastReceive=").append(this.lastReceive); 94 | sb.append(", lastSent=").append(this.lastSent); 95 | sb.append(", receiveNum=").append(this.receiveNum); 96 | sb.append(", sentNum=").append(this.sentNum); 97 | sb.append(", heartbeatNum=").append(this.heartbeatNum); 98 | sb.append(", maxChannelNum=").append(this.maxChannelNum); 99 | sb.append(", curChannelNum=").append(this.curChannelNum); 100 | sb.append("]"); 101 | return sb.toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/datasource/HsqlDataSource.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.datasource; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import javax.sql.DataSource; 6 | import java.io.*; 7 | import java.lang.reflect.InvocationHandler; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Proxy; 10 | import java.sql.*; 11 | import java.util.LinkedList; 12 | import java.util.logging.Logger; 13 | 14 | /** 15 | * 针对hsqldb的简单datasource实现 16 | * 17 | * @author daoshenzzg@163.com 18 | * @date 2019/1/14 11:19 19 | */ 20 | public class HsqlDataSource implements DataSource { 21 | private final String CLASSPATH_PREFIX = "classpath:"; 22 | /** 23 | * 连接池大小 24 | */ 25 | private int poolSize = 3; 26 | private String driverClassName; 27 | private String url; 28 | private String username; 29 | private String password; 30 | /** 31 | * 获取连接的超时时间 32 | */ 33 | private int timeout = 1000; 34 | /** 35 | * 建表脚本 36 | */ 37 | private String tableScript; 38 | private boolean inited = false; 39 | 40 | private LinkedList connectionPool = new LinkedList<>(); 41 | 42 | public HsqlDataSource() { 43 | } 44 | 45 | public HsqlDataSource(String driverClassName, String url, String username, String password, 46 | String tableScript, int poolSize, int timeout) { 47 | super(); 48 | this.poolSize = poolSize; 49 | this.driverClassName = driverClassName; 50 | this.url = url; 51 | this.username = username; 52 | this.password = password; 53 | this.tableScript = tableScript; 54 | this.timeout = timeout; 55 | } 56 | 57 | 58 | /** 59 | * 初始化datasource,datasource使用前必须初始化 60 | */ 61 | public void init() { 62 | InputStream is = null; 63 | try { 64 | Class.forName(driverClassName); 65 | if (poolSize <= 0) { 66 | throw new IllegalArgumentException("Invalid pool size " + poolSize); 67 | } 68 | 69 | if (StringUtils.isBlank(tableScript)) { 70 | throw new IllegalArgumentException("tableScript cannot be null"); 71 | } 72 | 73 | if (tableScript.startsWith(CLASSPATH_PREFIX)) { 74 | is = this.getClass().getClassLoader().getResourceAsStream(tableScript.substring(CLASSPATH_PREFIX.length())); 75 | } else { 76 | is = new FileInputStream(tableScript.substring(CLASSPATH_PREFIX.length())); 77 | } 78 | if (is == null) { 79 | throw new FileNotFoundException(tableScript + " cannot be opened because it does not exist"); 80 | } 81 | 82 | for (int i = 0; i < poolSize; i++) { 83 | Connection connection = DriverManager.getConnection(url, username, password); 84 | // 获取被代理的对象 85 | connection = ConnectionProxy.getProxyedConnection(connection, connectionPool); 86 | // 添加被代理的对象 87 | connectionPool.add(connection); 88 | } 89 | 90 | //开始建表 91 | createTable(is); 92 | } catch (Exception ex) { 93 | throw new RuntimeException(ex.getMessage(), ex); 94 | } finally { 95 | if (is != null) { 96 | try { 97 | is.close(); 98 | } catch (Exception ex) { 99 | } 100 | } 101 | } 102 | 103 | inited = true; 104 | } 105 | 106 | private void createTable(InputStream is) { 107 | Connection connection = null; 108 | Statement statement = null; 109 | try { 110 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 111 | String sql = null; 112 | 113 | connection = DriverManager.getConnection(url, username, password); 114 | statement = connection.createStatement(); 115 | while ((sql = br.readLine()) != null) { 116 | statement.execute(sql.trim()); 117 | } 118 | } catch (Exception ex) { 119 | throw new RuntimeException(ex.getMessage(), ex); 120 | } finally { 121 | if (statement != null) { 122 | try { 123 | statement.close(); 124 | } catch (Exception ex) { 125 | } 126 | } 127 | if (connection != null) { 128 | try { 129 | connection.close(); 130 | } catch (Exception ex) { 131 | } 132 | } 133 | } 134 | } 135 | 136 | @Override 137 | public PrintWriter getLogWriter() throws SQLException { 138 | throw new RuntimeException("Nonsupport Operation."); 139 | } 140 | 141 | @Override 142 | public void setLogWriter(PrintWriter out) throws SQLException { 143 | throw new RuntimeException("Nonsupport Operation."); 144 | } 145 | 146 | @Override 147 | public void setLoginTimeout(int seconds) throws SQLException { 148 | throw new RuntimeException("Nonsupport Operation."); 149 | } 150 | 151 | @Override 152 | public int getLoginTimeout() throws SQLException { 153 | return 0; 154 | } 155 | 156 | @Override 157 | public T unwrap(Class clz) throws SQLException { 158 | return (T) this; 159 | } 160 | 161 | @Override 162 | public boolean isWrapperFor(Class clz) throws SQLException { 163 | return DataSource.class.equals(clz); 164 | } 165 | 166 | @Override 167 | public Connection getConnection() throws SQLException { 168 | synchronized (connectionPool) { 169 | if (!inited) { 170 | init(); 171 | } 172 | 173 | if (connectionPool.size() == 0) { 174 | try { 175 | connectionPool.wait(timeout); 176 | } catch (InterruptedException ex) { 177 | throw new RuntimeException(ex); 178 | } 179 | 180 | return getConnection(); 181 | } else { 182 | Connection connection = connectionPool.removeFirst(); 183 | return connection; 184 | } 185 | } 186 | } 187 | 188 | static class ConnectionProxy implements InvocationHandler { 189 | private Object obj; 190 | private LinkedList connectionPool; 191 | 192 | private ConnectionProxy(Object obj, LinkedList connectionPool) { 193 | this.obj = obj; 194 | this.connectionPool = connectionPool; 195 | } 196 | 197 | public static Connection getProxyedConnection(Object obj, LinkedList connectionPool) { 198 | 199 | Object proxed = Proxy.newProxyInstance(obj.getClass().getClassLoader(), 200 | new Class[]{Connection.class}, 201 | new ConnectionProxy(obj, connectionPool)); 202 | 203 | return (Connection) proxed; 204 | } 205 | 206 | @Override 207 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 208 | String methodName = "close"; 209 | if (methodName.equals(method.getName())) { 210 | synchronized (connectionPool) { 211 | // 将被代理的对象放回池中 212 | connectionPool.add((Connection) proxy); 213 | // 通知等待线程去获取一个连接吧 214 | connectionPool.notify(); 215 | } 216 | return null; 217 | } else { 218 | return method.invoke(obj, args); 219 | } 220 | } 221 | } 222 | 223 | @Override 224 | public Connection getConnection(String username, String password) 225 | throws SQLException { 226 | return getConnection(); 227 | } 228 | 229 | public void setPoolSize(int poolSize) { 230 | this.poolSize = poolSize; 231 | } 232 | 233 | public void setDriverClassName(String driverClassName) { 234 | this.driverClassName = driverClassName; 235 | } 236 | 237 | public void setUrl(String url) { 238 | this.url = url; 239 | } 240 | 241 | public void setUsername(String username) { 242 | this.username = username; 243 | } 244 | 245 | public void setPassword(String password) { 246 | this.password = password; 247 | } 248 | 249 | public void setTimeout(int timeout) { 250 | this.timeout = timeout; 251 | } 252 | 253 | public void setTableScript(String tableScript) { 254 | this.tableScript = tableScript; 255 | } 256 | 257 | @Override 258 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 259 | return null; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/encrypt/AESEncrypt.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.encrypt; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.SecretKey; 5 | import javax.crypto.SecretKeyFactory; 6 | import javax.crypto.spec.PBEKeySpec; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import java.security.spec.KeySpec; 9 | 10 | /** 11 | * @author daoshenzzg@163.com 12 | * @date 2019/1/14 10:46 13 | */ 14 | public class AESEncrypt extends BaseEncrypt implements Encrypt { 15 | private static final String CIPHER_TRIPLE_AES = "AES/ECB/PKCS5Padding"; 16 | private static final byte[] salt = { (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 }; 17 | private SecretKey secret; 18 | 19 | public AESEncrypt(String password, int length) throws Exception{ 20 | SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 21 | KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 5, length); 22 | SecretKey tmp = factory.generateSecret(spec); 23 | secret = new SecretKeySpec(tmp.getEncoded(), "AES"); 24 | } 25 | 26 | @Override 27 | public byte[] encrypt(byte[] src) throws Exception { 28 | Cipher cipher = Cipher.getInstance(CIPHER_TRIPLE_AES); 29 | cipher.init(Cipher.ENCRYPT_MODE, secret); 30 | 31 | return cipher.doFinal(src); 32 | } 33 | 34 | @Override 35 | public byte[] decrypt(byte[] src) throws Exception { 36 | Cipher cipher = Cipher.getInstance(CIPHER_TRIPLE_AES); 37 | cipher.init(Cipher.DECRYPT_MODE, secret); 38 | return cipher.doFinal(src); 39 | } 40 | 41 | public static void main(String[] args) throws Exception { 42 | String testStr = "acd721232a191c87bfe3425d3e0f2b38"; 43 | AESEncrypt aesEncrypt = new AESEncrypt("test_key", 128); 44 | String enStr = aesEncrypt.encrypt(testStr, "UTF-8"); 45 | String deStr = aesEncrypt.decrypt(enStr, "UTF-8"); 46 | 47 | System.out.println("加密前:" + testStr); 48 | System.out.println("加密后:" + enStr); 49 | System.out.println("解密后:" + deStr); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/encrypt/BaseEncrypt.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.encrypt; 2 | 3 | import java.util.Base64; 4 | 5 | /** 6 | * @author daoshenzzg@163.com 7 | * @date 2019/1/14 10:43 8 | */ 9 | public abstract class BaseEncrypt implements Encrypt { 10 | 11 | @Override 12 | public String encrypt(String src, String charset) throws Exception { 13 | byte[] srcBytes = src.getBytes(charset); 14 | byte[] enBytes = encrypt(srcBytes); 15 | return Base64.getEncoder().encodeToString(enBytes); 16 | } 17 | 18 | @Override 19 | public String decrypt(String src, String charset) throws Exception { 20 | byte[] srcBytes = Base64.getDecoder().decode(src); 21 | byte[] deBytes = decrypt(srcBytes); 22 | return new String(deBytes, charset); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/encrypt/Encrypt.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.encrypt; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2019/1/14 10:41 6 | */ 7 | public interface Encrypt { 8 | /** 9 | * encrypt 10 | * 11 | * @param src 12 | * @return 13 | * @throws Exception 14 | */ 15 | byte[] encrypt(byte[] src) throws Exception; 16 | 17 | /** 18 | * decrypt 19 | * 20 | * @param src 21 | * @return 22 | * @throws Exception 23 | */ 24 | byte[] decrypt(byte[] src) throws Exception; 25 | 26 | /** 27 | * encrypt 28 | * 29 | * @param src 30 | * @param charset 31 | * @return 32 | * @throws Exception 33 | */ 34 | String encrypt(String src, String charset) throws Exception; 35 | 36 | /** 37 | * decrypt 38 | * 39 | * @param src 40 | * @param charset 41 | * @return 42 | * @throws Exception 43 | */ 44 | String decrypt(String src, String charset) throws Exception; 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/encrypt/RSAEncrypt.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.encrypt; 2 | 3 | import javax.crypto.Cipher; 4 | import java.security.*; 5 | import java.security.spec.PKCS8EncodedKeySpec; 6 | import java.security.spec.X509EncodedKeySpec; 7 | import java.util.Base64; 8 | 9 | /** 10 | * @author daoshenzzg@163.com 11 | * @date 2019/1/14 10:49 12 | */ 13 | public class RSAEncrypt extends BaseEncrypt implements Encrypt { 14 | 15 | private static final String CIPHER_TRIPLE_AES = "RSA/ECB/PKCS1Padding"; 16 | private PrivateKey privateKey; 17 | private PublicKey publicKey; 18 | 19 | public RSAEncrypt(KeyPair keyPair) throws Exception { 20 | this.privateKey = keyPair.getPrivate(); 21 | this.publicKey = keyPair.getPublic(); 22 | } 23 | 24 | public RSAEncrypt(String privateKey, String publicKey) 25 | throws Exception { 26 | byte[] bytes = Base64.getDecoder().decode(privateKey); 27 | PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(bytes); 28 | KeyFactory kf = KeyFactory.getInstance("RSA"); 29 | this.privateKey = kf.generatePrivate(priKeySpec); 30 | 31 | bytes = Base64.getDecoder().decode(publicKey); 32 | X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(bytes); 33 | this.publicKey = kf.generatePublic(pubKeySpec); 34 | } 35 | 36 | @Override 37 | public byte[] encrypt(byte[] src) throws Exception { 38 | Cipher cipher = Cipher.getInstance(CIPHER_TRIPLE_AES); 39 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 40 | return cipher.doFinal(src); 41 | } 42 | 43 | @Override 44 | public byte[] decrypt(byte[] src) throws Exception { 45 | Cipher cipher = Cipher.getInstance(CIPHER_TRIPLE_AES); 46 | cipher.init(Cipher.DECRYPT_MODE, privateKey); 47 | return cipher.doFinal(src); 48 | } 49 | 50 | public static KeyPair createRSAKeyPair() throws NoSuchAlgorithmException { 51 | return createRSAKeyPair("RSA", 512); 52 | } 53 | 54 | public static KeyPair createRSAKeyPair(String algorithm, int length) throws NoSuchAlgorithmException { 55 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm); 56 | // 密钥位数 57 | keyPairGen.initialize(length); 58 | // 密钥对 59 | return keyPairGen.generateKeyPair(); 60 | } 61 | 62 | public static void main(String[] args) throws Exception { 63 | String testStr = "hello world"; 64 | 65 | KeyPair keyPair = RSAEncrypt.createRSAKeyPair(); 66 | // 获取公钥密文 67 | String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); 68 | System.out.println(publicKey); 69 | // 获取私钥密文 70 | String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); 71 | System.out.println(privateKey); 72 | 73 | RSAEncrypt rsaEncrypt = new RSAEncrypt(keyPair); 74 | String enStr = rsaEncrypt.encrypt(testStr, "UTF-8"); 75 | String deStr = rsaEncrypt.decrypt(enStr, "UTF-8"); 76 | System.out.println("加密前:" + testStr); 77 | System.out.println("加密后:" + enStr); 78 | System.out.println("解密后:" + deStr); 79 | 80 | rsaEncrypt = new RSAEncrypt(privateKey, publicKey); 81 | deStr = rsaEncrypt.decrypt(enStr, "UTF-8"); 82 | System.out.println("加密前:" + testStr); 83 | System.out.println("加密后:" + enStr); 84 | System.out.println("解密后:" + deStr); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/exception/SocketRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.exception; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2018/12/30 15:08 6 | */ 7 | public class SocketRuntimeException extends RuntimeException { 8 | private static final long serialVersionUID = 1L; 9 | 10 | public SocketRuntimeException() { 11 | super(); 12 | } 13 | 14 | public SocketRuntimeException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public SocketRuntimeException(String message) { 19 | super(message); 20 | } 21 | 22 | public SocketRuntimeException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/exception/SocketTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.exception; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2018/12/30 15:10 6 | */ 7 | public class SocketTimeoutException extends RuntimeException { 8 | private static final long serialVersionUID = 1L; 9 | 10 | public SocketTimeoutException() { 11 | super(); 12 | } 13 | 14 | public SocketTimeoutException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public SocketTimeoutException(String message) { 19 | super(message); 20 | } 21 | 22 | public SocketTimeoutException(Throwable cause) { 23 | super(cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/future/InvokeFuture.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.future; 2 | 3 | import com.yb.socket.exception.SocketRuntimeException; 4 | import com.yb.socket.exception.SocketTimeoutException; 5 | import io.netty.channel.Channel; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.Semaphore; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.atomic.AtomicBoolean; 14 | 15 | /** 16 | * @author daoshenzzg@163.com 17 | * @date 2018/12/30 15:04 18 | */ 19 | public class InvokeFuture { 20 | private static final Logger logger = LoggerFactory.getLogger(InvokeFuture.class); 21 | 22 | protected Object result; 23 | protected AtomicBoolean done = new AtomicBoolean(false); 24 | protected AtomicBoolean success = new AtomicBoolean(false); 25 | protected Semaphore semaphore = new Semaphore(0); 26 | protected Throwable cause; 27 | protected Channel channel; 28 | protected Object attachment; 29 | protected List listeners = new ArrayList<>(); 30 | 31 | public InvokeFuture() { 32 | } 33 | 34 | public void addListener(InvokeFutureListener listener) { 35 | if (listener == null) { 36 | throw new NullPointerException("listener can not be null."); 37 | } 38 | notifyListener(listener); 39 | listeners.add(listener); 40 | } 41 | 42 | private void notifyListeners() { 43 | if (isDone()) { 44 | for (InvokeFutureListener listener : listeners) { 45 | try { 46 | listener.operationComplete(this); 47 | } catch (Exception ex) { 48 | logger.error("Failed to notify listeners when operation completed.", ex); 49 | } 50 | } 51 | } 52 | } 53 | 54 | private void notifyListener(InvokeFutureListener listener) { 55 | if (listener == null) { 56 | throw new NullPointerException("listener can not be null."); 57 | } 58 | 59 | if (isDone()) { 60 | try { 61 | listener.operationComplete(this); 62 | } catch (Exception ex) { 63 | logger.error("Failed to notify listener when operation completed.", ex); 64 | } 65 | } 66 | } 67 | 68 | public boolean cancel(boolean mayInterruptIfRunning) { 69 | return false; 70 | } 71 | 72 | public boolean isCancelled() { 73 | return false; 74 | } 75 | 76 | public boolean isDone() { 77 | return done.get(); 78 | } 79 | 80 | public Object getResult() throws SocketRuntimeException { 81 | if (!isDone()) { 82 | try { 83 | semaphore.acquire(); 84 | } catch (InterruptedException ex) { 85 | throw new RuntimeException(ex); 86 | } 87 | } 88 | if (cause != null) { 89 | throw new SocketRuntimeException(cause); 90 | } 91 | return this.result; 92 | } 93 | 94 | public void setResult(Object result) { 95 | this.result = result; 96 | done.set(true); 97 | success.set(true); 98 | 99 | semaphore.release(Integer.MAX_VALUE - semaphore.availablePermits()); 100 | notifyListeners(); 101 | } 102 | 103 | public Object getResult(long timeout, TimeUnit unit) { 104 | if (!isDone()) { 105 | try { 106 | if (!semaphore.tryAcquire(timeout, unit)) { 107 | setCause(new SocketTimeoutException("time out.")); 108 | } 109 | } catch (InterruptedException ex) { 110 | throw new SocketTimeoutException(ex); 111 | } 112 | } 113 | if (cause != null) { 114 | throw new SocketRuntimeException(cause); 115 | } 116 | return this.result; 117 | } 118 | 119 | public void setCause(Throwable cause) { 120 | this.cause = cause; 121 | done.set(true); 122 | success.set(false); 123 | semaphore.release(Integer.MAX_VALUE - semaphore.availablePermits()); 124 | notifyListeners(); 125 | } 126 | 127 | public boolean isSuccess() { 128 | return success.get(); 129 | } 130 | 131 | public Throwable getCause() { 132 | return cause; 133 | } 134 | 135 | public Channel getChannel() { 136 | return channel; 137 | } 138 | 139 | public void setChannel(Channel channel) { 140 | this.channel = channel; 141 | } 142 | 143 | public Object getAttachment() { 144 | return attachment; 145 | } 146 | 147 | public void setAttachment(Object attachment) { 148 | this.attachment = attachment; 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/future/InvokeFutureListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.future; 2 | 3 | /** 4 | * InvokeFuture监听器 5 | * 6 | * @author daoshenzzg@163.com 7 | * @date 2018/12/30 15:05 8 | */ 9 | public interface InvokeFutureListener { 10 | /** 11 | * 完成操作 12 | * 13 | * @param future 14 | * @throws Exception 15 | */ 16 | void operationComplete(InvokeFuture future) throws Exception; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/ChannelEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | import com.yb.socket.service.WrappedChannel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | import java.util.EventListener; 7 | 8 | /** 9 | * 通道事件监听器 10 | * 11 | * @author daoshenzzg@163.com 12 | * @date 2018/12/30 16:21 13 | */ 14 | public interface ChannelEventListener extends EventListener { 15 | 16 | /** 17 | * 通道连接 18 | * 19 | * @param ctx 20 | * @param channel 21 | * @return 22 | */ 23 | EventBehavior channelActive(ChannelHandlerContext ctx, WrappedChannel channel); 24 | 25 | /** 26 | * 通道关闭 27 | * 28 | * @param ctx 29 | * @param channel 30 | * @return 31 | */ 32 | EventBehavior channelInactive(ChannelHandlerContext ctx, WrappedChannel channel); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/DefaultExceptionListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | import com.yb.socket.service.WrappedChannel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * @author daoshenzzg@163.com 10 | * @date 2018/12/30 16:19 11 | */ 12 | public class DefaultExceptionListener implements ExceptionEventListener { 13 | private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionListener.class); 14 | 15 | @Override 16 | public EventBehavior exceptionCaught(ChannelHandlerContext ctx, WrappedChannel channel, Throwable cause) { 17 | if (cause != null && channel.remoteAddress() != null) { 18 | logger.warn("Exception caught on channel {}, caused by: '{}'.", channel.id().asShortText(), cause); 19 | } 20 | 21 | return EventBehavior.CONTINUE; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/DefaultMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | import com.yb.socket.future.InvokeFuture; 4 | import com.yb.socket.pojo.Request; 5 | import com.yb.socket.pojo.Response; 6 | import com.yb.socket.service.WrappedChannel; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * @author daoshenzzg@163.com 13 | * @date 2018/12/30 16:18 14 | */ 15 | public class DefaultMessageEventListener implements MessageEventListener { 16 | private static final Logger logger = LoggerFactory.getLogger(DefaultMessageEventListener.class); 17 | 18 | @Override 19 | public EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg) { 20 | if (logger.isDebugEnabled()) { 21 | logger.debug("Message received on channel '{}'.", channel.id().asShortText()); 22 | } 23 | 24 | if (msg != null) { 25 | if (msg instanceof Request) { 26 | Request request = (Request) msg; 27 | if (request.getMessage() != null) { 28 | // 处理请求消息 29 | processRequest(ctx, channel, request); 30 | } 31 | } else if (msg instanceof Response) { 32 | Response response = (Response) msg; 33 | // 处理反馈消息 34 | processResponse(ctx, response, channel); 35 | } 36 | } 37 | return EventBehavior.CONTINUE; 38 | } 39 | 40 | private void processRequest(ChannelHandlerContext ctx, WrappedChannel channel, Request request) { 41 | } 42 | 43 | private void processResponse(ChannelHandlerContext ctx, Response response, WrappedChannel channel) { 44 | // Future方式 45 | InvokeFuture future = channel.getFutures().remove(response.getSequence()); 46 | if (future != null) { 47 | if (response.getCause() != null) { 48 | future.setCause(response.getCause()); 49 | } else { 50 | future.setResult(response); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/DefaultMqttMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | import com.yb.socket.service.WrappedChannel; 4 | import com.yb.socket.service.server.Server; 5 | import com.yb.socket.service.server.ServerContext; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.mqtt.*; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * @author daoshenzzg@163.com 13 | * @date 2019/1/4 11:17 14 | */ 15 | public class DefaultMqttMessageEventListener implements MessageEventListener { 16 | private static final Logger logger = LoggerFactory.getLogger(DefaultMqttMessageEventListener.class); 17 | 18 | @Override 19 | public EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg) { 20 | if (msg instanceof MqttMessage) { 21 | MqttMessage message = (MqttMessage) msg; 22 | MqttMessageType messageType = message.fixedHeader().messageType(); 23 | switch (messageType) { 24 | case CONNECT: 25 | this.connect(channel, (MqttConnectMessage) message); 26 | break; 27 | case PUBLISH: 28 | this.publish(channel, (MqttPublishMessage) message); 29 | break; 30 | case SUBSCRIBE: 31 | this.subscribe(channel, (MqttSubscribeMessage) message); 32 | break; 33 | case UNSUBSCRIBE: 34 | this.unSubscribe(channel, (MqttUnsubscribeMessage) message); 35 | break; 36 | case PINGREQ: 37 | this.pingReq(channel, message); 38 | break; 39 | case DISCONNECT: 40 | this.disConnect(channel, message); 41 | break; 42 | default: 43 | if (logger.isDebugEnabled()) { 44 | logger.debug("Nonsupport server message type of '{}'.", messageType); 45 | } 46 | break; 47 | } 48 | } 49 | return EventBehavior.CONTINUE; 50 | } 51 | 52 | public void connect(WrappedChannel channel, MqttConnectMessage msg) { 53 | if (logger.isDebugEnabled()) { 54 | String clientId = msg.payload().clientIdentifier(); 55 | logger.debug("MQTT CONNECT received on channel '{}', clientId is '{}'.", 56 | channel.id().asShortText(), clientId); 57 | } 58 | 59 | MqttConnAckMessage okResp = (MqttConnAckMessage) MqttMessageFactory.newMessage(new MqttFixedHeader( 60 | MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 61 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_ACCEPTED, true), null); 62 | channel.writeAndFlush(okResp); 63 | } 64 | 65 | public void pingReq(WrappedChannel channel, MqttMessage msg) { 66 | if (logger.isDebugEnabled()) { 67 | logger.debug("MQTT pingReq received."); 68 | } 69 | 70 | Server server = ServerContext.getContext().getServer(); 71 | if(server != null) { 72 | server.getCountInfo().getHeartbeatNum().incrementAndGet(); 73 | } 74 | 75 | MqttMessage pingResp = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGRESP, false, 76 | MqttQoS.AT_MOST_ONCE, false, 0)); 77 | channel.writeAndFlush(pingResp); 78 | } 79 | 80 | public void disConnect(WrappedChannel channel, MqttMessage msg) { 81 | if (channel.isActive()) { 82 | channel.close(); 83 | 84 | if (logger.isDebugEnabled()) { 85 | logger.debug("MQTT channel '{}' was closed.", channel.id().asShortText()); 86 | } 87 | } 88 | } 89 | 90 | public void publish(WrappedChannel channel, MqttPublishMessage msg) { 91 | } 92 | 93 | public void subscribe(WrappedChannel channel, MqttSubscribeMessage msg) { 94 | MqttSubAckMessage subAckMessage = (MqttSubAckMessage) MqttMessageFactory.newMessage( 95 | new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 96 | MqttMessageIdVariableHeader.from(msg.variableHeader().messageId()), 97 | new MqttSubAckPayload(0)); 98 | channel.writeAndFlush(subAckMessage); 99 | } 100 | 101 | public void unSubscribe(WrappedChannel channel, MqttUnsubscribeMessage msg) { 102 | MqttUnsubAckMessage unSubAckMessage = (MqttUnsubAckMessage) MqttMessageFactory.newMessage( 103 | new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 104 | MqttMessageIdVariableHeader.from(msg.variableHeader().messageId()), null); 105 | channel.writeAndFlush(unSubAckMessage); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/EventBehavior.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2018/12/30 16:15 6 | */ 7 | public enum EventBehavior { 8 | /** 9 | * 继续消息传递 10 | */ 11 | CONTINUE, 12 | /** 13 | * 停止消息传递 14 | */ 15 | BREAK 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/ExceptionEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | import com.yb.socket.listener.EventBehavior; 4 | import com.yb.socket.service.WrappedChannel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | 7 | import java.util.EventListener; 8 | 9 | /** 10 | * 异常监听器 11 | * 12 | * @author daoshenzzg@163.com 13 | * @date 2018/12/30 16:20 14 | */ 15 | public interface ExceptionEventListener extends EventListener { 16 | /** 17 | * 异常捕获 18 | * 19 | * @param ctx 20 | * @param channel 21 | * @param cause 22 | * @return 23 | */ 24 | EventBehavior exceptionCaught(ChannelHandlerContext ctx, WrappedChannel channel, Throwable cause); 25 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/listener/MessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.listener; 2 | 3 | import com.yb.socket.service.WrappedChannel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | import java.util.EventListener; 7 | 8 | /** 9 | * 消息监听器 10 | * 11 | * @author daoshenzzg@163.com 12 | * @date 2018/12/30 16:15 13 | */ 14 | public interface MessageEventListener extends EventListener { 15 | /** 16 | * 接收消息 17 | * 18 | * @param ctx 19 | * @param channel 20 | * @param msg 21 | * @return 22 | */ 23 | EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author daoshenzzg@163.com 7 | * @date 2018/12/30 15:18 8 | */ 9 | public class BaseMessage implements Serializable { 10 | private static final long serialVersionUID = 1L; 11 | 12 | protected int sequence; 13 | 14 | public int getSequence() { 15 | return sequence; 16 | } 17 | public void setSequence(int sequence) { 18 | this.sequence = sequence; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/Heartbeat.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 心跳消息 7 | * 8 | * @author daoshenzzg@163.com 9 | * @date 2018/12/30 15:19 10 | */ 11 | public class Heartbeat extends BaseMessage implements Serializable { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | public static final byte[] BYTES = new byte[0]; 16 | 17 | private static Heartbeat instance = new Heartbeat(); 18 | 19 | public static Heartbeat getSingleton() { 20 | return instance; 21 | } 22 | 23 | private Heartbeat() { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/MqttRequest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo; 2 | 3 | import io.netty.handler.codec.mqtt.MqttQoS; 4 | 5 | /** 6 | * Mqtt 请求消息 7 | * 8 | * @author daoshenzzg@163.com 9 | * @date 2019/1/7 09:14 10 | */ 11 | public class MqttRequest { 12 | private boolean mutable = true; 13 | private byte[] payload; 14 | private MqttQoS qos = MqttQoS.AT_MOST_ONCE; 15 | private boolean retained = false; 16 | private boolean dup = false; 17 | private int messageId; 18 | 19 | public MqttRequest() { 20 | this.setPayload(new byte[0]); 21 | } 22 | 23 | public MqttRequest(byte[] payload) { 24 | this.setPayload(payload); 25 | } 26 | 27 | public byte[] getPayload() { 28 | return this.payload; 29 | } 30 | 31 | public void clearPayload() { 32 | this.checkMutable(); 33 | this.payload = new byte[0]; 34 | } 35 | 36 | public void setPayload(byte[] payload) { 37 | this.checkMutable(); 38 | if (payload == null) { 39 | throw new NullPointerException(); 40 | } else { 41 | this.payload = payload; 42 | } 43 | } 44 | 45 | public boolean isRetained() { 46 | return this.retained; 47 | } 48 | 49 | public void setRetained(boolean retained) { 50 | this.checkMutable(); 51 | this.retained = retained; 52 | } 53 | 54 | public MqttQoS getQos() { 55 | return qos; 56 | } 57 | 58 | public void setQos(MqttQoS qos) { 59 | this.qos = qos; 60 | } 61 | 62 | public boolean isMutable() { 63 | return mutable; 64 | } 65 | 66 | public void setMutable(boolean mutable) { 67 | this.mutable = mutable; 68 | } 69 | 70 | protected void checkMutable() throws IllegalStateException { 71 | if (!this.mutable) { 72 | throw new IllegalStateException(); 73 | } 74 | } 75 | 76 | public boolean isDup() { 77 | return dup; 78 | } 79 | 80 | public void setDup(boolean dup) { 81 | this.dup = dup; 82 | } 83 | 84 | public int getMessageId() { 85 | return messageId; 86 | } 87 | 88 | public void setMessageId(int messageId) { 89 | this.messageId = messageId; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return new String(this.payload); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/Request.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 请求消息 7 | * 8 | * @author daoshenzzg@163.com 9 | * @date 2018/12/30 15:20 10 | */ 11 | public class Request extends BaseMessage implements Serializable { 12 | private static final long serialVersionUID = 1L; 13 | 14 | private Object message; 15 | 16 | public Object getMessage() { 17 | return message; 18 | } 19 | 20 | public void setMessage(Object message) { 21 | this.message = message; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | StringBuilder sb = new StringBuilder(); 27 | sb.append("Request [sequence="); 28 | sb.append(sequence); 29 | sb.append(", message="); 30 | sb.append(message); 31 | sb.append("]"); 32 | return sb.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/Response.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 响应消息 7 | * 8 | * @author daoshenzzg@163.com 9 | * @date 2018/12/30 15:21 10 | */ 11 | public class Response extends BaseMessage implements Serializable { 12 | private static final long serialVersionUID = 1L; 13 | 14 | public static final int EXCEPTION = -1; 15 | 16 | public static final int SUCCESS = 0; 17 | 18 | private int code; 19 | private Object result; 20 | private Throwable cause; 21 | 22 | public Throwable getCause() { 23 | return cause; 24 | } 25 | 26 | public void setCause(Throwable cause) { 27 | this.cause = cause; 28 | } 29 | 30 | public int getCode() { 31 | return code; 32 | } 33 | 34 | public void setCode(int code) { 35 | this.code = code; 36 | } 37 | 38 | public Object getResult() { 39 | return result; 40 | } 41 | 42 | public void setResult(Object result) { 43 | this.result = result; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | StringBuilder sb = new StringBuilder(); 49 | sb.append("Response [sequence="); 50 | sb.append(sequence); 51 | sb.append(", code="); 52 | sb.append(code); 53 | sb.append(", result="); 54 | sb.append(result); 55 | sb.append("]"); 56 | return sb.toString(); 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/protocol/ProtocolRequest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo.protocol; 2 | 3 | import com.yb.socket.pojo.Request; 4 | 5 | import java.io.Serializable; 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | 9 | /** 10 | * 请求消息 11 | * 12 | * @author daoshenzzg@163.com 13 | * @date 2022-04-29 15:14 14 | */ 15 | public class ProtocolRequest extends Request implements Serializable { 16 | private static final long serialVersionUID = 1L; 17 | 18 | private byte version; 19 | private byte messageType; 20 | private int bodyLength; 21 | private byte[] reserved = new byte[10]; 22 | private Map body; 23 | 24 | public byte getVersion() { 25 | return version; 26 | } 27 | 28 | public void setVersion(byte version) { 29 | this.version = version; 30 | } 31 | 32 | public byte getMessageType() { 33 | return messageType; 34 | } 35 | 36 | public void setMessageType(byte messageType) { 37 | this.messageType = messageType; 38 | } 39 | 40 | public int getBodyLength() { 41 | return bodyLength; 42 | } 43 | 44 | public void setBodyLength(int bodyLength) { 45 | this.bodyLength = bodyLength; 46 | } 47 | 48 | public byte[] getReserved() { 49 | return reserved; 50 | } 51 | 52 | public void setReserved(byte[] reserved) { 53 | this.reserved = reserved; 54 | } 55 | 56 | public Map getBody() { 57 | return body; 58 | } 59 | 60 | public void setBody(Map body) { 61 | this.body = body; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "ProtocolRequest{" + 67 | "version=" + version + 68 | ", messageType=" + messageType + 69 | ", bodyLength=" + bodyLength + 70 | ", reserved=" + Arrays.toString(reserved) + 71 | ", body=" + body + 72 | '}'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/protocol/ProtocolResponse.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.pojo.protocol; 2 | 3 | import com.yb.socket.pojo.Response; 4 | 5 | import java.io.Serializable; 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | 9 | /** 10 | * 响应消息 11 | * 12 | * @author daoshenzzg@163.com 13 | * @date 2022-04-29 15:21 14 | */ 15 | public class ProtocolResponse extends Response implements Serializable { 16 | private static final long serialVersionUID = 1L; 17 | 18 | private byte version; 19 | private byte messageType; 20 | private int bodyLength; 21 | private byte[] reserved = new byte[10]; 22 | private Map body; 23 | 24 | public ProtocolResponse() { 25 | } 26 | 27 | public ProtocolResponse(ProtocolRequest request) { 28 | this.setVersion(request.getVersion()); 29 | this.setSequence(request.getSequence()); 30 | this.setReserved(reserved); 31 | } 32 | 33 | public byte getVersion() { 34 | return version; 35 | } 36 | 37 | public void setVersion(byte version) { 38 | this.version = version; 39 | } 40 | 41 | public byte getMessageType() { 42 | return messageType; 43 | } 44 | 45 | public void setMessageType(byte messageType) { 46 | this.messageType = messageType; 47 | } 48 | 49 | public int getBodyLength() { 50 | return bodyLength; 51 | } 52 | 53 | public void setBodyLength(int bodyLength) { 54 | this.bodyLength = bodyLength; 55 | } 56 | 57 | public byte[] getReserved() { 58 | return reserved; 59 | } 60 | 61 | public void setReserved(byte[] reserved) { 62 | this.reserved = reserved; 63 | } 64 | 65 | public Map getBody() { 66 | return body; 67 | } 68 | 69 | public void setBody(Map body) { 70 | this.body = body; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "ProtocolResponse{" + 76 | "version=" + version + 77 | ", messageType=" + messageType + 78 | ", bodyLength=" + bodyLength + 79 | ", reserved=" + Arrays.toString(reserved) + 80 | ", body=" + body + 81 | '}'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/pojo/protocol/README.md: -------------------------------------------------------------------------------- 1 | # 自定义变长消息协议 2 | Header定义 3 | Socket变长协议的Header总长度为20 Bytes,具体含义参见下表: 4 | 5 | | 序号 | 字段 | 长度 | 说明 | 6 | | --- | --- | --- | --- | 7 | | 1 | version | 1 Byte | 版本号。 | 8 | | 2 | messageType | 1 Byte | 消息类型。0-心跳;1-请求;2-响应。 | 9 | | 3 | bodyLength | 4 Bytes | Body长度。 | 10 | | 4 | seqID | 4 Bytes | 消息ID(SequenceID,由客户端传入,服务端使用客户端传入的值进行响应,服务端也可以指定值)。 | 11 | | 5 | reserved | 10 Bytes | 保留。 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/ChannelHandlerFunc.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | 5 | /** 6 | * @author daoshenzzg@163.com 7 | * @date 2022-03-10 14:52 8 | */ 9 | @FunctionalInterface 10 | public interface ChannelHandlerFunc { 11 | /** 12 | * 新建一个实例 13 | * 14 | * @return 15 | */ 16 | ChannelHandler newInstance(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/EventDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service; 2 | 3 | import com.yb.socket.listener.EventBehavior; 4 | import com.yb.socket.listener.ChannelEventListener; 5 | import com.yb.socket.listener.ExceptionEventListener; 6 | import com.yb.socket.listener.MessageEventListener; 7 | import io.netty.buffer.ByteBufHolder; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.EventListener; 13 | 14 | /** 15 | * 事件分发器(分发消息、通道、异常事件给相应的监听器,允许使用线程池模型以使worker线程迅速返回) 16 | * 17 | * @author daoshenzzg@163.com 18 | * @date 2018/12/30 16:26 19 | */ 20 | public class EventDispatcher { 21 | private static final Logger logger = LoggerFactory.getLogger(EventDispatcher.class); 22 | 23 | private Service service; 24 | 25 | public EventDispatcher(Service service) { 26 | if (service == null) { 27 | throw new IllegalArgumentException("service is null."); 28 | } 29 | this.service = service; 30 | } 31 | 32 | private void doMessageEvent(final ChannelHandlerContext ctx, final WrappedChannel channel, final Object msg) { 33 | try { 34 | for (EventListener listener : service.getEventListeners()) { 35 | if (listener instanceof MessageEventListener) { 36 | EventBehavior eventBehavior = ((MessageEventListener) listener).channelRead(ctx, channel, msg); 37 | if (EventBehavior.BREAK.equals(eventBehavior)) { 38 | break; 39 | } 40 | } 41 | } 42 | } catch (Exception ex) { 43 | dispatchExceptionCaught(ctx, channel, ex); 44 | } finally { 45 | } 46 | } 47 | 48 | private void doChannelEvent(final ChannelHandlerContext ctx, final WrappedChannel channel) { 49 | final boolean isConnected = (channel != null && channel.isActive()); 50 | try { 51 | for (EventListener listener : service.getEventListeners()) { 52 | if (listener instanceof ChannelEventListener) { 53 | ChannelEventListener channelEventListener = (ChannelEventListener) listener; 54 | 55 | EventBehavior eventBehavior; 56 | if (isConnected) { 57 | eventBehavior = channelEventListener.channelActive(ctx, channel); 58 | } else { 59 | eventBehavior = channelEventListener.channelInactive(ctx, channel); 60 | } 61 | 62 | if (EventBehavior.BREAK.equals(eventBehavior)) { 63 | break; 64 | } 65 | } 66 | } 67 | } catch (Exception ex) { 68 | dispatchExceptionCaught(ctx, channel, ex); 69 | } 70 | } 71 | 72 | private void doExceptionEvent(final ChannelHandlerContext ctx, final WrappedChannel channel, final Throwable cause) { 73 | try { 74 | for (EventListener listener : service.getEventListeners()) { 75 | if (listener instanceof ExceptionEventListener) { 76 | ExceptionEventListener exEventListener = (ExceptionEventListener) listener; 77 | 78 | EventBehavior eventBehavior = exEventListener.exceptionCaught(ctx, channel, cause); 79 | if (EventBehavior.BREAK.equals(eventBehavior)) { 80 | break; 81 | } 82 | } 83 | } 84 | } catch (Exception ex) { 85 | logger.error("Failed to dispatch exception event on channel '{}'.", channel.id().asShortText(), ex); 86 | } 87 | } 88 | 89 | public void dispatchMessageEvent(final ChannelHandlerContext ctx, final WrappedChannel channel, final Object msg) { 90 | if (service.isOpenExecutor()) { 91 | if (msg instanceof ByteBufHolder) { 92 | ((ByteBufHolder) msg).retain(); 93 | } 94 | service.messageExecutor.execute(new Runnable() { 95 | @Override 96 | public void run() { 97 | doMessageEvent(ctx, channel, msg); 98 | } 99 | }); 100 | } else { 101 | doMessageEvent(ctx, channel, msg); 102 | } 103 | } 104 | 105 | public void dispatchChannelEvent(final ChannelHandlerContext ctx, final WrappedChannel channel) { 106 | if (service.isOpenExecutor()) { 107 | service.channelExecutor.execute(new Runnable() { 108 | @Override 109 | public void run() { 110 | doChannelEvent(ctx, channel); 111 | } 112 | }); 113 | } else { 114 | doChannelEvent(ctx, channel); 115 | } 116 | } 117 | 118 | public void dispatchExceptionCaught(final ChannelHandlerContext ctx, final WrappedChannel channel, final Throwable cause) { 119 | if (service.isOpenExecutor()) { 120 | service.exceptionExecutor.execute(new Runnable() { 121 | @Override 122 | public void run() { 123 | doExceptionEvent(ctx, channel, cause); 124 | } 125 | }); 126 | } else { 127 | doExceptionEvent(ctx, channel, cause); 128 | } 129 | } 130 | 131 | public Service getService() { 132 | return service; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/Service.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.handler.timeout.IdleStateHandler; 6 | import org.apache.commons.lang3.concurrent.BasicThreadFactory; 7 | 8 | import java.util.ArrayList; 9 | import java.util.EventListener; 10 | import java.util.LinkedHashMap; 11 | import java.util.List; 12 | import java.util.concurrent.*; 13 | 14 | /** 15 | * @author daoshenzzg@163.com 16 | * @date 2018/12/30 13:55 17 | */ 18 | public abstract class Service { 19 | /** 20 | * socket类型 21 | */ 22 | protected SocketType socketType = SocketType.NORMAL; 23 | /** 24 | * 绑定端口,默认为8000 25 | */ 26 | protected int port = 8000; 27 | /** 28 | * 多IP情况下绑定指定的IP(可以不设置) 29 | */ 30 | protected String ip; 31 | /** 32 | * 是否启用keepAlive 33 | */ 34 | protected boolean keepAlive = true; 35 | /** 36 | * 是否启用tcpNoDelay 37 | */ 38 | protected boolean tcpNoDelay = true; 39 | /** 40 | * 工作线程池大小 41 | */ 42 | protected int workerCount; 43 | 44 | /** 45 | * 是否开户业务处理线程池 46 | */ 47 | protected boolean openExecutor = false; 48 | /** 49 | * 消息事件业务处理线程池 50 | */ 51 | protected ExecutorService messageExecutor; 52 | /** 53 | * 通道事件业务处理线程池 54 | */ 55 | protected ExecutorService channelExecutor; 56 | /** 57 | * 异常事件业务处理线程池 58 | */ 59 | protected ExecutorService exceptionExecutor; 60 | /** 61 | * 消息事件业务处理线程池最小保持的线程数 62 | */ 63 | protected int corePoolSize = 10; 64 | /** 65 | * 消息事件业务处理线程池最大线程数 66 | */ 67 | protected int maximumPoolSize = 150; 68 | /** 69 | * 消息事件业务处理线程池队列最大值 70 | */ 71 | protected int queueCapacity = 1000000; 72 | /** 73 | * 设置是否心跳检查 74 | */ 75 | protected boolean checkHeartbeat = true; 76 | /** 77 | * 心跳检查时的读空闲时间 78 | */ 79 | protected int readerIdleTimeSeconds = 30; 80 | /** 81 | * 心跳检查时的写空闲时间 82 | */ 83 | protected int writerIdleTimeSeconds = 10; 84 | /** 85 | * 心跳检查时的读写空闲时间 86 | */ 87 | protected int allIdleTimeSeconds = 0; 88 | 89 | protected ChannelInboundHandlerAdapter heartbeatHandler; 90 | 91 | protected LinkedHashMap handlers = new LinkedHashMap<>(); 92 | protected List eventListeners = new ArrayList<>(); 93 | 94 | protected EventDispatcher eventDispatcher; 95 | 96 | public Service() { 97 | // 默认工作线程数 98 | this.workerCount = Runtime.getRuntime().availableProcessors() + 1; 99 | //添加JVM关闭时的勾子 100 | Runtime.getRuntime().addShutdownHook(new ShutdownHook(this)); 101 | } 102 | 103 | protected void init() { 104 | if (openExecutor) { 105 | messageExecutor = new ThreadPoolExecutor( 106 | this.corePoolSize, 107 | this.maximumPoolSize, 108 | 60L, 109 | TimeUnit.SECONDS, 110 | new LinkedBlockingQueue<>(this.queueCapacity), 111 | new BasicThreadFactory.Builder().namingPattern("MessageEventProcessor-%d").daemon(true).build(), 112 | new ThreadPoolExecutor.AbortPolicy()); 113 | 114 | exceptionExecutor = Executors.newCachedThreadPool( 115 | new BasicThreadFactory.Builder().namingPattern("ExceptionEventProcessor-%d").daemon(true).build()); 116 | 117 | channelExecutor = Executors.newCachedThreadPool( 118 | new BasicThreadFactory.Builder().namingPattern("ChannelEventProcessor-%d").daemon(true).build()); 119 | } 120 | } 121 | 122 | /** 123 | * shutdown 124 | */ 125 | public abstract void shutdown(); 126 | 127 | public void setExecutor(ExecutorService executor) { 128 | if (executor == null) { 129 | throw new NullPointerException("executor is null."); 130 | } 131 | ExecutorService preExecutor = this.messageExecutor; 132 | this.messageExecutor = executor; 133 | 134 | List tasks = preExecutor.shutdownNow(); 135 | for (Runnable task : tasks) { 136 | this.messageExecutor.execute(task); 137 | } 138 | } 139 | 140 | public int getExecutorActiveCount() { 141 | if (messageExecutor instanceof ThreadPoolExecutor) { 142 | return ((ThreadPoolExecutor) messageExecutor).getActiveCount(); 143 | } 144 | return -1; 145 | } 146 | 147 | public long getExecutorCompletedTaskCount() { 148 | if (messageExecutor instanceof ThreadPoolExecutor) { 149 | return ((ThreadPoolExecutor) messageExecutor).getCompletedTaskCount(); 150 | } 151 | return -1; 152 | } 153 | 154 | public int getExecutorLargestPoolSize() { 155 | if (messageExecutor instanceof ThreadPoolExecutor) { 156 | return ((ThreadPoolExecutor) messageExecutor).getLargestPoolSize(); 157 | } 158 | return -1; 159 | } 160 | 161 | public int getExecutorPoolSize() { 162 | if (messageExecutor instanceof ThreadPoolExecutor) { 163 | return ((ThreadPoolExecutor) messageExecutor).getPoolSize(); 164 | } 165 | return -1; 166 | } 167 | 168 | public long getExecutorTaskCount() { 169 | if (messageExecutor instanceof ThreadPoolExecutor) { 170 | return ((ThreadPoolExecutor) messageExecutor).getTaskCount(); 171 | } 172 | return -1; 173 | } 174 | 175 | public int getExecutorQueueSize() { 176 | if (messageExecutor instanceof ThreadPoolExecutor) { 177 | return ((ThreadPoolExecutor) messageExecutor).getQueue().size(); 178 | } 179 | return -1; 180 | } 181 | 182 | public void addEventListener(EventListener listener) { 183 | this.eventListeners.add(listener); 184 | } 185 | 186 | public void addChannelHandler(String key, ChannelHandlerFunc handler) { 187 | this.handlers.put(key, handler); 188 | } 189 | 190 | public LinkedHashMap getHandlers() { 191 | return handlers; 192 | } 193 | 194 | public void setHandlers(LinkedHashMap handlers) { 195 | this.handlers = handlers; 196 | } 197 | 198 | public List getEventListeners() { 199 | return eventListeners; 200 | } 201 | 202 | public void setListeners(List listeners) { 203 | if (listeners == null) { 204 | listeners = new ArrayList<>(); 205 | } 206 | eventListeners = listeners; 207 | } 208 | 209 | public SocketType getSocketType() { 210 | return socketType; 211 | } 212 | 213 | public void setSocketType(SocketType socketType) { 214 | this.socketType = socketType; 215 | } 216 | 217 | public int getPort() { 218 | return port; 219 | } 220 | 221 | public void setPort(int port) { 222 | this.port = port; 223 | } 224 | 225 | public String getIp() { 226 | return ip; 227 | } 228 | 229 | public void setIp(String ip) { 230 | this.ip = ip; 231 | } 232 | 233 | public boolean isKeepAlive() { 234 | return keepAlive; 235 | } 236 | 237 | public void setKeepAlive(boolean keepAlive) { 238 | this.keepAlive = keepAlive; 239 | } 240 | 241 | public boolean isTcpNoDelay() { 242 | return tcpNoDelay; 243 | } 244 | 245 | public void setTcpNoDelay(boolean tcpNoDelay) { 246 | this.tcpNoDelay = tcpNoDelay; 247 | } 248 | 249 | public int getWorkerCount() { 250 | return workerCount; 251 | } 252 | 253 | public void setWorkerCount(int workerCount) { 254 | this.workerCount = workerCount; 255 | } 256 | 257 | public boolean isOpenExecutor() { 258 | return openExecutor; 259 | } 260 | 261 | public void setOpenExecutor(boolean openExecutor) { 262 | this.openExecutor = openExecutor; 263 | } 264 | 265 | public int getCorePoolSize() { 266 | return corePoolSize; 267 | } 268 | 269 | public void setCorePoolSize(int corePoolSize) { 270 | this.corePoolSize = corePoolSize; 271 | } 272 | 273 | public int getMaximumPoolSize() { 274 | return maximumPoolSize; 275 | } 276 | 277 | public void setMaximumPoolSize(int maximumPoolSize) { 278 | this.maximumPoolSize = maximumPoolSize; 279 | } 280 | 281 | public int getQueueCapacity() { 282 | return queueCapacity; 283 | } 284 | 285 | public void setQueueCapacity(int queueCapacity) { 286 | this.queueCapacity = queueCapacity; 287 | } 288 | 289 | public boolean isCheckHeartbeat() { 290 | return checkHeartbeat; 291 | } 292 | 293 | public void setCheckHeartbeat(boolean checkHeartbeat) { 294 | this.checkHeartbeat = checkHeartbeat; 295 | } 296 | 297 | public int getReaderIdleTimeSeconds() { 298 | return readerIdleTimeSeconds; 299 | } 300 | 301 | public void setReaderIdleTimeSeconds(int readerIdleTimeSeconds) { 302 | this.readerIdleTimeSeconds = readerIdleTimeSeconds; 303 | } 304 | 305 | public int getWriterIdleTimeSeconds() { 306 | return writerIdleTimeSeconds; 307 | } 308 | 309 | public void setWriterIdleTimeSeconds(int writerIdleTimeSeconds) { 310 | this.writerIdleTimeSeconds = writerIdleTimeSeconds; 311 | } 312 | 313 | public int getAllIdleTimeSeconds() { 314 | return allIdleTimeSeconds; 315 | } 316 | 317 | public void setAllIdleTimeSeconds(int allIdleTimeSeconds) { 318 | this.allIdleTimeSeconds = allIdleTimeSeconds; 319 | } 320 | 321 | class ShutdownHook extends Thread { 322 | private Service service; 323 | 324 | public ShutdownHook(Service service) { 325 | this.service = service; 326 | } 327 | 328 | @Override 329 | public void run() { 330 | service.shutdown(); 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/SocketType.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2018/12/30 13:47 6 | */ 7 | public enum SocketType { 8 | /** 9 | * 普通socket 10 | */ 11 | NORMAL, 12 | /** 13 | * MQTT 14 | */ 15 | MQTT, 16 | /** 17 | * MQTT web socket 18 | */ 19 | MQTT_WS; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/WrappedChannel.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service; 2 | 3 | import com.yb.socket.exception.SocketRuntimeException; 4 | import com.yb.socket.future.InvokeFuture; 5 | import com.yb.socket.pojo.Heartbeat; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.pojo.Response; 8 | import com.yb.socket.service.server.Server; 9 | import com.yb.socket.service.server.ServerContext; 10 | import io.netty.buffer.ByteBufAllocator; 11 | import io.netty.channel.*; 12 | import io.netty.util.Attribute; 13 | import io.netty.util.AttributeKey; 14 | 15 | import java.net.SocketAddress; 16 | import java.nio.channels.ClosedChannelException; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @author daoshenzzg@163.com 22 | * @date 2018/12/30 15:15 23 | */ 24 | public class WrappedChannel implements Channel { 25 | private Channel channel; 26 | private ConcurrentHashMap futures = new ConcurrentHashMap<>(); 27 | 28 | private final ChannelFutureListener sendSuccessListener = future -> { 29 | if (future.isSuccess()) { 30 | Server server = ServerContext.getContext().getServer(); 31 | if (server != null) { 32 | server.getCountInfo().getSentNum().incrementAndGet(); 33 | server.getCountInfo().setLastSent(System.currentTimeMillis()); 34 | } 35 | } 36 | }; 37 | 38 | public WrappedChannel(Channel channel) { 39 | if (channel == null) { 40 | throw new IllegalArgumentException("channel can not be null."); 41 | } 42 | this.channel = channel; 43 | } 44 | 45 | public ChannelFuture send(Object message) { 46 | return this.writeAndFlush(message); 47 | } 48 | 49 | public Response sendSync(final Request message, int timeout) { 50 | final InvokeFuture invokeFuture = new InvokeFuture(); 51 | try { 52 | invokeFuture.setChannel(this); 53 | // 存储InvokeFuture 54 | futures.put(message.getSequence(), invokeFuture); 55 | 56 | // 发送Request对象 57 | ChannelFuture channelFuture = writeAndFlush(message); 58 | channelFuture.addListener((ChannelFutureListener) future -> { 59 | if (!future.isSuccess()) { 60 | futures.remove(message.getSequence()); 61 | invokeFuture.setCause(future.cause()); 62 | } 63 | }); 64 | } catch (Throwable ex) { 65 | throw new SocketRuntimeException(ex); 66 | } 67 | 68 | Object retObj; 69 | // 设置超时时间 70 | if (timeout > 0) { 71 | // 等待返回,直到Response返回或超时 72 | retObj = invokeFuture.getResult(timeout, TimeUnit.MILLISECONDS); 73 | } else { 74 | // 一直等待,直到Response返回 75 | retObj = invokeFuture.getResult(); 76 | } 77 | 78 | return (Response) retObj; 79 | } 80 | 81 | public ConcurrentHashMap getFutures() { 82 | return futures; 83 | } 84 | 85 | public Channel getChannel() { 86 | return channel; 87 | } 88 | 89 | @Override 90 | public Attribute attr(AttributeKey arg0) { 91 | return channel.attr(arg0); 92 | } 93 | 94 | @Override 95 | public int compareTo(Channel o) { 96 | return channel.compareTo(o); 97 | } 98 | 99 | @Override 100 | public ByteBufAllocator alloc() { 101 | return channel.alloc(); 102 | } 103 | 104 | @Override 105 | public ChannelFuture bind(SocketAddress arg0) { 106 | return channel.bind(arg0); 107 | } 108 | 109 | @Override 110 | public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) { 111 | return channel.bind(arg0, arg1); 112 | } 113 | 114 | @Override 115 | public ChannelFuture close() { 116 | return this.innerClose(); 117 | } 118 | 119 | @Override 120 | public ChannelFuture close(ChannelPromise arg0) { 121 | return channel.close(arg0); 122 | } 123 | 124 | @Override 125 | public ChannelFuture closeFuture() { 126 | return channel.closeFuture(); 127 | } 128 | 129 | @Override 130 | public ChannelConfig config() { 131 | return channel.config(); 132 | } 133 | 134 | @Override 135 | public ChannelFuture connect(SocketAddress arg0) { 136 | return channel.connect(arg0); 137 | } 138 | 139 | @Override 140 | public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) { 141 | return channel.connect(arg0, arg1); 142 | } 143 | 144 | @Override 145 | public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) { 146 | return channel.connect(arg0, arg1); 147 | } 148 | 149 | @Override 150 | public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) { 151 | return channel.connect(arg0, arg1, arg2); 152 | } 153 | 154 | @Override 155 | public ChannelFuture deregister() { 156 | return channel.deregister(); 157 | } 158 | 159 | @Override 160 | public ChannelFuture deregister(ChannelPromise arg0) { 161 | return channel.deregister(arg0); 162 | } 163 | 164 | @Override 165 | public ChannelFuture disconnect() { 166 | return channel.disconnect(); 167 | } 168 | 169 | @Override 170 | public ChannelFuture disconnect(ChannelPromise arg0) { 171 | return channel.deregister(arg0); 172 | } 173 | 174 | @Override 175 | public EventLoop eventLoop() { 176 | return channel.eventLoop(); 177 | } 178 | 179 | @Override 180 | public Channel flush() { 181 | return channel.flush(); 182 | } 183 | 184 | @Override 185 | public boolean isActive() { 186 | return channel.isActive(); 187 | } 188 | 189 | @Override 190 | public boolean isOpen() { 191 | return channel.isOpen(); 192 | } 193 | 194 | @Override 195 | public boolean isRegistered() { 196 | return channel.isRegistered(); 197 | } 198 | 199 | @Override 200 | public boolean isWritable() { 201 | return channel.isWritable(); 202 | } 203 | 204 | @Override 205 | public SocketAddress localAddress() { 206 | return channel.localAddress(); 207 | } 208 | 209 | @Override 210 | public ChannelMetadata metadata() { 211 | return channel.metadata(); 212 | } 213 | 214 | @Override 215 | public ChannelFuture newFailedFuture(Throwable arg0) { 216 | return channel.newFailedFuture(arg0); 217 | } 218 | 219 | @Override 220 | public ChannelProgressivePromise newProgressivePromise() { 221 | return channel.newProgressivePromise(); 222 | } 223 | 224 | @Override 225 | public ChannelPromise newPromise() { 226 | return channel.newPromise(); 227 | } 228 | 229 | @Override 230 | public ChannelFuture newSucceededFuture() { 231 | return channel.newSucceededFuture(); 232 | } 233 | 234 | @Override 235 | public Channel parent() { 236 | return channel.parent(); 237 | } 238 | 239 | @Override 240 | public ChannelPipeline pipeline() { 241 | return channel.pipeline(); 242 | } 243 | 244 | @Override 245 | public Channel read() { 246 | return channel.read(); 247 | } 248 | 249 | @Override 250 | public SocketAddress remoteAddress() { 251 | return channel.remoteAddress(); 252 | } 253 | 254 | @Override 255 | public Unsafe unsafe() { 256 | return channel.unsafe(); 257 | } 258 | 259 | @Override 260 | public ChannelPromise voidPromise() { 261 | return channel.voidPromise(); 262 | } 263 | 264 | @Override 265 | public ChannelFuture write(Object message) { 266 | return this.write(message, true); 267 | } 268 | 269 | public ChannelFuture write(Object message, boolean isStatistic) { 270 | ChannelFuture future = channel.write(message); 271 | if (isStatistic) { 272 | if (message instanceof Heartbeat) { 273 | } else { 274 | future.addListener(sendSuccessListener); 275 | } 276 | } 277 | return future; 278 | } 279 | 280 | @Override 281 | public ChannelFuture write(Object message, ChannelPromise channelPromise) { 282 | return this.write(message, channelPromise, true); 283 | } 284 | 285 | public ChannelFuture write(Object message, ChannelPromise channelPromise, boolean isStatistic) { 286 | ChannelFuture future = channel.write(message, channelPromise); 287 | if (isStatistic) { 288 | if (message instanceof Heartbeat) { 289 | } else { 290 | future.addListener(sendSuccessListener); 291 | } 292 | } 293 | return future; 294 | } 295 | 296 | @Override 297 | public ChannelFuture writeAndFlush(Object message) { 298 | return this.writeAndFlush(message, true); 299 | } 300 | 301 | public ChannelFuture writeAndFlush(Object message, boolean isStatistic) { 302 | ChannelFuture future = channel.writeAndFlush(message); 303 | if (isStatistic) { 304 | if (message instanceof Heartbeat) { 305 | } else { 306 | future.addListener(sendSuccessListener); 307 | } 308 | } 309 | return future; 310 | } 311 | 312 | @Override 313 | public ChannelFuture writeAndFlush(Object message, ChannelPromise channelPromise) { 314 | return this.writeAndFlush(message, channelPromise, true); 315 | } 316 | 317 | public ChannelFuture writeAndFlush(Object message, ChannelPromise channelPromise, boolean isStatistic) { 318 | ChannelFuture future = channel.writeAndFlush(message, channelPromise); 319 | if (isStatistic) { 320 | if (message instanceof Heartbeat) { 321 | } else { 322 | future.addListener(sendSuccessListener); 323 | } 324 | } 325 | return future; 326 | } 327 | 328 | private ChannelFuture innerClose() { 329 | ChannelFuture channelFuture = channel.close(); 330 | 331 | // cancel所有等待中的InvokeFuture 332 | for (InvokeFuture future : futures.values()) { 333 | if (!future.isDone()) { 334 | future.setCause(new ClosedChannelException()); 335 | } 336 | } 337 | return channelFuture; 338 | } 339 | 340 | @Override 341 | public boolean hasAttr(AttributeKey arg0) { 342 | return channel.hasAttr(arg0); 343 | } 344 | 345 | @Override 346 | public long bytesBeforeUnwritable() { 347 | return channel.bytesBeforeUnwritable(); 348 | } 349 | 350 | @Override 351 | public long bytesBeforeWritable() { 352 | return channel.bytesBeforeUnwritable(); 353 | } 354 | 355 | @Override 356 | public ChannelId id() { 357 | return channel.id(); 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/center/ServerStateReportJob.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.center; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.pojo.Request; 5 | import com.yb.socket.service.client.Client; 6 | import com.yb.socket.service.server.Server; 7 | import com.yb.socket.util.AddressUtil; 8 | import com.yb.socket.util.Sequence; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * @author daoshenzzg@163.com 15 | * @date 2018/12/30 16:51 16 | */ 17 | public class ServerStateReportJob implements Runnable { 18 | private static final Logger logger = LoggerFactory.getLogger(ServerStateReportJob.class); 19 | 20 | private static final long DEFAULT_PERIOD_IN_MILLIS = 5000L; 21 | 22 | private long periodInMillis = DEFAULT_PERIOD_IN_MILLIS; 23 | 24 | 25 | private Server server; 26 | private Client client; 27 | 28 | public ServerStateReportJob(Server server, Client client) { 29 | this.server = server; 30 | this.client = client; 31 | } 32 | 33 | @Override 34 | public void run() { 35 | while (true) { 36 | try { 37 | if (server.hasRegisteredToCenter()) { 38 | if (!channelIsValid(client)) { 39 | logger.warn("Server '{}' currently has no valid channel to register center '{}' to report " + "state, just try later.", 40 | server.getServiceName(), client.getCurServer()); 41 | } else { 42 | // 向center汇报当前server状态。 43 | Request request = new Request(); 44 | request.setSequence(Sequence.getInstance().addAndGet("CENTER_CLIENT")); 45 | 46 | JSONObject json = new JSONObject(); 47 | json.put("action", "updateConnects"); 48 | json.put("service", server.getServiceName()); 49 | json.put("ip", StringUtils.isEmpty(server.getIp()) ? AddressUtil.getLocalIp() : server.getIp()); 50 | json.put("port", server.getPort()); 51 | json.put("connects", server.getChannels().size()); 52 | request.setMessage(json.toString()); 53 | 54 | client.send(request); 55 | 56 | logger.info("Server '{}' reported state to register center '{}' using sequence '{}'.", 57 | server.getServiceName(), client.getCurServer(), request.getSequence()); 58 | } 59 | } else { 60 | logger.warn("Server '{}' has not registered to center, no need to report server state now, just " + "try later.", server.getServiceName()); 61 | } 62 | } catch (Throwable ex) { 63 | logger.error("Server '{}' failed to report server state to register center.", server.getServiceName(), ex); 64 | } 65 | 66 | try { 67 | Thread.sleep(periodInMillis); 68 | } catch (InterruptedException ex) { 69 | logger.warn("Server state report job received InterruptedException when sleeping."); 70 | } 71 | } 72 | } 73 | 74 | private boolean channelIsValid(Client client) { 75 | if (client == null) { 76 | return false; 77 | } 78 | 79 | if (client.getChannel() == null || !client.getChannel().isActive()) { 80 | return false; 81 | } 82 | 83 | return true; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/client/BaseClient.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.client; 2 | 3 | import com.yb.socket.exception.SocketRuntimeException; 4 | import com.yb.socket.exception.SocketTimeoutException; 5 | import com.yb.socket.listener.DefaultMessageEventListener; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.pojo.Response; 8 | import com.yb.socket.service.*; 9 | import io.netty.bootstrap.Bootstrap; 10 | import io.netty.channel.*; 11 | import io.netty.channel.nio.NioEventLoopGroup; 12 | import io.netty.channel.socket.SocketChannel; 13 | import io.netty.channel.socket.nio.NioSocketChannel; 14 | import io.netty.handler.codec.http.HttpObjectAggregator; 15 | import io.netty.handler.codec.http.HttpRequestDecoder; 16 | import io.netty.handler.codec.http.HttpResponseEncoder; 17 | import io.netty.handler.timeout.IdleStateHandler; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.net.SocketAddress; 22 | import java.util.LinkedHashMap; 23 | import java.util.concurrent.Semaphore; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * @author daoshenzzg@163.com 28 | * @date 2018/12/30 16:23 29 | */ 30 | public class BaseClient extends Service { 31 | private static final Logger logger = LoggerFactory.getLogger(BaseClient.class); 32 | 33 | /** 34 | * 建立连接超时时间(毫秒), 默认3000 35 | */ 36 | protected int connectTimeout = 3000; 37 | /** 38 | * 同步调用默认超时时间(毫秒) 39 | */ 40 | protected int syncInvokeTimeout = 3000; 41 | 42 | protected ClientDispatchHandler dispatchHandler; 43 | /** 44 | * 当前连接Server 45 | */ 46 | protected SocketAddress curServer; 47 | 48 | protected WrappedChannel channel; 49 | 50 | protected Semaphore semaphore = new Semaphore(0); 51 | 52 | protected EventLoopGroup group; 53 | protected Bootstrap bootstrap; 54 | 55 | public BaseClient() { 56 | super(); 57 | } 58 | 59 | @Override 60 | protected void init() { 61 | super.init(); 62 | // 将一些handler放在这里初始化是为了防止多例的产生。 63 | if (checkHeartbeat) { 64 | heartbeatHandler = new ClientHeartbeatHandler(); 65 | } 66 | 67 | eventDispatcher = new EventDispatcher(this); 68 | dispatchHandler = new ClientDispatchHandler(eventDispatcher); 69 | 70 | group = new NioEventLoopGroup(); 71 | bootstrap = new Bootstrap(); 72 | this.addEventListener(new DefaultMessageEventListener()); 73 | } 74 | 75 | protected ChannelFuture doConnect(final SocketAddress socketAddress, boolean sync) { 76 | // 连接server 77 | curServer = socketAddress; 78 | try { 79 | ChannelFuture future = bootstrap.connect(socketAddress).sync(); 80 | future.addListener((ChannelFutureListener) ch -> { 81 | ch.await(); 82 | if (ch.isSuccess()) { 83 | channel = new WrappedChannel(ch.channel()); 84 | if (logger.isDebugEnabled()) { 85 | logger.debug("Connect to '{}' success.", socketAddress); 86 | } 87 | } else { 88 | logger.error("Failed to connect to '{}', caused by: '{}'.", socketAddress, ch.cause()); 89 | } 90 | 91 | semaphore.release(Integer.MAX_VALUE - semaphore.availablePermits()); 92 | }); 93 | future.channel().closeFuture(); 94 | 95 | if (sync) { 96 | Throwable cause = null; 97 | try { 98 | if (!semaphore.tryAcquire(connectTimeout, TimeUnit.MILLISECONDS)) { 99 | cause = new SocketTimeoutException("time out."); 100 | } 101 | } catch (InterruptedException ex) { 102 | throw new SocketTimeoutException(ex); 103 | } 104 | 105 | if (cause != null) { 106 | throw new SocketRuntimeException(cause); 107 | } 108 | } 109 | return future; 110 | } catch (Exception ex) { 111 | throw new SocketRuntimeException(ex); 112 | } 113 | } 114 | 115 | public ChannelFuture connect(final SocketAddress socketAddress) { 116 | return connect(socketAddress, true); 117 | } 118 | 119 | public ChannelFuture connect(final SocketAddress socketAddress, boolean sync) { 120 | init(); 121 | bootstrap.option(ChannelOption.SO_KEEPALIVE, keepAlive); 122 | bootstrap.option(ChannelOption.TCP_NODELAY, tcpNoDelay); 123 | bootstrap.group(group).channel(NioSocketChannel.class); 124 | 125 | bootstrap.handler(new ChannelInitializer() { 126 | @Override 127 | public void initChannel(SocketChannel ch) throws Exception { 128 | ChannelPipeline pipeline = ch.pipeline(); 129 | 130 | // 注册各种自定义Handler 131 | LinkedHashMap handlers = getHandlers(); 132 | for (String key : handlers.keySet()) { 133 | pipeline.addLast(key, handlers.get(key).newInstance()); 134 | } 135 | 136 | if (socketType.equals(SocketType.MQTT_WS)) { 137 | pipeline.addFirst("httpRequestDecoder", new HttpRequestDecoder()); 138 | pipeline.addFirst("httpObjectAggregator", new HttpObjectAggregator(65536)); 139 | pipeline.addFirst("httpResponseEncoder", new HttpResponseEncoder()); 140 | } 141 | 142 | if (checkHeartbeat) { 143 | IdleStateHandler timeoutHandler = new IdleStateHandler(readerIdleTimeSeconds, 144 | writerIdleTimeSeconds, allIdleTimeSeconds); 145 | pipeline.addLast("timeout", timeoutHandler); 146 | pipeline.addLast("idleHandler", heartbeatHandler); 147 | } 148 | 149 | // 注册事件分发Handler 150 | pipeline.addLast("dispatchHandler", dispatchHandler); 151 | } 152 | }); 153 | 154 | return doConnect(socketAddress, sync); 155 | } 156 | 157 | @Override 158 | public void shutdown() { 159 | if (group != null) { 160 | group.shutdownGracefully(); 161 | } 162 | } 163 | 164 | public ChannelFuture send(Object message) { 165 | if (channel == null) { 166 | throw new SocketRuntimeException("channel have not connect."); 167 | } 168 | return channel.send(message); 169 | } 170 | 171 | public Response sendWithSync(Request message) { 172 | if (channel == null) { 173 | throw new SocketRuntimeException("channel have not connect."); 174 | } 175 | return channel.sendSync(message, syncInvokeTimeout); 176 | } 177 | 178 | public Response sendWithSync(Request message, int timeout) { 179 | if (channel == null) { 180 | throw new SocketRuntimeException("channel have not connect."); 181 | } 182 | return channel.sendSync(message, timeout); 183 | } 184 | 185 | public int getConnectTimeout() { 186 | return connectTimeout; 187 | } 188 | 189 | public void setConnectTimeout(int connectTimeout) { 190 | this.connectTimeout = connectTimeout; 191 | } 192 | 193 | public int getSyncInvokeTimeout() { 194 | return syncInvokeTimeout; 195 | } 196 | 197 | public void setSyncInvokeTimeout(int syncInvokeTimeout) { 198 | this.syncInvokeTimeout = syncInvokeTimeout; 199 | } 200 | 201 | public SocketAddress getCurServer() { 202 | return curServer; 203 | } 204 | 205 | public void setCurServer(SocketAddress curServer) { 206 | this.curServer = curServer; 207 | } 208 | 209 | public WrappedChannel getChannel() { 210 | return channel; 211 | } 212 | 213 | public void setChannel(WrappedChannel channel) { 214 | this.channel = channel; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/client/Client.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.client; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.yb.socket.codec.JsonDecoder; 7 | import com.yb.socket.codec.JsonEncoder; 8 | import com.yb.socket.exception.SocketRuntimeException; 9 | import com.yb.socket.pojo.Request; 10 | import com.yb.socket.pojo.Response; 11 | import com.yb.socket.util.AddressUtil; 12 | import io.netty.channel.ChannelFuture; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.net.InetSocketAddress; 18 | import java.net.SocketAddress; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | /** 24 | * @author daoshenzzg@163.com 25 | * @date 2018/12/30 16:41 26 | */ 27 | public class Client extends BaseClient { 28 | private static final Logger logger = LoggerFactory.getLogger(Client.class); 29 | /** 30 | * 服务名称 31 | */ 32 | protected String serviceName; 33 | /** 34 | * 注册中心地址: IP1:PORT1,IP2:PORT2 35 | */ 36 | protected String centerAddr; 37 | private List serverList; 38 | /** 39 | * 是否发生异常 40 | */ 41 | private AtomicBoolean errorFlag = new AtomicBoolean(false); 42 | /** 43 | * 是否正在断线重连 44 | */ 45 | private AtomicBoolean reConnecting = new AtomicBoolean(false); 46 | 47 | public ChannelFuture connect() { 48 | return connect(true); 49 | } 50 | 51 | public ChannelFuture connect(boolean sync) { 52 | SocketAddress socketAddress; 53 | if (serverList == null) { 54 | if (StringUtils.isBlank(centerAddr)) { 55 | socketAddress = new InetSocketAddress(ip, port); 56 | serverList = new ArrayList<>(); 57 | serverList.add(socketAddress); 58 | } else { 59 | if (serverList == null) { 60 | serverList = getAddressByCenter(); 61 | } 62 | 63 | if (serverList == null || serverList.size() <= 0) { 64 | throw new SocketRuntimeException("can not get server list from centerAddr property."); 65 | } 66 | } 67 | } 68 | 69 | for (SocketAddress server : serverList) { 70 | try { 71 | return super.connect(server, sync); 72 | } catch (Exception ex) { // socket timeout 73 | } 74 | } 75 | throw new SocketRuntimeException("can not connect to server[ " + serverList + "]"); 76 | 77 | } 78 | 79 | @Override 80 | public ChannelFuture send(Object message) { 81 | if (errorFlag.get()) { 82 | reConnect(); 83 | } 84 | return super.send(message); 85 | } 86 | 87 | @Override 88 | public Response sendWithSync(Request message) { 89 | if (errorFlag.get()) { 90 | reConnect(); 91 | } 92 | return super.sendWithSync(message); 93 | } 94 | 95 | @Override 96 | public Response sendWithSync(Request message, int timeout) { 97 | if (errorFlag.get()) { 98 | reConnect(); 99 | } 100 | return super.sendWithSync(message, timeout); 101 | } 102 | 103 | private List getAddressByCenter() { 104 | BaseClient baseClient = new BaseClient(); 105 | try { 106 | SocketAddress[] addresses = AddressUtil.parseAddress(centerAddr); 107 | if (addresses == null || addresses.length <= 0) { 108 | return null; 109 | } 110 | 111 | for (int i = 0; i < addresses.length; i++) { 112 | try { 113 | baseClient.setCheckHeartbeat(false); 114 | baseClient.addChannelHandler("decoder", JsonDecoder::new); 115 | baseClient.addChannelHandler("encoder", JsonEncoder::new); 116 | //连接注册中心 117 | ChannelFuture future = baseClient.connect(addresses[i], true); 118 | future.await(); 119 | if (future.isSuccess() && future.cause() == null) { 120 | //获取server列表 121 | Request request = new Request(); 122 | request.setSequence(0); 123 | JSONObject json = new JSONObject(); 124 | json.put("action", "getServerInfo"); 125 | json.put("service", this.getServiceName()); 126 | request.setMessage(json.toString()); 127 | Response response = baseClient.sendWithSync(request); 128 | // 解析server列表 129 | if (response != null && response.getCode() == Response.SUCCESS) { 130 | JSONArray jsonArray = JSON.parseObject(response.getResult().toString()).getJSONArray("server_list"); 131 | 132 | List serverList = new ArrayList<>(jsonArray.size()); 133 | for (int index = 0, size = jsonArray.size(); index < size; index++) { 134 | JSONObject row = jsonArray.getJSONObject(index); 135 | SocketAddress server = new InetSocketAddress(row.getString("ip"), row.getIntValue("port")); 136 | serverList.add(server); 137 | } 138 | 139 | return serverList; 140 | } 141 | } 142 | } catch (Exception ex) { 143 | logger.error("Failed to connect to server '{}'.", addresses[i], ex); 144 | } 145 | } 146 | 147 | return null; 148 | } catch (Exception ex) { 149 | throw new SocketRuntimeException("Failed to get server address from center.", ex); 150 | } finally { 151 | baseClient.shutdown(); 152 | } 153 | } 154 | 155 | /** 156 | * 断线重连 157 | */ 158 | private void reConnect() { 159 | if (!errorFlag.get()) { 160 | return; 161 | } 162 | 163 | try { 164 | if (!reConnecting.get()) { 165 | reConnecting.set(true); 166 | 167 | //重连三次当前server 168 | for (int i = 1; i <= 3; i++) { 169 | try { 170 | ChannelFuture future = this.doConnect(curServer, true); 171 | future.await(); 172 | if (future.isSuccess() && future.cause() == null) { 173 | logger.info("尝试第 {} 次重连到 '{}' 成功!", i, curServer); 174 | errorFlag.set(false); 175 | return; 176 | } else { 177 | throw new Exception(future.cause()); 178 | } 179 | } catch (Exception ex) { 180 | logger.error("尝试第 {} 次重连到 '{}' 失败!", i, curServer, ex); 181 | } 182 | } 183 | 184 | //重连不上当前server,则尝试连接到serverList里的其它server 185 | for (SocketAddress server : serverList) { 186 | if (server.equals(curServer)) { 187 | continue; 188 | } 189 | try { 190 | ChannelFuture future = this.doConnect(server, true); 191 | future.await(); 192 | if (future.isSuccess() && future.cause() == null) { 193 | logger.info("尝试重连到 '{}' 成功!", server); 194 | 195 | errorFlag.set(false); 196 | return; 197 | } else { 198 | throw new Exception(future.cause()); 199 | } 200 | } catch (Exception ex) { 201 | logger.error("尝试重连到 '{}' 失败!", server); 202 | } 203 | } 204 | } 205 | } finally { 206 | reConnecting.set(false); 207 | } 208 | } 209 | 210 | public String getServiceName() { 211 | return serviceName; 212 | } 213 | 214 | public void setServiceName(String serviceName) { 215 | this.serviceName = serviceName; 216 | } 217 | 218 | public String getCenterAddr() { 219 | return centerAddr; 220 | } 221 | 222 | public void setCenterAddr(String centerAddr) { 223 | this.centerAddr = centerAddr; 224 | } 225 | 226 | public List getServerList() { 227 | return serverList; 228 | } 229 | 230 | public void setServerList(List serverList) { 231 | this.serverList = serverList; 232 | } 233 | 234 | public AtomicBoolean getErrorFlag() { 235 | return errorFlag; 236 | } 237 | 238 | public void setErrorFlag(AtomicBoolean errorFlag) { 239 | this.errorFlag = errorFlag; 240 | } 241 | 242 | public AtomicBoolean getReConnecting() { 243 | return reConnecting; 244 | } 245 | 246 | public void setReConnecting(AtomicBoolean reConnecting) { 247 | this.reConnecting = reConnecting; 248 | } 249 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/client/ClientDispatchHandler.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.client; 2 | 3 | import com.yb.socket.service.EventDispatcher; 4 | import com.yb.socket.service.WrappedChannel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author daoshenzzg@163.com 14 | * @date 2018/12/30 16:25 15 | */ 16 | public class ClientDispatchHandler extends ChannelInboundHandlerAdapter { 17 | private static final Logger logger = LoggerFactory.getLogger(ClientDispatchHandler.class); 18 | 19 | private EventDispatcher eventDispatcher; 20 | 21 | public ClientDispatchHandler(EventDispatcher eventDispatcher) { 22 | if (eventDispatcher == null) { 23 | throw new IllegalArgumentException("eventDispatcher"); 24 | } 25 | 26 | this.eventDispatcher = eventDispatcher; 27 | } 28 | 29 | 30 | @Override 31 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 32 | if (logger.isDebugEnabled()) { 33 | logger.debug("Message received from channel '{}' : '{}'.", ctx.channel().id().asShortText(), msg.toString()); 34 | } 35 | 36 | WrappedChannel channel = ((BaseClient) eventDispatcher.getService()).getChannel(); 37 | eventDispatcher.dispatchMessageEvent(ctx, channel, msg); 38 | } 39 | 40 | @Override 41 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 42 | if (logger.isDebugEnabled()) { 43 | logger.debug("Connected on channel '{}'.", ctx.channel().id().asShortText()); 44 | } 45 | 46 | WrappedChannel channel = ((BaseClient) eventDispatcher.getService()).getChannel(); 47 | if (channel == null) { 48 | channel = new WrappedChannel(ctx.channel()); 49 | ((BaseClient) eventDispatcher.getService()).setChannel(channel); 50 | } 51 | eventDispatcher.dispatchChannelEvent(ctx, channel); 52 | } 53 | 54 | @Override 55 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 56 | closeChannel(ctx); 57 | } 58 | 59 | @Override 60 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 61 | WrappedChannel channel = ((BaseClient) eventDispatcher.getService()).getChannel(); 62 | if (channel != null) { 63 | eventDispatcher.dispatchExceptionCaught(ctx, channel, cause); 64 | } 65 | // 处理IOException,主动关闭channel 66 | if (cause instanceof IOException) { 67 | ctx.close(); 68 | closeChannel(ctx); 69 | } 70 | } 71 | 72 | private void closeChannel(ChannelHandlerContext ctx) { 73 | WrappedChannel channel = ((BaseClient) eventDispatcher.getService()).getChannel(); 74 | if (channel != null) { 75 | ((BaseClient) eventDispatcher.getService()).setChannel(null); 76 | if (logger.isDebugEnabled()) { 77 | logger.debug("Channel '{}' was closed.", channel.id().asShortText()); 78 | } 79 | 80 | eventDispatcher.dispatchChannelEvent(ctx, channel); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/client/ClientHeartbeatHandler.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.client; 2 | 3 | import com.yb.socket.pojo.Heartbeat; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * @author daoshenzzg@163.com 15 | * @date 2018/12/30 16:33 16 | */ 17 | @ChannelHandler.Sharable 18 | public class ClientHeartbeatHandler extends ChannelInboundHandlerAdapter { 19 | private static final Logger logger = LoggerFactory.getLogger(ClientHeartbeatHandler.class); 20 | 21 | public ClientHeartbeatHandler() { 22 | } 23 | 24 | @Override 25 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 26 | if (evt instanceof IdleStateEvent) { 27 | IdleStateEvent e = (IdleStateEvent) evt; 28 | if (e.state() == IdleState.WRITER_IDLE) { 29 | Channel channel = ctx.channel(); 30 | if (channel != null) { 31 | if (logger.isDebugEnabled()) { 32 | logger.debug("WRITER_IDLE, send Heartbeat..."); 33 | } 34 | channel.writeAndFlush(Heartbeat.getSingleton()); 35 | } 36 | } else if (e.state() == IdleState.READER_IDLE) { 37 | } 38 | } 39 | super.userEventTriggered(ctx, evt); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/server/ServerContext.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.server; 2 | 3 | /** 4 | * 全局共享的Server服务 5 | * 6 | * @author daoshenzzg@163.com 7 | * @date 2018/12/30 14:48 8 | */ 9 | public class ServerContext { 10 | 11 | private ServerContext() { 12 | } 13 | 14 | private static ServerContext instance = new ServerContext(); 15 | 16 | public static ServerContext getContext() { 17 | return instance; 18 | } 19 | 20 | private Server server; 21 | 22 | public Server getServer() { 23 | return server; 24 | } 25 | 26 | public void setServer(Server server) { 27 | this.server = server; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/server/ServerDispatchHandler.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.server; 2 | 3 | import com.yb.socket.service.EventDispatcher; 4 | import com.yb.socket.service.WrappedChannel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author daoshenzzg@163.com 14 | * @date 2018/12/30 16:47 15 | */ 16 | public class ServerDispatchHandler extends ChannelInboundHandlerAdapter { 17 | private static final Logger logger = LoggerFactory.getLogger(ServerDispatchHandler.class); 18 | 19 | protected EventDispatcher eventDispatcher; 20 | 21 | public ServerDispatchHandler(EventDispatcher eventDispatcher) { 22 | if (eventDispatcher == null) { 23 | throw new IllegalArgumentException("eventDispatcher cannot be null."); 24 | } 25 | 26 | this.eventDispatcher = eventDispatcher; 27 | } 28 | 29 | @Override 30 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 31 | String channelId = ctx.channel().id().asShortText(); 32 | if (logger.isDebugEnabled()) { 33 | logger.debug("Message received from channel '{} : '{}'.", channelId, msg); 34 | } 35 | WrappedChannel channel = ((Server) eventDispatcher.getService()).getChannel(channelId); 36 | eventDispatcher.dispatchMessageEvent(ctx, channel, msg); 37 | super.channelRead(ctx, msg); 38 | } 39 | 40 | @Override 41 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 42 | WrappedChannel channel = new WrappedChannel(ctx.channel()); 43 | String channelId = channel.id().asShortText(); 44 | if (logger.isDebugEnabled()) { 45 | logger.debug("Connected on channel '{}'.", channelId); 46 | } 47 | ((Server) eventDispatcher.getService()).getChannels().put(channelId, channel); 48 | eventDispatcher.dispatchChannelEvent(ctx, channel); 49 | super.channelActive(ctx); 50 | } 51 | 52 | @Override 53 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 54 | closeChannel(ctx); 55 | super.channelInactive(ctx); 56 | } 57 | 58 | @Override 59 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 60 | String channelId = ctx.channel().id().asShortText(); 61 | WrappedChannel channel = ((Server) eventDispatcher.getService()).getChannel(channelId); 62 | if (channel != null) { 63 | eventDispatcher.dispatchExceptionCaught(ctx, channel, cause); 64 | } 65 | 66 | // 处理IOException,主动关闭channel 67 | if (cause instanceof IOException) { 68 | ctx.close(); 69 | closeChannel(ctx); 70 | } 71 | } 72 | 73 | private void closeChannel(ChannelHandlerContext ctx) { 74 | String channelId = ctx.channel().id().asShortText(); 75 | WrappedChannel channel = ((Server) eventDispatcher.getService()).getChannels().remove(channelId); 76 | if (channel != null) { 77 | if (logger.isDebugEnabled()) { 78 | logger.debug("Channel '{}' was closed.", channelId); 79 | } 80 | 81 | eventDispatcher.dispatchChannelEvent(ctx, channel); 82 | } 83 | ctx.close(); 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/service/server/ServerHeartbeatHandler.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.server; 2 | 3 | import com.yb.socket.pojo.Heartbeat; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import io.netty.handler.timeout.IdleState; 8 | import io.netty.handler.timeout.IdleStateEvent; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * @author daoshenzzg@163.com 14 | * @date 2018/12/30 16:13 15 | */ 16 | @ChannelHandler.Sharable 17 | public class ServerHeartbeatHandler extends ChannelInboundHandlerAdapter { 18 | private static final Logger logger = LoggerFactory.getLogger(ServerHeartbeatHandler.class); 19 | 20 | public ServerHeartbeatHandler() { 21 | } 22 | 23 | @Override 24 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 25 | if (evt instanceof IdleStateEvent) { 26 | IdleStateEvent e = (IdleStateEvent) evt; 27 | if (e.state() == IdleState.WRITER_IDLE) { 28 | } else if (e.state() == IdleState.READER_IDLE) { 29 | ctx.channel().close(); 30 | } 31 | } 32 | super.userEventTriggered(ctx, evt); 33 | } 34 | 35 | @Override 36 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 37 | if (msg instanceof Heartbeat) { 38 | if (logger.isDebugEnabled()) { 39 | logger.debug("Heartbeat received."); 40 | } 41 | 42 | Server server = ServerContext.getContext().getServer(); 43 | if(server != null) { 44 | server.getCountInfo().getHeartbeatNum().incrementAndGet(); 45 | } 46 | return; 47 | } 48 | super.channelRead(ctx, msg); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/status/StatusMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.status; 2 | 3 | import com.yb.socket.listener.EventBehavior; 4 | import com.yb.socket.listener.MessageEventListener; 5 | import com.yb.socket.service.WrappedChannel; 6 | import com.yb.socket.service.server.Server; 7 | import com.yb.socket.service.server.ServerContext; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author daoshenzzg@163.com 18 | * @date 2018/12/30 18:24 19 | */ 20 | public class StatusMessageEventListener implements MessageEventListener { 21 | private static final String CMD_STATUS = "get status"; 22 | private static final String CMD_CONFIG = "get config"; 23 | private static final String CMD_QUIT = "quit"; 24 | private static final String CMD_EXIT = "exit"; 25 | private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 26 | 27 | @Override 28 | public EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg) { 29 | if (msg instanceof String) { 30 | String command = (String) msg; 31 | 32 | if (!StringUtils.isBlank(command)) { 33 | Map resultMap = null; 34 | if (command.equalsIgnoreCase(CMD_STATUS)) { 35 | resultMap = doGetStatus(); 36 | } else if (command.equalsIgnoreCase(CMD_CONFIG)) { 37 | resultMap = doGetConfig(); 38 | } else if (command.equalsIgnoreCase(CMD_QUIT) || command.equalsIgnoreCase(CMD_EXIT)) { 39 | channel.close(); 40 | return EventBehavior.BREAK; 41 | } else { 42 | resultMap = new LinkedHashMap<>(); 43 | resultMap.put("error", "unsupported command:" + command); 44 | } 45 | 46 | channel.writeAndFlush(formatResultMap(resultMap), false); 47 | } 48 | } 49 | return EventBehavior.CONTINUE; 50 | } 51 | 52 | private String formatResultMap(Map resultMap) { 53 | if (resultMap == null || resultMap.size() <= 0) { 54 | return StringUtils.EMPTY; 55 | } 56 | 57 | StringBuilder sb = new StringBuilder(); 58 | sb.append("{").append(LINE_SEPARATOR); 59 | for (Map.Entry row : resultMap.entrySet()) { 60 | sb.append(String.format(" %-25s", row.getKey())).append(":\t").append(row.getValue()).append(LINE_SEPARATOR); 61 | } 62 | sb.append("}").append(LINE_SEPARATOR); 63 | 64 | return sb.toString(); 65 | } 66 | 67 | private Map doGetStatus() { 68 | Map resultMap = new LinkedHashMap<>(); 69 | Server busServer = ServerContext.getContext().getServer(); 70 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 71 | 72 | if (busServer != null) { 73 | //server名称 74 | resultMap.put("serviceName", busServer.getServiceName()); 75 | //server启动时间 76 | resultMap.put("serverStartTime", sdf.format(new Date(busServer.getStartTime()))); 77 | //当前连接数 78 | resultMap.put("clientNumber", busServer.getCountInfo().getCurChannelNum()); 79 | //最大连接数 80 | resultMap.put("maxClientNumber", busServer.getCountInfo().getMaxChannelNum()); 81 | //最后次接收消息时间 82 | if (busServer.getCountInfo().getLastReceive() > 0) { 83 | resultMap.put("lastReceiveTime", sdf.format(new Date(busServer.getCountInfo().getLastReceive()))); 84 | } else { 85 | resultMap.put("lastReceiveTime", "no message received"); 86 | } 87 | //最后次发送消息时间 88 | if (busServer.getCountInfo().getLastSent() > 0) { 89 | resultMap.put("lastSentTime", sdf.format(new Date(busServer.getCountInfo().getLastSent()))); 90 | } else { 91 | resultMap.put("lastSentTime", "no message send"); 92 | } 93 | //接收消息数 94 | resultMap.put("receiveNumber", busServer.getCountInfo().getReceiveNum().get()); 95 | //发送消息数 96 | resultMap.put("sentNumber", busServer.getCountInfo().getSentNum().get()); 97 | // 心跳次数 98 | resultMap.put("heartbeatNumber", busServer.getCountInfo().getHeartbeatNum().get()); 99 | } else { 100 | resultMap.put("error", "business server is not found!"); 101 | } 102 | 103 | return resultMap; 104 | } 105 | 106 | private Map doGetConfig() { 107 | Map resultMap = new LinkedHashMap<>(); 108 | Server busServer = ServerContext.getContext().getServer(); 109 | if (busServer != null) { 110 | //ip 111 | resultMap.put("ip", busServer.getIp() == null ? "not specified" : busServer.getIp()); 112 | //port 113 | resultMap.put("port", busServer.getPort()); 114 | //keepAlive 115 | resultMap.put("keepAlive", busServer.isKeepAlive()); 116 | //tcpNoDelay 117 | resultMap.put("tcpNoDelay", busServer.isTcpNoDelay()); 118 | //executorFlag 119 | resultMap.put("executorFlag", busServer.isOpenExecutor()); 120 | if (busServer.isOpenExecutor()) { 121 | //corePoolSize 122 | resultMap.put("corePoolSize", busServer.getCorePoolSize()); 123 | //maximumPoolSize 124 | resultMap.put("maximumPoolSize", busServer.getMaximumPoolSize()); 125 | //queueCapacity 126 | resultMap.put("queueCapacity", busServer.getQueueCapacity()); 127 | } 128 | 129 | //checkHeartbeat 130 | resultMap.put("checkHeartbeat", busServer.isCheckHeartbeat()); 131 | if (busServer.isCheckHeartbeat()) { 132 | //readerIdleTimeSeconds 133 | resultMap.put("readerIdleTimeSeconds", busServer.getReaderIdleTimeSeconds()); 134 | //writerIdleTimeSeconds 135 | resultMap.put("writerIdleTimeSeconds", busServer.getWriterIdleTimeSeconds()); 136 | //allIdleTimeSeconds 137 | resultMap.put("allIdleTimeSeconds", busServer.getAllIdleTimeSeconds()); 138 | } 139 | } else { 140 | resultMap.put("error", "business server is not found!"); 141 | } 142 | 143 | return resultMap; 144 | } 145 | } -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/status/StatusServer.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.status; 2 | 3 | import com.yb.socket.service.ChannelHandlerFunc; 4 | import com.yb.socket.service.server.Server; 5 | import com.yb.socket.service.server.ServerDispatchHandler; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.*; 8 | import io.netty.channel.epoll.EpollServerSocketChannel; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.codec.DelimiterBasedFrameDecoder; 12 | import io.netty.handler.codec.Delimiters; 13 | import io.netty.handler.codec.string.StringDecoder; 14 | import io.netty.handler.codec.string.StringEncoder; 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.net.InetSocketAddress; 20 | import java.util.LinkedHashMap; 21 | 22 | /** 23 | * @author daoshenzzg@163.com 24 | * @date 2018/12/30 18:23 25 | */ 26 | public class StatusServer extends Server { 27 | private static final Logger logger = LoggerFactory.getLogger(StatusServer.class); 28 | 29 | public StatusServer() { 30 | } 31 | 32 | @Override 33 | protected void init() { 34 | checkHeartbeat = false; 35 | openExecutor = false; 36 | openCount = false; 37 | super.init(); 38 | 39 | 40 | this.addChannelHandler("framer", () -> new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter())); 41 | this.addChannelHandler("decoder", StringDecoder::new); 42 | this.addChannelHandler("encoder", StringEncoder::new); 43 | this.addEventListener(new StatusMessageEventListener()); 44 | } 45 | 46 | @Override 47 | public ChannelFuture bind() { 48 | init(); 49 | 50 | bootstrap = new ServerBootstrap(); 51 | bootstrap.childOption(ChannelOption.SO_KEEPALIVE, keepAlive); 52 | bootstrap.childOption(ChannelOption.TCP_NODELAY, tcpNoDelay); 53 | bootstrap.group(bossGroup, workerGroup); 54 | bootstrap.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class); 55 | bootstrap.childHandler(new ChannelInitializer() { 56 | @Override 57 | public void initChannel(SocketChannel ch) throws Exception { 58 | ChannelPipeline pipeline = ch.pipeline(); 59 | 60 | // 注册各种自定义Handler 61 | LinkedHashMap handlers = getHandlers(); 62 | for (String key : handlers.keySet()) { 63 | pipeline.addLast(key, handlers.get(key).newInstance()); 64 | } 65 | //注册事件分发Handler 66 | ServerDispatchHandler dispatchHandler = new ServerDispatchHandler(eventDispatcher); 67 | pipeline.addLast("dispatchHandler", dispatchHandler); 68 | } 69 | }); 70 | 71 | // 监听端口 72 | InetSocketAddress socketAddress; 73 | if (StringUtils.isBlank(ip)) { 74 | socketAddress = new InetSocketAddress(port); 75 | } else { 76 | socketAddress = new InetSocketAddress(ip, port); 77 | } 78 | 79 | ChannelFuture future = bootstrap.bind(socketAddress); 80 | 81 | InetSocketAddress sockAddr = socketAddress; 82 | future.addListener((ChannelFutureListener) ch -> { 83 | ch.await(); 84 | if (ch.isSuccess()) { 85 | logger.info("Status Server started, listening on '{}.", sockAddr); 86 | } else { 87 | logger.error("Failed to start status server '{}', caused by: '{}'.", sockAddr, ch.cause()); 88 | } 89 | }); 90 | 91 | return future; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/util/AddressUtil.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.net.SocketAddress; 8 | import java.net.UnknownHostException; 9 | 10 | /** 11 | * @author daoshenzzg@163.com 12 | * @date 2018/12/30 16:42 13 | */ 14 | public class AddressUtil { 15 | 16 | public static String getLocalIp() { 17 | InetAddress address; 18 | try { 19 | address = InetAddress.getLocalHost(); 20 | return address.getHostAddress(); 21 | } catch (UnknownHostException ex) { 22 | throw new RuntimeException(ex); 23 | } 24 | } 25 | 26 | public static SocketAddress[] parseAddress(String addressArray) { 27 | if (StringUtils.isEmpty(addressArray)) { 28 | throw new IllegalArgumentException("addressArray can not be null or empty."); 29 | } 30 | 31 | String[] array = addressArray.split(","); 32 | SocketAddress[] addresses = new InetSocketAddress[array.length]; 33 | for (int i = 0; i < array.length; i++) { 34 | String address = array[i]; 35 | String[] parts = address.split(":"); 36 | if (parts.length == 2) { 37 | addresses[i] = new InetSocketAddress(parts[0].trim(), Integer.parseInt(parts[1].trim())); 38 | } else { 39 | throw new IllegalArgumentException("address " + address + " is invalid."); 40 | } 41 | } 42 | 43 | return addresses; 44 | } 45 | 46 | /** 47 | * @param portArray 48 | * @return int[] 返回类型 49 | * @Title: parsePort 50 | * @Description: 解析端口 51 | */ 52 | public static int[] parsePort(String portArray) { 53 | if (StringUtils.isEmpty(portArray)) { 54 | throw new IllegalArgumentException("portArray can not be null or empty."); 55 | } 56 | 57 | String[] array = portArray.split(","); 58 | int[] ports = new int[array.length]; 59 | for (int i = 0; i < array.length; i++) { 60 | ports[i] = Integer.parseInt(array[i].trim()); 61 | } 62 | 63 | return ports; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/yb/socket/util/Sequence.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | /** 10 | * @author daoshenzzg@163.com 11 | * @date 2018/12/30 18:31 12 | */ 13 | public class Sequence { 14 | private static Sequence instance = new Sequence(); 15 | private Map sequences = new ConcurrentHashMap<>(); 16 | 17 | private Sequence() { 18 | } 19 | 20 | public static Sequence getInstance() { 21 | return instance; 22 | } 23 | 24 | public Integer addAndGet(String sequenceName) { 25 | return addAndGet(sequenceName, 1); 26 | } 27 | 28 | public Integer addAndGet(String sequenceName, int step) { 29 | if (StringUtils.isBlank(sequenceName)) { 30 | return null; 31 | } 32 | 33 | AtomicInteger sequence = null; 34 | if (sequences.get(sequenceName) == null) { 35 | sequence = new AtomicInteger(); 36 | sequences.put(sequenceName, sequence); 37 | } else { 38 | sequence = sequences.get(sequenceName); 39 | } 40 | 41 | return sequence.addAndGet(step); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/center/CenterMock1.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.center; 2 | 3 | import com.yb.socket.codec.JsonDecoder; 4 | import com.yb.socket.codec.JsonEncoder; 5 | import com.yb.socket.service.server.Server; 6 | 7 | /** 8 | * @author daoshenzzg@163.com 9 | * @date 2019/1/4 14:47 10 | */ 11 | public class CenterMock1 { 12 | 13 | public static void main(String[] args) { 14 | Server server = new Server(); 15 | server.setPort(9000); 16 | server.setCheckHeartbeat(false); 17 | server.addChannelHandler("decoder", JsonDecoder::new); 18 | server.addChannelHandler("encoder", JsonEncoder::new); 19 | server.addEventListener(new com.yb.socket.center.CenterMockMessageEventListener()); 20 | server.bind(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/center/CenterMock2.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.center; 2 | 3 | import com.yb.socket.codec.JsonDecoder; 4 | import com.yb.socket.codec.JsonEncoder; 5 | import com.yb.socket.service.server.Server; 6 | 7 | /** 8 | * @author daoshenzzg@163.com 9 | * @date 2019/1/7 22:18 10 | */ 11 | public class CenterMock2 { 12 | 13 | public static void main(String[] args) { 14 | 15 | Server server = new Server(); 16 | server.setPort(9010); 17 | server.setCheckHeartbeat(false); 18 | server.addChannelHandler("decoder", JsonDecoder::new); 19 | server.addChannelHandler("encoder", JsonEncoder::new); 20 | server.addEventListener(new com.yb.socket.center.CenterMockMessageEventListener()); 21 | server.bind(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/center/CenterMockMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.center; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.yb.socket.listener.EventBehavior; 7 | import com.yb.socket.listener.MessageEventListener; 8 | import com.yb.socket.pojo.Request; 9 | import com.yb.socket.pojo.Response; 10 | import com.yb.socket.service.WrappedChannel; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * @author daoshenzzg@163.com 17 | * @date 2019/1/4 14:48 18 | */ 19 | public class CenterMockMessageEventListener implements MessageEventListener { 20 | private static final Logger logger = LoggerFactory.getLogger(CenterMockMessageEventListener.class); 21 | 22 | @Override 23 | public EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg) { 24 | if (msg instanceof Request) { 25 | Request request = (Request) msg; 26 | 27 | if (logger.isDebugEnabled()) { 28 | logger.debug("Message received: '{}'.", request.toString()); 29 | } 30 | 31 | Response response = new Response(); 32 | response.setSequence(request.getSequence()); 33 | response.setCode(0); 34 | 35 | if (request.getMessage() != null) { 36 | JSONObject jsonObject = JSON.parseObject(request.getMessage().toString()); 37 | String action = jsonObject.getString("action"); 38 | if (action.equalsIgnoreCase("getServerInfo")) { 39 | JSONObject json = new JSONObject(); 40 | JSONArray ret = new JSONArray(); 41 | JSONObject row1 = new JSONObject(); 42 | row1.put("ip", "127.0.0.1"); 43 | row1.put("port", 8000); 44 | JSONObject row2 = new JSONObject(); 45 | row2.put("ip", "127.0.0.1"); 46 | row2.put("port", 8010); 47 | 48 | ret.add(row1); 49 | ret.add(row2); 50 | json.put("server_list", ret); 51 | response.setResult(json.toString()); 52 | } else if (action.equalsIgnoreCase("updateConnects")) { 53 | } else if (action.equalsIgnoreCase("register")) { 54 | } 55 | } 56 | channel.writeAndFlush(response); 57 | } 58 | return EventBehavior.CONTINUE; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/center/ClientTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.center; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.codec.JsonDecoder; 5 | import com.yb.socket.codec.JsonEncoder; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.service.client.Client; 8 | 9 | /** 10 | * @author daoshenzzg@163.com 11 | * @date 2019/1/7 22:32 12 | */ 13 | public class ClientTest { 14 | 15 | public static void main(String[] args) throws Exception { 16 | Client client = new Client(); 17 | client.setCheckHeartbeat(false); 18 | client.setCenterAddr("127.0.0.1:9000,127.0.0.1:9010"); 19 | client.addChannelHandler("decoder", JsonDecoder::new); 20 | client.addChannelHandler("encoder", JsonEncoder::new); 21 | client.connect(); 22 | 23 | JSONObject message = new JSONObject(); 24 | message.put("action", "echo"); 25 | message.put("message", "hello"); 26 | 27 | for (int i = 0; i < 5; i++) { 28 | Request request = new Request(); 29 | request.setSequence(i); 30 | request.setMessage(message); 31 | client.send(request); 32 | Thread.sleep(5000L); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/center/Server1.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.center; 2 | 3 | import com.yb.socket.service.normal.JsonEchoMessageEventListener; 4 | import com.yb.socket.service.server.Server; 5 | 6 | /** 7 | * @author daoshenzzg@163.com 8 | * @date 2019/1/7 22:30 9 | */ 10 | public class Server1 { 11 | 12 | public static void main(String[] args) { 13 | Server server = new Server(); 14 | server.setPort(8000); 15 | server.setCheckHeartbeat(false); 16 | server.setCenterAddr("127.0.0.1:9000,127.0.0.1:9010"); 17 | server.addEventListener(new JsonEchoMessageEventListener()); 18 | server.bind(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/center/Server2.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.center; 2 | 3 | import com.yb.socket.service.normal.JsonEchoMessageEventListener; 4 | import com.yb.socket.service.server.Server; 5 | 6 | /** 7 | * @author daoshenzzg@163.com 8 | * @date 2019/1/7 22:31 9 | */ 10 | public class Server2 { 11 | 12 | public static void main(String[] args) { 13 | Server server = new Server(); 14 | server.setPort(8010); 15 | server.setCheckHeartbeat(false); 16 | server.setCenterAddr("127.0.0.1:9000,127.0.0.1:9010"); 17 | server.addEventListener(new JsonEchoMessageEventListener()); 18 | server.bind(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/database/DatabaseTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.database; 2 | 3 | import com.yb.socket.datasource.HsqlDataSource; 4 | import org.apache.commons.dbutils.QueryRunner; 5 | import org.apache.commons.dbutils.handlers.ArrayHandler; 6 | 7 | import java.sql.SQLException; 8 | 9 | /** 10 | * @author daoshenzzg@163.com 11 | * @date 2019/1/14 15:47 12 | */ 13 | public class DatabaseTest { 14 | 15 | public static void main(String[] args) throws Exception { 16 | HsqlDataSource dataSource = new HsqlDataSource(); 17 | dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); 18 | dataSource.setUrl("jdbc:hsqldb:mem:channels"); 19 | dataSource.setUsername("admin"); 20 | dataSource.setPassword(""); 21 | dataSource.setTableScript("classpath:createTable.sql"); 22 | dataSource.init(); 23 | 24 | QueryRunner qr = new QueryRunner(dataSource); 25 | 26 | long t1 = System.currentTimeMillis(); 27 | System.out.println("开始插入数据"); 28 | insert(qr); 29 | long t2 = System.currentTimeMillis(); 30 | System.out.println("插入数据耗时:" + (t2 - t1) + "ms"); 31 | 32 | System.out.println("开始查询数据"); 33 | query(qr); 34 | long t3 = System.currentTimeMillis(); 35 | System.out.println("查询数据耗时:" + (t3 - t2) + "ms"); 36 | 37 | } 38 | 39 | private static void insert(QueryRunner qr) throws SQLException { 40 | String sql = "insert into subscribe_user(channel_id, client_id, topic, create_time) values(?, ?, ?, ?)"; 41 | 42 | for (int i = 0; i < 10000; i++) { 43 | qr.execute(sql, "channelId_" +i,"clientId_" +i, "yb/notice/", System.currentTimeMillis()/1000); 44 | } 45 | } 46 | 47 | private static void query(QueryRunner qr) throws SQLException { 48 | String sql = "select * from subscribe_user where id=?"; 49 | for (int i = 0; i < 10000; i++) { 50 | Object[] arr = qr.query(sql, new ArrayHandler(), i); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/server/ChannelDO.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.mqtt; 2 | 3 | /** 4 | * @author daoshenzzg@163.com 5 | * @date 2019/2/26 14:58 6 | */ 7 | public class ChannelDO { 8 | 9 | public String channelId; 10 | public String clientId; 11 | public String topic; 12 | public int createTime; 13 | 14 | public String getChannelId() { 15 | return channelId; 16 | } 17 | 18 | public void setChannelId(String channelId) { 19 | this.channelId = channelId; 20 | } 21 | 22 | public String getClientId() { 23 | return clientId; 24 | } 25 | 26 | public void setClientId(String clientId) { 27 | this.clientId = clientId; 28 | } 29 | 30 | public String getTopic() { 31 | return topic; 32 | } 33 | 34 | public void setTopic(String topic) { 35 | this.topic = topic; 36 | } 37 | 38 | public int getCreateTime() { 39 | return createTime; 40 | } 41 | 42 | public void setCreateTime(int createTime) { 43 | this.createTime = createTime; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/server/MqttClientTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.server; 2 | 3 | import org.eclipse.paho.client.mqttv3.*; 4 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * @author daoshenzzg@163.com 10 | * @date 2018/12/30 18:40 11 | */ 12 | public class MqttClientTest { 13 | private static final Logger logger = LoggerFactory.getLogger(MqttServerTest.class); 14 | 15 | public static void main(String[] args) { 16 | final String broker = "tcp://127.0.0.1:8000"; 17 | final String clientId = "88888888"; 18 | final String topic = "yb/notice/"; 19 | MemoryPersistence persistence = new MemoryPersistence(); 20 | try { 21 | final MqttClient sampleClient = new MqttClient(broker, clientId, persistence); 22 | final MqttConnectOptions connOpts = new MqttConnectOptions(); 23 | logger.info("Connecting to broker: {}", broker); 24 | connOpts.setServerURIs(new String[]{broker}); 25 | connOpts.setUserName("admin"); 26 | connOpts.setPassword("123456".toCharArray()); 27 | connOpts.setCleanSession(true); 28 | connOpts.setKeepAliveInterval(90); 29 | connOpts.setAutomaticReconnect(true); 30 | connOpts.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1); 31 | sampleClient.setCallback(new MqttCallbackExtended() { 32 | public void connectComplete(boolean reconnect, String serverURI) { 33 | logger.info("connect success"); 34 | //连接成功,需要上传客户端所有的订阅关系 35 | 36 | try { 37 | sampleClient.subscribe(topic,0); 38 | } catch (Exception ex) { 39 | ex.printStackTrace(); 40 | } 41 | } 42 | 43 | public void connectionLost(Throwable throwable) { 44 | logger.error("server connection lost.", throwable); 45 | } 46 | 47 | public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { 48 | logger.info("message arrived. topic={}, message={}.", topic, new String(mqttMessage.getPayload())); 49 | } 50 | 51 | public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { 52 | logger.info("delivery complete. messageId={}.", iMqttDeliveryToken.getMessageId()); 53 | } 54 | }); 55 | sampleClient.connect(connOpts); 56 | for (int i = 0; i < 3; i++) { 57 | try { 58 | String content = "hello world!" + i; 59 | //此处消息体只需要传入 byte 数组即可,对于其他类型的消息,请自行完成二进制数据的转换 60 | final MqttMessage message = new MqttMessage(content.getBytes()); 61 | message.setQos(0); 62 | logger.info("public message '{}'", content); 63 | /** 64 | *消息发送到某个主题 Topic,所有订阅这个 Topic 的设备都能收到这个消息。 65 | * 遵循 MQTT 的发布订阅规范,Topic 也可以是多级 Topic。此处设置了发送到二级 Topic 66 | */ 67 | sampleClient.publish(topic, message); 68 | } catch (Exception ex) { 69 | ex.printStackTrace(); 70 | } 71 | } 72 | } catch (Exception me) { 73 | me.printStackTrace(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/server/MqttServerTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.server; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.pojo.MqttRequest; 5 | import com.yb.socket.service.SocketType; 6 | import com.yb.socket.service.WrappedChannel; 7 | import com.yb.socket.service.server.Server; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.support.ClassPathXmlApplicationContext; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * @author daoshenzzg@163.com 17 | * @date 2018/12/30 18:41 18 | */ 19 | public class MqttServerTest { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(MqttServerTest.class); 22 | 23 | public static void main(String[] args) throws Exception { 24 | Server server = new Server(); 25 | server.setPort(8000); 26 | server.setOpenCount(true); 27 | server.setCheckHeartbeat(true); 28 | server.setOpenStatus(true); 29 | server.addEventListener(new TestMqttMessageEventListener()); 30 | server.setSocketType(SocketType.MQTT); 31 | server.bind(); 32 | 33 | ApplicationContext context = new ClassPathXmlApplicationContext("subscribe.xml"); 34 | ServerContext.getContext().setApp(context); 35 | 36 | SubscribeServer subscribeServer = (SubscribeServer) context.getBean("subscribeServer"); 37 | 38 | //模拟推送 39 | JSONObject message = new JSONObject(); 40 | message.put("action", "echo"); 41 | message.put("message", "this is yb push message!"); 42 | 43 | MqttRequest mqttRequest = new MqttRequest((message.toString().getBytes())); 44 | 45 | String topic = "yb/notice/"; 46 | while (true) { 47 | List channelIds = subscribeServer.getChannelByTopic(topic); 48 | if (channelIds != null && channelIds.size() > 0) { 49 | logger.info("模拟推送消息"); 50 | for (String channelId : channelIds) { 51 | WrappedChannel channel = server.getChannel(channelId); 52 | server.send(channel, "yb/notice/", mqttRequest); 53 | } 54 | } 55 | Thread.sleep(1000L); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/server/ServerContext.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.server; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | 5 | /** 6 | * @author daoshenzzg@163.com 7 | * @date 2019/2/26 16:32 8 | */ 9 | public class ServerContext { 10 | 11 | private ServerContext() { 12 | } 13 | 14 | private static ServerContext instance = new ServerContext(); 15 | 16 | public static ServerContext getContext() { 17 | return instance; 18 | } 19 | 20 | private ApplicationContext app; 21 | 22 | public ApplicationContext getApp() { 23 | return app; 24 | } 25 | 26 | public void setApp(ApplicationContext app) { 27 | this.app = app; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/server/SubscribeServer.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.server; 2 | 3 | import com.yb.socket.datasource.HsqlDataSource; 4 | import org.apache.commons.dbutils.QueryRunner; 5 | import org.apache.commons.dbutils.handlers.ColumnListHandler; 6 | 7 | import java.sql.SQLException; 8 | import java.util.List; 9 | 10 | /** 11 | * @author daoshenzzg@163.com 12 | * @date 2019/2/26 14:31 13 | */ 14 | 15 | public class SubscribeServer { 16 | private QueryRunner qr; 17 | 18 | public SubscribeServer(HsqlDataSource dataSource) { 19 | this.qr = new QueryRunner(dataSource); 20 | } 21 | 22 | /** 23 | * 订阅主题 24 | * 25 | * @param clientId 26 | * @param channelId 27 | * @param topic 28 | * @throws SQLException 29 | */ 30 | public void subscribe(String clientId, String channelId, String topic) throws SQLException { 31 | String sql = "INSERT INTO subscribe_user(channel_id, client_id, topic, create_time) VALUES(?, ?, ?, ?)"; 32 | qr.execute(sql, channelId, clientId, topic, System.currentTimeMillis() / 1000); 33 | } 34 | 35 | /** 36 | * 取消订阅 37 | * 38 | * @param clientId 39 | * @param topic 40 | */ 41 | public void unSubscribe(String clientId, String topic) throws SQLException { 42 | String sql = "DELETE FROM subscribe_user WHERE client_id = ? AND topic = ?"; 43 | qr.execute(sql, clientId, topic); 44 | } 45 | 46 | /** 47 | * 取消订阅 48 | * 49 | * @param channelId 50 | */ 51 | public void unSubscribe(String channelId) throws SQLException { 52 | String sql = "DELETE FROM subscribe_user WHERE client_id = ? AND topic = ?"; 53 | qr.execute(sql, channelId); 54 | } 55 | 56 | /** 57 | * 根据主题查询订阅者 58 | * 59 | * @param topic 60 | * @return 61 | * @throws SQLException 62 | */ 63 | public List getChannelByTopic(String topic) throws SQLException { 64 | String sql = "SELECT channel_id FROM subscribe_user WHERE topic = ?"; 65 | return qr.query(sql, new ColumnListHandler<>("channel_id"), topic); 66 | } 67 | 68 | public QueryRunner getQr() { 69 | return qr; 70 | } 71 | 72 | public void setQr(QueryRunner qr) { 73 | this.qr = qr; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/push/server/TestMqttMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.push.server; 2 | 3 | import com.yb.socket.listener.DefaultMqttMessageEventListener; 4 | import com.yb.socket.service.WrappedChannel; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.handler.codec.mqtt.*; 7 | import io.netty.util.AttributeKey; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.context.ApplicationContext; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * @author daoshenzzg@163.com 16 | * @date 2019/2/26 14:27 17 | */ 18 | public class TestMqttMessageEventListener extends DefaultMqttMessageEventListener { 19 | private static final Logger logger = LoggerFactory.getLogger(TestMqttMessageEventListener.class); 20 | 21 | @Override 22 | public void connect(WrappedChannel channel, MqttConnectMessage msg) { 23 | String clientId = msg.payload().clientIdentifier(); 24 | channel.attr(AttributeKey.valueOf("clientId")).set(clientId); 25 | super.connect(channel, msg); 26 | } 27 | 28 | @Override 29 | public void disConnect(WrappedChannel channel, MqttMessage msg) { 30 | ApplicationContext context = ServerContext.getContext().getApp(); 31 | SubscribeServer subscribeServer = (SubscribeServer) context.getBean("subscribeServer"); 32 | String channelId = channel.id().asShortText(); 33 | try { 34 | subscribeServer.unSubscribe(channelId); 35 | logger.debug("取消订阅全部主题成功. channelId={}", channelId); 36 | } catch (Exception ex) { 37 | logger.error("取消订阅失败.", ex); 38 | } 39 | 40 | super.disConnect(channel, msg); 41 | } 42 | 43 | @Override 44 | public void subscribe(WrappedChannel channel, MqttSubscribeMessage msg) { 45 | ApplicationContext context = ServerContext.getContext().getApp(); 46 | SubscribeServer subscribeServer = (SubscribeServer) context.getBean("subscribeServer"); 47 | 48 | List topicSubscriptions = msg.payload().topicSubscriptions(); 49 | String clientId = (String) channel.getChannel().attr(AttributeKey.valueOf("clientId")).get(); 50 | String channelId = channel.id().asShortText(); 51 | topicSubscriptions.forEach(topicSubscription -> { 52 | String topic = topicSubscription.topicName(); 53 | MqttQoS mqttQoS = topicSubscription.qualityOfService(); 54 | logger.debug("开始订阅. clientId={}, topic={}, qos={}", clientId, topic, mqttQoS.value()); 55 | try { 56 | subscribeServer.subscribe(clientId, channelId, topic); 57 | logger.debug("订阅主题成功. clientId={}, channelId={}, topic={}", channelId, channelId, topic); 58 | } catch (Exception ex) { 59 | logger.error("订阅失败.", ex); 60 | } 61 | }); 62 | super.subscribe(channel, msg); 63 | } 64 | 65 | @Override 66 | public void unSubscribe(WrappedChannel channel, MqttUnsubscribeMessage msg) { 67 | ApplicationContext context = ServerContext.getContext().getApp(); 68 | SubscribeServer subscribeServer = (SubscribeServer) context.getBean("subscribeServer"); 69 | String clientId = (String) channel.getChannel().attr(AttributeKey.valueOf("clientId")).get(); 70 | List topics = msg.payload().topics(); 71 | topics.forEach(topic -> { 72 | try { 73 | subscribeServer.unSubscribe(clientId, topic); 74 | logger.debug("取消订阅主题成功. clientId={}, topic={}", clientId, topic); 75 | } catch (Exception ex) { 76 | logger.error("取消订阅失败.", ex); 77 | } 78 | }); 79 | 80 | super.unSubscribe(channel, msg); 81 | } 82 | 83 | @Override 84 | public void publish(WrappedChannel channel, MqttPublishMessage msg) { 85 | String topic = msg.variableHeader().topicName(); 86 | ByteBuf buf = msg.content().duplicate(); 87 | byte[] tmp = new byte[buf.readableBytes()]; 88 | buf.readBytes(tmp); 89 | String content = new String(tmp); 90 | String clientId = (String) channel.getChannel().attr(AttributeKey.valueOf("clientId")).get(); 91 | logger.info("channelId={}, topic={}, message={}", clientId, topic, content); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/mqtt/EchoMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.mqtt; 2 | 3 | import com.yb.socket.listener.DefaultMqttMessageEventListener; 4 | import com.yb.socket.service.WrappedChannel; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.handler.codec.mqtt.*; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * @author daoshenzzg@163.com 13 | * @date 2019/1/4 15:10 14 | */ 15 | public class EchoMessageEventListener extends DefaultMqttMessageEventListener { 16 | private static final Logger logger = LoggerFactory.getLogger(EchoMessageEventListener.class); 17 | 18 | @Override 19 | public void publish(WrappedChannel channel, MqttPublishMessage msg) { 20 | String topic = msg.variableHeader().topicName(); 21 | ByteBuf buf = msg.content().duplicate(); 22 | byte[] tmp = new byte[buf.readableBytes()]; 23 | buf.readBytes(tmp); 24 | String content = new String(tmp); 25 | 26 | MqttPublishMessage sendMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 27 | new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_MOST_ONCE, false, 0), 28 | new MqttPublishVariableHeader(topic, 0), 29 | Unpooled.buffer().writeBytes(content.toUpperCase().getBytes())); 30 | channel.writeAndFlush(sendMessage); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/mqtt/MqttClientTest1.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.mqtt;; 2 | 3 | import org.eclipse.paho.client.mqttv3.*; 4 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * @author daoshenzzg@163.com 10 | * @date 2018/12/30 18:40 11 | */ 12 | public class MqttClientTest1 { 13 | private static final Logger logger = LoggerFactory.getLogger(MqttServerTest.class); 14 | 15 | public static void main(String[] args) { 16 | final String broker = "tcp://127.0.0.1:8000"; 17 | final String clientId = "GID_XXX@@@ClientID_123"; 18 | final String topic = "yb/notice/"; 19 | MemoryPersistence persistence = new MemoryPersistence(); 20 | try { 21 | final MqttClient sampleClient = new MqttClient(broker, clientId, persistence); 22 | final MqttConnectOptions connOpts = new MqttConnectOptions(); 23 | logger.info("Connecting to broker: {}", broker); 24 | connOpts.setServerURIs(new String[]{broker}); 25 | connOpts.setUserName("admin"); 26 | connOpts.setPassword("123456".toCharArray()); 27 | connOpts.setCleanSession(true); 28 | connOpts.setKeepAliveInterval(90); 29 | connOpts.setAutomaticReconnect(true); 30 | connOpts.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1); 31 | sampleClient.setCallback(new MqttCallbackExtended() { 32 | public void connectComplete(boolean reconnect, String serverURI) { 33 | logger.info("connect success"); 34 | //连接成功,需要上传客户端所有的订阅关系 35 | 36 | try { 37 | sampleClient.subscribe(topic,0); 38 | } catch (Exception ex) { 39 | ex.printStackTrace(); 40 | } 41 | } 42 | 43 | public void connectionLost(Throwable throwable) { 44 | logger.error("server connection lost.", throwable); 45 | } 46 | 47 | public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { 48 | logger.info("message arrived. topic={}, message={}.", topic, new String(mqttMessage.getPayload())); 49 | } 50 | 51 | public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { 52 | logger.info("delivery complete. messageId={}.", iMqttDeliveryToken.getMessageId()); 53 | } 54 | }); 55 | sampleClient.connect(connOpts); 56 | for (int i = 0; i < 3; i++) { 57 | try { 58 | String content = "hello world!" + i; 59 | //此处消息体只需要传入 byte 数组即可,对于其他类型的消息,请自行完成二进制数据的转换 60 | final MqttMessage message = new MqttMessage(content.getBytes()); 61 | message.setQos(0); 62 | logger.info("public message '{}'", content); 63 | /** 64 | *消息发送到某个主题 Topic,所有订阅这个 Topic 的设备都能收到这个消息。 65 | * 遵循 MQTT 的发布订阅规范,Topic 也可以是多级 Topic。此处设置了发送到二级 Topic 66 | */ 67 | sampleClient.publish(topic, message); 68 | } catch (Exception ex) { 69 | ex.printStackTrace(); 70 | } 71 | } 72 | } catch (Exception me) { 73 | me.printStackTrace(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/mqtt/MqttClientTest2.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.mqtt;; 2 | 3 | import org.eclipse.paho.client.mqttv3.*; 4 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * @author daoshenzzg@163.com 10 | * @date 2018/12/30 18:40 11 | */ 12 | public class MqttClientTest2 { 13 | private static final Logger logger = LoggerFactory.getLogger(MqttServerTest.class); 14 | 15 | public static void main(String[] args) { 16 | final String broker = "tcp://127.0.0.1:8000"; 17 | final String clientId = "GID_XXX@@@ClientID_124"; 18 | final String topic = "/yb/notice2"; 19 | MemoryPersistence persistence = new MemoryPersistence(); 20 | try { 21 | final MqttClient sampleClient = new MqttClient(broker, clientId, persistence); 22 | final MqttConnectOptions connOpts = new MqttConnectOptions(); 23 | logger.info("Connecting to broker: {}", broker); 24 | connOpts.setServerURIs(new String[]{broker}); 25 | connOpts.setCleanSession(true); 26 | connOpts.setKeepAliveInterval(90); 27 | connOpts.setAutomaticReconnect(true); 28 | connOpts.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1); 29 | sampleClient.setCallback(new MqttCallbackExtended() { 30 | public void connectComplete(boolean reconnect, String serverURI) { 31 | logger.info("connect success"); 32 | //连接成功,需要上传客户端所有的订阅关系 33 | 34 | try { 35 | sampleClient.subscribe(topic,0); 36 | } catch (Exception ex) { 37 | ex.printStackTrace(); 38 | } 39 | } 40 | 41 | public void connectionLost(Throwable throwable) { 42 | logger.error("server connection lost.", throwable); 43 | } 44 | 45 | public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { 46 | logger.info("message arrived. topic={}, message={}.", topic, new String(mqttMessage.getPayload())); 47 | } 48 | 49 | public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { 50 | logger.info("delivery complete. messageId={}.", iMqttDeliveryToken.getMessageId()); 51 | } 52 | }); 53 | sampleClient.connect(connOpts); 54 | } catch (Exception me) { 55 | me.printStackTrace(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/mqtt/MqttServerTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.mqtt; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.pojo.MqttRequest; 5 | import com.yb.socket.service.SocketType; 6 | import com.yb.socket.service.WrappedChannel; 7 | import com.yb.socket.service.server.Server; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * @author daoshenzzg@163.com 13 | * @date 2018/12/30 18:41 14 | */ 15 | public class MqttServerTest { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(MqttServerTest.class); 18 | 19 | public static void main(String[] args) throws Exception { 20 | Server server = new Server(); 21 | server.setPort(8000); 22 | server.setOpenCount(true); 23 | server.setCheckHeartbeat(true); 24 | server.setOpenStatus(true); 25 | server.setOpenExecutor(true); 26 | server.addEventListener(new EchoMessageEventListener()); 27 | server.setSocketType(SocketType.MQTT); 28 | server.bind(); 29 | 30 | //模拟推送 31 | JSONObject message = new JSONObject(); 32 | message.put("action", "echo"); 33 | message.put("message", "this is yb push message!"); 34 | 35 | MqttRequest mqttRequest = new MqttRequest((message.toString().getBytes())); 36 | while (true) { 37 | if (server.getChannels().size() > 0) { 38 | logger.info("模拟推送消息"); 39 | for (WrappedChannel channel : server.getChannels().values()) { 40 | server.send(channel, "yb/notice/", mqttRequest); 41 | } 42 | } 43 | Thread.sleep(1000L); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/mqttws/MqttWsServerTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.mqttws; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.pojo.MqttRequest; 5 | import com.yb.socket.service.SocketType; 6 | import com.yb.socket.service.WrappedChannel; 7 | import com.yb.socket.service.mqtt.EchoMessageEventListener; 8 | import com.yb.socket.service.server.Server; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * MQTT web socket 14 | * @author daoshenzzg@163.com 15 | * @date 2018/12/30 18:41 16 | */ 17 | public class MqttWsServerTest { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(MqttWsServerTest.class); 20 | 21 | public static void main(String[] args) throws Exception { 22 | Server server = new Server(); 23 | server.setPort(8000); 24 | server.addEventListener(new EchoMessageEventListener()); 25 | server.setSocketType(SocketType.MQTT_WS); 26 | server.bind(); 27 | 28 | //模拟推送 29 | String message = "this is a web socket message!"; 30 | MqttRequest mqttRequest = new MqttRequest((message.getBytes())); 31 | while (true) { 32 | if (server.getChannels().size() > 0) { 33 | logger.info("模拟推送消息"); 34 | for (WrappedChannel channel : server.getChannels().values()) { 35 | server.send(channel, "yb/notice/", mqttRequest); 36 | } 37 | } 38 | Thread.sleep(1000L); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/normal/ClientTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.normal; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.codec.JsonDecoder; 5 | import com.yb.socket.codec.JsonEncoder; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.pojo.Response; 8 | import com.yb.socket.service.client.Client; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * @author daoshenzzg@163.com 14 | * @date 2018/12/30 18:43 15 | */ 16 | public class ClientTest { 17 | private static final Logger logger = LoggerFactory.getLogger(ClientTest.class); 18 | 19 | public static void main(String[] args) { 20 | 21 | Client client = new Client(); 22 | client.setIp("127.0.0.1"); 23 | client.setPort(8000); 24 | client.setConnectTimeout(10000); 25 | client.addChannelHandler("decoder", JsonDecoder::new); 26 | client.addChannelHandler("encoder", JsonEncoder::new); 27 | client.connect(); 28 | 29 | for (int i = 0; i < 2; i++) { 30 | JSONObject message = new JSONObject(); 31 | message.put("action", "echo"); 32 | message.put("message", "hello world!"); 33 | 34 | Request request = new Request(); 35 | request.setSequence(i); 36 | request.setMessage(message); 37 | Response response = client.sendWithSync(request, 3000); 38 | 39 | logger.info("成功接收到同步的返回: '{}'.", response); 40 | } 41 | 42 | client.shutdown(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/normal/JsonEchoMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.normal; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.yb.socket.listener.EventBehavior; 6 | import com.yb.socket.listener.MessageEventListener; 7 | import com.yb.socket.pojo.Request; 8 | import com.yb.socket.pojo.Response; 9 | import com.yb.socket.service.WrappedChannel; 10 | import io.netty.channel.ChannelHandlerContext; 11 | 12 | /** 13 | * @author daoshenzzg@163.com 14 | * @date 2019/1/3 21:45 15 | */ 16 | public class JsonEchoMessageEventListener implements MessageEventListener { 17 | @Override 18 | public EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg) { 19 | 20 | if (msg instanceof Request) { 21 | Request request = (Request) msg; 22 | if (request.getMessage() != null) { 23 | Response response = new Response(); 24 | response.setSequence(request.getSequence()); 25 | response.setCode(0); 26 | JSONObject data = JSON.parseObject(request.getMessage().toString()); 27 | response.setResult(data.getString("message").toUpperCase()); 28 | 29 | channel.writeAndFlush(response); 30 | } 31 | } 32 | 33 | return EventBehavior.CONTINUE; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/normal/ServerTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.normal; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.yb.socket.codec.JsonDecoder; 5 | import com.yb.socket.codec.JsonEncoder; 6 | import com.yb.socket.pojo.Request; 7 | import com.yb.socket.service.WrappedChannel; 8 | import com.yb.socket.service.server.Server; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * @author daoshenzzg@163.com 14 | * @date 2018/12/30 18:44 15 | */ 16 | public class ServerTest { 17 | private static final Logger logger = LoggerFactory.getLogger(ServerTest.class); 18 | 19 | public static void main(String[] args) throws Exception { 20 | Server server = new Server(); 21 | server.setPort(8000); 22 | server.addEventListener(new JsonEchoMessageEventListener()); 23 | server.addChannelHandler("decoder", JsonDecoder::new); 24 | server.addChannelHandler("encoder", JsonEncoder::new); 25 | server.bind(); 26 | 27 | //模拟推送 28 | JSONObject message = new JSONObject(); 29 | message.put("action", "echo"); 30 | message.put("message", "this is a normal socket message!"); 31 | 32 | Request request = new Request(); 33 | request.setSequence(0); 34 | request.setMessage(message); 35 | while (true) { 36 | if (server.getChannels().size() > 0) { 37 | logger.info("模拟推送消息"); 38 | for (WrappedChannel channel : server.getChannels().values()) { 39 | channel.send(request); 40 | Thread.sleep(5000L); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/protocol/ClientTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.protocol; 2 | 3 | import com.yb.socket.codec.protocol.ProtocolDecoder; 4 | import com.yb.socket.codec.protocol.ProtocolEncoder; 5 | import com.yb.socket.pojo.protocol.ProtocolRequest; 6 | import com.yb.socket.pojo.protocol.ProtocolResponse; 7 | import com.yb.socket.service.client.Client; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * @author daoshenzzg@163.com 16 | * @date 2022-04-29 15:55 17 | */ 18 | public class ClientTest { 19 | private static final Logger logger = LoggerFactory.getLogger(ClientTest.class); 20 | 21 | public static void main(String[] args) throws Exception { 22 | Client client = new Client(); 23 | client.setIp("127.0.0.1"); 24 | client.setPort(8000); 25 | client.setConnectTimeout(10000); 26 | client.setCheckHeartbeat(true); 27 | 28 | client.addChannelHandler("decoder", ProtocolEncoder::new); 29 | client.addChannelHandler("encoder", ProtocolDecoder::new); 30 | logger.info("Start to connect: {}.", System.currentTimeMillis()); 31 | 32 | client.connect(); 33 | logger.info("Connection completed: {}.", System.currentTimeMillis()); 34 | 35 | ProtocolRequest request = new ProtocolRequest(); 36 | request.setVersion((byte) 1); 37 | request.setSequence(123456); 38 | Map body = new HashMap<>(); 39 | Map data = new HashMap<>(); 40 | data.put("message", "hello world"); 41 | body.put("data", data); 42 | request.setBody(body); 43 | ProtocolResponse response = (ProtocolResponse) client.sendWithSync(request, 3000); 44 | logger.info("成功接收到同步的返回: '{}'.", response); 45 | 46 | Thread.sleep(60 * 1000L); 47 | 48 | client.shutdown(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/protocol/ProtocolEchoMessageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.protocol; 2 | 3 | import com.yb.socket.listener.EventBehavior; 4 | import com.yb.socket.listener.MessageEventListener; 5 | import com.yb.socket.pojo.protocol.ProtocolRequest; 6 | import com.yb.socket.pojo.protocol.ProtocolResponse; 7 | import com.yb.socket.service.WrappedChannel; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * @author daoshenzzg@163.com 17 | * @date 2022-04-29 15:53 18 | */ 19 | public class ProtocolEchoMessageEventListener implements MessageEventListener { 20 | private static final Logger logger = LoggerFactory.getLogger(ProtocolEchoMessageEventListener.class); 21 | 22 | @Override 23 | public EventBehavior channelRead(ChannelHandlerContext ctx, WrappedChannel channel, Object msg) { 24 | if (msg instanceof ProtocolRequest) { 25 | ProtocolRequest request = (ProtocolRequest) msg; 26 | ProtocolResponse response = new ProtocolResponse(request); 27 | 28 | Map body = new HashMap<>(); 29 | @SuppressWarnings("unchecked") 30 | Map data = (Map) request.getBody().get("data"); 31 | data.put("message", data.get("message").toUpperCase()); 32 | body.put("data", data); 33 | response.setBody(body); 34 | 35 | channel.writeAndFlush(response); 36 | logger.info("send response '{}' success.", response); 37 | } 38 | 39 | return EventBehavior.CONTINUE; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/yb/socket/service/protocol/ServerTest.java: -------------------------------------------------------------------------------- 1 | package com.yb.socket.service.protocol; 2 | 3 | import com.yb.socket.codec.protocol.ProtocolDecoder; 4 | import com.yb.socket.codec.protocol.ProtocolEncoder; 5 | import com.yb.socket.listener.DefaultExceptionListener; 6 | import com.yb.socket.service.server.Server; 7 | 8 | /** 9 | * @author daoshenzzg@163.com 10 | * @date 2022-04-29 15:52 11 | */ 12 | public class ServerTest { 13 | 14 | public static void main(String[] args) { 15 | Server server = new Server(); 16 | server.setPort(8000); 17 | server.setCheckHeartbeat(true); 18 | server.setOpenStatus(true); 19 | server.setStatusPort(15001); 20 | 21 | server.addEventListener(new ProtocolEchoMessageEventListener()); 22 | server.addEventListener(new DefaultExceptionListener()); 23 | server.addChannelHandler("decoder", ProtocolEncoder::new); 24 | server.addChannelHandler("encoder", ProtocolDecoder::new); 25 | 26 | server.bind(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/resources/createTable.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE subscribe_user(id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY, channel_id VARCHAR(32), client_id VARCHAR(32), topic VARCHAR(32), create_time INTEGER); 2 | CREATE INDEX idx_client_id on subscribe_user(client_id); 3 | CREATE INDEX idx_topic on subscribe_user(topic); 4 | CREATE UNIQUE INDEX idx_client_topic ON subscribe_user(client_id, topic); -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/subscribe.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------