fix local port bug

This commit is contained in:
Looly 2020-03-08 17:41:57 +08:00
parent 3c1060769e
commit fb4af337ea
4 changed files with 122 additions and 125 deletions

View File

@ -10,6 +10,7 @@
### Bug修复 ### Bug修复
* 【setting】 修复Props.toBean方法null的问题 * 【setting】 修复Props.toBean方法null的问题
* 【core 】 修复DataUtil.parseLocalDateTime无时间部分报错问题issue#I1B18H@Gitee * 【core 】 修复DataUtil.parseLocalDateTime无时间部分报错问题issue#I1B18H@Gitee
* 【core 】 修复NetUtil.isUsableLocalPort()判断问题issue#765@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -1,13 +1,25 @@
package cn.hutool.core.net; 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.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.DatagramSocket;
import java.net.IDN; import java.net.IDN;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.net.URL; import java.net.URL;
@ -21,36 +33,27 @@ import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; 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 class NetUtil {
public final static String LOCAL_IP = "127.0.0.1"; public final static String LOCAL_IP = "127.0.0.1";
/** 默认最小端口1024 */ /**
* 默认最小端口1024
*/
public static final int PORT_RANGE_MIN = 1024; public static final int PORT_RANGE_MIN = 1024;
/** 默认最大端口65535 */ /**
* 默认最大端口65535
*/
public static final int PORT_RANGE_MAX = 0xFFFF; public static final int PORT_RANGE_MAX = 0xFFFF;
/** /**
* 根据long值获取ip v4地址 * 根据long值获取ip v4地址
* *
* @param longIP IP的long表示形式 * @param longIP IP的long表示形式
* @return IP V4 地址 * @return IP V4 地址
*/ */
@ -70,7 +73,7 @@ public class NetUtil {
/** /**
* 根据ip地址计算出long型的数据 * 根据ip地址计算出long型的数据
* *
* @param strIP IP V4 地址 * @param strIP IP V4 地址
* @return long值 * @return long值
*/ */
@ -94,7 +97,7 @@ public class NetUtil {
/** /**
* 检测本地端口可用性<br> * 检测本地端口可用性<br>
* 来自org.springframework.util.SocketUtils * 来自org.springframework.util.SocketUtils
* *
* @param port 被检测的端口 * @param port 被检测的端口
* @return 是否可用 * @return 是否可用
*/ */
@ -103,18 +106,27 @@ public class NetUtil {
// 给定的IP未在指定端口范围中 // 给定的IP未在指定端口范围中
return false; return false;
} }
try {
ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName(LOCAL_IP)).close(); // issue#765@Github, 某些绑定非127.0.0.1的端口无法被检测到
return true; try (ServerSocket ss = new ServerSocket(port)) {
} catch (Exception e) { ss.setReuseAddress(true);
} catch (IOException ignored) {
return false; return false;
} }
try (DatagramSocket ds = new DatagramSocket(port)) {
ds.setReuseAddress(true);
} catch (IOException ignored) {
return false;
}
return true;
} }
/** /**
* 是否为有效的端口<br> * 是否为有效的端口<br>
* 此方法并不检查端口是否被占用 * 此方法并不检查端口是否被占用
* *
* @param port 端口号 * @param port 端口号
* @return 是否有效 * @return 是否有效
*/ */
@ -127,7 +139,7 @@ public class NetUtil {
* 查找1024~65535范围内的可用端口<br> * 查找1024~65535范围内的可用端口<br>
* 此方法只检测给定范围内的随机一个端口检测65535-1024次<br> * 此方法只检测给定范围内的随机一个端口检测65535-1024次<br>
* 来自org.springframework.util.SocketUtils * 来自org.springframework.util.SocketUtils
* *
* @return 可用的端口 * @return 可用的端口
* @since 4.5.4 * @since 4.5.4
*/ */
@ -139,7 +151,7 @@ public class NetUtil {
* 查找指定范围内的可用端口最大值为65535<br> * 查找指定范围内的可用端口最大值为65535<br>
* 此方法只检测给定范围内的随机一个端口检测65535-minPort次<br> * 此方法只检测给定范围内的随机一个端口检测65535-minPort次<br>
* 来自org.springframework.util.SocketUtils * 来自org.springframework.util.SocketUtils
* *
* @param minPort 端口最小值包含 * @param minPort 端口最小值包含
* @return 可用的端口 * @return 可用的端口
* @since 4.5.4 * @since 4.5.4
@ -152,14 +164,14 @@ public class NetUtil {
* 查找指定范围内的可用端口<br> * 查找指定范围内的可用端口<br>
* 此方法只检测给定范围内的随机一个端口检测maxPort-minPort次<br> * 此方法只检测给定范围内的随机一个端口检测maxPort-minPort次<br>
* 来自org.springframework.util.SocketUtils * 来自org.springframework.util.SocketUtils
* *
* @param minPort 端口最小值包含 * @param minPort 端口最小值包含
* @param maxPort 端口最大值包含 * @param maxPort 端口最大值包含
* @return 可用的端口 * @return 可用的端口
* @since 4.5.4 * @since 4.5.4
*/ */
public static int getUsableLocalPort(int minPort, int maxPort) { public static int getUsableLocalPort(int minPort, int maxPort) {
final int maxPortExclude = maxPort +1; final int maxPortExclude = maxPort + 1;
int randomPort; int randomPort;
for (int i = minPort; i < maxPortExclude; i++) { for (int i = minPort; i < maxPortExclude; i++) {
randomPort = RandomUtil.randomInt(minPort, maxPortExclude); randomPort = RandomUtil.randomInt(minPort, maxPortExclude);
@ -176,8 +188,8 @@ public class NetUtil {
* 来自org.springframework.util.SocketUtils * 来自org.springframework.util.SocketUtils
* *
* @param numRequested 尝试次数 * @param numRequested 尝试次数
* @param minPort 端口最小值包含 * @param minPort 端口最小值包含
* @param maxPort 端口最大值包含 * @param maxPort 端口最大值包含
* @return 可用的端口 * @return 可用的端口
* @since 4.5.4 * @since 4.5.4
*/ */
@ -198,7 +210,7 @@ public class NetUtil {
/** /**
* 判定是否为内网IP<br> * 判定是否为内网IP<br>
* 私有IPA类 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这个网段是环回地址 * 私有IPA类 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地址 * @param ipAddress IP地址
* @return 是否为内网IP * @return 是否为内网IP
*/ */
@ -221,9 +233,9 @@ public class NetUtil {
/** /**
* 相对URL转换为绝对URL * 相对URL转换为绝对URL
* *
* @param absoluteBasePath 基准路径绝对 * @param absoluteBasePath 基准路径绝对
* @param relativePath 相对路径 * @param relativePath 相对路径
* @return 绝对URL * @return 绝对URL
*/ */
public static String toAbsoluteUrl(String absoluteBasePath, String relativePath) { public static String toAbsoluteUrl(String absoluteBasePath, String relativePath) {
@ -237,7 +249,7 @@ public class NetUtil {
/** /**
* 隐藏掉IP地址的最后一部分为 * 代替 * 隐藏掉IP地址的最后一部分为 * 代替
* *
* @param ip IP地址 * @param ip IP地址
* @return 隐藏部分后的IP * @return 隐藏部分后的IP
*/ */
@ -247,7 +259,7 @@ public class NetUtil {
/** /**
* 隐藏掉IP地址的最后一部分为 * 代替 * 隐藏掉IP地址的最后一部分为 * 代替
* *
* @param ip IP地址 * @param ip IP地址
* @return 隐藏部分后的IP * @return 隐藏部分后的IP
*/ */
@ -259,8 +271,8 @@ public class NetUtil {
* 构建InetSocketAddress<br> * 构建InetSocketAddress<br>
* 当host中包含端口时隔开使用host中的端口否则使用默认端口<br> * 当host中包含端口时隔开使用host中的端口否则使用默认端口<br>
* 给定host为空时使用本地host127.0.0.1 * 给定host为空时使用本地host127.0.0.1
* *
* @param host Host * @param host Host
* @param defaultPort 默认端口 * @param defaultPort 默认端口
* @return InetSocketAddress * @return InetSocketAddress
*/ */
@ -286,7 +298,7 @@ public class NetUtil {
/** /**
* 通过域名得到IP * 通过域名得到IP
* *
* @param hostName HOST * @param hostName HOST
* @return ip address or hostName if UnknownHostException * @return ip address or hostName if UnknownHostException
*/ */
@ -314,9 +326,9 @@ public class NetUtil {
} }
NetworkInterface netInterface; NetworkInterface netInterface;
while(networkInterfaces.hasMoreElements()){ while (networkInterfaces.hasMoreElements()) {
netInterface = networkInterfaces.nextElement(); netInterface = networkInterfaces.nextElement();
if(null != netInterface && name.equals(netInterface.getName())){ if (null != netInterface && name.equals(netInterface.getName())) {
return netInterface; return netInterface;
} }
} }
@ -326,7 +338,7 @@ public class NetUtil {
/** /**
* 获取本机所有网卡 * 获取本机所有网卡
* *
* @return 所有网卡异常返回<code>null</code> * @return 所有网卡异常返回<code>null</code>
* @since 3.0.1 * @since 3.0.1
*/ */
@ -344,7 +356,7 @@ public class NetUtil {
/** /**
* 获得本机的IPv4地址列表<br> * 获得本机的IPv4地址列表<br>
* 返回的IP列表有序按照系统设备顺序 * 返回的IP列表有序按照系统设备顺序
* *
* @return IP地址列表 {@link LinkedHashSet} * @return IP地址列表 {@link LinkedHashSet}
*/ */
public static LinkedHashSet<String> localIpv4s() { public static LinkedHashSet<String> localIpv4s() {
@ -356,7 +368,7 @@ public class NetUtil {
/** /**
* 获得本机的IPv6地址列表<br> * 获得本机的IPv6地址列表<br>
* 返回的IP列表有序按照系统设备顺序 * 返回的IP列表有序按照系统设备顺序
* *
* @return IP地址列表 {@link LinkedHashSet} * @return IP地址列表 {@link LinkedHashSet}
* @since 4.5.17 * @since 4.5.17
*/ */
@ -368,7 +380,7 @@ public class NetUtil {
/** /**
* 地址列表转换为IP地址列表 * 地址列表转换为IP地址列表
* *
* @param addressList 地址{@link Inet4Address} 列表 * @param addressList 地址{@link Inet4Address} 列表
* @return IP地址字符串列表 * @return IP地址字符串列表
* @since 4.5.17 * @since 4.5.17
@ -385,7 +397,7 @@ public class NetUtil {
/** /**
* 获得本机的IP地址列表包括Ipv4和Ipv6<br> * 获得本机的IP地址列表包括Ipv4和Ipv6<br>
* 返回的IP列表有序按照系统设备顺序 * 返回的IP列表有序按照系统设备顺序
* *
* @return IP地址列表 {@link LinkedHashSet} * @return IP地址列表 {@link LinkedHashSet}
*/ */
public static LinkedHashSet<String> localIps() { public static LinkedHashSet<String> localIps() {
@ -395,7 +407,7 @@ public class NetUtil {
/** /**
* 获取所有满足过滤条件的本地IP地址对象 * 获取所有满足过滤条件的本地IP地址对象
* *
* @param addressFilter 过滤器null表示不过滤获取所有地址 * @param addressFilter 过滤器null表示不过滤获取所有地址
* @return 过滤后的地址对象列表 * @return 过滤后的地址对象列表
* @since 4.5.17 * @since 4.5.17
@ -432,9 +444,9 @@ public class NetUtil {
* 获取本机网卡IP地址这个地址为所有网卡中非回路地址的第一个<br> * 获取本机网卡IP地址这个地址为所有网卡中非回路地址的第一个<br>
* 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取<br> * 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取<br>
* 此方法不会抛出异常获取失败将返回<code>null</code><br> * 此方法不会抛出异常获取失败将返回<code>null</code><br>
* * <p>
* 参考http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java * 参考http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java
* *
* @return 本机网卡IP地址获取失败返回<code>null</code> * @return 本机网卡IP地址获取失败返回<code>null</code>
* @since 3.0.7 * @since 3.0.7
*/ */
@ -448,16 +460,16 @@ public class NetUtil {
/** /**
* 获取本机网卡IP地址规则如下 * 获取本机网卡IP地址规则如下
* *
* <pre> * <pre>
* 1. 查找所有网卡地址必须非回路loopback地址非局域网地址siteLocalIPv4地址 * 1. 查找所有网卡地址必须非回路loopback地址非局域网地址siteLocalIPv4地址
* 2. 如果无满足要求的地址调用 {@link InetAddress#getLocalHost()} 获取地址 * 2. 如果无满足要求的地址调用 {@link InetAddress#getLocalHost()} 获取地址
* </pre> * </pre>
* * <p>
* 此方法不会抛出异常获取失败将返回<code>null</code><br> * 此方法不会抛出异常获取失败将返回<code>null</code><br>
* * <p>
* https://github.com/looly/hutool/issues/428 * https://github.com/looly/hutool/issues/428
* *
* @return 本机网卡IP地址获取失败返回<code>null</code> * @return 本机网卡IP地址获取失败返回<code>null</code>
* @since 3.0.1 * @since 3.0.1
*/ */
@ -467,7 +479,7 @@ public class NetUtil {
return false == address.isLoopbackAddress() return false == address.isLoopbackAddress()
// 非地区本地地址指10.0.0.0 ~ 10.255.255.255172.16.0.0 ~ 172.31.255.255192.168.0.0 ~ 192.168.255.255 // 非地区本地地址指10.0.0.0 ~ 10.255.255.255172.16.0.0 ~ 172.31.255.255192.168.0.0 ~ 192.168.255.255
&& false == address.isSiteLocalAddress() && false == address.isSiteLocalAddress()
// 需为IPV4地址 // 需为IPV4地址
&& address instanceof Inet4Address; && address instanceof Inet4Address;
}); });
@ -486,7 +498,7 @@ public class NetUtil {
/** /**
* 获得本机MAC地址 * 获得本机MAC地址
* *
* @return 本机MAC地址 * @return 本机MAC地址
*/ */
public static String getLocalMacAddress() { public static String getLocalMacAddress() {
@ -495,7 +507,7 @@ public class NetUtil {
/** /**
* 获得指定地址信息中的MAC地址使用分隔符- * 获得指定地址信息中的MAC地址使用分隔符-
* *
* @param inetAddress {@link InetAddress} * @param inetAddress {@link InetAddress}
* @return MAC地址-分隔 * @return MAC地址-分隔
*/ */
@ -505,9 +517,9 @@ public class NetUtil {
/** /**
* 获得指定地址信息中的MAC地址 * 获得指定地址信息中的MAC地址
* *
* @param inetAddress {@link InetAddress} * @param inetAddress {@link InetAddress}
* @param separator 分隔符推荐使用-或者: * @param separator 分隔符推荐使用-或者:
* @return MAC地址-分隔 * @return MAC地址-分隔
*/ */
public static String getMacAddress(InetAddress inetAddress, String separator) { public static String getMacAddress(InetAddress inetAddress, String separator) {
@ -539,7 +551,7 @@ public class NetUtil {
/** /**
* 创建 {@link InetSocketAddress} * 创建 {@link InetSocketAddress}
* *
* @param host 域名或IP地址空表示任意地址 * @param host 域名或IP地址空表示任意地址
* @param port 端口0表示系统分配临时端口 * @param port 端口0表示系统分配临时端口
* @return {@link InetSocketAddress} * @return {@link InetSocketAddress}
@ -553,13 +565,12 @@ public class NetUtil {
} }
/** /**
*
* 简易的使用Socket发送数据 * 简易的使用Socket发送数据
* *
* @param host Server主机 * @param host Server主机
* @param port Server端口 * @param port Server端口
* @param isBlock 是否阻塞方式 * @param isBlock 是否阻塞方式
* @param data 需要发送的数据 * @param data 需要发送的数据
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @since 3.3.0 * @since 3.3.0
*/ */
@ -573,9 +584,8 @@ public class NetUtil {
} }
/** /**
*
* 使用普通Socket发送数据 * 使用普通Socket发送数据
* *
* @param host Server主机 * @param host Server主机
* @param port Server端口 * @param port Server端口
* @param data 数据 * @param data 数据
@ -598,8 +608,8 @@ public class NetUtil {
/** /**
* 是否在CIDR规则配置范围内<br> * 是否在CIDR规则配置范围内<br>
* 方法来自成都小邓 * 方法来自成都小邓
* *
* @param ip 需要验证的IP * @param ip 需要验证的IP
* @param cidr CIDR规则 * @param cidr CIDR规则
* @return 是否在范围内 * @return 是否在范围内
* @since 4.0.6 * @since 4.0.6
@ -617,7 +627,7 @@ public class NetUtil {
/** /**
* Unicode域名转puny code * Unicode域名转puny code
* *
* @param unicode Unicode域名 * @param unicode Unicode域名
* @return puny code * @return puny code
* @since 4.1.22 * @since 4.1.22
@ -628,7 +638,7 @@ public class NetUtil {
/** /**
* 从多级反向代理中获得第一个非unknown IP地址 * 从多级反向代理中获得第一个非unknown IP地址
* *
* @param ip 获得的IP地址 * @param ip 获得的IP地址
* @return 第一个非unknown IP地址 * @return 第一个非unknown IP地址
* @since 4.4.1 * @since 4.4.1
@ -649,7 +659,7 @@ public class NetUtil {
/** /**
* 检测给定字符串是否为未知多用于检测HTTP请求相关<br> * 检测给定字符串是否为未知多用于检测HTTP请求相关<br>
* *
* @param checkString 被检测的字符串 * @param checkString 被检测的字符串
* @return 是否未知 * @return 是否未知
* @since 4.4.1 * @since 4.4.1
@ -658,38 +668,39 @@ public class NetUtil {
return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
} }
/** /**
* 检测IP地址是否能ping通 * 检测IP地址是否能ping通
* *
* @param ip IP地址 * @param ip IP地址
* @return 返回是否ping通 * @return 返回是否ping通
*/ */
public static boolean ping(String ip) { public static boolean ping(String ip) {
return ping(ip, 200); 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 // ----------------------------------------------------------------------------------------- Private method start
/** /**
* 指定IP的long是否在指定范围内 * 指定IP的long是否在指定范围内
* *
* @param userIp 用户IP * @param userIp 用户IP
* @param begin 开始IP * @param begin 开始IP
* @param end 结束IP * @param end 结束IP
* @return 是否在范围内 * @return 是否在范围内
*/ */
private static boolean isInner(long userIp, long begin, long end) { private static boolean isInner(long userIp, long begin, long end) {

View File

@ -379,35 +379,17 @@ public class RandomUtil {
* *
* @param length 长度 * @param length 长度
* @return 随机索引 * @return 随机索引
* @since 5.2.1
*/ */
public static int[] createRandomList(int length){ public static int[] randomInts(int length){
int[] list = ArrayUtil.range(length); final int[] range = ArrayUtil.range(length);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
int random = randomInt(i,length); 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 <T> 元素类型
* @return 随机列表
*/
public static <T> List<T> randomEleList(List<T> source, int count){
if(count >= source.size()){
return source;
}
int[] randomList = ArrayUtil.sub(createRandomList(source.size()),0,count);
List<T> result = new ArrayList<>();
for (int e: randomList){
result.add(source.get(e));
}
return result;
}
/** /**
* 获得一个随机的字符串只包含数字和字符 * 获得一个随机的字符串只包含数字和字符
* *

View File

@ -1,14 +1,12 @@
package cn.hutool.core.net; 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.Assert;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import cn.hutool.core.lang.PatternPool; import java.net.InetAddress;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ReUtil;
/** /**
* NetUtil单元测试 * NetUtil单元测试
@ -54,4 +52,9 @@ public class NetUtilTest {
long ipLong = NetUtil.ipv4ToLong("127.0.0.1"); long ipLong = NetUtil.ipv4ToLong("127.0.0.1");
Assert.assertEquals(2130706433L, ipLong); Assert.assertEquals(2130706433L, ipLong);
} }
@Test
public void isUsableLocalPortTest(){
Assert.assertTrue(NetUtil.isUsableLocalPort(80));
}
} }