From d94db1375706c02df283b614fbc55423574a641d Mon Sep 17 00:00:00 2001 From: looly Date: Tue, 12 Feb 2019 14:58:51 +0000 Subject: [PATCH 01/17] fix sh and prepare 4.5.0 --- CHANGELOG.md | 7 + README.md | 8 +- bin/replaceVersion.sh | 34 +++ bin/update_version.sh | 18 ++ bin/version.txt | 1 + docs/index.html | 454 ------------------------------------- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- 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-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 83 insertions(+), 477 deletions(-) create mode 100644 bin/replaceVersion.sh create mode 100644 bin/version.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 08cc1b2a5d..0d176c4139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ------------------------------------------------------------------------------------------------------------- +## 4.5.0 + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 4.4.5 ### 新特性 diff --git a/README.md b/README.md index f2b8592e90..5d22416698 100644 --- a/README.md +++ b/README.md @@ -86,21 +86,21 @@ Hutool是Hu + tool的自造词,谐音“糊涂”,寓意追求“万事都 cn.hutool hutool-all - 4.4.5 + 4.5.0 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:4.4.5' +compile 'cn.hutool:hutool-all:4.5.0' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/4.4.5/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/4.4.5/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/4.5.0/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/4.5.0/) > 注意 > Hutool只支持JDK7+,对应Android平台没有测试,部分方法并不支持。 diff --git a/bin/replaceVersion.sh b/bin/replaceVersion.sh new file mode 100644 index 0000000000..2157a23632 --- /dev/null +++ b/bin/replaceVersion.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +#----------------------------------------------------------- +# 此脚本用于每次升级Hutool时替换相应位置的版本号 +#----------------------------------------------------------- + +set -o errexit + +pwd=`pwd` + +echo "当前路径:${pwd}" + +if [ -n "$1" ];then + new_version="$1" + old_version=`cat ${pwd}/bin/version.txt` + echo "$old_version 替换为新版本 $new_version" +else + # 参数错误,退出 + echo "ERROR: 请指定新版本!" + exit +fi + +if [ ! -n "$old_version" ]; then + echo "ERROR: 旧版本不存在,请确认bin/version.txt中信息正确" + exit +fi + +# 替换README.md中的版本 +sed -i "s/${old_version}/${new_version}/g" $pwd/README.md +# 替换docs/index.html中的版本 +sed -i "s/${old_version}/${new_version}/g" $pwd/docs/index.html + +# 保留新版本号 +echo "$new_version" > $pwd/bin/version.txt diff --git a/bin/update_version.sh b/bin/update_version.sh index b2975c62d8..1bd49d8650 100644 --- a/bin/update_version.sh +++ b/bin/update_version.sh @@ -1,3 +1,21 @@ #!/bin/bash +#------------------------------------------------ +# 升级Hutool版本,包括: +# 1. 升级pom.xml中的版本号 +# 2. 替换README.md和docs中的版本号 +#------------------------------------------------ + +if [ ! -n "$1" ]; then + echo "ERROR: 新版本不存在,请指定参数1" + exit +fi + +# 替换所有模块pom.xml中的版本 mvn versions:set -DnewVersion=$1 + +# 不带-SNAPSHOT的版本号,用于替换其它地方 +version=${1%-SNAPSHOT} + +# 替换其它地方的版本 +`pwd`/bin/replaceVersion.sh "$version" diff --git a/bin/version.txt b/bin/version.txt new file mode 100644 index 0000000000..a84947d6ff --- /dev/null +++ b/bin/version.txt @@ -0,0 +1 @@ +4.5.0 diff --git a/docs/index.html b/docs/index.html index a33cfb7b4e..e69de29bb2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,454 +0,0 @@ - - - - - - - - - - - - - - - - - - Hutool — A set of tools that keep Java sweet. - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-
-

- Hutool - v4.4.5 -

-

A set of tools that keep Java sweet.

- -
-
-
-
-
- - -
- -
- -
-
- -
- -
- -
-

Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。

-

Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。

-
    -
  • Web开发
  • -
  • 与其它框架无耦合
  • -
  • 高度可替换
  • -
-
- -
-

Hutool的设计思想是尽量减少重复的定义,让项目中的util这个package尽量少,总的来说有如下的几个思想:

-
    -
  • 方法优先于对象
  • -
  • 自动识别优于用户定义
  • -
  • 便捷性与灵活性并存
  • -
  • 适配与兼容
  • -
  • 可选依赖原则
  • -
  • 无侵入原则
  • -
-
- -
-
Maven:在项目的pom.xml的dependencies中加入以下内容:
-
-									<dependency>
-									      <groupId>cn.hutool</groupId>
-									      <artifactId>hutool-all</artifactId>
-									      <version>4.4.5</version>
-									  </dependency>
-								
-
Gradle:
-
-									compile 'cn.hutool:hutool-all:4.4.5'
-								
-

- 从Maven安装 -

-
- -
- -
- -
-
- -
-
- -
-
- - - - Watch Video -
-
-
-

Hutool 是什么

-

Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类

-
-
-
-
-

日期工具

-

通过DateUtil类,提供高度便捷的日期访问、处理和转换方式。

-
-
-
-
-
-

HTTP客户端

-

通过HttpUtil对HTTP客户端的封装,实现便捷的HTTP请求,并简化文件上传操作。

-
-
-
-
-
-

转换工具

-

通过Convert类中的相应静态方法,提供一整套的类型转换解决方案,并通过ConverterRegistry工厂类自定义转换。

-
-
- -
-
-
-

配置文件工具(Setting)

-

通过Setting对象,提供兼容Properties文件的更加强大的配置文件工具,用于解决中文、分组等JDK配置文件存在的诸多问题。

-
-
-
-
-
-

日志工具

-

Hutool的日志功能,通过抽象Log接口,提供对Slf4j、LogBack、Log4j、JDK-Logging的全面兼容支持。

-
-
-
-
-
-

JDBC工具类(DB模块)

-

通过db模块,提供对MySQL、Oracle等关系型数据库的JDBC封装,借助ActiveRecord思想,大大简化数据库操作。

-
-
-
-

Hutool的更多功能,期待你的探索:

-

- 参考教程 - API 文档 -

-
- -
- -
- - -
-
-
-

开发团队

-

我们不是一个人在战斗

-
-
-
- -

路小磊

-

二手Java码农,Python和前端爱好者

-

一个非职业的码农,混迹于非IT圈子,利用8小时之外做自己喜欢的事情,爱前端,爱数码,爱美女。

- -
- -
-
-
- -

深山码农

-

崇拜自由的生活和善良的人性

-

深山耕耘互金行业多年,熟悉互金系统架构和设计,喜欢研究新技术,善于发现和解决问题

- -
-
-
-
- -

Chinaboy

-

相信自己,明天会更好

-

一个奔波于IT圈子的程序猿,拥有自己的梦想,喜欢美女、喜欢音乐、爱打篮球儿...

- -
-
-
-
- -

汪汪90

-

悲观的乐观主义者

-

Java程序员一枚,喜欢从生活中领悟技术,喜欢关注技术细节,ennio morricone 音乐的死忠粉。

- -
-
-
-
- -

普辉辉

-

java码农,爱技术、爱旅游

-

java码农,爱技术、爱旅游、一直活跃在互联网技术圈。
 

- -
-
-
-
- - -
-
-
-

加入讨论

-

通过以下方式加入讨论,或为Hutool添砖加瓦

-
-
-
- - 871141901 -
- -
- - Gitee Issues -
- -
- -
-
- - -
-
-
-

赞助商

-

为Hutool提供赞助,也许他们也会为你提供更优惠的服务

-
-
-
-
-
- - -
-
-
-

友情链接

-

为Hutool提供各种帮助和支持的朋友们,我们一起共奋进

-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-

- © 2017 Hutool Project. All Rights Reserved.
- Designed by Looly, Hosted by Coding Pages. -

-
-
-
    -
  • -
  • -
  • -
-
-
-
-
-
- -
- - -
- -
- - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index a86f9cf4d3..f4250eabfc 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index f9b73e4c7e..d97f72d6dd 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 4e2f9fa1b9..017a93c2a8 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-bloomFilter diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index c2386b2c19..345d433979 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 6a70a383ca..c50d6bdf0c 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 9c414db63d..16770f9d11 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 68c2cbcfe2..2bea0ccacb 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 49bc38018d..069b9dc040 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index eb5ce7ef9c..8aea75ddac 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 53fe3ee544..e5d21982c7 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 6e6e3c9312..8d74aaa906 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index cadf2948ff..3b379b0c39 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 74aaa17b28..c10349da35 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 278fcf1c1e..8689f716c5 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6f05ba2bef..309e90bb6f 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index bf50b4bf32..fded6631ed 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index e6b4d71c43..c1e39ca0c5 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-setting diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index b1556768fe..fb1e7764cb 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 2a7b1e6998..85c8a7b3ea 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 4.4.5 + 4.5.0-SNAPSHOT hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From 46711ff768f6e1fc79ec7c77c5aa225830a171da Mon Sep 17 00:00:00 2001 From: looly Date: Tue, 12 Feb 2019 16:13:18 +0000 Subject: [PATCH 02/17] add socket module --- bin/replaceVersion.sh | 12 +- hutool-socket/pom.xml | 26 +++ .../main/java/cn/hutool/socket/NioClient.java | 83 +++++++++ .../main/java/cn/hutool/socket/NioServer.java | 174 ++++++++++++++++++ .../java/cn/hutool/socket}/Operation.java | 2 +- .../java/cn/hutool/socket/aio/AioServer.java | 130 +++++++++++++ .../cn/hutool/socket/aio/package-info.java | 7 + .../java/cn/hutool/socket/package-info.java | 7 + pom.xml | 1 + 9 files changed, 435 insertions(+), 7 deletions(-) create mode 100644 hutool-socket/pom.xml create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/NioClient.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/NioServer.java rename {hutool-core/src/main/java/cn/hutool/core/net => hutool-socket/src/main/java/cn/hutool/socket}/Operation.java (91%) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/package-info.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/package-info.java diff --git a/bin/replaceVersion.sh b/bin/replaceVersion.sh index 2157a23632..3c79fdd2fc 100644 --- a/bin/replaceVersion.sh +++ b/bin/replaceVersion.sh @@ -11,18 +11,18 @@ pwd=`pwd` echo "当前路径:${pwd}" if [ -n "$1" ];then - new_version="$1" + new_version="$1" old_version=`cat ${pwd}/bin/version.txt` - echo "$old_version 替换为新版本 $new_version" + echo "$old_version 替换为新版本 $new_version" else # 参数错误,退出 - echo "ERROR: 请指定新版本!" - exit + echo "ERROR: 请指定新版本!" + exit fi if [ ! -n "$old_version" ]; then - echo "ERROR: 旧版本不存在,请确认bin/version.txt中信息正确" - exit + echo "ERROR: 旧版本不存在,请确认bin/version.txt中信息正确" + exit fi # 替换README.md中的版本 diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml new file mode 100644 index 0000000000..8e152ca82c --- /dev/null +++ b/hutool-socket/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + jar + + + cn.hutool + hutool-parent + 4.5.0-SNAPSHOT + + + hutool-socket + ${project.artifactId} + Hutool套接字,包括BIO、NIO、AIO封装 + + + + cn.hutool + hutool-core + ${project.parent.version} + + + diff --git a/hutool-socket/src/main/java/cn/hutool/socket/NioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/NioClient.java new file mode 100644 index 0000000000..b38171d139 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/NioClient.java @@ -0,0 +1,83 @@ +package cn.hutool.socket; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import cn.hutool.core.io.IORuntimeException; + +/** + * NIO客户端 + * + * @author looly + * @since 4.4.5 + */ +public class NioClient { + + private SocketChannel channel; + + /** + * 构造 + * + * @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 { + this.channel = SocketChannel.open(address); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 处理读事件
+ * 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传世来的消息 + * + * @param buffer 服务端数据存储缓存 + */ + public NioClient read(ByteBuffer buffer) { + try { + this.channel.read(buffer); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 实现写逻辑
+ * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息 + * + * @param datas 发送的数据 + */ + public NioClient write(ByteBuffer... datas) { + try { + this.channel.write(datas); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/NioServer.java b/hutool-socket/src/main/java/cn/hutool/socket/NioServer.java new file mode 100644 index 0000000000..db30f897e2 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/NioServer.java @@ -0,0 +1,174 @@ +package cn.hutool.socket; + +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; +import java.nio.channels.SocketChannel; +import java.util.Iterator; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + +/** + * 基于NIO的Socket服务端实现 + * + * @author looly + * + */ +public abstract class NioServer implements Closeable { + + private Selector selector; + private ServerSocketChannel serverSocketChannel; + + /** + * 构造 + * + * @param port 端口 + */ + public NioServer(int port) { + init(new InetSocketAddress(port)); + } + + /** + * 初始化 + * + * @param address 地址和端口 + * @return this + */ + public NioServer init(InetSocketAddress address) { + try { + // 打开服务器套接字通道 + this.serverSocketChannel = ServerSocketChannel.open(); + // 设置为非阻塞状态 + serverSocketChannel.configureBlocking(false); + // 获取通道相关联的套接字 + final ServerSocket serverSocket = serverSocketChannel.socket(); + // 绑定端口号 + serverSocket.bind(address); + + // 打开一个选择器 + selector = Selector.open(); + // 服务器套接字注册到Selector中 并指定Selector监控连接事件 + serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + return this; + } + + /** + * 开始监听 + */ + public void listen() { + try { + doListen(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 开始监听 + * + * @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) { + // 有客户端接入此服务端 + 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); + } + + // 读事件就绪 + 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); + } + } + + @Override + public void close() throws IOException { + 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); + // 注册通道 + channel.register(selector, ops.getValue()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/Operation.java b/hutool-socket/src/main/java/cn/hutool/socket/Operation.java similarity index 91% rename from hutool-core/src/main/java/cn/hutool/core/net/Operation.java rename to hutool-socket/src/main/java/cn/hutool/socket/Operation.java index 21140cd11f..57696910cf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/Operation.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/Operation.java @@ -1,4 +1,4 @@ -package cn.hutool.core.net; +package cn.hutool.socket; import java.nio.channels.SelectionKey; 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 new file mode 100644 index 0000000000..8a7e6b41c5 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioServer.java @@ -0,0 +1,130 @@ +package cn.hutool.socket.aio; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousChannelGroup; +import java.nio.channels.AsynchronousServerSocketChannel; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import cn.hutool.core.io.BufferUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 基于AIO的Socket服务端实现 + * + * @author looly + * + */ +public class AioServer implements Closeable { + + AsynchronousServerSocketChannel channel; + + /** + * 构造 + * + * @param port 端口 + */ + public AioServer(int port) { + init(new InetSocketAddress(port)); + } + + /** + * 初始化 + * + * @param address 地址和端口 + * @return this + */ + public AioServer init(InetSocketAddress address) { + ExecutorService threadPool = Executors.newFixedThreadPool(20); + AsynchronousChannelGroup group; + try { + group = AsynchronousChannelGroup.withThreadPool(threadPool); + this.channel = AsynchronousServerSocketChannel.open(group); + this.channel.bind(address); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 开始监听 + */ + public void listen() { + try { + doListen(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 开始监听 + * + * @throws IOException IO异常 + */ + private void doListen() throws IOException { + this.channel.accept(null, new CompletionHandler() { + + @Override + public void completed(AsynchronousSocketChannel result, Void attachment) { + channel.accept(attachment, this); + try { + Console.log(result.getRemoteAddress().toString()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + final ByteBuffer buffer = ByteBuffer.allocate(2); + while(true) { + result.read(buffer, null, new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + buffer.flip(); + byte[] readBytes = BufferUtil.readBytes(buffer); + Console.log(StrUtil.str(readBytes, CharsetUtil.CHARSET_UTF_8)); + } + + @Override + public void failed(Throwable exc, Void attachment) { + exc.printStackTrace(); + } + }); + } + } + + @Override + public void failed(Throwable exc, Void attachment) { + exc.printStackTrace(); + } + }); + } + + @Override + public void close() throws IOException { + IoUtil.close(this.channel); + } + + public static void main(String[] args) { + final AioServer aioServer = new AioServer(8899); + ThreadUtil.execute(new Runnable() { + + @Override + public void run() { + aioServer.listen(); + } + }); + Console.log("####"); + } +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/package-info.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/package-info.java new file mode 100644 index 0000000000..57339acf3b --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/package-info.java @@ -0,0 +1,7 @@ +/** + * AIO相关封装 + * + * @author looly + * + */ +package cn.hutool.socket.aio; \ No newline at end of file diff --git a/hutool-socket/src/main/java/cn/hutool/socket/package-info.java b/hutool-socket/src/main/java/cn/hutool/socket/package-info.java new file mode 100644 index 0000000000..c6e6affbb3 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/package-info.java @@ -0,0 +1,7 @@ +/** + * Socket套接字相关工具类封装 + * + * @author looly + * + */ +package cn.hutool.socket; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 85c8a7b3ea..d24a72b6c7 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ hutool-json hutool-poi hutool-captcha + hutool-socket From 67fd8da5ec4403674e5a95cf545bf93a065faef9 Mon Sep 17 00:00:00 2001 From: looly Date: Wed, 13 Feb 2019 16:32:31 +0000 Subject: [PATCH 03/17] add code for aio --- .../java/cn/hutool/core/io/BufferUtil.java | 44 ++++- .../main/java/cn/hutool/core/io/IoUtil.java | 56 +++++- .../hutool/core/thread/ExecutorBuilder.java | 19 ++- .../main/java/cn/hutool/cron/Scheduler.java | 3 +- hutool-socket/pom.xml | 5 + .../java/cn/hutool/socket/SocketUtil.java | 46 +++++ .../cn/hutool/socket/aio/AcceptHandler.java | 36 ++++ .../java/cn/hutool/socket/aio/AioServer.java | 159 +++++++++++------- .../cn/hutool/socket/aio/ReadHandler.java | 50 ++++++ .../java/cn/hutool/socket/AioServerTest.java | 10 ++ 10 files changed, 351 insertions(+), 77 deletions(-) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java create mode 100644 hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java index 4d132e7601..ce20ece10c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java @@ -3,6 +3,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; /** @@ -66,6 +67,29 @@ public static ByteBuffer copy(ByteBuffer src, int srcStart, ByteBuffer dest, int return dest; } + /** + * 读取剩余部分并转为UTF-8编码字符串 + * + * @param buffer ByteBuffer + * @return 字符串 + * @since 4.5.0 + */ + public static String readUtf8Str(ByteBuffer buffer) { + return readStr(buffer, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 读取剩余部分并转为字符串 + * + * @param buffer ByteBuffer + * @param charset 编码 + * @return 字符串 + * @since 4.5.0 + */ + public static String readStr(ByteBuffer buffer, Charset charset) { + return StrUtil.str(readBytes(buffer), charset); + } + /** * 读取剩余部分bytes
* @@ -89,14 +113,14 @@ public static byte[] readBytes(ByteBuffer buffer) { */ public static byte[] readBytes(ByteBuffer buffer, int maxLength) { final int remaining = buffer.remaining(); - if(maxLength > remaining) { + if (maxLength > remaining) { maxLength = remaining; } byte[] ab = new byte[maxLength]; buffer.get(ab); return ab; } - + /** * 读取指定区间的数据 * @@ -110,7 +134,7 @@ public static byte[] readBytes(ByteBuffer buffer, int start, int end) { System.arraycopy(buffer.array(), start, bs, 0, bs.length); return bs; } - + /** * 一行的末尾位置,查找位置时位移ByteBuffer到结束位置 * @@ -124,6 +148,7 @@ public static int lineEnd(ByteBuffer buffer) { /** * 一行的末尾位置,查找位置时位移ByteBuffer到结束位置
* 支持的换行符如下: + * *
 	 * 1. \r\n
 	 * 2. \n
@@ -149,23 +174,24 @@ public static int lineEnd(ByteBuffer buffer, int maxLength) {
 				// 只有\r无法确认换行
 				canEnd = false;
 			}
-			
+
 			if (charIndex - primitivePosition > maxLength) {
-				//查找到尽头,未找到,还原位置
+				// 查找到尽头,未找到,还原位置
 				buffer.position(primitivePosition);
 				throw new IndexOutOfBoundsException(StrUtil.format("Position is out of maxLength: {}", maxLength));
 			}
 		}
-		
-		//查找到buffer尽头,未找到,还原位置
+
+		// 查找到buffer尽头,未找到,还原位置
 		buffer.position(primitivePosition);
-		//读到结束位置
+		// 读到结束位置
 		return -1;
 	}
 
 	/**
 	 * 读取一行,如果buffer中最后一部分并非完整一行,则返回null
* 支持的换行符如下: + * *
 	 * 1. \r\n
 	 * 2. \n
@@ -185,7 +211,7 @@ public static String readLine(ByteBuffer buffer, Charset charset) {
 		} else if (endPosition == startPosition) {
 			return StrUtil.EMPTY;
 		}
-		
+
 		return null;
 	}
 }
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
index 3d5018f35c..552e4125d5 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
@@ -52,7 +52,7 @@
 public class IoUtil {
 
 	/** 默认缓存大小 */
-	public static final int DEFAULT_BUFFER_SIZE = 1024;
+	public static final int DEFAULT_BUFFER_SIZE = 2048;
 	/** 默认中等缓存大小 */
 	public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 4096;
 	/** 默认大缓存大小 */
@@ -224,6 +224,33 @@ public static long copy(FileInputStream in, FileOutputStream out) throws IORunti
 		}
 	}
 
+	/**
+	 * 拷贝流,使用NIO,不会关闭流
+	 * 
+	 * @param in {@link ReadableByteChannel}
+	 * @param out {@link WritableByteChannel}
+	 * @return 拷贝的字节数
+	 * @throws IORuntimeException IO异常
+	 * @since 4.5.0
+	 */
+	public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
+		return copy(in, out, DEFAULT_BUFFER_SIZE);
+	}
+
+	/**
+	 * 拷贝流,使用NIO,不会关闭流
+	 * 
+	 * @param in {@link ReadableByteChannel}
+	 * @param out {@link WritableByteChannel}
+	 * @param bufferSize 缓冲大小,如果小于等于0,使用默认
+	 * @return 拷贝的字节数
+	 * @throws IORuntimeException IO异常
+	 * @since 4.5.0
+	 */
+	public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
+		return copy(in, out, bufferSize, null);
+	}
+
 	/**
 	 * 拷贝流,使用NIO,不会关闭流
 	 * 
@@ -384,6 +411,20 @@ public static String read(InputStream in, Charset charset) throws IORuntimeExcep
 		return null == charset ? out.toString() : out.toString(charset);
 	}
 
+	/**
+	 * 从流中读取内容,读取完毕后并不关闭流
+	 * 
+	 * @param channel 可读通道,读取完毕后并不关闭通道
+	 * @param charset 字符集
+	 * @return 内容
+	 * @throws IORuntimeException IO异常
+	 * @since 4.5.0
+	 */
+	public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
+		FastByteArrayOutputStream out = read(channel);
+		return null == charset ? out.toString() : out.toString(charset);
+	}
+
 	/**
 	 * 从流中读取内容,读到输出流中
 	 * 
@@ -397,6 +438,19 @@ public static FastByteArrayOutputStream read(InputStream in) throws IORuntimeExc
 		return out;
 	}
 
+	/**
+	 * 从流中读取内容,读到输出流中
+	 * 
+	 * @param channel 可读通道,读取完毕后并不关闭通道
+	 * @return 输出流
+	 * @throws IORuntimeException IO异常
+	 */
+	public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
+		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
+		copy(channel, Channels.newChannel(out));
+		return out;
+	}
+
 	/**
 	 * 从Reader中读取String,读取完毕后并不关闭Reader
 	 * 
diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java b/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java
index b69397ab6f..f92582b2a0 100644
--- a/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java
@@ -74,7 +74,7 @@ public ExecutorBuilder setKeepAliveTime(long keepAliveTime) {
 	 * 设置队列,用于存在未执行的线程
* 可选队列有: *
-	 * 1. SynchronousQueue    它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程
+	 * 1. SynchronousQueue    它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
 	 * 2. LinkedBlockingQueue 无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maximumPoolSize无效
 	 * 3. ArrayBlockingQueue  有界队列,相对无界队列有利于控制队列大小,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
 	 * 
@@ -88,13 +88,26 @@ public ExecutorBuilder setWorkQueue(BlockingQueue workQueue) { } /** - * 使用{@link SynchronousQueue} 做为等待队列 + * 使用{@link SynchronousQueue} 做为等待队列(非公平策略)
+ * 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略 * * @return this * @since 4.1.11 */ public ExecutorBuilder useSynchronousQueue() { - return setWorkQueue(new SynchronousQueue()); + return useSynchronousQueue(false); + } + + /** + * 使用{@link SynchronousQueue} 做为等待队列
+ * 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略 + * + * @param fair 是否使用公平访问策略 + * @return this + * @since 4.5.0 + */ + public ExecutorBuilder useSynchronousQueue(boolean fair) { + return setWorkQueue(new SynchronousQueue(fair)); } /** diff --git a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java index c7531e62b9..05e221e89d 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java @@ -71,7 +71,7 @@ public class Scheduler { protected TaskExecutorManager taskExecutorManager; /** 监听管理器列表 */ protected TaskListenerManager listenerManager = new TaskListenerManager(); - /** 线程池 */ + /** 线程池,用于执行TaskLauncher和TaskExecutor */ protected ExecutorService threadExecutor; // --------------------------------------------------------- Getters and Setters start @@ -363,6 +363,7 @@ public Scheduler start() { throw new CronException("Schedule is started!"); } + // 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有现成避免线程重复创建 this.threadExecutor = ExecutorBuilder.create().useSynchronousQueue().setThreadFactory(// ThreadFactoryBuilder.create().setNamePrefix("hutool-cron-").setDaemon(this.daemon).build()// ).build(); diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 8e152ca82c..01b3c78e7c 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -22,5 +22,10 @@ hutool-core ${project.parent.version} + + cn.hutool + hutool-log + ${project.parent.version} + diff --git a/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java b/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java new file mode 100644 index 0000000000..39f3a41e63 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java @@ -0,0 +1,46 @@ +package cn.hutool.socket; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.ClosedChannelException; + +import cn.hutool.core.io.IORuntimeException; + +/** + * Socket相关工具类 + * + * @author looly + * @since 4.5.0 + */ +public class SocketUtil { + + /** + * 获取远程端的地址信息,包括host和端口
+ * null表示channel为null或者远程主机未连接 + * + * @param channel {@link AsynchronousSocketChannel} + * @return 远程端的地址信息,包括host和端口,null表示channel为null或者远程主机未连接 + */ + public static SocketAddress getRemoteAddress(AsynchronousSocketChannel channel) { + try { + return (null == channel) ? null : channel.getRemoteAddress(); + } catch (ClosedChannelException e) { + // Channel未打开或已关闭,返回null表示未连接 + return null; + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 远程主机是否处于连接状态
+ * 通过判断远程地址获取成功与否判断 + * + * @param channel {@link AsynchronousSocketChannel} + * @return 远程主机是否处于连接状态 + */ + public static boolean isConnected(AsynchronousSocketChannel channel) { + return null != getRemoteAddress(channel); + } +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java new file mode 100644 index 0000000000..f1ee47ad9b --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java @@ -0,0 +1,36 @@ +package cn.hutool.socket.aio; + +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; + +import cn.hutool.log.StaticLog; +import cn.hutool.socket.SocketUtil; + +/** + * 接入完成回调 + * + * @author looly + * + */ +public class AcceptHandler implements CompletionHandler { + + @Override + public void completed(AsynchronousSocketChannel socketChannel, AioServer aioServer) { + //继续等待接入 + aioServer.accept(); + + // 处理接入后的相关动作 + StaticLog.debug("客户端 {} 接入。", SocketUtil.getRemoteAddress(socketChannel)); + + // 处理读 + final ByteBuffer readBuffer = ByteBuffer.allocate(5); + socketChannel.read(readBuffer, readBuffer, new ReadHandler(socketChannel)); + } + + @Override + public void failed(Throwable exc, AioServer attachment) { + StaticLog.error(exc); + } + +} 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 8a7e6b41c5..b958094284 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,23 +1,15 @@ package cn.hutool.socket.aio; -import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.ByteBuffer; +import java.net.SocketOption; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; -import java.nio.channels.AsynchronousSocketChannel; -import java.nio.channels.CompletionHandler; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import cn.hutool.core.io.BufferUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; /** * 基于AIO的Socket服务端实现 @@ -25,9 +17,11 @@ * @author looly * */ -public class AioServer implements Closeable { +public class AioServer { - AsynchronousServerSocketChannel channel; + private AsynchronousChannelGroup group; + private AsynchronousServerSocketChannel channel; + private AcceptHandler acceptHandler; /** * 构造 @@ -45,86 +39,125 @@ public AioServer(int port) { * @return this */ public AioServer init(InetSocketAddress address) { - ExecutorService threadPool = Executors.newFixedThreadPool(20); - AsynchronousChannelGroup group; + + // TODO 需要自定义线程池大小 + ExecutorService threadPool = Executors.newFixedThreadPool(2); try { - group = AsynchronousChannelGroup.withThreadPool(threadPool); - this.channel = AsynchronousServerSocketChannel.open(group); - this.channel.bind(address); + this.group = AsynchronousChannelGroup.withThreadPool(threadPool); + this.channel = AsynchronousServerSocketChannel.open(group).bind(address); } catch (IOException e) { throw new IORuntimeException(e); } + this.acceptHandler = new AcceptHandler(); + return this; } /** * 开始监听 + * + * @param sync 是否阻塞 + */ + public void start() { + start(false); + } + + /** + * 开始监听 + * + * @param sync 是否阻塞 */ - public void listen() { + public void start(boolean sync) { try { - doListen(); + doStart(sync); } catch (IOException e) { throw new IORuntimeException(e); } } /** - * 开始监听 - * + * 设置 Socket 的 Option 选项
+ * 选项见:{@link java.net.StandardSocketOptions} + * + * @param 选项泛型 + * @param name {@link SocketOption} 枚举 + * @param value SocketOption参数 * @throws IOException IO异常 */ - private void doListen() throws IOException { - this.channel.accept(null, new CompletionHandler() { + public void setOption(SocketOption name, T value) throws IOException { + this.channel.setOption(name, value); + } - @Override - public void completed(AsynchronousSocketChannel result, Void attachment) { - channel.accept(attachment, this); - try { - Console.log(result.getRemoteAddress().toString()); - } catch (IOException e) { - throw new IORuntimeException(e); - } + /** + * 获取{@link AsynchronousServerSocketChannel} + * + * @return {@link AsynchronousServerSocketChannel} + */ + public AsynchronousServerSocketChannel getChannel() { + return this.channel; + } - final ByteBuffer buffer = ByteBuffer.allocate(2); - while(true) { - result.read(buffer, null, new CompletionHandler() { - - @Override - public void completed(Integer result, Void attachment) { - buffer.flip(); - byte[] readBytes = BufferUtil.readBytes(buffer); - Console.log(StrUtil.str(readBytes, CharsetUtil.CHARSET_UTF_8)); - } - - @Override - public void failed(Throwable exc, Void attachment) { - exc.printStackTrace(); - } - }); - } - } + /** + * 处理接入的客户端 + * + * @return this + */ + public AioServer accept() { + this.channel.accept(this, this.acceptHandler); + return this; + } - @Override - public void failed(Throwable exc, Void attachment) { - exc.printStackTrace(); - } - }); + /** + * 服务是否开启状态 + * + * @return 服务是否开启状态 + */ + public boolean isOpen() { + return (null == this.channel) ? false : this.channel.isOpen(); } - @Override - public void close() throws IOException { + /** + * 关闭服务 + */ + public void close() { IoUtil.close(this.channel); + + if(null != this.group && false == this.group.isShutdown()) { + try { + this.group.shutdownNow(); + } catch (IOException e) { + //ignore + } + } + + // 结束阻塞 + synchronized (this) { + this.notify(); + } } - public static void main(String[] args) { - final AioServer aioServer = new AioServer(8899); - ThreadUtil.execute(new Runnable() { + // ------------------------------------------------------------------------------------- Private method start + /** + * 开始监听 + * + * @param sync 是否阻塞 + * @throws IOException IO异常 + */ + private void doStart(boolean sync) throws IOException { + + // 接收客户端连接 + accept(); - @Override - public void run() { - aioServer.listen(); + if (sync) { + // 阻塞当前线程,保证在main方法中执行不被退出 + synchronized (this) { + try { + this.wait(); + } catch (InterruptedException e) { + // ignore + } } - }); - Console.log("####"); + } } + // ------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java new file mode 100644 index 0000000000..eda3ee07c5 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java @@ -0,0 +1,50 @@ +package cn.hutool.socket.aio; + +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; + +import cn.hutool.core.io.BufferUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.log.StaticLog; + +/** + * 读取完成回调 + * + * @author looly + * + */ +public class ReadHandler implements CompletionHandler { + + AsynchronousSocketChannel socketChannel; + + public ReadHandler(AsynchronousSocketChannel socketChannel) { + this.socketChannel = socketChannel; + } + + @Override + public void completed(Integer result, ByteBuffer buffer) { + if(result < 0) { + // 客户端关闭 + return; + } + + buffer.flip();//读模式 + + +// StaticLog.debug("客户端消息:[{}] {}", result, BufferUtil.readUtf8Str(buffer)); + Console.print(BufferUtil.readUtf8Str(buffer)); + + // 继续读取 + if(this.socketChannel.isOpen()) { + buffer.clear(); + socketChannel.read(buffer, buffer, this); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + StaticLog.error(exc); + } + +} diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java new file mode 100644 index 0000000000..58d6a0159a --- /dev/null +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java @@ -0,0 +1,10 @@ +package cn.hutool.socket; + +import cn.hutool.socket.aio.AioServer; + +public class AioServerTest { + public static void main(String[] args) { + AioServer aioServer = new AioServer(8899); + aioServer.start(true); + } +} From 15ea61d69b6eb73f559cf6d9f49e7e01802ef6bf Mon Sep 17 00:00:00 2001 From: looly Date: Thu, 14 Feb 2019 01:55:33 +0000 Subject: [PATCH 04/17] fix bc import bug --- hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java | 4 +--- .../main/java/cn/hutool/socket/{ => nio}/NioClient.java | 2 +- .../main/java/cn/hutool/socket/{ => nio}/NioServer.java | 2 +- .../main/java/cn/hutool/socket/{ => nio}/Operation.java | 2 +- .../src/main/java/cn/hutool/socket/nio/package-info.java | 7 +++++++ 5 files changed, 11 insertions(+), 6 deletions(-) rename hutool-socket/src/main/java/cn/hutool/socket/{ => nio}/NioClient.java (93%) rename hutool-socket/src/main/java/cn/hutool/socket/{ => nio}/NioServer.java (95%) rename hutool-socket/src/main/java/cn/hutool/socket/{ => nio}/Operation.java (91%) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/nio/package-info.java diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index c7a9f89a0e..cbb01cac49 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -34,8 +34,6 @@ import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; -import org.bouncycastle.math.ec.ECCurve; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; @@ -662,7 +660,7 @@ public static PublicKey decodeECPoint(String encode, String curveName) { */ public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) { final org.bouncycastle.jce.spec.ECNamedCurveParameterSpec namedSpec = org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(curveName); - final ECCurve curve = namedSpec.getCurve(); + final org.bouncycastle.math.ec.ECCurve curve = namedSpec.getCurve(); final EllipticCurve ecCurve = new EllipticCurve(// new ECFieldFp(curve.getField().getCharacteristic()), // curve.getA().toBigInteger(), // diff --git a/hutool-socket/src/main/java/cn/hutool/socket/NioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java similarity index 93% rename from hutool-socket/src/main/java/cn/hutool/socket/NioClient.java rename to hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java index b38171d139..ebe9470a0f 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/NioClient.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioClient.java @@ -1,4 +1,4 @@ -package cn.hutool.socket; +package cn.hutool.socket.nio; import java.io.IOException; import java.net.InetSocketAddress; diff --git a/hutool-socket/src/main/java/cn/hutool/socket/NioServer.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java similarity index 95% rename from hutool-socket/src/main/java/cn/hutool/socket/NioServer.java rename to hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java index db30f897e2..c5c002e3c2 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/NioServer.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java @@ -1,4 +1,4 @@ -package cn.hutool.socket; +package cn.hutool.socket.nio; import java.io.Closeable; import java.io.IOException; diff --git a/hutool-socket/src/main/java/cn/hutool/socket/Operation.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java similarity index 91% rename from hutool-socket/src/main/java/cn/hutool/socket/Operation.java rename to hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java index 57696910cf..e231d54be8 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/Operation.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java @@ -1,4 +1,4 @@ -package cn.hutool.socket; +package cn.hutool.socket.nio; import java.nio.channels.SelectionKey; diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/package-info.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/package-info.java new file mode 100644 index 0000000000..fee9128e1b --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/package-info.java @@ -0,0 +1,7 @@ +/** + * NIO相关封装 + * + * @author looly + * + */ +package cn.hutool.socket.nio; \ No newline at end of file From 2947db079b088448649ac0afa1ad0d29de9bd24c Mon Sep 17 00:00:00 2001 From: looly Date: Thu, 14 Feb 2019 02:17:09 +0000 Subject: [PATCH 05/17] add BCUtil --- CHANGELOG.md | 3 + .../main/java/cn/hutool/crypto/BCUtil.java | 78 +++++++++++++++++++ .../main/java/cn/hutool/crypto/KeyUtil.java | 28 +------ 3 files changed, 84 insertions(+), 25 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d176c4139..70ee782f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ ## 4.5.0 ### 新特性 +* 【socket】 增加Socket模块 + ### Bug修复 +* 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java new file mode 100644 index 0000000000..74e056090b --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java @@ -0,0 +1,78 @@ +package cn.hutool.crypto; + +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; + +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.ECPointUtil; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; +import org.bouncycastle.math.ec.ECCurve; + +/** + * Bouncy Castle相关工具类封装 + * + * @author looly + *@since 4.5.0 + */ +public class BCUtil { + /** + * 编码压缩EC公钥(基于BouncyCastle)
+ * 见:https://www.cnblogs.com/xinzhao/p/8963724.html + * + * @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey + * @return 压缩得到的X + * @since 4.4.4 + */ + public static byte[] encodeECPublicKey(PublicKey publicKey) { + return ((BCECPublicKey) publicKey).getQ().getEncoded(true); + } + + /** + * 解码恢复EC压缩公钥,支持Base64和Hex编码,(基于BouncyCastle)
+ * 见:https://www.cnblogs.com/xinzhao/p/8963724.html + * + * @param encode 压缩公钥 + * @param curveName EC曲线名 + * @since 4.4.4 + */ + public static PublicKey decodeECPoint(String encode, String curveName) { + return decodeECPoint(SecureUtil.decodeKey(encode), curveName); + } + + /** + * 解码恢复EC压缩公钥,支持Base64和Hex编码,(基于BouncyCastle)
+ * 见:https://www.cnblogs.com/xinzhao/p/8963724.html + * + * @param encodeByte 压缩公钥 + * @param curveName EC曲线名 + * @since 4.4.4 + */ + public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) { + final ECNamedCurveParameterSpec namedSpec = ECNamedCurveTable.getParameterSpec(curveName); + final ECCurve curve = namedSpec.getCurve(); + final EllipticCurve ecCurve = new EllipticCurve(// + new ECFieldFp(curve.getField().getCharacteristic()), // + curve.getA().toBigInteger(), // + curve.getB().toBigInteger()); + // 根据X恢复点Y + final ECPoint point = ECPointUtil.decodePoint(ecCurve, encodeByte); + + // 根据曲线恢复公钥格式 + ECParameterSpec ecSpec = new ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN()); + + final KeyFactory PubKeyGen = KeyUtil.getKeyFactory("EC"); + try { + return PubKeyGen.generatePublic(new ECPublicKeySpec(point, ecSpec)); + } catch (GeneralSecurityException e) { + throw new CryptoException(e); + } + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index cbb01cac49..3be6de1d63 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,7 +1,6 @@ package cn.hutool.crypto; import java.io.InputStream; -import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -17,11 +16,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.ECFieldFp; import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.EllipticCurve; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @@ -635,7 +630,7 @@ public static Certificate getCertificate(KeyStore keyStore, String alias) { * @since 4.4.4 */ public static byte[] encodeECPublicKey(PublicKey publicKey) { - return ((org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey) publicKey).getQ().getEncoded(true); + return BCUtil.encodeECPublicKey(publicKey); } /** @@ -647,7 +642,7 @@ public static byte[] encodeECPublicKey(PublicKey publicKey) { * @since 4.4.4 */ public static PublicKey decodeECPoint(String encode, String curveName) { - return decodeECPoint(SecureUtil.decodeKey(encode), curveName); + return BCUtil.decodeECPoint(encode, curveName); } /** @@ -659,23 +654,6 @@ public static PublicKey decodeECPoint(String encode, String curveName) { * @since 4.4.4 */ public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) { - final org.bouncycastle.jce.spec.ECNamedCurveParameterSpec namedSpec = org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(curveName); - final org.bouncycastle.math.ec.ECCurve curve = namedSpec.getCurve(); - final EllipticCurve ecCurve = new EllipticCurve(// - new ECFieldFp(curve.getField().getCharacteristic()), // - curve.getA().toBigInteger(), // - curve.getB().toBigInteger()); - // 根据X恢复点Y - final ECPoint point = org.bouncycastle.jce.ECPointUtil.decodePoint(ecCurve, encodeByte); - - // 根据曲线恢复公钥格式 - java.security.spec.ECParameterSpec ecSpec = new org.bouncycastle.jce.spec.ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN()); - - final KeyFactory PubKeyGen = getKeyFactory("EC"); - try { - return PubKeyGen.generatePublic(new ECPublicKeySpec(point, ecSpec)); - } catch (GeneralSecurityException e) { - throw new CryptoException(e); - } + return BCUtil.decodeECPoint(encodeByte, curveName); } } From e16cb8fce4c94e5366894f7f12ed9070f5568f20 Mon Sep 17 00:00:00 2001 From: looly Date: Fri, 15 Feb 2019 04:01:52 +0000 Subject: [PATCH 06/17] add code for socket --- CHANGELOG.md | 1 + .../java/cn/hutool/core/date/DatePattern.java | 3 +- .../java/cn/hutool/core/date/DateUtil.java | 3 +- .../cn/hutool/core/date/DateUtilTest.java | 6 + .../cn/hutool/socket/aio/AcceptHandler.java | 7 +- .../java/cn/hutool/socket/aio/AioServer.java | 34 ++++- .../java/cn/hutool/socket/aio/AioSession.java | 143 ++++++++++++++++++ .../java/cn/hutool/socket/aio/IoAction.java | 20 +++ .../java/cn/hutool/socket/aio/Protocol.java | 43 ++++++ .../cn/hutool/socket/aio/ReadHandler.java | 34 ++--- .../java/cn/hutool/socket/AioServerTest.java | 26 +++- 11 files changed, 286 insertions(+), 34 deletions(-) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ee782f14..8c79eaa6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Bug修复 * 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 +* 【core】 修正DateUtil.formatHttpDate方法 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java b/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java index 280ee6d1a9..06e515abc6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java @@ -1,5 +1,6 @@ package cn.hutool.core.date; +import java.util.Locale; import java.util.TimeZone; import cn.hutool.core.date.format.FastDateFormat; @@ -68,7 +69,7 @@ public class DatePattern { /** HTTP头中日期时间格式:EEE, dd MMM yyyy HH:mm:ss z */ public final static String HTTP_DATETIME_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z"; /** HTTP头中日期时间格式 {@link FastDateFormat}:EEE, dd MMM yyyy HH:mm:ss z */ - public final static FastDateFormat HTTP_DATETIME_FORMAT = FastDateFormat.getInstance(HTTP_DATETIME_PATTERN); + public final static FastDateFormat HTTP_DATETIME_FORMAT = FastDateFormat.getInstance(HTTP_DATETIME_PATTERN, TimeZone.getTimeZone("GMT"), Locale.US); /** JDK中日期时间格式:EEE MMM dd HH:mm:ss zzz yyyy */ public final static String JDK_DATETIME_PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy"; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 17bf464ee5..218b57ea31 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -562,7 +562,8 @@ public static String formatTime(Date date) { } /** - * 格式化为Http的标准日期格式 + * 格式化为Http的标准日期格式
+ * 标准日期格式遵循RFC 1123规范,格式类似于:Fri, 31 Dec 1999 23:59:59 GMT * * @param date 被格式化的日期 * @return HTTP标准形式日期字符串 diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 1e5a4d62bc..9166ca3865 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -453,4 +453,10 @@ public void yearAndQTest() { Assert.assertEquals(1, list2.size()); Assert.assertEquals("20184", list2.get(0)); } + + @Test + public void formatHttpDateTest() { + String formatHttpDate = DateUtil.formatHttpDate(DateUtil.parse("2019-01-02 22:32:01")); + Assert.assertEquals("Wed, 02 Jan 2019 14:32:01 GMT", formatHttpDate); + } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java index f1ee47ad9b..f40229ee05 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java @@ -1,6 +1,5 @@ package cn.hutool.socket.aio; -import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; @@ -22,10 +21,12 @@ public void completed(AsynchronousSocketChannel socketChannel, AioServer aioServ // 处理接入后的相关动作 StaticLog.debug("客户端 {} 接入。", SocketUtil.getRemoteAddress(socketChannel)); + + //创建Session会话 + final AioSession session = new AioSession(socketChannel, aioServer.getIoAction()); // 处理读 - final ByteBuffer readBuffer = ByteBuffer.allocate(5); - socketChannel.read(readBuffer, readBuffer, new ReadHandler(socketChannel)); + session.read(); } @Override 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 b958094284..73a1ace5bc 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 @@ -3,6 +3,7 @@ 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 java.util.concurrent.ExecutorService; @@ -10,6 +11,8 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; /** * 基于AIO的Socket服务端实现 @@ -18,10 +21,12 @@ * */ public class AioServer { + private static final Log log = LogFactory.get(); private AsynchronousChannelGroup group; private AsynchronousServerSocketChannel channel; private AcceptHandler acceptHandler; + private IoAction ioAction; /** * 构造 @@ -84,8 +89,28 @@ public void start(boolean sync) { * @param value SocketOption参数 * @throws IOException IO异常 */ - public void setOption(SocketOption name, T value) throws IOException { + public AioServer setOption(SocketOption name, T value) throws IOException { this.channel.setOption(name, value); + return this; + } + + /** + * 获取IO处理器 + * @return {@link IoAction} + */ + public IoAction getIoAction() { + return this.ioAction; + } + + /** + * 设置IO处理器,单例存在 + * + * @param ioAction {@link IoAction} + * @return this; + */ + public AioServer setIoAction(IoAction ioAction) { + this.ioAction = ioAction; + return this; } /** @@ -121,12 +146,12 @@ public boolean isOpen() { */ public void close() { IoUtil.close(this.channel); - - if(null != this.group && false == this.group.isShutdown()) { + + if (null != this.group && false == this.group.isShutdown()) { try { this.group.shutdownNow(); } catch (IOException e) { - //ignore + // ignore } } @@ -144,6 +169,7 @@ public void close() { * @throws IOException IO异常 */ private void doStart(boolean sync) throws IOException { + log.debug("Aio Server started, waiting for accept."); // 接收客户端连接 accept(); diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java new file mode 100644 index 0000000000..78aca41fd9 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java @@ -0,0 +1,143 @@ +package cn.hutool.socket.aio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + +/** + * AIO会话
+ * 每个客户端对应一个会话对象 + * + * @author looly + * + */ +public class AioSession { + + private AsynchronousSocketChannel channel; + private ByteBuffer readBuffer; + private ByteBuffer writeBuffer; + private IoAction ioAction; + + /** + * 构造 + * + * @param channel {@link AsynchronousSocketChannel} + */ + public AioSession(AsynchronousSocketChannel channel, IoAction ioAction) { + this.channel = channel; + this.readBuffer = ByteBuffer.allocate(IoUtil.DEFAULT_BUFFER_SIZE); + this.writeBuffer = ByteBuffer.allocate(IoUtil.DEFAULT_BUFFER_SIZE); + this.ioAction = ioAction; + } + + /** + * 获取{@link AsynchronousSocketChannel} + * + * @return {@link AsynchronousSocketChannel} + */ + public AsynchronousSocketChannel getChannel() { + return this.channel; + } + + /** + * 获取读取Buffer + * + * @return 读取Buffer + */ + public ByteBuffer getReadBuffer() { + return this.readBuffer; + } + + /** + * 获取写Buffer + * + * @return 写Buffer + */ + public ByteBuffer getWriteBuffer() { + return this.writeBuffer; + } + + /** + * 获取消息处理器 + * + * @return {@link IoAction} + */ + public IoAction getIoAction() { + return this.ioAction; + } + + /** + * 读取数据到Buffer + * + * @return this + */ + public AioSession read() { + if (isOpen()) { + this.readBuffer.clear(); + this.channel.read(this.readBuffer, this, new ReadHandler()); + } + return this; + } + + /** + * 写数据到目标端 + * + * @return this + */ + public AioSession write(ByteBuffer data) { + this.channel.write(data); + return this; + } + + /** + * 会话是否打开状态
+ * 当Socket保持连接时会话始终打开 + * + * @return 会话是否打开状态 + */ + public boolean isOpen() { + return (null == this.channel) ? false : this.channel.isOpen(); + } + + /** + * 关闭输出 + * @return this + */ + public AioSession closeIn() { + if(null != this.channel) { + try { + this.channel.shutdownInput(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + return this; + } + + /** + * 关闭输出 + * @return this + */ + public AioSession closeOut() { + if(null != this.channel) { + try { + this.channel.shutdownOutput(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + return this; + } + + /** + * 关闭会话 + * @return this + */ + public AioSession close() { + IoUtil.close(this.channel); + return this; + } +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java new file mode 100644 index 0000000000..216352e9d4 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java @@ -0,0 +1,20 @@ +package cn.hutool.socket.aio; + +/** + * Socket流处理接口
+ * 实现此接口用于处理接收到的消息,发送指定消息 + * + * @author looly + * + * @param 经过解码器解码后的数据类型 + */ +public interface IoAction { + + /** + * 执行数据处理 + * + * @param session Socket Session会话 + * @param data 解码后的数据 + */ + public void doAction(AioSession session, T data); +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java new file mode 100644 index 0000000000..d50e50a73a --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, org.smartboot. All rights reserved. + * project name: smart-socket + * file name: Protocol.java + * Date: 2017-11-25 + * Author: sandao + */ + +package cn.hutool.socket.aio; + +import java.nio.ByteBuffer; + +/** + * 协议接口
+ * 通过实现此接口完成消息的编码和解码 + * + *

+ * 所有Socket使用相同协议对象,类成员变量和对象成员变量易造成并发读写问题。 + *

+ * + * @author Looly + */ +public interface Protocol { + /** + * 编码数据用于写出 + * + * @param session 本次需要解码的session + * @param writeBuffer 待处理的读buffer + * @param data 写出的数据 + * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 + */ + ByteBuffer encode(AioSession session, ByteBuffer writeBuffer, T data); + + /** + * 对于从Socket流中获取到的数据采用当前Protocol的实现类协议进行解析。 + * + * + * @param session 本次需要解码的session + * @param readBuffer 待处理的读buffer + * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 + */ + T decode(AioSession session, ByteBuffer readBuffer); +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java index eda3ee07c5..42623fff20 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java @@ -1,11 +1,8 @@ package cn.hutool.socket.aio; import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; -import cn.hutool.core.io.BufferUtil; -import cn.hutool.core.lang.Console; import cn.hutool.log.StaticLog; /** @@ -14,36 +11,25 @@ * @author looly * */ -public class ReadHandler implements CompletionHandler { - - AsynchronousSocketChannel socketChannel; - - public ReadHandler(AsynchronousSocketChannel socketChannel) { - this.socketChannel = socketChannel; - } +public class ReadHandler implements CompletionHandler { @Override - public void completed(Integer result, ByteBuffer buffer) { - if(result < 0) { + public void completed(Integer result, AioSession session) { + if (result < 0) { // 客户端关闭 return; } - - buffer.flip();//读模式 - - -// StaticLog.debug("客户端消息:[{}] {}", result, BufferUtil.readUtf8Str(buffer)); - Console.print(BufferUtil.readUtf8Str(buffer)); - + + final ByteBuffer readBuffer = session.getReadBuffer(); + readBuffer.flip();// 读模式 + session.getIoAction().doAction(session, readBuffer); + // 继续读取 - if(this.socketChannel.isOpen()) { - buffer.clear(); - socketChannel.read(buffer, buffer, this); - } + session.read(); } @Override - public void failed(Throwable exc, ByteBuffer attachment) { + public void failed(Throwable exc, AioSession attachment) { StaticLog.error(exc); } diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java index 58d6a0159a..cfc1124ad4 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java @@ -1,10 +1,34 @@ package cn.hutool.socket; +import java.nio.ByteBuffer; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.socket.aio.AioServer; +import cn.hutool.socket.aio.AioSession; +import cn.hutool.socket.aio.IoAction; public class AioServerTest { public static void main(String[] args) { AioServer aioServer = new AioServer(8899); - aioServer.start(true); + aioServer.setIoAction(new IoAction() { + + @Override + public void doAction(AioSession session, ByteBuffer data) { + Console.log(data); + + StringBuilder response = StrUtil.builder()// + .append("HTTP/1.1 200 OK\r\n")// + .append("Date: ").append(DateUtil.formatHttpDate(DateUtil.date())).append("\r\n")// + .append("Content-Type: text/html; charset=UTF-8\r\n")// + .append("\r\n") + .append("Hello Hutool socket");// + + session.write(ByteBuffer.wrap(response.toString().getBytes(CharsetUtil.CHARSET_UTF_8))); + session.closeOut(); + } + }).start(true); } } From a813d475e4fc250b0f3a1e6dd96c9cd1013b38af Mon Sep 17 00:00:00 2001 From: looly Date: Fri, 15 Feb 2019 08:36:36 +0000 Subject: [PATCH 07/17] fix test --- .../java/cn/hutool/socket/aio/AioServer.java | 12 ++--------- .../java/cn/hutool/socket/AioServerTest.java | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 19 deletions(-) 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 73a1ace5bc..50f63c973c 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 @@ -58,15 +58,6 @@ public AioServer init(InetSocketAddress address) { return this; } - /** - * 开始监听 - * - * @param sync 是否阻塞 - */ - public void start() { - start(false); - } - /** * 开始监听 * @@ -93,9 +84,10 @@ public AioServer setOption(SocketOption name, T value) throws IOException this.channel.setOption(name, value); return this; } - + /** * 获取IO处理器 + * * @return {@link IoAction} */ public IoAction getIoAction() { diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java index cfc1124ad4..6e0a82159e 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java @@ -11,7 +11,9 @@ import cn.hutool.socket.aio.IoAction; public class AioServerTest { + public static void main(String[] args) { + AioServer aioServer = new AioServer(8899); aioServer.setIoAction(new IoAction() { @@ -19,15 +21,16 @@ public static void main(String[] args) { public void doAction(AioSession session, ByteBuffer data) { Console.log(data); - StringBuilder response = StrUtil.builder()// - .append("HTTP/1.1 200 OK\r\n")// - .append("Date: ").append(DateUtil.formatHttpDate(DateUtil.date())).append("\r\n")// - .append("Content-Type: text/html; charset=UTF-8\r\n")// - .append("\r\n") - .append("Hello Hutool socket");// - - session.write(ByteBuffer.wrap(response.toString().getBytes(CharsetUtil.CHARSET_UTF_8))); - session.closeOut(); + if(false == data.hasRemaining()) { + StringBuilder response = StrUtil.builder()// + .append("HTTP/1.1 200 OK\r\n")// + .append("Date: ").append(DateUtil.formatHttpDate(DateUtil.date())).append("\r\n")// + .append("Content-Type: text/html; charset=UTF-8\r\n")// + .append("\r\n") + .append("Hello Hutool socket");// + session.write(ByteBuffer.wrap(response.toString().getBytes(CharsetUtil.CHARSET_UTF_8))); + session.closeOut(); + } } }).start(true); } From ddf210523f77918c9cc8fddea66074ed46cebe73 Mon Sep 17 00:00:00 2001 From: looly Date: Tue, 19 Feb 2019 15:39:07 +0000 Subject: [PATCH 08/17] fix bugs --- CHANGELOG.md | 2 + .../java/cn/hutool/core/lang/PatternPool.java | 2 + .../java/cn/hutool/core/lang/Validator.java | 28 ++++++++ .../cn/hutool/core/util/ImageUtilTest.java | 6 +- .../main/java/cn/hutool/crypto/BCUtil.java | 2 +- .../java/cn/hutool/crypto/asymmetric/SM2.java | 65 ++++++++++++++----- .../main/java/cn/hutool/extra/ftp/Ftp.java | 11 +++- .../hutool/extra/qrcode/QrCodeUtilTest.java | 4 +- 8 files changed, 96 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c79eaa6c0..36130bc00c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ ### 新特性 * 【socket】 增加Socket模块 +* 【core】 Validator增加isIpV4方法(issue#IRQ6W@Gitee) ### Bug修复 * 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 * 【core】 修正DateUtil.formatHttpDate方法 +* 【extra】 修复FTP.ls无法遍历文件问题(issue#IRTA3@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java index 379eb19dbd..11c725823b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java @@ -26,6 +26,8 @@ public class PatternPool { public final static Pattern GROUP_VAR = Pattern.compile("\\$(\\d+)"); /** IP v4 */ public final static Pattern IPV4 = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"); + /** IP v4 */ + public final static Pattern IPV6 = Pattern.compile("(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"); /** 货币 */ public final static Pattern MONEY = Pattern.compile("^(\\d+(?:\\.\\d+)?)$"); /** 邮件,符合RFC 5322规范,正则来自:http://emailregex.com/ */ diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java index 968c21d09a..0c3015581b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java @@ -30,6 +30,8 @@ private Validator() { public final static Pattern GROUP_VAR = PatternPool.GROUP_VAR; /** IP v4 */ public final static Pattern IPV4 = PatternPool.IPV4; + /** IP v6 */ + public final static Pattern IPV6 = PatternPool.IPV6; /** 货币 */ public final static Pattern MONEY = PatternPool.MONEY; /** 邮件 */ @@ -810,6 +812,32 @@ public static T validateIpv4(T value, String errorMsg) } return value; } + + /** + * 验证是否为IPV6地址 + * + * @param value 值 + * @return 是否为IPV6地址 + */ + public static boolean isIpv6(CharSequence value) { + return isMactchRegex(IPV6, value); + } + + /** + * 验证是否为IPV6地址 + * + * @param 字符串类型 + * @param value 值 + * @param errorMsg 验证错误的信息 + * @return 验证后的值 + * @throws ValidateException 验证异常 + */ + public static T validateIpv6(T value, String errorMsg) throws ValidateException { + if (false == isIpv6(value)) { + throw new ValidateException(errorMsg); + } + return value; + } /** * 验证是否为MAC地址 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ImageUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ImageUtilTest.java index 7aebfe7f44..84dc855b3e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ImageUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ImageUtilTest.java @@ -34,10 +34,10 @@ public void cutTest() { } @Test - @Ignore +// @Ignore public void rotateTest() throws IOException { - BufferedImage image = ImageUtil.rotate(ImageIO.read(FileUtil.file("e:/line.png")), 45); - ImageUtil.write(image, FileUtil.file("e:/result.png")); + BufferedImage image = ImageUtil.rotate(ImageIO.read(FileUtil.file("e:/pic/366466.jpg")), 180); + ImageUtil.write(image, FileUtil.file("e:/pic/result.png")); } @Test diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java index 74e056090b..cd1d03d525 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java @@ -52,7 +52,7 @@ public static PublicKey decodeECPoint(String encode, String curveName) { * 见:https://www.cnblogs.com/xinzhao/p/8963724.html * * @param encodeByte 压缩公钥 - * @param curveName EC曲线名 + * @param curveName EC曲线名,例如{@link KeyUtil#SM2_DEFAULT_CURVE} * @since 4.4.4 */ public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) { diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index f151613669..436ea00915 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -29,6 +29,9 @@ public class SM2 extends AbstractAsymmetricCrypto { protected SM2Engine engine; protected SM2Signer signer; + private CipherParameters cipherParamsForPublicKey; + private CipherParameters cipherParamsForPrivateKey; + // ------------------------------------------------------------------ Constructor start /** * 构造,生成新的私钥公钥对 @@ -90,9 +93,21 @@ public SM2 init(PrivateKey privateKey, PublicKey publicKey) { return this.init(ALGORITHM_SM2, privateKey, publicKey); } + @Override + protected SM2 init(String algorithm, PrivateKey privateKey, PublicKey publicKey) { + super.init(algorithm, privateKey, publicKey); + return initCipherParams(); + } + // --------------------------------------------------------------------------------- Encrypt /** - * 加密 + * 加密,SM2非对称加密的结果由C1,C2,C3三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C2 密文数据
+	 * C3 SM3的摘要值
+	 * 
* * @param data 被加密的bytes * @param keyType 私钥或公钥 {@link KeyType} @@ -107,7 +122,7 @@ public byte[] encrypt(byte[] data, KeyType keyType) throws CryptoException { } final SM2Engine engine = this.engine; try { - engine.init(true, new ParametersWithRandom(generateCipherParameters(keyType))); + engine.init(true, new ParametersWithRandom(getCipherParameters(keyType))); return engine.processBlock(data, 0, data.length); } catch (Exception e) { throw new CryptoException(e); @@ -133,7 +148,7 @@ public byte[] decrypt(byte[] data, KeyType keyType) throws CryptoException { } final SM2Engine engine = this.engine; try { - engine.init(false, generateCipherParameters(keyType)); + engine.init(false, getCipherParameters(keyType)); return engine.processBlock(data, 0, data.length); } catch (Exception e) { throw new CryptoException(e); @@ -152,7 +167,7 @@ public byte[] decrypt(byte[] data, KeyType keyType) throws CryptoException { public byte[] sign(byte[] data) { return sign(data, null); } - + /** * 用私钥对信息生成数字签名 * @@ -167,7 +182,7 @@ public byte[] sign(byte[] data, byte[] id) { } final SM2Signer signer = this.signer; try { - CipherParameters param = new ParametersWithRandom(generateCipherParameters(KeyType.PrivateKey)); + CipherParameters param = new ParametersWithRandom(getCipherParameters(KeyType.PrivateKey)); if (id != null) { param = new ParametersWithID(param, id); } @@ -180,7 +195,7 @@ public byte[] sign(byte[] data, byte[] id) { lock.unlock(); } } - + /** * 用公钥检验数字签名的合法性 * @@ -207,7 +222,7 @@ public boolean verify(byte[] data, byte[] sign, byte[] id) { } final SM2Signer signer = this.signer; try { - CipherParameters param = generateCipherParameters(KeyType.PublicKey); + CipherParameters param = getCipherParameters(KeyType.PublicKey); if (id != null) { param = new ParametersWithID(param, id); } @@ -223,23 +238,41 @@ public boolean verify(byte[] data, byte[] sign, byte[] id) { // ------------------------------------------------------------------------------------------------------------------------- Private method start /** - * 生成{@link CipherParameters} + * 初始化加密解密参数 * - * @param keyType Key类型枚举,包括私钥或公钥 - * @return {@link CipherParameters} + * @return this */ - private CipherParameters generateCipherParameters(KeyType keyType) { + private SM2 initCipherParams() { try { - switch (keyType) { - case PublicKey: - return ECUtil.generatePublicKeyParameter(this.publicKey); - case PrivateKey: - return ECUtil.generatePrivateKeyParameter(this.privateKey); + if (null != this.publicKey) { + this.cipherParamsForPublicKey = ECUtil.generatePublicKeyParameter(this.publicKey); + } + if (null != privateKey) { + this.cipherParamsForPrivateKey = ECUtil.generatePrivateKeyParameter(this.privateKey); } } catch (InvalidKeyException e) { throw new CryptoException(e); } + + return this; + } + + /** + * 获取密钥类型对应的加密参数对象{@link CipherParameters} + * + * @param keyType Key类型枚举,包括私钥或公钥 + * @return {@link CipherParameters} + */ + private CipherParameters getCipherParameters(KeyType keyType) { + switch (keyType) { + case PublicKey: + return this.cipherParamsForPublicKey; + case PrivateKey: + return this.cipherParamsForPrivateKey; + } + return null; } + // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index c0f47ed00d..354b62628f 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -5,13 +5,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.List; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; @@ -164,11 +164,18 @@ public String pwd() { @Override public List ls(String path) { + FTPFile[] ftpFiles; try { - return CollUtil.toList(this.client.listNames(path)); + ftpFiles = this.client.listFiles(); } catch (IOException e) { throw new FtpException(e); } + + final List fileNames = new ArrayList<>(); + for (FTPFile ftpFile : ftpFiles) { + fileNames.add(ftpFile.getName()); + } + return fileNames; } @Override diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index eb5637778f..e5af44cdd9 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -45,9 +45,9 @@ public void generateWithLogoTest() { } @Test - @Ignore +// @Ignore public void decodeTest() { - String decode = QrCodeUtil.decode(FileUtil.file("e:/1.png")); + String decode = QrCodeUtil.decode(FileUtil.file("e:/pic/qr.png")); Console.log(decode); } } From a789981312a9ced22b34e998cebb651b132d71f0 Mon Sep 17 00:00:00 2001 From: looly Date: Wed, 20 Feb 2019 15:18:28 +0000 Subject: [PATCH 09/17] fx bugs --- CHANGELOG.md | 3 + .../java/cn/hutool/core/io/FileTypeUtil.java | 8 +- .../java/cn/hutool/core/util/NumberUtil.java | 74 ++++ .../main/java/cn/hutool/crypto/KeyUtil.java | 31 +- .../main/java/cn/hutool/crypto/SmUtil.java | 43 ++- .../java/cn/hutool/crypto/asymmetric/SM2.java | 112 ++++-- .../hutool/crypto/asymmetric/SM2Engine.java | 341 ++++++++++++++++++ .../cn/hutool/crypto/digest/Digester.java | 16 +- .../java/cn/hutool/crypto/test/SM2Test.java | 9 +- .../cn/hutool/extra/qrcode/QrCodeUtil.java | 4 +- .../java/cn/hutool/extra/qrcode/QrConfig.java | 4 +- .../java/cn/hutool/socket/SocketUtil.java | 2 +- .../java/cn/hutool/socket/aio/AioServer.java | 9 +- .../java/cn/hutool/socket/aio/AioSession.java | 34 +- .../java/cn/hutool/socket/aio/MsgDecoder.java | 7 + .../cn/hutool/socket/aio/ReadHandler.java | 36 -- 16 files changed, 633 insertions(+), 100 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java delete mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 36130bc00c..be5443686e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,14 @@ ### 新特性 * 【socket】 增加Socket模块 * 【core】 Validator增加isIpV4方法(issue#IRQ6W@Gitee) +* 【crypto】 增加SM2Engine,支持C1C2C3和C1C3C2两种模式 ### Bug修复 * 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 * 【core】 修正DateUtil.formatHttpDate方法 * 【extra】 修复FTP.ls无法遍历文件问题(issue#IRTA3@Gitee) +* 【extra】 修复QrCodeUtil中ratio参数失效问题,调整默认纠错为M(感谢@【上海】皮皮今) +* 【core】 修复FileTypeUtil对jpg文件识别问题(issue#275@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java index 3311de58f9..2f572580a5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java @@ -26,10 +26,10 @@ private FileTypeUtil() { static { fileTypeMap = new ConcurrentHashMap<>(); -// fileTypeMap.put("ffd8ffe000104a464946", "jpg"); // JPEG (jpg) - fileTypeMap.put("ffd8ffe", "jpg"); // JPEG (jpg) - fileTypeMap.put("89504e470d0a1a0a", "png"); // PNG (png) - fileTypeMap.put("47494638396126026f01", "gif"); // GIF (gif) + fileTypeMap.put("ffd8ff", "jpg"); // JPEG (jpg) + fileTypeMap.put("89504e47", "png"); // PNG (png) + fileTypeMap.put("4749463837", "gif"); // GIF (gif) + fileTypeMap.put("4749463839", "gif"); // GIF (gif) fileTypeMap.put("49492a00227105008037", "tif"); // TIFF (tif) fileTypeMap.put("424d228c010000000000", "bmp"); // 16色位图(bmp) fileTypeMap.put("424d8240090000000000", "bmp"); // 24位位图(bmp) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index 45e11867e2..206c33ab0a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -2225,6 +2225,80 @@ public static int toInt(byte[] bytes) { | (bytes[3] & 0xff); } + /** + * 以无符号字节数组的形式返回传入值。 + * + * @param value 需要转换的值 + * @return 无符号bytes + * @since 4.5.0 + */ + public static byte[] toUnsignedByteArray(BigInteger value) { + byte[] bytes = value.toByteArray(); + + if (bytes[0] == 0) { + byte[] tmp = new byte[bytes.length - 1]; + System.arraycopy(bytes, 1, tmp, 0, tmp.length); + + return tmp; + } + + return bytes; + } + + /** + * 以无符号字节数组的形式返回传入值。 + * + * @param length bytes长度 + * @param value 需要转换的值 + * @return 无符号bytes + * @since 4.5.0 + */ + public static byte[] toUnsignedByteArray(int length, BigInteger value) { + byte[] bytes = value.toByteArray(); + if (bytes.length == length) { + return bytes; + } + + int start = bytes[0] == 0 ? 1 : 0; + int count = bytes.length - start; + + if (count > length) { + throw new IllegalArgumentException("standard length exceeded for value"); + } + + byte[] tmp = new byte[length]; + System.arraycopy(bytes, start, tmp, tmp.length - count, count); + return tmp; + } + + /** + * 无符号bytes转{@link BigInteger} + * + * @param buf buf 无符号bytes + * @return {@link BigInteger} + * @since 4.5.0 + */ + public static BigInteger fromUnsignedByteArray(byte[] buf) { + return new BigInteger(1, buf); + } + + /** + * 无符号bytes转{@link BigInteger} + * + * @param buf 无符号bytes + * @param off 起始位置 + * @param length 长度 + * @return {@link BigInteger} + */ + public static BigInteger fromUnsignedByteArray(byte[] buf, int off, int length) { + byte[] mag = buf; + if (off != 0 || length != buf.length) { + mag = new byte[length]; + System.arraycopy(buf, off, mag, 0, length); + } + return new BigInteger(1, mag); + } + // ------------------------------------------------------------------------------------------- Private method start private static int mathSubnode(int selectNum, int minNum) { if (selectNum == minNum) { diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 3be6de1d63..0753e97428 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -14,6 +14,7 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; @@ -597,13 +598,11 @@ public static Certificate readCertificate(String type, InputStream in, char[] pa * @return {@link Certificate} */ public static Certificate readCertificate(String type, InputStream in) { - Certificate certificate; try { - certificate = CertificateFactory.getInstance(type).generateCertificate(in); - } catch (Exception e) { + return getCertificateFactory(type).generateCertificate(in); + } catch (CertificateException e) { throw new CryptoException(e); } - return certificate; } /** @@ -620,6 +619,30 @@ public static Certificate getCertificate(KeyStore keyStore, String alias) { throw new CryptoException(e); } } + + /** + * 获取{@link CertificateFactory} + * + * @param type 类型,例如X.509 + * @return {@link KeyPairGenerator} + * @since 4.5.0 + */ + public static CertificateFactory getCertificateFactory(String type) { + Provider provider = null; + try { + provider = ProviderFactory.createBouncyCastleProvider(); + } catch (NoClassDefFoundError e) { + // ignore + } + + CertificateFactory factory; + try { + factory = (null == provider) ? CertificateFactory.getInstance(type) : CertificateFactory.getInstance(type, provider); + } catch (CertificateException e) { + throw new CryptoException(e); + } + return factory; + } /** * 编码压缩EC公钥(基于BouncyCastle)
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java index 3ae46a3288..ada50b572c 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java @@ -3,6 +3,8 @@ import java.io.File; import java.io.InputStream; +import org.bouncycastle.crypto.params.ECDomainParameters; + import cn.hutool.crypto.asymmetric.SM2; import cn.hutool.crypto.digest.Digester; import cn.hutool.crypto.symmetric.SymmetricCrypto; @@ -18,7 +20,7 @@ public class SmUtil { private static String SM3 = "SM3"; private static String SM4 = "SM4"; - + /** * 创建SM2算法对象
* 生成新的私钥公钥对 @@ -96,7 +98,7 @@ public static String sm3(InputStream data) { public static String sm3(File dataFile) { return new Digester(SM3).digestHex(dataFile); } - + /** * SM4加密,生成随机KEY。注意解密时必须使用相同 {@link SymmetricCrypto}对象或者使用相同KEY
* 例: @@ -127,6 +129,39 @@ public static SymmetricCrypto sm4() { public static SymmetricCrypto sm4(byte[] key) { return new SymmetricCrypto(SM4, key); } - - + + /** + * bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2 + * + * @param c1c2c3 加密后的bytes,顺序为C1C2C3 + * @param ecDomainParameters {@link ECDomainParameters} + * @return 加密后的bytes,顺序为C1C3C2 + */ + public static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3, ECDomainParameters ecDomainParameters) { + // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + final int c1Len = (ecDomainParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; + final int c3Len = 32; // new SM3Digest().getDigestSize(); + byte[] result = new byte[c1c2c3.length]; + System.arraycopy(c1c2c3, 0, result, 0, c1Len); // c1 + System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); // c3 + System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len); // c2 + return result; + } + + /** + * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密 + * + * @param 加密后的bytes,顺序为C1C3C2 + * @return c1c2c3 加密后的bytes,顺序为C1C2C3 + */ + public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2, ECDomainParameters ecDomainParameters) { + // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + final int c1Len = (ecDomainParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; + final int c3Len = 32; // new SM3Digest().getDigestSize(); + byte[] result = new byte[c1c3c2.length]; + System.arraycopy(c1c3c2, 0, result, 0, c1Len); // c1: 0->65 + System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len); // c2 + System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); // c3 + return result; + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 436ea00915..071f40304d 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -5,7 +5,8 @@ import java.security.PublicKey; import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithID; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.signers.SM2Signer; @@ -13,10 +14,12 @@ import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode; /** * 国密SM2算法实现,基于BC库
- * SM2算法只支持公钥加密,私钥解密 + * SM2算法只支持公钥加密,私钥解密
+ * 参考:https://blog.csdn.net/pridas/article/details/86118774 * * @author looly * @since 4.3.2 @@ -29,8 +32,9 @@ public class SM2 extends AbstractAsymmetricCrypto { protected SM2Engine engine; protected SM2Signer signer; - private CipherParameters cipherParamsForPublicKey; - private CipherParameters cipherParamsForPrivateKey; + private SM2Mode mode; + private ECPublicKeyParameters publicKeyParams; + private ECPrivateKeyParameters privateKeyParams; // ------------------------------------------------------------------ Constructor start /** @@ -116,16 +120,16 @@ protected SM2 init(String algorithm, PrivateKey privateKey, PublicKey publicKey) */ @Override public byte[] encrypt(byte[] data, KeyType keyType) throws CryptoException { - lock.lock(); - if (null == this.engine) { - this.engine = new SM2Engine(); + if (KeyType.PublicKey != keyType) { + throw new IllegalArgumentException("Encrypt is only support by public key"); } - final SM2Engine engine = this.engine; + ckeckKey(keyType); + + lock.lock(); + final SM2Engine engine = getEngine(); try { engine.init(true, new ParametersWithRandom(getCipherParameters(keyType))); return engine.processBlock(data, 0, data.length); - } catch (Exception e) { - throw new CryptoException(e); } finally { lock.unlock(); } @@ -142,16 +146,16 @@ public byte[] encrypt(byte[] data, KeyType keyType) throws CryptoException { */ @Override public byte[] decrypt(byte[] data, KeyType keyType) throws CryptoException { - lock.lock(); - if (null == this.engine) { - this.engine = new SM2Engine(); + if (KeyType.PrivateKey != keyType) { + throw new IllegalArgumentException("Decrypt is only support by private key"); } - final SM2Engine engine = this.engine; + ckeckKey(keyType); + + lock.lock(); + final SM2Engine engine = getEngine(); try { engine.init(false, getCipherParameters(keyType)); return engine.processBlock(data, 0, data.length); - } catch (Exception e) { - throw new CryptoException(e); } finally { lock.unlock(); } @@ -177,10 +181,7 @@ public byte[] sign(byte[] data) { */ public byte[] sign(byte[] data, byte[] id) { lock.lock(); - if (null == this.signer) { - this.signer = new SM2Signer(); - } - final SM2Signer signer = this.signer; + final SM2Signer signer = getSigner(); try { CipherParameters param = new ParametersWithRandom(getCipherParameters(KeyType.PrivateKey)); if (id != null) { @@ -217,10 +218,7 @@ public boolean verify(byte[] data, byte[] sign) { */ public boolean verify(byte[] data, byte[] sign, byte[] id) { lock.lock(); - if (null == this.signer) { - this.signer = new SM2Signer(); - } - final SM2Signer signer = this.signer; + final SM2Signer signer = getSigner(); try { CipherParameters param = getCipherParameters(KeyType.PublicKey); if (id != null) { @@ -236,6 +234,20 @@ public boolean verify(byte[] data, byte[] sign, byte[] id) { } } + /** + * 设置加密类型 + * + * @param mode {@link SM2Mode} + * @return this + */ + public SM2 setMode(SM2Mode mode) { + this.mode = mode; + if (null != this.engine) { + this.engine.setMode(mode); + } + return this; + } + // ------------------------------------------------------------------------------------------------------------------------- Private method start /** * 初始化加密解密参数 @@ -245,10 +257,10 @@ public boolean verify(byte[] data, byte[] sign, byte[] id) { private SM2 initCipherParams() { try { if (null != this.publicKey) { - this.cipherParamsForPublicKey = ECUtil.generatePublicKeyParameter(this.publicKey); + this.publicKeyParams = (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(this.publicKey); } if (null != privateKey) { - this.cipherParamsForPrivateKey = ECUtil.generatePrivateKeyParameter(this.privateKey); + this.privateKeyParams = (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(this.privateKey); } } catch (InvalidKeyException e) { throw new CryptoException(e); @@ -266,13 +278,57 @@ private SM2 initCipherParams() { private CipherParameters getCipherParameters(KeyType keyType) { switch (keyType) { case PublicKey: - return this.cipherParamsForPublicKey; + return this.publicKeyParams; case PrivateKey: - return this.cipherParamsForPrivateKey; + return this.privateKeyParams; } return null; } + /** + * 检查对应类型的Key是否存在 + * + * @param keyType key类型 + */ + private void ckeckKey(KeyType keyType) { + switch (keyType) { + case PublicKey: + if (null == this.publicKey) { + throw new NullPointerException("No public key provided"); + } + break; + case PrivateKey: + if (null == this.privateKey) { + throw new NullPointerException("No private key provided"); + } + break; + } + } + + /** + * 获取{@link SM2Engine} + * + * @return {@link SM2Engine} + */ + private SM2Engine getEngine() { + if (null == this.engine) { + this.engine = new SM2Engine(this.mode); + } + return this.engine; + } + + /** + * 获取{@link SM2Signer} + * + * @return {@link SM2Signer} + */ + private SM2Signer getSigner() { + if (null == this.signer) { + this.signer = new SM2Signer(); + } + return this.signer; + } + // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java new file mode 100644 index 0000000000..fd9e96e42d --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java @@ -0,0 +1,341 @@ +package cn.hutool.crypto.asymmetric; + +import java.math.BigInteger; +import java.util.Random; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECMultiplier; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointCombMultiplier; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.Memoable; +import org.bouncycastle.util.Pack; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.CryptoException; + +/** + * SM2加密解密引擎,来自Bouncy Castle库的SM2Engine类改造
+ * SM2加密后的数据格式为(两种模式): + * + *
+ * curve(C1) | data(C2) | digest(C3)
+ * curve(C1) | digest(C3) | data(C2)
+ * 
+ * + * @author looly, bouncycastle + * @since 4.5.0 + */ +public class SM2Engine { + + private final Digest digest; + + private boolean forEncryption; + private ECKeyParameters ecKey; + private ECDomainParameters ecParams; + private int curveLength; + private Random random; + /** 加密解密模式 */ + private SM2Mode mode; + + /** + * 构造 + */ + public SM2Engine() { + this(new SM3Digest()); + } + + /** + * 构造 + * + * @param mode SM2密钥生成模式,可选C1C2C3和C1C3C2 + */ + public SM2Engine(SM2Mode mode) { + this(new SM3Digest(), mode); + } + + /** + * 构造 + * + * @param digest 摘要算法啊 + */ + public SM2Engine(Digest digest) { + this(digest, null); + } + + /** + * 构造 + * + * @param digest 摘要算法啊 + * @param mode SM2密钥生成模式,可选C1C2C3和C1C3C2 + */ + public SM2Engine(Digest digest, SM2Mode mode) { + this.digest = digest; + this.mode = ObjectUtil.defaultIfNull(mode, SM2Mode.C1C2C3); + } + + /** + * 初始化引擎 + * + * @param forEncryption 是否为加密模式 + * @param param {@link CipherParameters},此处应为{@link ParametersWithRandom}(加密时)或{@link ECKeyParameters}(解密时) + */ + public void init(boolean forEncryption, CipherParameters param) { + this.forEncryption = forEncryption; + + if (param instanceof ParametersWithRandom) { + final ParametersWithRandom rParam = (ParametersWithRandom) param; + this.ecKey = (ECKeyParameters) rParam.getParameters(); + this.random = rParam.getRandom(); + } else { + this.ecKey = (ECKeyParameters) param; + } + this.ecParams = this.ecKey.getParameters(); + + if (forEncryption) { + // 检查曲线点 + final ECPoint ecPoint = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH()); + if (ecPoint.isInfinity()) { + throw new IllegalArgumentException("invalid key: [h]Q at infinity"); + } + + // 检查随机参数 + if (null == this.random) { + this.random = CryptoServicesRegistrar.getSecureRandom(); + } + } + + // 曲线位长度 + this.curveLength = (this.ecParams.getCurve().getFieldSize() + 7) / 8; + } + + /** + * 处理块,包括加密和解密 + * + * @param in 数据 + * @param inOff 数据开始位置 + * @param inLen 数据长度 + * @return 结果 + */ + public byte[] processBlock(byte[] in, int inOff, int inLen) { + if (forEncryption) { + return encrypt(in, inOff, inLen); + } else { + return decrypt(in, inOff, inLen); + } + } + + /** + * 设置加密类型 + * + * @param mode {@link SM2Mode} + * @return this + */ + public SM2Engine setMode(SM2Mode mode) { + this.mode = mode; + return this; + } + + /** + * SM2算法模式
+ * 在SM2算法中,C1C2C3为旧标准模式,C1C3C2为新标准模式 + * + * @author looly + * + */ + public static enum SM2Mode { + C1C2C3, C1C3C2; + } + + protected ECMultiplier createBasePointMultiplier() { + return new FixedPointCombMultiplier(); + } + + // --------------------------------------------------------------------------------------------------- Private method start + /** + * 加密 + * + * @param in 数据 + * @param inOff 位置 + * @param inLen 长度 + * @return 密文 + */ + private byte[] encrypt(byte[] in, int inOff, int inLen) { + // 加密数据 + byte[] c2 = new byte[inLen]; + System.arraycopy(in, inOff, c2, 0, c2.length); + + final ECMultiplier multiplier = createBasePointMultiplier(); + + byte[] c1; + ECPoint kPB; + BigInteger k; + do { + k = nextK(); + //产生随机数计算出曲线点C1 + c1 = multiplier.multiply(ecParams.getG(), k).normalize().getEncoded(false); + kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize(); + kdf(digest, kPB, c2); + } while (notEncrypted(c2, in, inOff)); + + // 杂凑值,效验数据 + byte[] c3 = new byte[digest.getDigestSize()]; + + addFieldElement(digest, kPB.getAffineXCoord()); + digest.update(in, inOff, inLen); + addFieldElement(digest, kPB.getAffineYCoord()); + + digest.doFinal(c3, 0); + + // 按照莫属输出结果 + switch (mode) { + case C1C3C2: + return Arrays.concatenate(c1, c3, c2); + default: + return Arrays.concatenate(c1, c2, c3); + } + } + + /** + * 解密,只支持私钥解密 + * + * @param in 密文 + * @param inOff 位置 + * @param inLen 长度 + * @return 解密后的内容 + */ + private byte[] decrypt(byte[] in, int inOff, int inLen) { + // 获取曲线点 + final byte[] c1 = new byte[this.curveLength * 2 + 1]; + System.arraycopy(in, inOff, c1, 0, c1.length); + + ECPoint c1P = this.ecParams.getCurve().decodePoint(c1); + if (c1P.multiply(this.ecParams.getH()).isInfinity()) { + throw new CryptoException("[h]C1 at infinity"); + } + c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize(); + + final int digestSize = this.digest.getDigestSize(); + + // 解密C2数据 + final byte[] c2 = new byte[inLen - c1.length - digestSize]; + + if (SM2Mode.C1C3C2 == this.mode) { + // C2位于第三部分 + System.arraycopy(in, inOff + c1.length + digestSize, c2, 0, c2.length); + } else { + // C2位于第二部分 + System.arraycopy(in, inOff + c1.length, c2, 0, c2.length); + } + kdf(digest, c1P, c2); + + // 使用摘要验证C2数据 + final byte[] c3 = new byte[digestSize]; + + addFieldElement(digest, c1P.getAffineXCoord()); + digest.update(c2, 0, c2.length); + addFieldElement(digest, c1P.getAffineYCoord()); + digest.doFinal(c3, 0); + + int check = 0; + for (int i = 0; i != c3.length; i++) { + check |= c3[i] ^ in[inOff + c1.length + ((SM2Mode.C1C3C2 == this.mode) ? 0 : c2.length) + i]; + } + + Arrays.fill(c1, (byte) 0); + Arrays.fill(c3, (byte) 0); + + if (check != 0) { + Arrays.fill(c2, (byte) 0); + throw new CryptoException("invalid cipher text"); + } + + return c2; + } + + private boolean notEncrypted(byte[] encData, byte[] in, int inOff) { + for (int i = 0; i != encData.length; i++) { + if (encData[i] != in[inOff]) { + return false; + } + } + return true; + } + + /** + * 解密数据 + * + * @param digest {@link Digest} + * @param c1 c1点 + * @param encData 密文 + */ + private void kdf(Digest digest, ECPoint c1, byte[] encData) { + int digestSize = digest.getDigestSize(); + byte[] buf = new byte[Math.max(4, digestSize)]; + int off = 0; + + Memoable memo = null; + Memoable copy = null; + + if (digest instanceof Memoable) { + addFieldElement(digest, c1.getAffineXCoord()); + addFieldElement(digest, c1.getAffineYCoord()); + memo = (Memoable) digest; + copy = memo.copy(); + } + + int ct = 0; + + while (off < encData.length) { + if (memo != null) { + memo.reset(copy); + } else { + addFieldElement(digest, c1.getAffineXCoord()); + addFieldElement(digest, c1.getAffineYCoord()); + } + + Pack.intToBigEndian(++ct, buf, 0); + digest.update(buf, 0, 4); + digest.doFinal(buf, 0); + + int xorLen = Math.min(digestSize, encData.length - off); + xor(encData, buf, off, xorLen); + off += xorLen; + } + } + + private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) { + for (int i = 0; i != dRemaining; i++) { + data[dOff + i] ^= kdfOut[i]; + } + } + + private BigInteger nextK() { + int qBitLength = ecParams.getN().bitLength(); + + BigInteger k; + do { + k = new BigInteger(qBitLength, random); + } while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0); + + return k; + } + + private void addFieldElement(Digest digest, ECFieldElement v) { + byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); + + digest.update(p, 0, p.length); + } + // --------------------------------------------------------------------------------------------------- Private method start +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java index 6be4111b6e..45f24c91f6 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java @@ -168,17 +168,17 @@ public String digestHex(File file) { * @return 摘要bytes */ public byte[] digest(byte[] data) { - if(null != this.salt) { + if (null != this.salt) { digest.update(this.salt); } int digestCount = Math.max(1, this.digestCount); byte[] result = data; - for(int i = 0; i < digestCount; i++) { + for (int i = 0; i < digestCount; i++) { result = doDigest(result); } return result; } - + /** * 生成摘要 * @@ -278,4 +278,14 @@ public String digestHex(InputStream data, int bufferLength) { public MessageDigest getDigest() { return digest; } + + /** + * 获取散列长度,0表示不支持此方法 + * + * @return 散列长度,0表示不支持此方法 + * @since 4.5.0 + */ + public int getDigestLength() { + return this.digest.getDigestLength(); + } } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java index e213bb3194..3345b6b79e 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java @@ -15,6 +15,7 @@ import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; +import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode; /** * SM2算法单元测试 @@ -30,11 +31,9 @@ public void generateKeyPairTest() { Assert.assertNotNull(pair.getPrivate()); Assert.assertNotNull(pair.getPublic()); } - + @Test public void KeyPairOIDTest() { - new SM2();//保证BC被加载 - // OBJECT IDENTIFIER 1.2.156.10197.1.301 String OID = "06082A811CCF5501822D"; KeyPair pair = SecureUtil.generateKeyPair("SM2"); @@ -49,6 +48,7 @@ public void sm2CustomKeyTest() { byte[] publicKey = pair.getPublic().getEncoded(); SM2 sm2 = SmUtil.sm2(privateKey, publicKey); + sm2.setMode(SM2Mode.C1C3C2); // 公钥加密,私钥解密 byte[] encrypt = sm2.encrypt(StrUtil.bytes("我是一段测试aaaa", CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey); @@ -126,7 +126,7 @@ public void sm2SignAndVerifyUseKeyTest() { boolean verify = sm2.verify(content.getBytes(), sign); Assert.assertTrue(verify); } - + @Test public void sm2PublicKeyEncodeDecodeTest() { KeyPair pair = SecureUtil.generateKeyPair("SM2"); @@ -139,5 +139,4 @@ public void sm2PublicKeyEncodeDecodeTest() { Assert.assertEquals(HexUtil.encodeHexStr(publicKey.getEncoded()), HexUtil.encodeHexStr(Hexdecode.getEncoded())); Assert.assertEquals(HexUtil.encodeHexStr(publicKey.getEncoded()), HexUtil.encodeHexStr(B64decode.getEncoded())); } - } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index 1db552e9a0..aab6b15092 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -180,10 +180,10 @@ public static BufferedImage generate(String content, BarcodeFormat format, QrCon int height; // 按照最短的边做比例缩放 if (qrWidth < qrHeight) { - width = qrWidth / 6; + width = qrWidth / config.ratio; height = logoImg.getHeight(null) * width / logoImg.getWidth(null); } else { - height = qrHeight / 6; + height = qrHeight / config.ratio; width = logoImg.getWidth(null) * height / logoImg.getHeight(null); } ImageUtil.pressImage(image, logoImg, new Rectangle(width, height), 1); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java index 8652553c16..3cf990bf2c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java @@ -34,13 +34,13 @@ public class QrConfig { /** 边距1~4 */ protected Integer margin = 2; /** 纠错级别 */ - protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.L; + protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.M; /** 编码 */ protected Charset charset = CharsetUtil.CHARSET_UTF_8; /** 二维码中的Logo */ protected Image img; /** 二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5 */ - protected int ratio; + protected int ratio = 6; /** * 创建QrConfig diff --git a/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java b/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java index 39f3a41e63..0cd856ebb6 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/SocketUtil.java @@ -14,7 +14,7 @@ * @since 4.5.0 */ public class SocketUtil { - + /** * 获取远程端的地址信息,包括host和端口
* null表示channel为null或者远程主机未连接 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 50f63c973c..5616de2ef6 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 @@ -6,11 +6,10 @@ import java.nio.ByteBuffer; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; @@ -46,9 +45,11 @@ public AioServer(int port) { public AioServer init(InetSocketAddress address) { // TODO 需要自定义线程池大小 - ExecutorService threadPool = Executors.newFixedThreadPool(2); try { - this.group = AsynchronousChannelGroup.withThreadPool(threadPool); + this.group = AsynchronousChannelGroup.withFixedThreadPool(// + 2, //默认线程池大小 + ThreadFactoryBuilder.create().setNamePrefix("Huool-socket-").build()// + ); this.channel = AsynchronousServerSocketChannel.open(group).bind(address); } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java index 78aca41fd9..d6814953bd 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java @@ -3,9 +3,11 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.log.StaticLog; /** * AIO会话
@@ -59,7 +61,7 @@ public ByteBuffer getReadBuffer() { public ByteBuffer getWriteBuffer() { return this.writeBuffer; } - + /** * 获取消息处理器 * @@ -77,7 +79,22 @@ public IoAction getIoAction() { public AioSession read() { if (isOpen()) { this.readBuffer.clear(); - this.channel.read(this.readBuffer, this, new ReadHandler()); + this.channel.read(this.readBuffer, this, new CompletionHandler() { + + @Override + public void completed(Integer result, AioSession session) { + readBuffer.flip();// 读模式 + ioAction.doAction(session, readBuffer); + + // 继续读取 + session.read(); + } + + @Override + public void failed(Throwable exc, AioSession attachment) { + StaticLog.error(exc); + } + }); } return this; } @@ -101,13 +118,14 @@ public AioSession write(ByteBuffer data) { public boolean isOpen() { return (null == this.channel) ? false : this.channel.isOpen(); } - + /** * 关闭输出 + * * @return this */ public AioSession closeIn() { - if(null != this.channel) { + if (null != this.channel) { try { this.channel.shutdownInput(); } catch (IOException e) { @@ -116,13 +134,14 @@ public AioSession closeIn() { } return this; } - + /** * 关闭输出 + * * @return this */ public AioSession closeOut() { - if(null != this.channel) { + if (null != this.channel) { try { this.channel.shutdownOutput(); } catch (IOException e) { @@ -131,9 +150,10 @@ public AioSession closeOut() { } return this; } - + /** * 关闭会话 + * * @return this */ public AioSession close() { diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java new file mode 100644 index 0000000000..84ced74b60 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java @@ -0,0 +1,7 @@ +package cn.hutool.socket.aio; + +import java.nio.ByteBuffer; + +public interface MsgDecoder{ + T decode(ByteBuffer buffer); +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java deleted file mode 100644 index 42623fff20..0000000000 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.hutool.socket.aio; - -import java.nio.ByteBuffer; -import java.nio.channels.CompletionHandler; - -import cn.hutool.log.StaticLog; - -/** - * 读取完成回调 - * - * @author looly - * - */ -public class ReadHandler implements CompletionHandler { - - @Override - public void completed(Integer result, AioSession session) { - if (result < 0) { - // 客户端关闭 - return; - } - - final ByteBuffer readBuffer = session.getReadBuffer(); - readBuffer.flip();// 读模式 - session.getIoAction().doAction(session, readBuffer); - - // 继续读取 - session.read(); - } - - @Override - public void failed(Throwable exc, AioSession attachment) { - StaticLog.error(exc); - } - -} From c3a7e1fbd11e62fc9b8f670075a5760ee4609fb2 Mon Sep 17 00:00:00 2001 From: looly Date: Wed, 20 Feb 2019 15:27:33 +0000 Subject: [PATCH 10/17] add protocol --- .../java/cn/hutool/socket/aio/MsgDecoder.java | 7 --- .../java/cn/hutool/socket/aio/Protocol.java | 43 ------------------- .../cn/hutool/socket/protocol/MsgDecoder.java | 24 +++++++++++ .../cn/hutool/socket/protocol/MsgEncoder.java | 24 +++++++++++ .../cn/hutool/socket/protocol/Protocol.java | 23 ++++++++++ .../hutool/socket/protocol/package-info.java | 7 +++ 6 files changed, 78 insertions(+), 50 deletions(-) delete mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java delete mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgDecoder.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/protocol/Protocol.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/protocol/package-info.java diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java deleted file mode 100644 index 84ced74b60..0000000000 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/MsgDecoder.java +++ /dev/null @@ -1,7 +0,0 @@ -package cn.hutool.socket.aio; - -import java.nio.ByteBuffer; - -public interface MsgDecoder{ - T decode(ByteBuffer buffer); -} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java deleted file mode 100644 index d50e50a73a..0000000000 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/Protocol.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2017, org.smartboot. All rights reserved. - * project name: smart-socket - * file name: Protocol.java - * Date: 2017-11-25 - * Author: sandao - */ - -package cn.hutool.socket.aio; - -import java.nio.ByteBuffer; - -/** - * 协议接口
- * 通过实现此接口完成消息的编码和解码 - * - *

- * 所有Socket使用相同协议对象,类成员变量和对象成员变量易造成并发读写问题。 - *

- * - * @author Looly - */ -public interface Protocol { - /** - * 编码数据用于写出 - * - * @param session 本次需要解码的session - * @param writeBuffer 待处理的读buffer - * @param data 写出的数据 - * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 - */ - ByteBuffer encode(AioSession session, ByteBuffer writeBuffer, T data); - - /** - * 对于从Socket流中获取到的数据采用当前Protocol的实现类协议进行解析。 - * - * - * @param session 本次需要解码的session - * @param readBuffer 待处理的读buffer - * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 - */ - T decode(AioSession session, ByteBuffer readBuffer); -} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgDecoder.java b/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgDecoder.java new file mode 100644 index 0000000000..243380d29f --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgDecoder.java @@ -0,0 +1,24 @@ +package cn.hutool.socket.protocol; + +import java.nio.ByteBuffer; + +import cn.hutool.socket.aio.AioSession; + +/** + * 消息解码器 + * + * @author looly + * + * @param 解码后的目标类型 + */ +public interface MsgDecoder { + /** + * 对于从Socket流中获取到的数据采用当前MsgDecoder的实现类协议进行解析。 + * + * + * @param session 本次需要解码的session + * @param readBuffer 待处理的读buffer + * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 + */ + T decode(AioSession session, ByteBuffer readBuffer); +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java b/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java new file mode 100644 index 0000000000..ac5090dd56 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java @@ -0,0 +1,24 @@ +package cn.hutool.socket.protocol; + +import java.nio.ByteBuffer; + +import cn.hutool.socket.aio.AioSession; + +/** + * 消息编码器 + * + * @author looly + * + * @param 编码前后的数据类型 + */ +public interface MsgEncoder { + /** + * 编码数据用于写出 + * + * @param session 本次需要解码的session + * @param writeBuffer 待处理的读buffer + * @param data 写出的数据 + * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 + */ + void encode(AioSession session, ByteBuffer writeBuffer, T data); +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/protocol/Protocol.java b/hutool-socket/src/main/java/cn/hutool/socket/protocol/Protocol.java new file mode 100644 index 0000000000..7e9fa53984 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/protocol/Protocol.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017, org.smartboot. All rights reserved. + * project name: smart-socket + * file name: Protocol.java + * Date: 2017-11-25 + * Author: sandao + */ + +package cn.hutool.socket.protocol; + +/** + * 协议接口
+ * 通过实现此接口完成消息的编码和解码 + * + *

+ * 所有Socket使用相同协议对象,类成员变量和对象成员变量易造成并发读写问题。 + *

+ * + * @author Looly + */ +public interface Protocol extends MsgEncoder, MsgDecoder { + +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/protocol/package-info.java b/hutool-socket/src/main/java/cn/hutool/socket/protocol/package-info.java new file mode 100644 index 0000000000..edb069568a --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/protocol/package-info.java @@ -0,0 +1,7 @@ +/** + * 消息协议接口及实现 + * + * @author looly + * + */ +package cn.hutool.socket.protocol; \ No newline at end of file From 18cc9994b1ad2cbb086850ed7c70266413b41a5f Mon Sep 17 00:00:00 2001 From: looly Date: Thu, 21 Feb 2019 13:53:51 +0000 Subject: [PATCH 11/17] fix --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/core/text/StrSpliter.java | 2 +- .../src/main/java/cn/hutool/core/util/IdcardUtil.java | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be5443686e..e883211592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * 【socket】 增加Socket模块 * 【core】 Validator增加isIpV4方法(issue#IRQ6W@Gitee) * 【crypto】 增加SM2Engine,支持C1C2C3和C1C3C2两种模式 +* 【core】 StrUtil.splitTrim支持其它空白符(issue#IRVPC@Gitee) ### Bug修复 * 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java index b93ae04fe1..e0ea2e4193 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java @@ -494,7 +494,7 @@ public static String[] splitByLength(String str, int len) { private static List addToList(List list, String part, boolean isTrim, boolean ignoreEmpty){ part = part.toString(); if(isTrim){ - part = part.trim(); + part = StrUtil.trim(part); } if(false == ignoreEmpty || false == part.isEmpty()){ list.add(part); diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index e230b9175a..1a0bb7bd4b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -208,6 +208,11 @@ public static boolean isvalidCard18(String idCard) { if (CHINA_ID_MAX_LENGTH != idCard.length()) { return false; } + + //校验生日 + if(false == Validator.isBirthday(idCard.substring(6, 14))) { + return false; + } // 前17位 String code17 = idCard.substring(0, 17); From 30e0fc3717c8ff882d08913da8e2950d12790876 Mon Sep 17 00:00:00 2001 From: looly Date: Fri, 22 Feb 2019 16:09:02 +0000 Subject: [PATCH 12/17] fix --- CHANGELOG.md | 2 + README.md | 1 + .../cn/hutool/bloomfilter/bitMap/BitMap.java | 21 ++++ .../cn/hutool/bloomfilter/bitMap/IntMap.java | 17 ++- .../cn/hutool/bloomfilter/bitMap/LongMap.java | 18 ++- .../bloomfilter/filter/AbstractFilter.java | 36 ++++-- .../main/java/cn/hutool/core/date/Zodiac.java | 14 ++- .../hutool/core/thread/ExecutorBuilder.java | 46 ++++++-- .../java/cn/hutool/core/date/ZodiacTest.java | 4 +- .../main/java/cn/hutool/http/HttpRequest.java | 14 ++- .../hutool/socket/SocketRuntimeException.java | 33 ++++++ .../cn/hutool/socket/aio/AcceptHandler.java | 17 +-- .../java/cn/hutool/socket/aio/AioClient.java | 111 ++++++++++++++++++ .../java/cn/hutool/socket/aio/AioSession.java | 68 +++++++---- .../java/cn/hutool/socket/aio/IoAction.java | 19 ++- .../cn/hutool/socket/aio/SimpleIoAction.java | 25 ++++ .../java/cn/hutool/socket/AioClientTest.java | 25 ++++ .../java/cn/hutool/socket/AioServerTest.java | 16 ++- 18 files changed, 426 insertions(+), 61 deletions(-) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/SocketRuntimeException.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java create mode 100644 hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e883211592..45b786e3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * 【core】 Validator增加isIpV4方法(issue#IRQ6W@Gitee) * 【crypto】 增加SM2Engine,支持C1C2C3和C1C3C2两种模式 * 【core】 StrUtil.splitTrim支持其它空白符(issue#IRVPC@Gitee) +* 【http】 请求支持DELETE附带参数模式(issue#IRW9E@Gitee) +* 【bloomFilter】调整BitMap注释 ### Bug修复 * 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 diff --git a/README.md b/README.md index 5d22416698..e6a206f6f6 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ Hutool是Hu + tool的自造词,谐音“糊涂”,寓意追求“万事都 - hutool-json JSON实现 - hutool-captcha 图片验证码实现 - hutool-poi 针对POI中Excel的封装 +- hutool-socket 基于Java的NIO和AIO的Socket封装 可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。 diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/BitMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/BitMap.java index 9d6c1e6bb7..48af8a37c7 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/BitMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/BitMap.java @@ -1,13 +1,34 @@ package cn.hutool.bloomfilter.bitMap; +/** + * BitMap接口,用于将某个int或long值映射到一个数组中,从而判定某个值是否存在 + * + * @author looly + * + */ public interface BitMap { public final int MACHINE32 = 32; public final int MACHINE64 = 64; + /** + * 加入值 + * + * @param i 值 + */ public void add(long i); + /** + * 检查是否包含值 + * + * @param i 值 + */ public boolean contains(long i); + /** + * 移除值 + * + * @param i 值 + */ public void remove(long i); } \ No newline at end of file diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java index 32050213aa..85c449d1ce 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java @@ -2,29 +2,39 @@ /** * 过滤器BitMap在32位机器上.这个类能发生更好的效果.一般情况下建议使用此类 + * * @author loolly * */ public class IntMap implements BitMap { - private static final int MAX = Integer.MAX_VALUE; + private int[] ints = null; + + /** + * 构造 + */ public IntMap() { ints = new int[93750000]; } + /** + * 构造 + * + * @param size 容量 + */ public IntMap(int size) { ints = new int[size]; } - private int[] ints = null; - + @Override public void add(long i) { int r = (int) (i / BitMap.MACHINE32); int c = (int) (i % BitMap.MACHINE32); ints[r] = (int) (ints[r] | (1 << c)); } + @Override public boolean contains(long i) { int r = (int) (i / BitMap.MACHINE32); int c = (int) (i % BitMap.MACHINE32); @@ -34,6 +44,7 @@ public boolean contains(long i) { return false; } + @Override public void remove(long i) { int r = (int) (i / BitMap.MACHINE32); int c = (int) (i % BitMap.MACHINE32); diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java index f04288c506..e2726914c1 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java @@ -2,6 +2,7 @@ /** * 过滤器BitMap在64位机器上.这个类能发生更好的效果.一般机器不建议使用 + * * @author loolly * */ @@ -9,22 +10,32 @@ public class LongMap implements BitMap { private static final long MAX = Long.MAX_VALUE; + private long[] longs = null; + + /** + * 构造 + */ public LongMap() { longs = new long[93750000]; } + /** + * 构造 + * + * @param size 容量 + */ public LongMap(int size) { longs = new long[size]; } - private long[] longs = null; - + @Override public void add(long i) { int r = (int) (i / BitMap.MACHINE64); long c = i % BitMap.MACHINE64; longs[r] = longs[r] | (1 << c); } + @Override public boolean contains(long i) { int r = (int) (i / BitMap.MACHINE64); long c = i % BitMap.MACHINE64; @@ -34,9 +45,10 @@ public boolean contains(long i) { return false; } + @Override public void remove(long i) { int r = (int) (i / BitMap.MACHINE64); - long c =i % BitMap.MACHINE64; + long c = i % BitMap.MACHINE64; longs[r] = longs[r] & (((1 << (c + 1)) - 1) ^ MAX); } diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java index d7da02e6de..630879c1cc 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java @@ -17,25 +17,42 @@ public abstract class AbstractFilter implements BloomFilter { protected long size = 0; + /** + * 构造 + * + * @param maxValue 最大值 + * @param machineNum 机器位数 + */ public AbstractFilter(long maxValue, int machineNum) { init(maxValue, machineNum); } + /** + * 构造32位 + * + * @param maxValue 最大值 + */ public AbstractFilter(long maxValue) { this(maxValue, BitMap.MACHINE32); } + /** + * 初始化 + * + * @param maxValue 最大值 + * @param machineNum 机器位数 + */ public void init(long maxValue, int machineNum) { this.size = maxValue; switch (machineNum) { - case BitMap.MACHINE32: - bm = new IntMap((int) (size / machineNum)); - break; - case BitMap.MACHINE64: - bm = new LongMap((int) (size / machineNum)); - break; - default: - throw new RuntimeException("Error Machine number!"); + case BitMap.MACHINE32: + bm = new IntMap((int) (size / machineNum)); + break; + case BitMap.MACHINE64: + bm = new LongMap((int) (size / machineNum)); + break; + default: + throw new RuntimeException("Error Machine number!"); } } @@ -57,8 +74,9 @@ public boolean add(String str) { /** * 自定义Hash方法 + * * @param str 字符串 * @return HashCode */ - public abstract long hash(String str) ; + public abstract long hash(String str); } \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Zodiac.java b/hutool-core/src/main/java/cn/hutool/core/date/Zodiac.java index 31ffad142a..27cdfb30d0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Zodiac.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Zodiac.java @@ -39,13 +39,25 @@ public static String getZodiac(Calendar calendar) { } return getZodiac(calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); } - + /** * 通过生日计算星座 * * @param month 月,从0开始计数 * @param day 天 * @return 星座名 + * @since 4.5.0 + */ + public static String getZodiac(Month month, int day) { + return getZodiac(month.getValue(), day); + } + + /** + * 通过生日计算星座 + * + * @param month 月,从0开始计数,见{@link Month#getValue()} + * @param day 天 + * @return 星座名 */ public static String getZodiac(int month, int day) { // 在分隔日前为前一个星座,否则为后一个星座 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java b/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java index f92582b2a0..d772a2de00 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java @@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit; import cn.hutool.core.builder.Builder; +import cn.hutool.core.util.ObjectUtil; /** * {@link ThreadPoolExecutor} 建造者 @@ -19,12 +20,20 @@ */ public class ExecutorBuilder implements Builder { + /** 初始池大小 */ private int corePoolSize; + /** 最大池大小(允许同时执行的最大线程数) */ private int maxPoolSize = Integer.MAX_VALUE; + /** 线程存活时间,既当池中线程多于初始大小时,多出的线程保留的时长 */ private long keepAliveTime = TimeUnit.SECONDS.toNanos(60); + /** 队列,用于存在未执行的线程 */ private BlockingQueue workQueue; + /** 线程工厂,用于自定义线程创建 */ private ThreadFactory threadFactory; + /** 当线程阻塞(block)时的异常处理器,所谓线程阻塞既线程池和等待队列已满,无法处理线程时采取的策略 */ private RejectedExecutionHandler handler; + /** 线程执行超时后是否回收线程 */ + private Boolean allowCoreThreadTimeOut; /** * 设置初始池大小,默认0 @@ -73,6 +82,7 @@ public ExecutorBuilder setKeepAliveTime(long keepAliveTime) { /** * 设置队列,用于存在未执行的线程
* 可选队列有: + * *
 	 * 1. SynchronousQueue    它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
 	 * 2. LinkedBlockingQueue 无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maximumPoolSize无效
@@ -86,7 +96,7 @@ public ExecutorBuilder setWorkQueue(BlockingQueue workQueue) {
 		this.workQueue = workQueue;
 		return this;
 	}
-	
+
 	/**
 	 * 使用{@link SynchronousQueue} 做为等待队列(非公平策略)
* 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略 @@ -97,7 +107,7 @@ public ExecutorBuilder setWorkQueue(BlockingQueue workQueue) { public ExecutorBuilder useSynchronousQueue() { return useSynchronousQueue(false); } - + /** * 使用{@link SynchronousQueue} 做为等待队列
* 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略 @@ -136,6 +146,17 @@ public ExecutorBuilder setHandler(RejectedExecutionHandler handler) { return this; } + /** + * 设置线程执行超时后是否回收线程 + * + * @param allowCoreThreadTimeOut 线程执行超时后是否回收线程 + * @return this + */ + public ExecutorBuilder setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { + this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; + return this; + } + /** * 创建ExecutorBuilder,开始构建 * @@ -164,19 +185,26 @@ private static ThreadPoolExecutor build(ExecutorBuilder builder) { final int maxPoolSize = builder.maxPoolSize; final long keepAliveTime = builder.keepAliveTime; final BlockingQueue workQueue; - if(null != builder.workQueue) { + if (null != builder.workQueue) { workQueue = builder.workQueue; } else { - //corePoolSize为0则要使用SynchronousQueue避免无限阻塞 + // corePoolSize为0则要使用SynchronousQueue避免无限阻塞 workQueue = (corePoolSize <= 0) ? new SynchronousQueue() : new LinkedBlockingQueue(); } final ThreadFactory threadFactory = (null != builder.threadFactory) ? builder.threadFactory : Executors.defaultThreadFactory(); - final RejectedExecutionHandler handler = builder.handler; + RejectedExecutionHandler handler = ObjectUtil.defaultIfNull(builder.handler, new ThreadPoolExecutor.AbortPolicy()); - if (null == handler) { - return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.NANOSECONDS, workQueue, threadFactory); - } else { - return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.NANOSECONDS, workQueue, threadFactory, handler); + final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(// + corePoolSize, // + maxPoolSize, // + keepAliveTime, TimeUnit.NANOSECONDS, // + workQueue, // + threadFactory, // + handler// + ); + if (null != builder.allowCoreThreadTimeOut) { + threadPoolExecutor.allowCoreThreadTimeOut(builder.allowCoreThreadTimeOut); } + return threadPoolExecutor; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java index c5f3803e37..986e490d54 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java @@ -7,7 +7,9 @@ public class ZodiacTest { @Test public void getZodiacTest() { - Assert.assertEquals("处女座", Zodiac.getZodiac(7, 28)); + Assert.assertEquals("摩羯座", Zodiac.getZodiac(Month.JANUARY, 19)); + Assert.assertEquals("水瓶座", Zodiac.getZodiac(Month.JANUARY, 20)); + Assert.assertEquals("巨蟹座", Zodiac.getZodiac(6, 17)); } @Test diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 5904b86fdc..78a0e07d69 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -790,6 +790,18 @@ public HttpRequest setSSLProtocol(String protocol) { return this; } + /** + * 设置是否rest模式 + * + * @param isRest 是否rest模式 + * @return this + * @since 4.5.0 + */ + public HttpRequest setRest(boolean isRest) { + this.isRest = isRest; + return this; + } + /** * 执行Reuqest请求 * @@ -928,7 +940,7 @@ private HttpResponse sendRedirectIfPosible() { */ private void send() throws HttpException { try { - if (Method.POST.equals(this.method) || Method.PUT.equals(this.method) || this.isRest) { + if (Method.POST.equals(this.method) || Method.PUT.equals(this.method) || Method.DELETE.equals(this.method) || this.isRest) { if (CollectionUtil.isEmpty(this.fileForm)) { sendFormUrlEncoded();// 普通表单 } else { diff --git a/hutool-socket/src/main/java/cn/hutool/socket/SocketRuntimeException.java b/hutool-socket/src/main/java/cn/hutool/socket/SocketRuntimeException.java new file mode 100644 index 0000000000..0c0baf63ba --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/SocketRuntimeException.java @@ -0,0 +1,33 @@ +package cn.hutool.socket; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * Socket异常 + * + * @author xiaoleilu + */ +public class SocketRuntimeException extends RuntimeException { + private static final long serialVersionUID = 8247610319171014183L; + + public SocketRuntimeException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public SocketRuntimeException(String message) { + super(message); + } + + public SocketRuntimeException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public SocketRuntimeException(String message, Throwable throwable) { + super(message, throwable); + } + + public SocketRuntimeException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java index f40229ee05..ea37096c3c 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java @@ -1,10 +1,10 @@ package cn.hutool.socket.aio; +import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import cn.hutool.log.StaticLog; -import cn.hutool.socket.SocketUtil; /** * 接入完成回调 @@ -16,21 +16,22 @@ public class AcceptHandler implements CompletionHandler ioAction = aioServer.getIoAction(); + ioAction.accept(socketChannel); + + // 创建Session会话 + final AioSession session = new AioSession(socketChannel, ioAction); // 处理读 session.read(); } @Override - public void failed(Throwable exc, AioServer attachment) { + public void failed(Throwable exc, AioServer aioServer) { StaticLog.error(exc); } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java new file mode 100644 index 0000000000..0e06e289c6 --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java @@ -0,0 +1,111 @@ +package cn.hutool.socket.aio; + +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.AsynchronousSocketChannel; +import java.util.concurrent.ExecutionException; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.thread.ThreadFactoryBuilder; +import cn.hutool.socket.SocketRuntimeException; + +/** + * Aio Socket客户端 + * + * @author looly + * @since 4.5.0 + */ +public class AioClient { + + private AioSession session; + + public AioClient(InetSocketAddress address, IoAction ioAction) { + this(createChannel(address), ioAction); + } + + /** + * 构造 + * + * @param channel {@link AsynchronousSocketChannel} + * @param ioAction IO处理类 + */ + public AioClient(AsynchronousSocketChannel channel, IoAction ioAction) { + ioAction.accept(channel); + this.session = new AioSession(channel, ioAction); + } + + /** + * 设置 Socket 的 Option 选项
+ * 选项见:{@link java.net.StandardSocketOptions} + * + * @param 选项泛型 + * @param name {@link SocketOption} 枚举 + * @param value SocketOption参数 + * @throws IOException IO异常 + */ + public AioClient setOption(SocketOption name, T value) throws IOException { + this.session.getChannel().setOption(name, value); + return this; + } + + /** + * 获取IO处理器 + * + * @return {@link IoAction} + */ + public IoAction getIoAction() { + return this.session.getIoAction(); + } + + /** + * 从服务端读取数据 + * @return + */ + public AioClient read() { + this.session.read(); + return this; + } + + /** + * 写数据到服务端 + * + * @return this + */ + public AioClient write(ByteBuffer data) { + this.session.write(data); + return this; + } + + // ------------------------------------------------------------------------------------- Private method start + /** + * 初始化 + * + * @param address 地址和端口 + * @return this + */ + private static AsynchronousSocketChannel createChannel(InetSocketAddress address) { + + AsynchronousSocketChannel channel; + // TODO 需要自定义线程池大小 + try { + AsynchronousChannelGroup group = AsynchronousChannelGroup.withFixedThreadPool(// + 2, // 默认线程池大小 + ThreadFactoryBuilder.create().setNamePrefix("Huool-socket-").build()// + ); + channel = AsynchronousSocketChannel.open(group); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + try { + channel.connect(address).get(); + } catch (InterruptedException | ExecutionException e) { + throw new SocketRuntimeException(e); + } + return channel; + } + // ------------------------------------------------------------------------------------- Private method end +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java index d6814953bd..43a82b1e48 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java @@ -4,10 +4,10 @@ import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; +import java.util.concurrent.Future; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.log.StaticLog; /** * AIO会话
@@ -77,35 +77,62 @@ public IoAction getIoAction() { * @return this */ public AioSession read() { + return read(new CompletionHandler() { + + @Override + public void completed(Integer result, AioSession session) { + readBuffer.flip();// 读模式 + ioAction.doAction(session, readBuffer); + } + + @Override + public void failed(Throwable exc, AioSession session) { + ioAction.failed(exc, session); + } + }); + } + + /** + * 读取数据到Buffer + * + * @param handler {@link CompletionHandler} + * @return this + */ + public AioSession read(CompletionHandler handler) { if (isOpen()) { this.readBuffer.clear(); - this.channel.read(this.readBuffer, this, new CompletionHandler() { - - @Override - public void completed(Integer result, AioSession session) { - readBuffer.flip();// 读模式 - ioAction.doAction(session, readBuffer); - - // 继续读取 - session.read(); - } - - @Override - public void failed(Throwable exc, AioSession attachment) { - StaticLog.error(exc); - } - }); + this.channel.read(this.readBuffer, this, handler); } return this; } + /** + * 写数据到目标端,并关闭输出 + * + * @return this + */ + public AioSession writeAndClose(ByteBuffer data) { + write(data); + return closeOut(); + } + /** * 写数据到目标端 * + * @return {@link Future} + */ + public Future write(ByteBuffer data) { + return this.channel.write(data); + } + + /** + * 写数据到目标端 + * + * @param handler {@link CompletionHandler} * @return this */ - public AioSession write(ByteBuffer data) { - this.channel.write(data); + public AioSession write(ByteBuffer data, CompletionHandler handler) { + this.channel.write(data, this, handler); return this; } @@ -156,8 +183,7 @@ public AioSession closeOut() { * * @return this */ - public AioSession close() { + public void close() { IoUtil.close(this.channel); - return this; } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java index 216352e9d4..02819eeff4 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java @@ -1,5 +1,7 @@ package cn.hutool.socket.aio; +import java.nio.channels.AsynchronousSocketChannel; + /** * Socket流处理接口
* 实现此接口用于处理接收到的消息,发送指定消息 @@ -10,11 +12,26 @@ */ public interface IoAction { + /** + * 接收客户端连接事件处理 + * + * @param socketChannel 连接Socket对象 + */ + void accept(AsynchronousSocketChannel socketChannel); + /** * 执行数据处理 * * @param session Socket Session会话 * @param data 解码后的数据 */ - public void doAction(AioSession session, T data); + void doAction(AioSession session, T data); + + /** + * 数据读取失败的回调事件处理 + * + * @param exc 异常 + * @param session Session + */ + void failed(Throwable exc, AioSession session); } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java new file mode 100644 index 0000000000..84f8680d1d --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java @@ -0,0 +1,25 @@ +package cn.hutool.socket.aio; + +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; + +import cn.hutool.log.StaticLog; + +/** + * 简易IO信息处理类
+ * 简单实现了accept和failed事件 + * + * @author looly + * + */ +public abstract class SimpleIoAction implements IoAction { + + @Override + public void accept(AsynchronousSocketChannel socketChannel) { + } + + @Override + public void failed(Throwable exc, AioSession session) { + StaticLog.error(exc); + } +} diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java new file mode 100644 index 0000000000..c35300ebe0 --- /dev/null +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java @@ -0,0 +1,25 @@ +package cn.hutool.socket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +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; + +public class AioClientTest { + public static void main(String[] args) { + AioClient client = new AioClient(new InetSocketAddress("localhost", 8899), new SimpleIoAction() { + + @Override + public void doAction(AioSession session, ByteBuffer data) { + Console.log(StrUtil.utf8Str(data)); + } + }); + + client.write(ByteBuffer.wrap("Hello".getBytes())); + client.read(); + } +} diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java index 6e0a82159e..50dd0dec6b 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java @@ -1,21 +1,28 @@ package cn.hutool.socket; import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; 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.IoAction; +import cn.hutool.socket.aio.SimpleIoAction; public class AioServerTest { public static void main(String[] args) { AioServer aioServer = new AioServer(8899); - aioServer.setIoAction(new IoAction() { + aioServer.setIoAction(new SimpleIoAction() { + + @Override + public void accept(AsynchronousSocketChannel socketChannel) { + StaticLog.debug("客户端接入:{}", SocketUtil.getRemoteAddress(socketChannel)); + } @Override public void doAction(AioSession session, ByteBuffer data) { @@ -28,8 +35,9 @@ public void doAction(AioSession session, ByteBuffer data) { .append("Content-Type: text/html; charset=UTF-8\r\n")// .append("\r\n") .append("Hello Hutool socket");// - session.write(ByteBuffer.wrap(response.toString().getBytes(CharsetUtil.CHARSET_UTF_8))); - session.closeOut(); + session.writeAndClose(ByteBuffer.wrap(response.toString().getBytes(CharsetUtil.CHARSET_UTF_8))); + }else { + session.read(); } } }).start(true); From d3198356c5ce1750a1267649148f604712d71ef6 Mon Sep 17 00:00:00 2001 From: looly Date: Sun, 24 Feb 2019 15:47:39 +0000 Subject: [PATCH 13/17] fix client --- .../src/main/java/cn/hutool/socket/aio/AioClient.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java index 0e06e289c6..f82ea8a17b 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java @@ -22,6 +22,12 @@ public class AioClient { private AioSession session; + /** + * 构造 + * + * @param address 地址 + * @param ioAction IO处理类 + */ public AioClient(InetSocketAddress address, IoAction ioAction) { this(createChannel(address), ioAction); } From 5f7ce51c2d483ff12d6d17f479dad8f1e34dfa36 Mon Sep 17 00:00:00 2001 From: looly Date: Mon, 25 Feb 2019 13:44:46 +0000 Subject: [PATCH 14/17] fix cache --- CHANGELOG.md | 1 + .../cn/hutool/cache/impl/AbstractCache.java | 109 +++++++++--------- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b786e3a7..6417f00d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * 【extra】 修复FTP.ls无法遍历文件问题(issue#IRTA3@Gitee) * 【extra】 修复QrCodeUtil中ratio参数失效问题,调整默认纠错为M(感谢@【上海】皮皮今) * 【core】 修复FileTypeUtil对jpg文件识别问题(issue#275@Github) +* 【cache】 修复cache使用读锁导致的删除节点并发问题(issue#IRZTL@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java index e040632b73..1632500015 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java @@ -13,8 +13,8 @@ * 超时和限制大小的缓存的默认实现
* 继承此抽象缓存需要:
*
    - *
  • 创建一个新的Map
  • - *
  • 实现 prune 策略
  • + *
  • 创建一个新的Map
  • + *
  • 实现 prune 策略
  • *
* * @author Looly,jodd @@ -22,7 +22,7 @@ * @param 键类型 * @param 值类型 */ -public abstract class AbstractCache implements Cache{ +public abstract class AbstractCache implements Cache { protected Map> cacheMap; @@ -34,15 +34,15 @@ public abstract class AbstractCache implements Cache{ protected int capacity; /** 缓存失效时长, 0 表示没有设置,单位毫秒 */ protected long timeout; - + /** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */ protected boolean existCustomTimeout; - + /** 命中数 */ protected int hitCount; /** 丢失数 */ protected int missCount; - + // ---------------------------------------------------------------- put start @Override public void put(K key, V object) { @@ -74,27 +74,25 @@ public boolean containsKey(K key) { readLock.lock(); try { - //不存在或已移除 + // 不存在或已移除 final CacheObj co = cacheMap.get(key); if (co == null) { return false; } - - //过期 - if (co.isExpired() == true) { - // remove(key); // 此方法无法获得锁 - removeWithoutLock(key); - missCount++; - return false; - } - //命中 - return true; + if (false == co.isExpired()) { + // 命中 + return true; + } } finally { readLock.unlock(); } + + // 过期 + remove(key, true); + return false; } - + /** * @return 命中数 */ @@ -108,7 +106,7 @@ public int getHitCount() { public int getMissCount() { return missCount; } - + @Override public V get(K key) { return get(key, true); @@ -119,29 +117,27 @@ public V get(K key, boolean isUpdateLastAccess) { readLock.lock(); try { - //不存在或已移除 + // 不存在或已移除 final CacheObj co = cacheMap.get(key); if (co == null) { missCount++; return null; } - - //过期 - if (co.isExpired() == true) { - // remove(key); // 此方法无法获得锁 - removeWithoutLock(key); - missCount++; - return null; - } - //命中 - hitCount++; - return co.get(isUpdateLastAccess); + if (false == co.isExpired()) { + // 命中 + hitCount++; + return co.get(isUpdateLastAccess); + } } finally { readLock.unlock(); } + + // 过期 + remove(key, true); + return null; } - + // ---------------------------------------------------------------- get end @Override @@ -150,7 +146,7 @@ public Iterator iterator() { CacheObjIterator copiedIterator = (CacheObjIterator) this.cacheObjIterator(); return new CacheValuesIterator(copiedIterator); } - + @Override public Iterator> cacheObjIterator() { CopiedIter> copiedIterator; @@ -166,11 +162,12 @@ public Iterator> cacheObjIterator() { // ---------------------------------------------------------------- prune start /** * 清理实现 + * * @return 清理数 */ protected abstract int pruneCache(); - @Override + @Override public final int prune() { writeLock.lock(); try { @@ -189,21 +186,22 @@ public int capacity() { /** * @return 默认缓存失效时长。
- * 每个对象可以单独设置失效时长 + * 每个对象可以单独设置失效时长 */ @Override public long timeout() { return timeout; } - + /** * 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用 + * * @return 过期对象清理是否可用,内部使用 */ protected boolean isPruneExpiredActive() { return (timeout != 0) || existCustomTimeout; } - + @Override public boolean isFull() { return (capacity > 0) && (cacheMap.size() >= capacity); @@ -211,16 +209,7 @@ public boolean isFull() { @Override public void remove(K key) { - writeLock.lock(); - CacheObj co; - try { - co = cacheMap.remove(key); - } finally { - writeLock.unlock(); - } - if(null != co){ - onRemove(co.key, co.obj); - } + remove(key, false); } @Override @@ -242,28 +231,40 @@ public int size() { public boolean isEmpty() { return cacheMap.isEmpty(); } - + @Override public String toString() { return this.cacheMap.toString(); } // ---------------------------------------------------------------- common end - + /** * 对象移除回调。默认无动作 + * * @param key 键 * @param cachedObject 被缓存的对象 */ protected void onRemove(K key, V cachedObject) { } - + /** - * 移除元素,无锁 + * 移除key对应的对象 + * * @param key 键 + * @param withMissCount 是否计数丢失数 */ - private void removeWithoutLock(K key) { - CacheObj co = cacheMap.remove(key); - if(null != co){ + private void remove(K key, boolean withMissCount) { + writeLock.lock(); + CacheObj co; + try { + co = cacheMap.remove(key); + if (withMissCount) { + this.missCount--; + } + } finally { + writeLock.unlock(); + } + if (null != co) { onRemove(co.key, co.obj); } } From f08e15b0591c286cebb5286697c1478caaffc94e Mon Sep 17 00:00:00 2001 From: looly Date: Mon, 25 Feb 2019 16:13:00 +0000 Subject: [PATCH 15/17] fix aio --- .../java/cn/hutool/core/io/BufferUtil.java | 36 +++++- .../java/cn/hutool/socket/SocketConfig.java | 114 ++++++++++++++++++ .../cn/hutool/socket/aio/AcceptHandler.java | 15 ++- .../java/cn/hutool/socket/aio/AioClient.java | 40 ++++-- .../java/cn/hutool/socket/aio/AioServer.java | 30 +++-- .../java/cn/hutool/socket/aio/AioSession.java | 59 ++++++--- .../java/cn/hutool/socket/aio/IoAction.java | 12 +- .../cn/hutool/socket/aio/ReadHandler.java | 25 ++++ .../cn/hutool/socket/aio/SimpleIoAction.java | 3 +- .../java/cn/hutool/socket/AioClientTest.java | 8 +- .../java/cn/hutool/socket/AioServerTest.java | 10 +- 11 files changed, 289 insertions(+), 63 deletions(-) create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java create mode 100644 hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java diff --git a/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java index ce20ece10c..fb15159da8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/BufferUtil.java @@ -66,7 +66,7 @@ public static ByteBuffer copy(ByteBuffer src, int srcStart, ByteBuffer dest, int System.arraycopy(src.array(), srcStart, dest.array(), destStart, length); return dest; } - + /** * 读取剩余部分并转为UTF-8编码字符串 * @@ -214,4 +214,38 @@ public static String readLine(ByteBuffer buffer, Charset charset) { return null; } + + /** + * 创建新Buffer + * + * @param data 数据 + * @return {@link ByteBuffer} + * @since 4.5.0 + */ + public static ByteBuffer create(byte[] data) { + return ByteBuffer.wrap(data); + } + + /** + * 从字符串创建新Buffer + * + * @param data 数据 + * @param charset 编码 + * @return {@link ByteBuffer} + * @since 4.5.0 + */ + public static ByteBuffer create(CharSequence data, Charset charset) { + return create(StrUtil.bytes(data, charset)); + } + + /** + * 从字符串创建新Buffer,使用UTF-8编码 + * + * @param data 数据 + * @return {@link ByteBuffer} + * @since 4.5.0 + */ + public static ByteBuffer createUtf8(CharSequence data) { + return create(StrUtil.utf8Bytes(data)); + } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java b/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java new file mode 100644 index 0000000000..1b12d32aef --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java @@ -0,0 +1,114 @@ +package cn.hutool.socket; + +import cn.hutool.core.io.IoUtil; + +/** + * Socket通讯配置 + * + * @author looly + * + */ +public class SocketConfig { + + /** CPU核心数 */ + private static int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + + /** 共享线程池大小,此线程池用于接收和处理用户连接 */ + private int threadPoolSize = CPU_COUNT; + + /** 读取超时时长,小于等于0表示默认 */ + private long readTimeout; + /** 写出超时时长,小于等于0表示默认 */ + private long writeTimeout; + + /** 读取缓存大小 */ + private int readBufferSize = IoUtil.DEFAULT_BUFFER_SIZE; + /** 写出缓存大小 */ + private int writeBufferSize = IoUtil.DEFAULT_BUFFER_SIZE; + + /** + * 获取共享线程池大小,此线程池用于接收和处理用户连接 + * + * @return 共享线程池大小,此线程池用于接收和处理用户连接 + */ + public int getThreadPoolSize() { + return threadPoolSize; + } + + /** + * 设置共享线程池大小,此线程池用于接收和处理用户连接 + * + * @param threadPoolSize 共享线程池大小,此线程池用于接收和处理用户连接 + */ + public void setThreadPoolSize(int threadPoolSize) { + this.threadPoolSize = threadPoolSize; + } + + /** + * 获取读取超时时长,小于等于0表示默认 + * + * @return 读取超时时长,小于等于0表示默认 + */ + public long getReadTimeout() { + return readTimeout; + } + + /** + * 设置读取超时时长,小于等于0表示默认 + * + * @param readTimeout 读取超时时长,小于等于0表示默认 + */ + public void setReadTimeout(long readTimeout) { + this.readTimeout = readTimeout; + } + + /** + * 获取写出超时时长,小于等于0表示默认 + * + * @return 写出超时时长,小于等于0表示默认 + */ + public long getWriteTimeout() { + return writeTimeout; + } + + /** + * 设置写出超时时长,小于等于0表示默认 + * + * @param writeTimeout 写出超时时长,小于等于0表示默认 + */ + public void setWriteTimeout(long writeTimeout) { + this.writeTimeout = writeTimeout; + } + + /** + * 获取读取缓存大小 + * @return 读取缓存大小 + */ + public int getReadBufferSize() { + return readBufferSize; + } + + /** + * 设置读取缓存大小 + * @param readBufferSize 读取缓存大小 + */ + public void setReadBufferSize(int readBufferSize) { + this.readBufferSize = readBufferSize; + } + + /** + * 获取写出缓存大小 + * @return 写出缓存大小 + */ + public int getWriteBufferSize() { + return writeBufferSize; + } + + /** + * 设置写出缓存大小 + * @param writeBufferSize 写出缓存大小 + */ + public void setWriteBufferSize(int writeBufferSize) { + this.writeBufferSize = writeBufferSize; + } +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java index ea37096c3c..c2f53b761c 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AcceptHandler.java @@ -7,7 +7,7 @@ import cn.hutool.log.StaticLog; /** - * 接入完成回调 + * 接入完成回调,单例使用 * * @author looly * @@ -16,17 +16,16 @@ public class AcceptHandler implements CompletionHandler ioAction = aioServer.getIoAction(); - ioAction.accept(socketChannel); - + final IoAction ioAction = aioServer.ioAction; // 创建Session会话 - final AioSession session = new AioSession(socketChannel, ioAction); + final AioSession session = new AioSession(socketChannel, ioAction, aioServer.config); + // 处理请求接入(同步) + ioAction.accept(session); - // 处理读 + // 处理读(异步) session.read(); } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java index f82ea8a17b..ceaf0a9b21 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java @@ -10,6 +10,7 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.thread.ThreadFactoryBuilder; +import cn.hutool.socket.SocketConfig; import cn.hutool.socket.SocketRuntimeException; /** @@ -27,9 +28,21 @@ public class AioClient { * * @param address 地址 * @param ioAction IO处理类 + * @param config 配置项 */ public AioClient(InetSocketAddress address, IoAction ioAction) { - this(createChannel(address), ioAction); + this(address, ioAction, new SocketConfig()); + } + + /** + * 构造 + * + * @param address 地址 + * @param ioAction IO处理类 + * @param config 配置项 + */ + public AioClient(InetSocketAddress address, IoAction ioAction, SocketConfig config) { + this(createChannel(address, config.getThreadPoolSize()), ioAction, config); } /** @@ -37,10 +50,11 @@ public AioClient(InetSocketAddress address, IoAction ioAction) { * * @param channel {@link AsynchronousSocketChannel} * @param ioAction IO处理类 + * @param config 配置项 */ - public AioClient(AsynchronousSocketChannel channel, IoAction ioAction) { - ioAction.accept(channel); - this.session = new AioSession(channel, ioAction); + public AioClient(AsynchronousSocketChannel channel, IoAction ioAction, SocketConfig config) { + this.session = new AioSession(channel, ioAction, config); + ioAction.accept(this.session); } /** @@ -68,6 +82,7 @@ public IoAction getIoAction() { /** * 从服务端读取数据 + * * @return */ public AioClient read() { @@ -85,27 +100,34 @@ public AioClient write(ByteBuffer data) { return this; } + /** + * 关闭客户端 + */ + public void close() { + this.session.close(); + } + // ------------------------------------------------------------------------------------- Private method start /** * 初始化 * * @param address 地址和端口 + * @param poolSize 线程池大小 * @return this */ - private static AsynchronousSocketChannel createChannel(InetSocketAddress address) { + private static AsynchronousSocketChannel createChannel(InetSocketAddress address, int poolSize) { AsynchronousSocketChannel channel; - // TODO 需要自定义线程池大小 try { AsynchronousChannelGroup group = AsynchronousChannelGroup.withFixedThreadPool(// - 2, // 默认线程池大小 + poolSize, // 默认线程池大小 ThreadFactoryBuilder.create().setNamePrefix("Huool-socket-").build()// ); channel = AsynchronousSocketChannel.open(group); } catch (IOException e) { throw new IORuntimeException(e); - } - + } + try { channel.connect(address).get(); } catch (InterruptedException | ExecutionException e) { 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 5616de2ef6..8b51f8ef7d 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 @@ -12,6 +12,7 @@ import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; +import cn.hutool.socket.SocketConfig; /** * 基于AIO的Socket服务端实现 @@ -21,19 +22,32 @@ */ public class AioServer { private static final Log log = LogFactory.get(); + private static AcceptHandler ACCEPT_HANDLER = new AcceptHandler(); private AsynchronousChannelGroup group; private AsynchronousServerSocketChannel channel; - private AcceptHandler acceptHandler; - private IoAction ioAction; - + protected IoAction ioAction; + protected SocketConfig config; + + /** * 构造 * * @param port 端口 */ public AioServer(int port) { - init(new InetSocketAddress(port)); + this(new InetSocketAddress(port), new SocketConfig()); + } + + /** + * 构造 + * + * @param address 地址 + * @param config {@link SocketConfig} 配置项 + */ + public AioServer(InetSocketAddress address, SocketConfig config) { + this.config = config; + init(address); } /** @@ -43,19 +57,15 @@ public AioServer(int port) { * @return this */ public AioServer init(InetSocketAddress address) { - - // TODO 需要自定义线程池大小 try { this.group = AsynchronousChannelGroup.withFixedThreadPool(// - 2, //默认线程池大小 + config.getThreadPoolSize(), // 默认线程池大小 ThreadFactoryBuilder.create().setNamePrefix("Huool-socket-").build()// ); this.channel = AsynchronousServerSocketChannel.open(group).bind(address); } catch (IOException e) { throw new IORuntimeException(e); } - this.acceptHandler = new AcceptHandler(); - return this; } @@ -121,7 +131,7 @@ public AsynchronousServerSocketChannel getChannel() { * @return this */ public AioServer accept() { - this.channel.accept(this, this.acceptHandler); + this.channel.accept(this, ACCEPT_HANDLER); return this; } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java index 43a82b1e48..afe9e501d4 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java @@ -1,13 +1,17 @@ package cn.hutool.socket.aio; import java.io.IOException; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.socket.SocketConfig; +import cn.hutool.socket.SocketUtil; /** * AIO会话
@@ -18,20 +22,28 @@ */ public class AioSession { + private static final ReadHandler READ_HANDLER = new ReadHandler(); + private AsynchronousSocketChannel channel; + private IoAction ioAction; private ByteBuffer readBuffer; private ByteBuffer writeBuffer; - private IoAction ioAction; + /** 读取超时时长,小于等于0表示默认 */ + private long readTimeout; + /** 写出超时时长,小于等于0表示默认 */ + private long writeTimeout; /** * 构造 * * @param channel {@link AsynchronousSocketChannel} + * @param ioAction IO消息处理类 + * @param config 配置项 */ - public AioSession(AsynchronousSocketChannel channel, IoAction ioAction) { + public AioSession(AsynchronousSocketChannel channel, IoAction ioAction, SocketConfig config) { this.channel = channel; - this.readBuffer = ByteBuffer.allocate(IoUtil.DEFAULT_BUFFER_SIZE); - this.writeBuffer = ByteBuffer.allocate(IoUtil.DEFAULT_BUFFER_SIZE); + this.readBuffer = ByteBuffer.allocate(config.getReadBufferSize()); + this.writeBuffer = ByteBuffer.allocate(config.getWriteBufferSize()); this.ioAction = ioAction; } @@ -71,25 +83,22 @@ public IoAction getIoAction() { return this.ioAction; } + /** + * 获取远程主机(客户端)地址和端口 + * + * @return 远程主机(客户端)地址和端口 + */ + public SocketAddress getRemoteAddress() { + return SocketUtil.getRemoteAddress(this.channel); + } + /** * 读取数据到Buffer * * @return this */ public AioSession read() { - return read(new CompletionHandler() { - - @Override - public void completed(Integer result, AioSession session) { - readBuffer.flip();// 读模式 - ioAction.doAction(session, readBuffer); - } - - @Override - public void failed(Throwable exc, AioSession session) { - ioAction.failed(exc, session); - } - }); + return read(READ_HANDLER); } /** @@ -101,7 +110,7 @@ public void failed(Throwable exc, AioSession session) { public AioSession read(CompletionHandler handler) { if (isOpen()) { this.readBuffer.clear(); - this.channel.read(this.readBuffer, this, handler); + this.channel.read(this.readBuffer, Math.max(this.readTimeout, 0L), TimeUnit.MILLISECONDS, this, handler); } return this; } @@ -124,7 +133,7 @@ public AioSession writeAndClose(ByteBuffer data) { public Future write(ByteBuffer data) { return this.channel.write(data); } - + /** * 写数据到目标端 * @@ -132,7 +141,7 @@ public Future write(ByteBuffer data) { * @return this */ public AioSession write(ByteBuffer data, CompletionHandler handler) { - this.channel.write(data, this, handler); + this.channel.write(data, Math.max(this.writeTimeout, 0L), TimeUnit.MILLISECONDS, this, handler); return this; } @@ -185,5 +194,15 @@ public AioSession closeOut() { */ public void close() { IoUtil.close(this.channel); + this.readBuffer = null; + this.writeBuffer = null; + } + + /** + * 执行读,用于读取事件结束的回调 + */ + protected void callbackRead() { + readBuffer.flip();// 读模式 + ioAction.doAction(this, readBuffer); } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java index 02819eeff4..b871d271c7 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/IoAction.java @@ -1,7 +1,5 @@ package cn.hutool.socket.aio; -import java.nio.channels.AsynchronousSocketChannel; - /** * Socket流处理接口
* 实现此接口用于处理接收到的消息,发送指定消息 @@ -13,14 +11,14 @@ public interface IoAction { /** - * 接收客户端连接事件处理 + * 接收客户端连接(会话建立)事件处理 * - * @param socketChannel 连接Socket对象 + * @param session 会话 */ - void accept(AsynchronousSocketChannel socketChannel); + void accept(AioSession session); /** - * 执行数据处理 + * 执行数据处理(消息读取) * * @param session Socket Session会话 * @param data 解码后的数据 @@ -28,7 +26,7 @@ public interface IoAction { void doAction(AioSession session, T data); /** - * 数据读取失败的回调事件处理 + * 数据读取失败的回调事件处理(消息读取失败) * * @param exc 异常 * @param session Session diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java new file mode 100644 index 0000000000..73d7e4338a --- /dev/null +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/ReadHandler.java @@ -0,0 +1,25 @@ +package cn.hutool.socket.aio; + +import java.nio.channels.CompletionHandler; + +import cn.hutool.socket.SocketRuntimeException; + +/** + * 数据读取完成回调,调用Session中相应方法处理消息,单例使用 + * + * @author looly + * + */ +public class ReadHandler implements CompletionHandler { + + @Override + public void completed(Integer result, AioSession session) { + session.callbackRead(); + } + + @Override + public void failed(Throwable exc, AioSession session) { + throw new SocketRuntimeException(exc); + } + +} diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java index 84f8680d1d..8e891e42ba 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/SimpleIoAction.java @@ -1,7 +1,6 @@ package cn.hutool.socket.aio; import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousSocketChannel; import cn.hutool.log.StaticLog; @@ -15,7 +14,7 @@ public abstract class SimpleIoAction implements IoAction { @Override - public void accept(AsynchronousSocketChannel socketChannel) { + public void accept(AioSession session) { } @Override diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java index c35300ebe0..872c3cee7f 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioClientTest.java @@ -15,11 +15,17 @@ public static void main(String[] args) { @Override public void doAction(AioSession session, ByteBuffer data) { - Console.log(StrUtil.utf8Str(data)); + if(data.hasRemaining()) { + Console.log(StrUtil.utf8Str(data)); + session.read(); + } + Console.log("OK"); } }); client.write(ByteBuffer.wrap("Hello".getBytes())); client.read(); + + client.close(); } } diff --git a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java index 50dd0dec6b..6fecb90f02 100644 --- a/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java +++ b/hutool-socket/src/test/java/cn/hutool/socket/AioServerTest.java @@ -1,11 +1,10 @@ package cn.hutool.socket; import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousSocketChannel; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.BufferUtil; import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; import cn.hutool.socket.aio.AioServer; @@ -20,8 +19,9 @@ public static void main(String[] args) { aioServer.setIoAction(new SimpleIoAction() { @Override - public void accept(AsynchronousSocketChannel socketChannel) { - StaticLog.debug("客户端接入:{}", SocketUtil.getRemoteAddress(socketChannel)); + public void accept(AioSession session) { + StaticLog.debug("【客户端】:{} 连接。", session.getRemoteAddress()); + session.write(BufferUtil.createUtf8("=== Welcome to Hutool socket server. ===")); } @Override @@ -35,7 +35,7 @@ public void doAction(AioSession session, ByteBuffer data) { .append("Content-Type: text/html; charset=UTF-8\r\n")// .append("\r\n") .append("Hello Hutool socket");// - session.writeAndClose(ByteBuffer.wrap(response.toString().getBytes(CharsetUtil.CHARSET_UTF_8))); + session.writeAndClose(BufferUtil.createUtf8(response)); }else { session.read(); } From b51e0b4669362733e6bf958b813d174a43961055 Mon Sep 17 00:00:00 2001 From: looly Date: Mon, 25 Feb 2019 16:52:06 +0000 Subject: [PATCH 16/17] fix sm2 --- .../main/java/cn/hutool/crypto/SmUtil.java | 83 ++++++++++++++++++- .../hutool/crypto/asymmetric/SM2Engine.java | 66 +++++++++------ .../java/cn/hutool/socket/aio/AioClient.java | 3 +- .../java/cn/hutool/socket/aio/AioSession.java | 2 - .../cn/hutool/socket/protocol/MsgEncoder.java | 1 - 5 files changed, 125 insertions(+), 30 deletions(-) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java index ada50b572c..76643c4352 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java @@ -1,10 +1,19 @@ package cn.hutool.crypto; import java.io.File; +import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import cn.hutool.core.io.IORuntimeException; import cn.hutool.crypto.asymmetric.SM2; import cn.hutool.crypto.digest.Digester; import cn.hutool.crypto.symmetric.SymmetricCrypto; @@ -18,6 +27,8 @@ */ public class SmUtil { + private final static int RS_LEN = 32; + private static String SM3 = "SM3"; private static String SM4 = "SM4"; @@ -151,7 +162,8 @@ public static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3, ECDomainParameters ecDo /** * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密 * - * @param 加密后的bytes,顺序为C1C3C2 + * @param c1c3c2 加密后的bytes,顺序为C1C3C2 + * @param ecDomainParameters {@link ECDomainParameters} * @return c1c2c3 加密后的bytes,顺序为C1C2C3 */ public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2, ECDomainParameters ecDomainParameters) { @@ -164,4 +176,73 @@ public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2, ECDomainParameters ecDo System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); // c3 return result; } + + /** + * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
+ * 来自:https://blog.csdn.net/pridas/article/details/86118774 + * + * @param rsDer rs in asn1 format + * @return sign result in plain byte array + * @since 4.5.0 + */ + public static byte[] rsAsn1ToPlain(byte[] rsDer) { + ASN1Sequence seq = ASN1Sequence.getInstance(rsDer); + byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue()); + byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue()); + byte[] result = new byte[RS_LEN * 2]; + System.arraycopy(r, 0, result, 0, r.length); + System.arraycopy(s, 0, result, RS_LEN, s.length); + return result; + } + + /** + * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
+ * 来自:https://blog.csdn.net/pridas/article/details/86118774 + * + * @param sign in plain byte array + * @return rs result in asn1 format + * @since 4.5.0 + */ + public static byte[] rsPlainToAsn1(byte[] sign) { + if (sign.length != RS_LEN * 2) { + throw new CryptoException("err rs. "); + } + BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2)); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(r)); + v.add(new ASN1Integer(s)); + try { + return new DERSequence(v).getEncoded("DER"); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + // -------------------------------------------------------------------------------------------------------- Private method start + /** + * BigInteger转固定长度bytes + * + * @param rOrS {@link BigInteger} + * @return 固定长度bytes + * @since 4.5.0 + */ + private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) { + // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123, + // r and s are the result of mod n, so they should be less than n and have length<=32 + byte[] rs = rOrS.toByteArray(); + if (rs.length == RS_LEN) { + return rs; + } else if (rs.length == RS_LEN + 1 && rs[0] == 0) { + return Arrays.copyOfRange(rs, 1, RS_LEN + 1); + } else if (rs.length < RS_LEN) { + byte[] result = new byte[RS_LEN]; + Arrays.fill(result, (byte) 0); + System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length); + return result; + } else { + throw new CryptoException("Error rs: {}", Hex.toHexString(rs)); + } + } + // -------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java index fd9e96e42d..91a6e1722c 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java @@ -183,20 +183,20 @@ private byte[] encrypt(byte[] in, int inOff, int inLen) { BigInteger k; do { k = nextK(); - //产生随机数计算出曲线点C1 + // 产生随机数计算出曲线点C1 c1 = multiplier.multiply(ecParams.getG(), k).normalize().getEncoded(false); kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize(); - kdf(digest, kPB, c2); + kdf(kPB, c2); } while (notEncrypted(c2, in, inOff)); // 杂凑值,效验数据 byte[] c3 = new byte[digest.getDigestSize()]; - addFieldElement(digest, kPB.getAffineXCoord()); - digest.update(in, inOff, inLen); - addFieldElement(digest, kPB.getAffineYCoord()); + addFieldElement(kPB.getAffineXCoord()); + this.digest.update(in, inOff, inLen); + addFieldElement(kPB.getAffineYCoord()); - digest.doFinal(c3, 0); + this.digest.doFinal(c3, 0); // 按照莫属输出结果 switch (mode) { @@ -238,15 +238,15 @@ private byte[] decrypt(byte[] in, int inOff, int inLen) { // C2位于第二部分 System.arraycopy(in, inOff + c1.length, c2, 0, c2.length); } - kdf(digest, c1P, c2); + kdf(c1P, c2); // 使用摘要验证C2数据 final byte[] c3 = new byte[digestSize]; - addFieldElement(digest, c1P.getAffineXCoord()); - digest.update(c2, 0, c2.length); - addFieldElement(digest, c1P.getAffineYCoord()); - digest.doFinal(c3, 0); + addFieldElement(c1P.getAffineXCoord()); + this.digest.update(c2, 0, c2.length); + addFieldElement(c1P.getAffineYCoord()); + this.digest.doFinal(c3, 0); int check = 0; for (int i = 0; i != c3.length; i++) { @@ -276,11 +276,11 @@ private boolean notEncrypted(byte[] encData, byte[] in, int inOff) { /** * 解密数据 * - * @param digest {@link Digest} * @param c1 c1点 * @param encData 密文 */ - private void kdf(Digest digest, ECPoint c1, byte[] encData) { + private void kdf(ECPoint c1, byte[] encData) { + final Digest digest = this.digest; int digestSize = digest.getDigestSize(); byte[] buf = new byte[Math.max(4, digestSize)]; int off = 0; @@ -289,8 +289,8 @@ private void kdf(Digest digest, ECPoint c1, byte[] encData) { Memoable copy = null; if (digest instanceof Memoable) { - addFieldElement(digest, c1.getAffineXCoord()); - addFieldElement(digest, c1.getAffineYCoord()); + addFieldElement(c1.getAffineXCoord()); + addFieldElement(c1.getAffineYCoord()); memo = (Memoable) digest; copy = memo.copy(); } @@ -301,8 +301,8 @@ private void kdf(Digest digest, ECPoint c1, byte[] encData) { if (memo != null) { memo.reset(copy); } else { - addFieldElement(digest, c1.getAffineXCoord()); - addFieldElement(digest, c1.getAffineYCoord()); + addFieldElement(c1.getAffineXCoord()); + addFieldElement(c1.getAffineYCoord()); } Pack.intToBigEndian(++ct, buf, 0); @@ -315,27 +315,45 @@ private void kdf(Digest digest, ECPoint c1, byte[] encData) { } } + /** + * 异或 + * + * @param data 数据 + * @param kdfOut kdf输出值 + * @param dOff d偏移 + * @param dRemaining d剩余 + */ private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) { for (int i = 0; i != dRemaining; i++) { data[dOff + i] ^= kdfOut[i]; } } + /** + * 下一个K值 + * + * @return K值 + */ private BigInteger nextK() { - int qBitLength = ecParams.getN().bitLength(); + final int qBitLength = this.ecParams.getN().bitLength(); BigInteger k; do { - k = new BigInteger(qBitLength, random); - } while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0); + k = new BigInteger(qBitLength, this.random); + } while (k.equals(ECConstants.ZERO) || k.compareTo(this.ecParams.getN()) >= 0); return k; } - private void addFieldElement(Digest digest, ECFieldElement v) { - byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); - - digest.update(p, 0, p.length); + /** + * 增加字段节点 + * + * @param digest + * @param v + */ + private void addFieldElement(ECFieldElement v) { + final byte[] p = BigIntegers.asUnsignedByteArray(this.curveLength, v.toBigInteger()); + this.digest.update(p, 0, p.length); } // --------------------------------------------------------------------------------------------------- Private method start } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java index ceaf0a9b21..7dd8ca2689 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioClient.java @@ -28,7 +28,6 @@ public class AioClient { * * @param address 地址 * @param ioAction IO处理类 - * @param config 配置项 */ public AioClient(InetSocketAddress address, IoAction ioAction) { this(address, ioAction, new SocketConfig()); @@ -83,7 +82,7 @@ public IoAction getIoAction() { /** * 从服务端读取数据 * - * @return + * @return this */ public AioClient read() { this.session.read(); diff --git a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java index afe9e501d4..b16a7b3f2b 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/aio/AioSession.java @@ -189,8 +189,6 @@ public AioSession closeOut() { /** * 关闭会话 - * - * @return this */ public void close() { IoUtil.close(this.channel); diff --git a/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java b/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java index ac5090dd56..8e563a4adf 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/protocol/MsgEncoder.java @@ -18,7 +18,6 @@ public interface MsgEncoder { * @param session 本次需要解码的session * @param writeBuffer 待处理的读buffer * @param data 写出的数据 - * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 */ void encode(AioSession session, ByteBuffer writeBuffer, T data); } From 8ab804602ccfa43d3163526f9ac3582e1d0a0f05 Mon Sep 17 00:00:00 2001 From: looly Date: Mon, 25 Feb 2019 17:01:49 +0000 Subject: [PATCH 17/17] release 4.5.0 --- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- 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 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index f4250eabfc..bdb8a80454 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index d97f72d6dd..d13625baeb 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 017a93c2a8..095b4c95c4 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-bloomFilter diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 345d433979..709ce4c021 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index c50d6bdf0c..dfb02e855b 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 16770f9d11..f179314198 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 2bea0ccacb..a8a44de3ff 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 069b9dc040..583b3bea4a 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 8aea75ddac..53d44551a7 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index e5d21982c7..59b9f2158f 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 8d74aaa906..146ae0ab4a 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 3b379b0c39..d479e3e79f 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index c10349da35..e7a7f74ad0 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 8689f716c5..b86c8aa52a 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 309e90bb6f..508cf0f173 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index fded6631ed..c16b9086ae 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index c1e39ca0c5..4afce6ab45 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 01b3c78e7c..a651326c8d 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index fb1e7764cb..13fd26b2d4 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool-system diff --git a/pom.xml b/pom.xml index d24a72b6c7..6e0f4ca7e6 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 4.5.0-SNAPSHOT + 4.5.0 hutool 提供丰富的Java工具方法 https://github.com/looly/hutool