我正在尝试开发一个系统,其中有不同的节点运行在不同的系统或在同一系统的不同端口上。
现在,所有节点都创建了一个套接字,将目标IP作为称为引导节点的特殊节点的IP。然后,节点创建自己的ServerSocket并开始侦听连接。
引导节点维护一个节点列表,并在查询时返回它们。
现在我需要的是节点必须将其IP注册到引导节点。我尝试使用cli.getInetAddress()一旦客户端连接到引导节点的ServerSocket,但这不起作用。
我需要客户端注册其PPP IP,如果可用;
否则,局域网IP(如果可用);
否则,它必须注册127.0.0.1,假设它是同一台计算机。
使用代码:
System.out.println(Inet4Address.getLocalHost().getHostAddress());
or
System.out.println(InetAddress.getLocalHost().getHostAddress());
我的PPP连接IP地址是:117.204.44.192,但上面返回我192.168.1.2
EDIT
我正在使用以下代码:
Enumeration e = NetworkInterface.getNetworkInterfaces();
while(e.hasMoreElements())
{
NetworkInterface n = (NetworkInterface) e.nextElement();
Enumeration ee = n.getInetAddresses();
while (ee.hasMoreElements())
{
InetAddress i = (InetAddress) ee.nextElement();
System.out.println(i.getHostAddress());
}
}
我能够获得所有网络接口相关的所有IP地址,但我如何区分它们?这是我得到的输出:
127.0.0.1
192.168.1.2
192.168.56.1
117.204.44.19
import java.net.DatagramSocket;
import java.net.InetAddress;
try(final DatagramSocket socket = new DatagramSocket()){
socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
ip = socket.getLocalAddress().getHostAddress();
}
这种方式适用于有多个网络接口的情况。它总是返回首选出站IP。目的地8.8.8.8不需要可达。
Connect on a UDP socket has the following effect: it sets the destination for Send/Recv, discards all packets from other addresses, and - which is what we use - transfers the socket into "connected" state, settings its appropriate fields. This includes checking the existence of the route to the destination according to the system's routing table and setting the local endpoint accordingly. The last part seems to be undocumented officially but it looks like an integral trait of Berkeley sockets API (a side effect of UDP "connected" state) that works reliably in both Windows and Linux across versions and distributions.
因此,此方法将提供用于连接到指定远程主机的本地地址。没有建立真正的连接,因此指定的远端ip不可达。
编辑:
正如@macomgil所说,对于MacOS,你可以这样做:
Socket socket = new Socket();
socket.connect(new InetSocketAddress("google.com", 80));
System.out.println(socket.getLocalAddress());
import java.net.DatagramSocket;
import java.net.InetAddress;
try(final DatagramSocket socket = new DatagramSocket()){
socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
ip = socket.getLocalAddress().getHostAddress();
}
这种方式适用于有多个网络接口的情况。它总是返回首选出站IP。目的地8.8.8.8不需要可达。
Connect on a UDP socket has the following effect: it sets the destination for Send/Recv, discards all packets from other addresses, and - which is what we use - transfers the socket into "connected" state, settings its appropriate fields. This includes checking the existence of the route to the destination according to the system's routing table and setting the local endpoint accordingly. The last part seems to be undocumented officially but it looks like an integral trait of Berkeley sockets API (a side effect of UDP "connected" state) that works reliably in both Windows and Linux across versions and distributions.
因此,此方法将提供用于连接到指定远程主机的本地地址。没有建立真正的连接,因此指定的远端ip不可达。
编辑:
正如@macomgil所说,对于MacOS,你可以这样做:
Socket socket = new Socket();
socket.connect(new InetSocketAddress("google.com", 80));
System.out.println(socket.getLocalAddress());
你的计算机可以有多个网络接口,每个网络接口有多个inetaddress。如果过滤掉任何本地地址,则提醒的地址是非本地地址,其中可以有一个、没有或多个。
不幸的是,Java中的网络API仍然使用(旧的)枚举而不是迭代器和流,我们可以通过将它们包装为流来进行对抗。所以我们要做的就是
所有网络接口及其地址的流,以及
过滤掉本地的
代码:
private Stream<InetAddress> getNonLocalIpAddresses() throws IOException {
return enumerationAsStream(NetworkInterface.getNetworkInterfaces())
.flatMap(networkInterface -> enumerationAsStream(networkInterface.getInetAddresses()))
.filter(inetAddress -> !inetAddress.isAnyLocalAddress())
.filter(inetAddress -> !inetAddress.isSiteLocalAddress())
.filter(inetAddress -> !inetAddress.isLoopbackAddress())
.filter(inetAddress -> !inetAddress.isLinkLocalAddress());
}
在我的机器上,这目前返回两个IPv6地址。
要获得这些inetaddress中的第一个:
private String getMyIp() throws IOException {
return getNonLocalIpAddresses()
.map(InetAddress::getHostAddress)
.findFirst()
.orElseThrow(NoSuchElementException::new);
}
将枚举包装为流的方法:
public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
new Iterator<>() {
public T next() { return e.nextElement(); }
public boolean hasNext() { return e.hasMoreElements(); }
}, Spliterator.ORDERED), false);
}
在大多数情况下,这可能有点棘手。
从表面上看,InetAddress.getLocalHost()应该提供该主机的IP地址。问题是一台主机可能有很多网络接口,而一个接口可能绑定到多个IP地址。最重要的是,并不是所有的IP地址都可以在您的机器或局域网之外访问。例如,它们可以是虚拟网络设备的IP地址、私有网络IP地址等等。
这意味着InetAddress.getLocalHost()返回的IP地址可能不是要使用的正确IP地址。
你怎么处理这个问题呢?
One approach is to use NetworkInterface.getNetworkInterfaces() to get all of the known network interfaces on the host, and then iterate over each NI's addresses.
Another approach is to (somehow) get the externally advertized FQDN for the host, and use InetAddress.getByName() to look up the primary IP address. (But how do you get it, and how do you deal with a DNS-based load balancer?)
A variation of the previous is to get the preferred FQDN from a config file or a command line parameter.
Another variation is to get the preferred IP address from a config file or a command line parameter.
总之,InetAddress.getLocalHost()通常可以工作,但是对于代码运行在“复杂”网络环境中的情况,您可能需要提供另一种方法。
我能够获得所有网络接口相关的所有IP地址,但我如何区分它们?
Any address in the range 127.xxx.xxx.xxx is a "loopback" address. It is only visible to "this" host.
Any address in the range 192.168.xxx.xxx is a private (aka site local) IP address. These are reserved for use within an organization. The same applies to 10.xxx.xxx.xxx addresses, and 172.16.xxx.xxx through 172.31.xxx.xxx.
Addresses in the range 169.254.xxx.xxx are link local IP addresses. These are reserved for use on a single network segment.
Addresses in the range 224.xxx.xxx.xxx through 239.xxx.xxx.xxx are multicast addresses.
The address 255.255.255.255 is the broadcast address.
Anything else should be a valid public point-to-point IPv4 address.
事实上,InetAddress API提供了测试环回、链路本地、站点本地、组播和广播地址的方法。您可以使用这些来分类您获得的哪个IP地址是最合适的。