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