这篇文章讲述了PowerJob获取本地IP离奇曲折的经过,以及开放了诸多的可配置参数,打开了我新世界的大窗户。求个关注,求个点赞,求一个评论。
获取地址的操作,本来不应该作为什么重点,但是因为一点小小的意外,导致我对这个环节格外的研究了一下,所以就总结了一下。
先来一段文字,描述一下大致的流程,然后再从源代码中研究一下:
-
先是判断内存是否已经存了IP地址,如果存了,则直接返回保存了个IP地址。
-
从jvm虚拟机中获取配置绑定的IP地址,如果绑定了,则直接返回绑定的IP地址。
-
获取所有的网卡信息,进行遍历。
-
忽略一些无效的网卡信息,比如:虚拟机网口,关闭的网口,启动时配置的忽略网口(主要是用过网卡名字和描述名字)。
-
通过启动时的配置“powerjob.network.interface.preferred”,获取对应的地址,如果有,则直接返回对应的IP地址。
-
对剩余的网卡信息进行遍历,如果遍历到的IP地址合法有效(格式正确并可以ping通),则直接返回该合法有效的ip。
-
直接返回第一条格式正确的IP地址。
-
获取InetAddress.getLocalHost()得到的IP地址。
经过上述一系列的复杂操作,如果没有配置的话,获得到IP地址可能会无效。
简单的开端
public static String getLocalHost() {
//1.先是判断内存是否已经存了IP地址,如果存了,则直接返回保存了个IP地址。
if (HOST_ADDRESS != null) {
return HOST_ADDRESS;
}
//2.从jvm虚拟机中获取配置绑定的IP地址,如果绑定了,则直接返回绑定的IP地址。
String addressFromJVM = System.getProperty(PowerJobDKey.BIND_LOCAL_ADDRESS);
if (StringUtils.isNotEmpty(addressFromJVM)) {
log.info("[Net] use address from[{}]: {}", PowerJobDKey.BIND_LOCAL_ADDRESS, addressFromJVM);
return HOST_ADDRESS = addressFromJVM;
}
//第三步在这个方法里面,但是还需要不断的深入才能找到!
InetAddress address = getLocalAddress();
if (address != null) {
return HOST_ADDRESS = address.getHostAddress();
}
return LOCALHOST_VALUE;
}
一切的开端都是从上面的代码开始的。开始很简单,过程却很复杂。
曲折的经过
第1,2步已经出现,这第3步的出现却需要层层的传送~
public static InetAddress getLocalAddress() {
//这个方法只是一个传送门,将其传送到getLocalAddress0
}
private static InetAddress getLocalAddress0() {
//这个方法也只是传送门,不过这个方法在之后还会出现的
InetAddress addressOp = getFirstReachableInetAddress( findNetworkInterface());
... ...
return localAddress;
}
public static NetworkInterface findNetworkInterface() {
//传送门依旧,该方法之后也会再次出现
List<NetworkInterface> validNetworkInterfaces = emptyList();
try {
validNetworkInterfaces = getValidNetworkInterfaces();
} catch (Throwable e) {
log.warn("[Net] findNetworkInterface failed", e);
}
... ...
}
private static List<NetworkInterface> getValidNetworkInterfaces() throws SocketException {
List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
//3.获取所有的网卡信息,进行遍历。
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
//4.忽略一些无效的网卡信息,比如:虚拟机网口,关闭的网口,
//启动时配置的忽略网口(主要是用过网卡名字和描述名字)。
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
if (ignoreNetworkInterface(networkInterface)) {
continue;
}
// 根据用户 -D 参数忽略网卡
if (ignoreInterfaceByConfig(networkInterface.getDisplayName()) || ignoreInterfaceByConfig(networkInterface.getName())) {
continue;
}
validNetworkInterfaces.add(networkInterface);
}
return validNetworkInterfaces;
}
//忽略的网卡内容
private static boolean ignoreNetworkInterface(NetworkInterface networkInterface) throws SocketException {
return networkInterface == null
|| networkInterface.isLoopback()
|| networkInterface.isVirtual()
|| !networkInterface.isUp();
}
static boolean ignoreInterfaceByConfig(String interfaceName) {
String regex = System.getProperty(PowerJobDKey.IGNORED_NETWORK_INTERFACE_REGEX);
if (StringUtils.isBlank(regex)) {
return false;
}
if (interfaceName.matches(regex)) {
log.info("[Net] ignore network interface: {} by regex({})", interfaceName, regex);
return true;
}
return false;
}
精彩的高潮
找到了本地所有的网卡信息,并且忽略掉了很多没有用的网卡信息,接下来就是通过偏好来选择合适的网卡地址来进行通信,一开始我没有发现这一条信息,在官方文档中也没有找到对应的配置,一度以为这个ip地址无法选择,甚至我使用的服务器,第一条网卡信息是docker的,结果就直接给我用的docker的地址,直接给我整混乱了,还好我还能看懂这么一点代码,不得不说,作者这个代码确实厉害,直接打开我新世界的大门,但是你开门开的好,你得跟我说一声啊,你不说我都没法往门里进啊。
public static NetworkInterface findNetworkInterface() {
List<NetworkInterface> validNetworkInterfaces = emptyList();
try {
validNetworkInterfaces = getValidNetworkInterfaces();
} catch (Throwable e) {
log.warn("[Net] findNetworkInterface failed", e);
}
// Try to find the preferred one
for (NetworkInterface networkInterface : validNetworkInterfaces) {
if (isPreferredNetworkInterface(networkInterface)) {
log.info("[Net] use preferred network interface: {}", networkInterface.getDisplayName());
return networkInterface;
}
}
...
return first(validNetworkInterfaces);
}
public static boolean isPreferredNetworkInterface(NetworkInterface networkInterface) {
//5.通过启动时的配置“powerjob.network.interface.preferred”,获取对应的地址,如果有,则直接返回对应的IP地址。
String preferredNetworkInterface = System.getProperty(PowerJobDKey.PREFERRED_NETWORK_INTERFACE);
if (Objects.equals(networkInterface.getDisplayName(), preferredNetworkInterface)) {
return true;
}
// 兼容直接使用网卡名称的情况,比如 Realtek PCIe GBE Family Controller
return Objects.equals(networkInterface.getName(), preferredNetworkInterface);
}
无奈的结局
我认为通过偏好选择网卡信息就已经非常好了,如果没有偏好设置,默认选择第一条网卡信息这个策略也是不错,之后是选择一条能够访问的地址,最后如果都不行,就破罐子破摔的来获取一个地址,反正必须要返回一个地址,即使这个地址有问题,也得返回了,如果要是一般的网络环境,我觉得这也挺好的,万一有那么一个公司,内网互相通讯还需要代理,这可就恶心了,太恶心了。
public static NetworkInterface findNetworkInterface() {
List<NetworkInterface> validNetworkInterfaces = emptyList();
try {
validNetworkInterfaces = getValidNetworkInterfaces();
} catch (Throwable e) {
log.warn("[Net] findNetworkInterface failed", e);
}
// Try to find the preferred one
for (NetworkInterface networkInterface : validNetworkInterfaces) {
if (isPreferredNetworkInterface(networkInterface)) {
log.info("[Net] use preferred network interface: {}", networkInterface.getDisplayName());
return networkInterface;
}
}
//6.对剩余的网卡信息进行遍历,如果遍历到的IP地址合法有效(格式正确并可以ping通),则直接返回该合法有效的ip。
for (NetworkInterface networkInterface : validNetworkInterfaces) {
InetAddress addressOp = getFirstReachableInetAddress(networkInterface);
if (addressOp != null) {
return networkInterface;
}
}
//7.直接返回第一条格式正确的IP地址。
return first(validNetworkInterfaces);
}
private static InetAddress getFirstReachableInetAddress(NetworkInterface networkInterface) {
if(networkInterface == null ){
return null;
}
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
if (addressOp.isPresent()) {
try {
if (addressOp.get().isReachable(100)) {
return addressOp.get();
}
} catch (IOException e) {
// ignore
}
}
}
return null;
}
public static <T> T first(Collection<T> values) {
if (values == null || values.isEmpty()) {
return null;
}
if (values instanceof List) {
List<T> list = (List<T>) values;
return list.get(0);
} else {
return values.iterator().next();
}
}
private static InetAddress getLocalAddress0() {
// @since 2.7.6, choose the {@link NetworkInterface} first
try {
InetAddress addressOp = getFirstReachableInetAddress( findNetworkInterface());
if (addressOp != null) {
return addressOp;
}
} catch (Throwable e) {
log.warn("[Net] getLocalAddress0 failed.", e);
}
InetAddress localAddress = null;
try {
//8.获取InetAddress.getLocalHost()得到的IP地址。
localAddress = InetAddress.getLocalHost();
Optional<InetAddress> addressOp = toValidAddress(localAddress);
if (addressOp.isPresent()) {
return addressOp.get();
}
} catch (Throwable e) {
log.warn("[Net] getLocalAddress0 failed.", e);
}
return localAddress;
}
总结
其实大部分人是不需要了解这部分代码的,基本都不会有啥问题,因为大部分人使用的都是正常人使用的网络,只有我们公司这1万来人用的是不正常人使用的网络,但是万一遇到这方面的问题,了解一下还是好的。