diff --git a/CHANGELOG.md b/CHANGELOG.md index c39170c2f..3473da7ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Bug修复 * 【setting】 修复Props.toBean方法null的问题 * 【core 】 修复DataUtil.parseLocalDateTime无时间部分报错问题(issue#I1B18H@Gitee) +* 【core 】 修复NetUtil.isUsableLocalPort()判断问题(issue#765@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index c095f4f8b..904fcb287 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -1,13 +1,25 @@ package cn.hutool.core.net; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; + import java.io.IOException; import java.io.OutputStream; +import java.net.DatagramSocket; import java.net.IDN; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; +import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.URL; @@ -21,36 +33,27 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; -import javax.net.ServerSocketFactory; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Filter; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; - /** * 网络相关工具 - * - * @author xiaoleilu * + * @author xiaoleilu */ public class NetUtil { public final static String LOCAL_IP = "127.0.0.1"; - /** 默认最小端口,1024 */ + /** + * 默认最小端口,1024 + */ public static final int PORT_RANGE_MIN = 1024; - /** 默认最大端口,65535 */ + /** + * 默认最大端口,65535 + */ public static final int PORT_RANGE_MAX = 0xFFFF; /** * 根据long值获取ip v4地址 - * + * * @param longIP IP的long表示形式 * @return IP V4 地址 */ @@ -70,7 +73,7 @@ public class NetUtil { /** * 根据ip地址计算出long型的数据 - * + * * @param strIP IP V4 地址 * @return long值 */ @@ -94,7 +97,7 @@ public class NetUtil { /** * 检测本地端口可用性
* 来自org.springframework.util.SocketUtils - * + * * @param port 被检测的端口 * @return 是否可用 */ @@ -103,18 +106,27 @@ public class NetUtil { // 给定的IP未在指定端口范围中 return false; } - try { - ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName(LOCAL_IP)).close(); - return true; - } catch (Exception e) { + + // issue#765@Github, 某些绑定非127.0.0.1的端口无法被检测到 + try (ServerSocket ss = new ServerSocket(port)) { + ss.setReuseAddress(true); + } catch (IOException ignored) { return false; } + + try (DatagramSocket ds = new DatagramSocket(port)) { + ds.setReuseAddress(true); + } catch (IOException ignored) { + return false; + } + + return true; } /** * 是否为有效的端口
* 此方法并不检查端口是否被占用 - * + * * @param port 端口号 * @return 是否有效 */ @@ -127,7 +139,7 @@ public class NetUtil { * 查找1024~65535范围内的可用端口
* 此方法只检测给定范围内的随机一个端口,检测65535-1024次
* 来自org.springframework.util.SocketUtils - * + * * @return 可用的端口 * @since 4.5.4 */ @@ -139,7 +151,7 @@ public class NetUtil { * 查找指定范围内的可用端口,最大值为65535
* 此方法只检测给定范围内的随机一个端口,检测65535-minPort次
* 来自org.springframework.util.SocketUtils - * + * * @param minPort 端口最小值(包含) * @return 可用的端口 * @since 4.5.4 @@ -152,14 +164,14 @@ public class NetUtil { * 查找指定范围内的可用端口
* 此方法只检测给定范围内的随机一个端口,检测maxPort-minPort次
* 来自org.springframework.util.SocketUtils - * + * * @param minPort 端口最小值(包含) * @param maxPort 端口最大值(包含) * @return 可用的端口 * @since 4.5.4 */ public static int getUsableLocalPort(int minPort, int maxPort) { - final int maxPortExclude = maxPort +1; + final int maxPortExclude = maxPort + 1; int randomPort; for (int i = minPort; i < maxPortExclude; i++) { randomPort = RandomUtil.randomInt(minPort, maxPortExclude); @@ -176,8 +188,8 @@ public class NetUtil { * 来自org.springframework.util.SocketUtils * * @param numRequested 尝试次数 - * @param minPort 端口最小值(包含) - * @param maxPort 端口最大值(包含) + * @param minPort 端口最小值(包含) + * @param maxPort 端口最大值(包含) * @return 可用的端口 * @since 4.5.4 */ @@ -198,7 +210,7 @@ public class NetUtil { /** * 判定是否为内网IP
* 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址 - * + * * @param ipAddress IP地址 * @return 是否为内网IP */ @@ -221,9 +233,9 @@ public class NetUtil { /** * 相对URL转换为绝对URL - * + * * @param absoluteBasePath 基准路径,绝对 - * @param relativePath 相对路径 + * @param relativePath 相对路径 * @return 绝对URL */ public static String toAbsoluteUrl(String absoluteBasePath, String relativePath) { @@ -237,7 +249,7 @@ public class NetUtil { /** * 隐藏掉IP地址的最后一部分为 * 代替 - * + * * @param ip IP地址 * @return 隐藏部分后的IP */ @@ -247,7 +259,7 @@ public class NetUtil { /** * 隐藏掉IP地址的最后一部分为 * 代替 - * + * * @param ip IP地址 * @return 隐藏部分后的IP */ @@ -259,8 +271,8 @@ public class NetUtil { * 构建InetSocketAddress
* 当host中包含端口时(用“:”隔开),使用host中的端口,否则使用默认端口
* 给定host为空时使用本地host(127.0.0.1) - * - * @param host Host + * + * @param host Host * @param defaultPort 默认端口 * @return InetSocketAddress */ @@ -286,7 +298,7 @@ public class NetUtil { /** * 通过域名得到IP - * + * * @param hostName HOST * @return ip address or hostName if UnknownHostException */ @@ -314,9 +326,9 @@ public class NetUtil { } NetworkInterface netInterface; - while(networkInterfaces.hasMoreElements()){ + while (networkInterfaces.hasMoreElements()) { netInterface = networkInterfaces.nextElement(); - if(null != netInterface && name.equals(netInterface.getName())){ + if (null != netInterface && name.equals(netInterface.getName())) { return netInterface; } } @@ -326,7 +338,7 @@ public class NetUtil { /** * 获取本机所有网卡 - * + * * @return 所有网卡,异常返回null * @since 3.0.1 */ @@ -344,7 +356,7 @@ public class NetUtil { /** * 获得本机的IPv4地址列表
* 返回的IP列表有序,按照系统设备顺序 - * + * * @return IP地址列表 {@link LinkedHashSet} */ public static LinkedHashSet localIpv4s() { @@ -356,7 +368,7 @@ public class NetUtil { /** * 获得本机的IPv6地址列表
* 返回的IP列表有序,按照系统设备顺序 - * + * * @return IP地址列表 {@link LinkedHashSet} * @since 4.5.17 */ @@ -368,7 +380,7 @@ public class NetUtil { /** * 地址列表转换为IP地址列表 - * + * * @param addressList 地址{@link Inet4Address} 列表 * @return IP地址字符串列表 * @since 4.5.17 @@ -385,7 +397,7 @@ public class NetUtil { /** * 获得本机的IP地址列表(包括Ipv4和Ipv6)
* 返回的IP列表有序,按照系统设备顺序 - * + * * @return IP地址列表 {@link LinkedHashSet} */ public static LinkedHashSet localIps() { @@ -395,7 +407,7 @@ public class NetUtil { /** * 获取所有满足过滤条件的本地IP地址对象 - * + * * @param addressFilter 过滤器,null表示不过滤,获取所有地址 * @return 过滤后的地址对象列表 * @since 4.5.17 @@ -432,9 +444,9 @@ public class NetUtil { * 获取本机网卡IP地址,这个地址为所有网卡中非回路地址的第一个
* 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取。
* 此方法不会抛出异常,获取失败将返回null
- * + *

* 参考:http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java - * + * * @return 本机网卡IP地址,获取失败返回null * @since 3.0.7 */ @@ -448,16 +460,16 @@ public class NetUtil { /** * 获取本机网卡IP地址,规则如下: - * + * *

 	 * 1. 查找所有网卡地址,必须非回路(loopback)地址、非局域网地址(siteLocal)、IPv4地址
 	 * 2. 如果无满足要求的地址,调用 {@link InetAddress#getLocalHost()} 获取地址
 	 * 
- * + *

* 此方法不会抛出异常,获取失败将返回null
- * + *

* 见:https://github.com/looly/hutool/issues/428 - * + * * @return 本机网卡IP地址,获取失败返回null * @since 3.0.1 */ @@ -467,7 +479,7 @@ public class NetUtil { return false == address.isLoopbackAddress() // 非地区本地地址,指10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255 && false == address.isSiteLocalAddress() - // 需为IPV4地址 + // 需为IPV4地址 && address instanceof Inet4Address; }); @@ -486,7 +498,7 @@ public class NetUtil { /** * 获得本机MAC地址 - * + * * @return 本机MAC地址 */ public static String getLocalMacAddress() { @@ -495,7 +507,7 @@ public class NetUtil { /** * 获得指定地址信息中的MAC地址,使用分隔符“-” - * + * * @param inetAddress {@link InetAddress} * @return MAC地址,用-分隔 */ @@ -505,9 +517,9 @@ public class NetUtil { /** * 获得指定地址信息中的MAC地址 - * + * * @param inetAddress {@link InetAddress} - * @param separator 分隔符,推荐使用“-”或者“:” + * @param separator 分隔符,推荐使用“-”或者“:” * @return MAC地址,用-分隔 */ public static String getMacAddress(InetAddress inetAddress, String separator) { @@ -539,7 +551,7 @@ public class NetUtil { /** * 创建 {@link InetSocketAddress} - * + * * @param host 域名或IP地址,空表示任意地址 * @param port 端口,0表示系统分配临时端口 * @return {@link InetSocketAddress} @@ -553,13 +565,12 @@ public class NetUtil { } /** - * * 简易的使用Socket发送数据 - * - * @param host Server主机 - * @param port Server端口 + * + * @param host Server主机 + * @param port Server端口 * @param isBlock 是否阻塞方式 - * @param data 需要发送的数据 + * @param data 需要发送的数据 * @throws IORuntimeException IO异常 * @since 3.3.0 */ @@ -573,9 +584,8 @@ public class NetUtil { } /** - * * 使用普通Socket发送数据 - * + * * @param host Server主机 * @param port Server端口 * @param data 数据 @@ -598,8 +608,8 @@ public class NetUtil { /** * 是否在CIDR规则配置范围内
* 方法来自:【成都】小邓 - * - * @param ip 需要验证的IP + * + * @param ip 需要验证的IP * @param cidr CIDR规则 * @return 是否在范围内 * @since 4.0.6 @@ -617,7 +627,7 @@ public class NetUtil { /** * Unicode域名转puny code - * + * * @param unicode Unicode域名 * @return puny code * @since 4.1.22 @@ -628,7 +638,7 @@ public class NetUtil { /** * 从多级反向代理中获得第一个非unknown IP地址 - * + * * @param ip 获得的IP地址 * @return 第一个非unknown IP地址 * @since 4.4.1 @@ -649,7 +659,7 @@ public class NetUtil { /** * 检测给定字符串是否为未知,多用于检测HTTP请求相关
- * + * * @param checkString 被检测的字符串 * @return 是否未知 * @since 4.4.1 @@ -658,38 +668,39 @@ public class NetUtil { return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } - /** - * 检测IP地址是否能ping通 - * - * @param ip IP地址 - * @return 返回是否ping通 - */ - public static boolean ping(String ip) { - return ping(ip, 200); - } + /** + * 检测IP地址是否能ping通 + * + * @param ip IP地址 + * @return 返回是否ping通 + */ + public static boolean ping(String ip) { + return ping(ip, 200); + } + + /** + * 检测IP地址是否能ping通 + * + * @param ip IP地址 + * @param timeout 检测超时(毫秒) + * @return 是否ping通 + */ + public static boolean ping(String ip, int timeout) { + try { + return InetAddress.getByName(ip).isReachable(timeout); // 当返回值是true时,说明host是可用的,false则不可。 + } catch (Exception ex) { + return false; + } + } - /** - * 检测IP地址是否能ping通 - * - * @param ip IP地址 - * @param timeout 检测超时(毫秒) - * @return 是否ping通 - */ - public static boolean ping(String ip, int timeout) { - try { - return InetAddress.getByName(ip).isReachable(timeout); // 当返回值是true时,说明host是可用的,false则不可。 - } catch (Exception ex) { - return false; - } - } - // ----------------------------------------------------------------------------------------- Private method start + /** * 指定IP的long是否在指定范围内 - * + * * @param userIp 用户IP - * @param begin 开始IP - * @param end 结束IP + * @param begin 开始IP + * @param end 结束IP * @return 是否在范围内 */ private static boolean isInner(long userIp, long begin, long end) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index d29cd14d4..58cbed0df 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -379,35 +379,17 @@ public class RandomUtil { * * @param length 长度 * @return 随机索引 + * @since 5.2.1 */ - public static int[] createRandomList(int length){ - int[] list = ArrayUtil.range(length); + public static int[] randomInts(int length){ + final int[] range = ArrayUtil.range(length); for (int i = 0; i < length; i++) { int random = randomInt(i,length); - ArrayUtil.swap(list,i,random); + ArrayUtil.swap(range,i,random); } - return list; + return range; } - /** - * 随机获得列表中的一定量的元素,返回List - * - * @param source 列表 - * @param count 随机取出的个数 - * @param 元素类型 - * @return 随机列表 - */ - public static List randomEleList(List source, int count){ - if(count >= source.size()){ - return source; - } - int[] randomList = ArrayUtil.sub(createRandomList(source.size()),0,count); - List result = new ArrayList<>(); - for (int e: randomList){ - result.add(source.get(e)); - } - return result; - } /** * 获得一个随机的字符串(只包含数字和字符) * diff --git a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java index 6a9910b4d..32a16a7ad 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java @@ -1,14 +1,12 @@ package cn.hutool.core.net; -import java.net.InetAddress; - +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.util.ReUtil; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import cn.hutool.core.lang.PatternPool; -import cn.hutool.core.net.NetUtil; -import cn.hutool.core.util.ReUtil; +import java.net.InetAddress; /** * NetUtil单元测试 @@ -54,4 +52,9 @@ public class NetUtilTest { long ipLong = NetUtil.ipv4ToLong("127.0.0.1"); Assert.assertEquals(2130706433L, ipLong); } + + @Test + public void isUsableLocalPortTest(){ + Assert.assertTrue(NetUtil.isUsableLocalPort(80)); + } }