Browse Source

nio enhancement

Looly 5 years ago
parent
commit
04917da6e2
38 changed files with 422 additions and 420 deletions
  1. 11 2
      CHANGELOG.md
  2. 4 4
      README.md
  3. 1 1
      bin/version.txt
  4. 1 1
      docs/js/version.js
  5. 1 1
      hutool-all/pom.xml
  6. 1 1
      hutool-aop/pom.xml
  7. 1 1
      hutool-bloomFilter/pom.xml
  8. 1 1
      hutool-bom/pom.xml
  9. 1 1
      hutool-cache/pom.xml
  10. 1 1
      hutool-captcha/pom.xml
  11. 1 1
      hutool-core/pom.xml
  12. 5 1
      hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java
  13. 1 1
      hutool-cron/pom.xml
  14. 1 1
      hutool-crypto/pom.xml
  15. 1 1
      hutool-db/pom.xml
  16. 1 1
      hutool-dfa/pom.xml
  17. 1 1
      hutool-extra/pom.xml
  18. 1 1
      hutool-http/pom.xml
  19. 1 1
      hutool-json/pom.xml
  20. 1 1
      hutool-log/pom.xml
  21. 1 1
      hutool-poi/pom.xml
  22. 1 1
      hutool-script/pom.xml
  23. 1 1
      hutool-setting/pom.xml
  24. 1 1
      hutool-socket/pom.xml
  25. 10 15
      hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java
  26. 38 0
      hutool-socket/src/main/java/cn/hutool/socket/nio/AcceptHandler.java
  27. 17 0
      hutool-socket/src/main/java/cn/hutool/socket/nio/ChannelHandler.java
  28. 111 141
      hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java
  29. 44 72
      hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java
  30. 37 0
      hutool-socket/src/main/java/cn/hutool/socket/nio/NioUtil.java
  31. 0 67
      hutool-socket/src/test/java/cn/hutool/socket/NioClientTest.java
  32. 0 82
      hutool-socket/src/test/java/cn/hutool/socket/NioServerTest.java
  33. 4 7
      hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java
  34. 3 6
      hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java
  35. 59 0
      hutool-socket/src/test/java/cn/hutool/socket/nio/NioClientTest.java
  36. 56 0
      hutool-socket/src/test/java/cn/hutool/socket/nio/NioServerTest.java
  37. 1 1
      hutool-system/pom.xml
  38. 1 1
      pom.xml

+ 11 - 2
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修复#
 
 -------------------------------------------------------------------------------------------------------------
 

+ 4 - 4
README.md

@@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
 <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
-    <version>5.3.11</version>
+    <version>5.4.0</version>
 </dependency>
 ```
 
 ### 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平台没有测试,不能保证所有工具类或工具方法可用。

+ 1 - 1
bin/version.txt

@@ -1 +1 @@
-5.3.11
+5.4.0

+ 1 - 1
docs/js/version.js

@@ -1 +1 @@
-var version = '5.3.11'
+var version = '5.4.0'

+ 1 - 1
hutool-all/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-all</artifactId>

+ 1 - 1
hutool-aop/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-aop</artifactId>

+ 1 - 1
hutool-bloomFilter/pom.xml

@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-bloomFilter</artifactId>

+ 1 - 1
hutool-bom/pom.xml

@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-bom</artifactId>

+ 1 - 1
hutool-cache/pom.xml

@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-cache</artifactId>

+ 1 - 1
hutool-captcha/pom.xml

@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-captcha</artifactId>

+ 1 - 1
hutool-core/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-core</artifactId>

+ 5 - 1
hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java

@@ -2419,7 +2419,11 @@ public class StrUtil {
 
 	/**
 	 * 将对象转为字符串<br>
-	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+	 *
+	 * <pre>
+	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
+	 * 2、对象数组会调用Arrays.toString方法
+	 * </pre>
 	 *
 	 * @param obj 对象
 	 * @return 字符串

+ 1 - 1
hutool-cron/pom.xml

@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-cron</artifactId>

+ 1 - 1
hutool-crypto/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-crypto</artifactId>

+ 1 - 1
hutool-db/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-db</artifactId>

+ 1 - 1
hutool-dfa/pom.xml

@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-dfa</artifactId>

+ 1 - 1
hutool-extra/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-extra</artifactId>

+ 1 - 1
hutool-http/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-http</artifactId>

+ 1 - 1
hutool-json/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-json</artifactId>

+ 1 - 1
hutool-log/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-log</artifactId>

+ 1 - 1
hutool-poi/pom.xml

@@ -8,7 +8,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-poi</artifactId>

+ 1 - 1
hutool-script/pom.xml

@@ -8,7 +8,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-script</artifactId>

+ 1 - 1
hutool-setting/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-setting</artifactId>

+ 1 - 1
hutool-socket/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-socket</artifactId>

+ 10 - 15
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.");
 
 		// 接收客户端连接

+ 38 - 0
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<ServerSocketChannel, NioServer> {
+
+	@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);
+	}
+
+}

+ 17 - 0
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);
+}

+ 111 - 141
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 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 host 服务器地址
+	 * @param port 端口
+	 */
+	public NioClient(String host, int port) {
+		init(new InetSocketAddress(host, port));
+	}
 
-    /**
-     * 初始化
-     *
-     * @param address 地址和端口
-     * @return this
-     */
-    public NioClient init(InetSocketAddress address) {
-        try {
-            //创建一个SocketChannel对象,配置成非阻塞模式
-            this.channel = SocketChannel.open();
-            channel.configureBlocking(false);
+	/**
+	 * 构造
+	 *
+	 * @param address 服务器地址
+	 */
+	public NioClient(InetSocketAddress address) {
+		init(address);
+	}
 
-            //创建一个选择器,并把SocketChannel交给selector对象
-            this.selector = Selector.open();
-            channel.register(selector, SelectionKey.OP_CONNECT);
+	/**
+	 * 初始化
+	 *
+	 * @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;
+	}
 
-            //发起建立连接的请求,这里会立即返回,当连接建立完成后,SocketChannel就会被选取出来
-            channel.connect(address);
-        } catch (IOException e) {
-            throw new IORuntimeException(e);
-        }
-        return this;
-    }
+	/**
+	 * 开始监听
+	 */
+	public void listen() {
+		ThreadUtil.execute(() -> {
+			try {
+				doListen();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		});
+	}
 
 	/**
-	 * 检查连接是否建立完成
+	 * 开始监听
+	 *
+	 * @throws IOException IO异常
 	 */
-    public boolean waitConnect() throws IOException {
-    	boolean isConnect = false;
-		while (0 != this.selector.select()) {
+	private void doListen() throws IOException {
+		while (this.selector.isOpen() && 0 != this.selector.select()) {
+			// 返回已选择键的集合
 			final Iterator<SelectionKey> 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<SelectionKey> 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);
-        }
-    }
+	/**
+	 * 处理SelectionKey
+	 *
+	 * @param key SelectionKey
+	 */
+	private void handle(SelectionKey key) {
+		// 读事件就绪
+		if (key.isReadable()) {
+			final SocketChannel socketChannel = (SocketChannel) key.channel();
+			read(socketChannel);
+		}
+	}
 
-    /**
-     * 处理读事件<br>
-     * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息
-     *
-     * @param socketChannel SocketChannel
-     */
-    protected abstract void read(SocketChannel socketChannel);
+	/**
+	 * 处理读事件<br>
+	 * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息
+	 *
+	 * @param socketChannel SocketChannel
+	 */
+	protected abstract void read(SocketChannel socketChannel);
 
-    /**
-     * 实现写逻辑<br>
-     * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息
-     *
-     * @param datas 发送的数据
-     * @return this
-     */
-    public NioClient write(ByteBuffer... datas) {
-        try {
-            this.channel.write(datas);
-        } catch (IOException e) {
-            throw new IORuntimeException(e);
-        }
-        return this;
-    }
+	/**
+	 * 实现写逻辑<br>
+	 * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息
+	 *
+	 * @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();
+	/**
+	 * 获取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);
-		closeListen();
-    }
+	@Override
+	public void close() {
+		IoUtil.close(this.selector);
+		IoUtil.close(this.channel);
+	}
 }

+ 44 - 72
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,24 +48,53 @@ 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();
+	}
+
+	/**
 	 * 开始监听
 	 */
 	public void 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<SelectionKey> 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);
 	}
-
-	/**
-	 * 处理读事件<br>
-	 * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息
-	 * 
-	 * @param socketChannel SocketChannel
-	 */
-	protected abstract void read(SocketChannel socketChannel);
-
-	/**
-	 * 实现写逻辑<br>
-	 * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息
-	 * 
-	 * @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);
-		}
-	}
 }

+ 37 - 0
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);
+		}
+	}
+}

+ 0 - 67
hutool-socket/src/test/java/cn/hutool/socket/NioClientTest.java

@@ -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));
-            }
-        }
-    }
-}

+ 0 - 82
hutool-socket/src/test/java/cn/hutool/socket/NioServerTest.java

@@ -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);
-        }
-    }
-}

+ 4 - 7
hutool-socket/src/test/java/cn/hutool/socket/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) {

+ 3 - 6
hutool-socket/src/test/java/cn/hutool/socket/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 {
 	

+ 59 - 0
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));
+			}
+		}
+	}
+}

+ 56 - 0
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);
+		}
+	}
+}

+ 1 - 1
hutool-system/pom.xml

@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
-		<version>5.3.11-SNAPSHOT</version>
+		<version>5.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hutool-system</artifactId>

+ 1 - 1
pom.xml

@@ -8,7 +8,7 @@
 
 	<groupId>cn.hutool</groupId>
 	<artifactId>hutool-parent</artifactId>
-	<version>5.3.11-SNAPSHOT</version>
+	<version>5.4.0-SNAPSHOT</version>
 	<name>hutool</name>
 	<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
 	<url>https://github.com/looly/hutool</url>