├── .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 | 
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 | 
20 |
21 | 说明:
22 |
23 | 1、客户端直连SLB/CLB,SLB/CLB通过最小连接数策略选择一个后端Server建立连接。
24 |
25 | 备注:该方案适用于云原生架构,可以节省web-server服务器成本,及客户端复杂度。
26 |
27 |
28 | ** 技术方案选型最终需要根据实际业务情况来决定 **
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
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 | 1万Clients订阅的消息下行能力
62 | 对应下行负载情况
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | #### MQTT协议-消息上行能力
74 |
75 |
76 |
77 | 4000Clients订阅消息上行能力
78 | 对应上行负载情况
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | #### MQTT协议-查看连接数情况
90 |
91 |
92 |
93 | 查看连接数(telnet 10.43.204.61 8001; get status)
94 | 查看连接数(ss -l)
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
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 |
--------------------------------------------------------------------------------