GetLocalHost
直接通過InetAddress.getLocalHost()來獲取,其主要邏輯如下
InetAddress.getLocalHost();
String hostname = impl.getLocalHostName();
if(hostname.equals("localhost")){
return impl.loopvacjAddress();
}
InetAddress.getAddressesFromNameService(hostname, null);
nameService.lookupAllHostAddr(host);
在linux中的hostname是個變量,由系統初始化的時候, 在shell啟動腳本 “/etc/rc.d/rc.sysinit” 中實現,主要是讀取“/etc/sysconfig/network” 中的HOSTNAME的值 可以通過命令 hostname xxx
修改 hostname。
這里有幾個注意點:
1. 如果文件中沒有hostname,那么會使用默認的localhost
2. 如果發現hostname的值是localhost 或者 localhost.localdomain, 根據自己的實際ip查找/etc/hosts中這個ip對應的hostname。
3. 如果沒有,則使用localhost 或者localhost.localdomain
如果hostname是localhost,就會直接返回環回地址,如IPv4的127.0.0.1
如果不是的話,則會先看緩存里的CachedLocalHost的值,如果緩存的時間離現在小於5s的話,則直接返回緩存里的內容,如果間隔時間超過5s,則重新查詢
重新查詢是通過NameService去獲取IP地址的,具體的實現類是DNSNameService,其中NameServices是InetAddress是成員變量,通過static代碼塊初始化的
主要實現都是通過native的系統調用,查看/etc/resolv.conf下配置的nameserver和/etc/hosts下面的配置,然后使用DNS協議查詢,查詢后將其緩存。
如果DNS查詢不到的話,會拋出異常,UnKnownHostName。
一般來說,沒有自己去進行一些主動的配置的話,會就拿到127.0.0.1這種IP,顯然是無效IP
獲取所有網卡的IP
換另外一種思路,通過本機網絡設備所綁定的網卡來獲取本機的IP
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
try {
NetworkInterface network = interfaces.nextElement();
if(network.isVirtual()){
continue;/**如果是虛擬網卡,排除此網卡*/
}
Enumeration<InetAddress> addresses = network.getInetAddresses();
if (addresses != null) {
while (addresses.hasMoreElements()) {
try {
InetAddress address = addresses.nextElement();
if (isValidAddress(address)) {
return address;
}
} catch (Throwable e) {
LOGGER.warn("Failed to retriving ip address, " + e.getMessage(), e);
}
}
}
} catch (Throwable e) {
LOGGER.warn("Failed to retriving ip address, " + e.getMessage(), e);
}
}
}
這種方法就是,拿到的是所有網絡設備的屬性,假裝過濾虛擬網卡,找到第一個屬於有效IP的地址。
如何判斷是有效IP?
LOCALHOST = "127.0.0.1";
ANYHOST = "0.0.0.0";
LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$");
IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
但是有一點劣勢就是,不知道哪塊才是真正用來和外界通信的網卡,比如我的開發機
就經常出現這個拿到192.168.122.1的情況,virbr0是一個虛擬網卡,可是java拿到他的時候,虛擬的屬性卻是false。
當然,這塊網卡可以卸載,不過不在討論范圍。
這也是一個問題,這個網卡明明是虛擬網卡,但是java拿到它的時候,屬性就不是虛擬的,沒辦法,誰讓這個接口實質性調用的是一個native的getAll方法。
通過連接遠程端口
最好的方式自然是通過Socket去連接一個遠程端口,這樣就能很方便地知道本機與外部通信時候使用的IP了。
try {
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(host, port);
socket.connect(addr, 1000);
return socket.getLocalAddress();
} finally {
try {
socket.close();
} catch (Throwable e) {
}
}
} catch (Exception e) {
LOGGER.warn("Failed to retrive local address by connecting to dest host,ip={},port={},e={}", host, port, e);
}
這種方式拿到的本機IP就比較保險了
當然, 比如你連接本機的端口,拿到的地址還會是127.0.0.1
連接本地局域網內的機器,拿到的會是本機局域網段的地址,比如我的機器是10.97.26.154
連接一個具有公網ip的機器的端口,拿到的還是本機局域網段的地址,比如我的機器是10.97.26.154
其實這個,還是也拿到網卡的地址,當你使用哪個網卡去連接此端口的時候,就會得到哪個網卡所綁定的地址。
IP地址綁定
服務啟動的時候,如果不確定應該綁定在哪個地址,則應該使用0.0.0.0,這樣的話,通過所有本機的網卡的地址,都能訪問此服務。
如果綁定的是127.0.0.1的話,那么只端口只對本機提供服務。