From 04917da6e2936cc81f9b42009147ab4fa7db5ef4 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 1 Aug 2020 18:58:44 +0800 Subject: [PATCH] nio enhancement --- CHANGELOG.md | 13 +- README.md | 8 +- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- .../java/cn/hutool/core/util/StrUtil.java | 6 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- .../java/cn/hutool/socket/aio/AioServer.java | 25 +- .../cn/hutool/socket/nio/AcceptHandler.java | 38 +++ .../cn/hutool/socket/nio/ChannelHandler.java | 17 ++ .../java/cn/hutool/socket/nio/NioClient.java | 266 ++++++++---------- .../java/cn/hutool/socket/nio/NioServer.java | 116 +++----- .../java/cn/hutool/socket/nio/NioUtil.java | 37 +++ .../java/cn/hutool/socket/NioClientTest.java | 67 ----- .../java/cn/hutool/socket/NioServerTest.java | 82 ------ .../socket/{ => aio}/AioClientTest.java | 11 +- .../socket/{ => aio}/AioServerTest.java | 9 +- .../cn/hutool/socket/nio/NioClientTest.java | 59 ++++ .../cn/hutool/socket/nio/NioServerTest.java | 56 ++++ hutool-system/pom.xml | 2 +- pom.xml | 2 +- 38 files changed, 429 insertions(+), 427 deletions(-) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/nio/AcceptHandler.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/nio/ChannelHandler.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/nio/NioUtil.java delete mode 100644 hutool-socket/src/test/java/cn/hutool/socket/NioClientTest.java delete mode 100644 hutool-socket/src/test/java/cn/hutool/socket/NioServerTest.java rename hutool-socket/src/test/java/cn/hutool/socket/{ => aio}/AioClientTest.java (77%) rename hutool-socket/src/test/java/cn/hutool/socket/{ => aio}/AioServerTest.java (86%) create mode 100644 hutool-socket/src/test/java/cn/hutool/socket/nio/NioClientTest.java create mode 100644 hutool-socket/src/test/java/cn/hutool/socket/nio/NioServerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ba932a47e..239c61a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,16 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.11 (2020-08-01) +# 5.4.0 (2020-08-01) + +### 新特性 +* 【socket】 对NioServer和NioClient改造(pr#992@Github) + +### Bug修复# + +------------------------------------------------------------------------------------------------------------- + +# 5.3.11 (2020-08-01) ### 新特性 * 【captcha】 AbstractCaptcha增加getImageBase64Data方法(pr#985@Github) @@ -13,7 +22,7 @@ * 【core 】 MapUtil增加getXXX的默认值重载(issue#I1PTGI@Gitee) * 【core 】 CalendarUtil增加parseByPatterns方法(issue#993@Github) -### Bug修复 +### Bug修复# ------------------------------------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 29275bcc2..497f63cc7 100644 --- a/README.md +++ b/README.md @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.11 + 5.4.0 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.11' +compile 'cn.hutool:hutool-all:5.4.0' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.11/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.11/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.0/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.0/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index c652d0283..8a30e8f94 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.11 +5.4.0 diff --git a/docs/js/version.js b/docs/js/version.js index 9b651e43c..89c52259a 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.11' \ No newline at end of file +var version = '5.4.0' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 27da194e2..8e1b1406a 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 981d89cae..3fbd05602 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 3232645f0..51a477842 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index fee0b7430..af0af1211 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index ce6c6f91f..7dbc37a52 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 1759e5a5f..7e574dcc5 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index d9d078f82..c44694c50 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index d1cb98617..6868939b0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2419,7 +2419,11 @@ public class StrUtil { /** * 将对象转为字符串
- * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + *
+	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
+	 * 2、对象数组会调用Arrays.toString方法
+	 * 
* * @param obj 对象 * @return 字符串 diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 8f9ee8345..7e31044cd 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 709f62444..a2876efc7 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 7d882bbb2..f68516a50 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index ebcb96aa6..1f49c4ddc 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 83c5d85d7..7c1b509d6 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index b33b256e7..ded057af9 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 9deac6997..7ccbc35a8 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 3cbb2d5d9..734ac9599 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 30af39861..ea7d1db81 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 92af6e89d..a26ce400f 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 15284b4f7..8e827a525 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 84f5ca62e..6831afd7a 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-socket diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java index 086a38dea..db045207c 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java @@ -1,13 +1,5 @@ package cn.hutool.socket.aio; -import java.io.Closeable; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketOption; -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousChannelGroup; -import java.nio.channels.AsynchronousServerSocketChannel; - import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.thread.ThreadFactoryBuilder; @@ -16,6 +8,14 @@ import cn.hutool.log.Log; import cn.hutool.log.LogFactory; import cn.hutool.socket.SocketConfig; +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketOption; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousChannelGroup; +import java.nio.channels.AsynchronousServerSocketChannel; + /** * 基于AIO的Socket服务端实现 * @@ -76,11 +76,7 @@ public class AioServer implements Closeable { * @param sync 是否阻塞 */ public void start(boolean sync) { - try { - doStart(sync); - } catch (IOException e) { - throw new IORuntimeException(e); - } + doStart(sync); } /** @@ -173,9 +169,8 @@ public class AioServer implements Closeable { * 开始监听 * * @param sync 是否阻塞 - * @throws IOException IO异常 */ - private void doStart(boolean sync) throws IOException { + private void doStart(boolean sync) { log.debug("Aio Server started, waiting for accept."); // 接收客户端连接 diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/AcceptHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/AcceptHandler.java new file mode 100644 index 000000000..3a685a060 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/AcceptHandler.java @@ -0,0 +1,38 @@ +package cn.hutool.socket.nio; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.log.StaticLog; + +import java.io.IOException; +import java.nio.channels.CompletionHandler; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +/** + * 接入完成回调,单例使用 + * + * @author looly + */ +public class AcceptHandler implements CompletionHandler { + + @Override + public void completed(ServerSocketChannel serverSocketChannel, NioServer nioServer) { + SocketChannel socketChannel; + try { + // 获取连接到此服务器的客户端通道 + socketChannel = serverSocketChannel.accept(); + StaticLog.debug("Client [{}] accepted.", socketChannel.getRemoteAddress()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + // SocketChannel通道的可读事件注册到Selector中 + NioUtil.registerChannel(nioServer.getSelector(), socketChannel, Operation.READ); + } + + @Override + public void failed(Throwable exc, NioServer nioServer) { + StaticLog.error(exc); + } + +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/ChannelHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/ChannelHandler.java new file mode 100644 index 000000000..84a9d1e7f --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/ChannelHandler.java @@ -0,0 +1,17 @@ +package cn.hutool.socket.nio; + +import java.nio.channels.SocketChannel; + +/** + * NIO数据处理接口,通过实现此接口,可以从{@link SocketChannel}中读写数据 + * + */ +public interface ChannelHandler { + + /** + * 处理NIO数据 + * + * @param socketChannel {@link SocketChannel} + */ + void handle(SocketChannel socketChannel); +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java index ec112bc64..07a3de382 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java @@ -2,7 +2,7 @@ package cn.hutool.socket.nio; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.thread.ThreadFactoryBuilder; +import cn.hutool.core.thread.ThreadUtil; import java.io.Closeable; import java.io.IOException; @@ -10,12 +10,8 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; /** * NIO客户端 @@ -24,159 +20,133 @@ import java.util.concurrent.ThreadFactory; * @since 4.4.5 */ public abstract class NioClient implements Closeable { - - private Selector selector; - private SocketChannel channel; - private ExecutorService executorService; - - /** - * 构造 - * - * @param host 服务器地址 - * @param port 端口 - */ - public NioClient(String host, int port) { - init(new InetSocketAddress(host, port)); - } - - /** - * 构造 - * - * @param address 服务器地址 - */ - public NioClient(InetSocketAddress address) { - init(address); - } - - /** - * 初始化 - * - * @param address 地址和端口 - * @return this - */ - public NioClient init(InetSocketAddress address) { - try { - //创建一个SocketChannel对象,配置成非阻塞模式 - this.channel = SocketChannel.open(); - channel.configureBlocking(false); - - //创建一个选择器,并把SocketChannel交给selector对象 - this.selector = Selector.open(); - channel.register(selector, SelectionKey.OP_CONNECT); - - //发起建立连接的请求,这里会立即返回,当连接建立完成后,SocketChannel就会被选取出来 - channel.connect(address); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return this; - } + private Selector selector; + private SocketChannel channel; /** - * 检查连接是否建立完成 + * 构造 + * + * @param host 服务器地址 + * @param port 端口 */ - public boolean waitConnect() throws IOException { - boolean isConnect = false; - while (0 != this.selector.select()) { + public NioClient(String host, int port) { + init(new InetSocketAddress(host, port)); + } + + /** + * 构造 + * + * @param address 服务器地址 + */ + public NioClient(InetSocketAddress address) { + init(address); + } + + /** + * 初始化 + * + * @param address 地址和端口 + * @return this + */ + public NioClient init(InetSocketAddress address) { + try { + //创建一个SocketChannel对象,配置成非阻塞模式 + this.channel = SocketChannel.open(); + channel.configureBlocking(false); + channel.connect(address); + + //创建一个选择器,并把SocketChannel交给selector对象 + this.selector = Selector.open(); + channel.register(this.selector, SelectionKey.OP_READ); + + // 等待建立连接 + //noinspection StatementWithEmptyBody + while (false == channel.finishConnect()){} + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 开始监听 + */ + public void listen() { + ThreadUtil.execute(() -> { + try { + doListen(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + /** + * 开始监听 + * + * @throws IOException IO异常 + */ + private void doListen() throws IOException { + while (this.selector.isOpen() && 0 != this.selector.select()) { + // 返回已选择键的集合 final Iterator keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) { - //连接建立完成 - SelectionKey key = keyIter.next(); - if (key.isConnectable()) { - if (this.channel.finishConnect()) { - this.channel.register(selector, SelectionKey.OP_READ); - isConnect = true; - } - } + handle(keyIter.next()); keyIter.remove(); - break; - } - if (isConnect) { - break; } } - return isConnect; } - /** - * 开始监听 - */ - public void listen() { - this.executorService = Executors.newSingleThreadExecutor(r -> { - final Thread thread = Executors.defaultThreadFactory().newThread(r); - thread.setName("nio-client-listen"); - return thread; - }); - this.executorService.execute(() -> { - try { - doListen(); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } - - /** - * 开始监听 - * - * @throws IOException IO异常 - */ - private void doListen() throws IOException { - while (0 != this.selector.select()) { - // 返回已选择键的集合 - final Iterator keyIter = selector.selectedKeys().iterator(); - while (keyIter.hasNext()) { - handle(keyIter.next()); - keyIter.remove(); - } - } - } - - /** - * 处理SelectionKey - * - * @param key SelectionKey - */ - private void handle(SelectionKey key) throws IOException { - // 读事件就绪 - if (key.isReadable()) { - final SocketChannel socketChannel = (SocketChannel) key.channel(); - read(socketChannel); - } - } - - /** - * 处理读事件
- * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息 - * - * @param socketChannel SocketChannel - */ - protected abstract void read(SocketChannel socketChannel); - - /** - * 实现写逻辑
- * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息 - * - * @param datas 发送的数据 - * @return this - */ - public NioClient write(ByteBuffer... datas) { - try { - this.channel.write(datas); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return this; - } - - public void closeListen() { - this.executorService.shutdown(); + /** + * 处理SelectionKey + * + * @param key SelectionKey + */ + private void handle(SelectionKey key) { + // 读事件就绪 + if (key.isReadable()) { + final SocketChannel socketChannel = (SocketChannel) key.channel(); + read(socketChannel); + } } - @Override - public void close() { - IoUtil.close(this.selector); - IoUtil.close(this.channel); - closeListen(); - } + /** + * 处理读事件
+ * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息 + * + * @param socketChannel SocketChannel + */ + protected abstract void read(SocketChannel socketChannel); + + /** + * 实现写逻辑
+ * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息 + * + * @param datas 发送的数据 + * @return this + */ + public NioClient write(ByteBuffer... datas) { + try { + this.channel.write(datas); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 获取SocketChannel + * + * @return SocketChannel + * @since 5.3.10 + */ + public SocketChannel getChannel() { + return this.channel; + } + + @Override + public void close() { + IoUtil.close(this.selector); + IoUtil.close(this.channel); + } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java index 20e8307f6..2ce10cc33 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java @@ -2,12 +2,11 @@ package cn.hutool.socket.nio; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.log.Log; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -20,10 +19,14 @@ import java.util.Iterator; * @author looly * */ -public abstract class NioServer implements Closeable { +public class NioServer implements Closeable { + private static final Log log = Log.get(); + + private static final AcceptHandler ACCEPT_HANDLER = new AcceptHandler(); private Selector selector; private ServerSocketChannel serverSocketChannel; + private ChannelHandler handler; /** * 构造 @@ -45,23 +48,52 @@ public abstract class NioServer implements Closeable { // 打开服务器套接字通道 this.serverSocketChannel = ServerSocketChannel.open(); // 设置为非阻塞状态 - serverSocketChannel.configureBlocking(false); - // 获取通道相关联的套接字 - final ServerSocket serverSocket = serverSocketChannel.socket(); + this.serverSocketChannel.configureBlocking(false); // 绑定端口号 - serverSocket.bind(address); + this.serverSocketChannel.bind(address); // 打开一个选择器 - selector = Selector.open(); + this.selector = Selector.open(); // 服务器套接字注册到Selector中 并指定Selector监控连接事件 - serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { throw new IORuntimeException(e); } + log.debug("Server listen on: [{}]...", address); + return this; } + /** + * 设置NIO数据处理器 + * + * @param handler {@link ChannelHandler} + * @return this + */ + public NioServer setChannelHandler(ChannelHandler handler){ + this.handler = handler; + return this; + } + + /** + * 获取{@link Selector} + * + * @return {@link Selector} + */ + public Selector getSelector(){ + return this.selector; + } + + /** + * 启动NIO服务端,即开始监听 + * + * @see #listen() + */ + public void start(){ + listen(); + } + /** * 开始监听 */ @@ -79,7 +111,7 @@ public abstract class NioServer implements Closeable { * @throws IOException IO异常 */ private void doListen() throws IOException { - while (0 != this.selector.select()) { + while (this.selector.isOpen() && 0 != this.selector.select()) { // 返回已选择键的集合 final Iterator keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) { @@ -97,35 +129,13 @@ public abstract class NioServer implements Closeable { private void handle(SelectionKey key) { // 有客户端接入此服务端 if (key.isAcceptable()) { - // 获取通道 转化为要处理的类型 - final ServerSocketChannel server = (ServerSocketChannel) key.channel(); - SocketChannel socketChannel; - try { - // 获取连接到此服务器的客户端通道 - socketChannel = server.accept(); - } catch (IOException e) { - throw new IORuntimeException(e); - } - - // SocketChannel通道的可读事件注册到Selector中 - registerChannel(selector, socketChannel, Operation.READ); + ACCEPT_HANDLER.completed((ServerSocketChannel) key.channel(), this); } // 读事件就绪 if (key.isReadable()) { final SocketChannel socketChannel = (SocketChannel) key.channel(); - read(socketChannel); - - // SocketChannel通道的可写事件注册到Selector中 - registerChannel(selector, socketChannel, Operation.WRITE); - } - - // 写事件就绪 - if (key.isWritable()) { - final SocketChannel socketChannel = (SocketChannel) key.channel(); - write(socketChannel); - // SocketChannel通道的可读事件注册到Selector中 - registerChannel(selector, socketChannel, Operation.READ); + handler.handle(socketChannel); } } @@ -134,42 +144,4 @@ public abstract class NioServer implements Closeable { IoUtil.close(this.selector); IoUtil.close(this.serverSocketChannel); } - - /** - * 处理读事件
- * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息 - * - * @param socketChannel SocketChannel - */ - protected abstract void read(SocketChannel socketChannel); - - /** - * 实现写逻辑
- * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息 - * - * @param socketChannel SocketChannel - */ - protected abstract void write(SocketChannel socketChannel); - - /** - * 注册通道到指定Selector上 - * - * @param selector Selector - * @param channel 通道 - * @param ops 注册的通道监听类型 - */ - private void registerChannel(Selector selector, SelectableChannel channel, Operation ops) { - if (channel == null) { - return; - } - - try { - channel.configureBlocking(false); - // 注册通道 - //noinspection MagicConstant - channel.register(selector, ops.getValue()); - } catch (IOException e) { - throw new IORuntimeException(e); - } - } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioUtil.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioUtil.java new file mode 100644 index 000000000..6964c8608 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioUtil.java @@ -0,0 +1,37 @@ +package cn.hutool.socket.nio; + +import cn.hutool.core.io.IORuntimeException; + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.Selector; + +/** + * NIO工具类 + * + * @since 5.4.0 + */ +public class NioUtil { + + /** + * 注册通道的指定操作到指定Selector上 + * + * @param selector Selector + * @param channel 通道 + * @param ops 注册的通道监听(操作)类型 + */ + public static void registerChannel(Selector selector, SelectableChannel channel, Operation ops) { + if (channel == null) { + return; + } + + try { + channel.configureBlocking(false); + // 注册通道 + //noinspection MagicConstant + channel.register(selector, ops.getValue()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } +} diff --git a/hutool-socket/src/test/java/cn/hutool/socket/NioClientTest.java b/hutool-socket/src/test/java/cn/hutool/socket/NioClientTest.java deleted file mode 100644 index 1bc217b81..000000000 --- a/hutool-socket/src/test/java/cn/hutool/socket/NioClientTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.hutool.socket; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.socket.nio.NioClient; -import lombok.SneakyThrows; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; -import java.nio.charset.Charset; -import java.util.Iterator; -import java.util.Scanner; -import java.util.Set; - -public class NioClientTest { - - @SneakyThrows - public static void main(String[] args) { - NioClient client = new NioClient("127.0.0.1", 8080) { - @SneakyThrows - @Override - protected void read(SocketChannel sc) { - ByteBuffer readBuffer = ByteBuffer.allocate(1024); - //从channel读数据到缓冲区 - int readBytes = sc.read(readBuffer); - if (readBytes > 0){ - //Flips this buffer. The limit is set to the current position and then - // the position is set to zero,就是表示要从起始位置开始读取数据 - readBuffer.flip(); - //eturns the number of elements between the current position and the limit. - // 要读取的字节长度 - byte[] bytes = new byte[readBuffer.remaining()]; - //将缓冲区的数据读到bytes数组 - readBuffer.get(bytes); - String body = new String(bytes, "UTF-8"); - System.out.println("the read client receive message: " + body); - }else if(readBytes < 0){ - sc.close(); - } - } - }; - if (client.waitConnect()) { - client.listen(); - } - ByteBuffer buffer = ByteBuffer.wrap("client 发生到 server".getBytes()); - client.write(buffer); - buffer = ByteBuffer.wrap("client 再次发生到 server".getBytes()); - client.write(buffer); - - /** - * 在控制台向服务器端发送数据 - */ - System.out.println("请在下方畅所欲言"); - Scanner scanner = new Scanner(System.in); - while (scanner.hasNextLine()) { - String request = scanner.nextLine(); - if (request != null && request.trim().length() > 0) { - client.write( - Charset.forName("UTF-8") - .encode("测试client" + ": " + request)); - } - } - } -} \ No newline at end of file diff --git a/hutool-socket/src/test/java/cn/hutool/socket/NioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/NioServerTest.java deleted file mode 100644 index 39d0b6848..000000000 --- a/hutool-socket/src/test/java/cn/hutool/socket/NioServerTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.hutool.socket; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.socket.nio.NioServer; -import lombok.SneakyThrows; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.util.Set; - -public class NioServerTest { - - public static void main(String[] args) { - NioServer server = new NioServer(8080) { - @SneakyThrows - @Override - protected void read(SocketChannel sc) { - ByteBuffer readBuffer = ByteBuffer.allocate(1024); - //从channel读数据到缓冲区 - int readBytes = sc.read(readBuffer); - if (readBytes > 0){ - //Flips this buffer. The limit is set to the current position and then - // the position is set to zero,就是表示要从起始位置开始读取数据 - readBuffer.flip(); - //eturns the number of elements between the current position and the limit. - // 要读取的字节长度 - byte[] bytes = new byte[readBuffer.remaining()]; - //将缓冲区的数据读到bytes数组 - readBuffer.get(bytes); - String body = new String(bytes, "UTF-8"); - System.out.println("the read server receive message: " + body); - doWrite(sc, body); - }else if(readBytes < 0){ - sc.close(); - } - } - - @SneakyThrows - @Override - protected void write(SocketChannel sc) { - ByteBuffer readBuffer = ByteBuffer.allocate(1024); - //从channel读数据到缓冲区 - int readBytes = sc.read(readBuffer); - if (readBytes > 0){ - //Flips this buffer. The limit is set to the current position and then - // the position is set to zero,就是表示要从起始位置开始读取数据 - readBuffer.flip(); - //eturns the number of elements between the current position and the limit. - // 要读取的字节长度 - byte[] bytes = new byte[readBuffer.remaining()]; - //将缓冲区的数据读到bytes数组 - readBuffer.get(bytes); - String body = new String(bytes, "UTF-8"); - System.out.println("the write server receive message: " + body); - doWrite(sc, body); - }else if(readBytes < 0){ - sc.close(); - } - } - }; - server.listen(); - } - - public static void doWrite(SocketChannel channel, String response) throws IOException { - response = "我们已收到消息:"+response; - if(!StrUtil.isBlank(response)){ - byte [] bytes = response.getBytes(); - //分配一个bytes的length长度的ByteBuffer - ByteBuffer write = ByteBuffer.allocate(bytes.length); - //将返回数据写入缓冲区 - write.put(bytes); - write.flip(); - //将缓冲数据写入渠道,返回给客户端 - channel.write(write); - } - } -} \ No newline at end of file diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java b/hutool-socket/src/test/java/cn/hutool/socket/aio/AioClientTest.java similarity index 77% rename from hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java rename to hutool-socket/src/test/java/cn/hutool/socket/aio/AioClientTest.java index 872c3cee7..015ca9afb 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/aio/AioClientTest.java @@ -1,13 +1,10 @@ -package cn.hutool.socket; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; +package cn.hutool.socket.aio; import cn.hutool.core.lang.Console; import cn.hutool.core.util.StrUtil; -import cn.hutool.socket.aio.AioClient; -import cn.hutool.socket.aio.AioSession; -import cn.hutool.socket.aio.SimpleIoAction; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; public class AioClientTest { public static void main(String[] args) { diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/aio/AioServerTest.java similarity index 86% rename from hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java rename to hutool-socket/src/test/java/cn/hutool/socket/aio/AioServerTest.java index aa3ac8496..67bfdf3e5 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/aio/AioServerTest.java @@ -1,15 +1,12 @@ -package cn.hutool.socket; - -import java.nio.ByteBuffer; +package cn.hutool.socket.aio; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.BufferUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; -import cn.hutool.socket.aio.AioServer; -import cn.hutool.socket.aio.AioSession; -import cn.hutool.socket.aio.SimpleIoAction; + +import java.nio.ByteBuffer; public class AioServerTest { diff --git a/hutool-socket/src/test/java/cn/hutool/socket/nio/NioClientTest.java b/hutool-socket/src/test/java/cn/hutool/socket/nio/NioClientTest.java new file mode 100644 index 000000000..a39f04f40 --- /dev/null +++ b/hutool-socket/src/test/java/cn/hutool/socket/nio/NioClientTest.java @@ -0,0 +1,59 @@ +package cn.hutool.socket.nio; + +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import lombok.SneakyThrows; + +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.Scanner; + +public class NioClientTest { + + @SneakyThrows + public static void main(String[] args) { + NioClient client = new NioClient("127.0.0.1", 8080) { + + @SneakyThrows + @Override + protected void read(SocketChannel sc) { + ByteBuffer readBuffer = ByteBuffer.allocate(1024); + //从channel读数据到缓冲区 + int readBytes = sc.read(readBuffer); + if (readBytes > 0) { + //Flips this buffer. The limit is set to the current position and then + // the position is set to zero,就是表示要从起始位置开始读取数据 + readBuffer.flip(); + //returns the number of elements between the current position and the limit. + // 要读取的字节长度 + byte[] bytes = new byte[readBuffer.remaining()]; + //将缓冲区的数据读到bytes数组 + readBuffer.get(bytes); + String body = StrUtil.utf8Str(bytes); + Console.log("the read client receive message: " + body); + } else if (readBytes < 0) { + sc.close(); + } + } + }; + + client.listen(); + ByteBuffer buffer = ByteBuffer.wrap("client 发生到 server".getBytes()); + client.write(buffer); + buffer = ByteBuffer.wrap("client 再次发生到 server".getBytes()); + client.write(buffer); + + // 在控制台向服务器端发送数据 + Console.log("请在下方畅所欲言"); + Scanner scanner = new Scanner(System.in); + while (scanner.hasNextLine()) { + String request = scanner.nextLine(); + if (request != null && request.trim().length() > 0) { + client.write( + CharsetUtil.CHARSET_UTF_8 + .encode("测试client" + ": " + request)); + } + } + } +} \ No newline at end of file diff --git a/hutool-socket/src/test/java/cn/hutool/socket/nio/NioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/nio/NioServerTest.java new file mode 100644 index 000000000..53212ed37 --- /dev/null +++ b/hutool-socket/src/test/java/cn/hutool/socket/nio/NioServerTest.java @@ -0,0 +1,56 @@ +package cn.hutool.socket.nio; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.StrUtil; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +public class NioServerTest { + + public static void main(String[] args) { + NioServer server = new NioServer(8080); + server.setChannelHandler((sc)->{ + ByteBuffer readBuffer = ByteBuffer.allocate(1024); + try{ + //从channel读数据到缓冲区 + int readBytes = sc.read(readBuffer); + if (readBytes > 0) { + //Flips this buffer. The limit is set to the current position and then + // the position is set to zero,就是表示要从起始位置开始读取数据 + readBuffer.flip(); + //eturns the number of elements between the current position and the limit. + // 要读取的字节长度 + byte[] bytes = new byte[readBuffer.remaining()]; + //将缓冲区的数据读到bytes数组 + readBuffer.get(bytes); + String body = StrUtil.utf8Str(bytes); + Console.log("the read server receive message: " + body); + doWrite(sc, body); + } else if (readBytes < 0) { + IoUtil.close(sc); + } + } catch (IOException e){ + throw new IORuntimeException(e); + } + }); + server.listen(); + } + + public static void doWrite(SocketChannel channel, String response) throws IOException { + response = "我们已收到消息:" + response; + if (!StrUtil.isBlank(response)) { + byte[] bytes = response.getBytes(); + //分配一个bytes的length长度的ByteBuffer + ByteBuffer write = ByteBuffer.allocate(bytes.length); + //将返回数据写入缓冲区 + write.put(bytes); + write.flip(); + //将缓冲数据写入渠道,返回给客户端 + channel.write(write); + } + } +} \ No newline at end of file diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index dc05fa122..862c51a0e 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 05375f290..e20c04709 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.11-SNAPSHOT + 5.4.0-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool