IPv6編程——Java
1. Java支持IPv6
Java 從 1.4 版開始支持 Linux 和Solaris 平台上的 IPv6。1.5 版起又加入了 Windows 平台上的支持。
在 IPv6 的環境下開發 Java 應用,或者移植已有的 IPv4 環境下開發的Java 應用到 IPv6 環境中來,對於 IPv6 網絡地址的驗證是必須的步驟,尤其是對那些提供了 UI(用戶接口)的 Java 應用。
2. 獲取本機IPv6地址
有時為了能夠注冊 listener,開發人員需要使用本機的 IPv6 地址,這一地址不能簡單得通過 InetAddress.getLocalhost() 獲得。因為這樣有可能獲得諸如 0:0:0:0:0:0:0:1 這樣的特殊地址。使用這樣的地址,其他服務器將無法把通知發送到本機上,因此必須先進行過濾,選出確實可用的地址。以下代碼實現了這一功能,思路是遍歷網絡接口的各個地址,直至找到符合要求的地址。
package com.text;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
public class Get_IPv6 {
public static void main(String[] args) throws IOException{
String str = getLocalIPv6Address();
System.out.println(str);
}
public static String getLocalIPv6Address() throws IOException {
InetAddress inetAddress = null;
Enumeration<NetworkInterface> networkInterfaces =
NetworkInterface
.getNetworkInterfaces();
outer:
while (networkInterfaces.hasMoreElements()) {
Enumeration<InetAddress> inetAds =
networkInterfaces.nextElement()
.getInetAddresses();
while (inetAds.hasMoreElements()) {
inetAddress = inetAds.nextElement();
//Check if it's ipv6 address and reserved address
if (inetAddress instanceof Inet6Address
&& !isReservedAddr(inetAddress)) {
break outer;
}
}
}
String ipAddr = inetAddress.getHostAddress();
// Filter network card No
int index = ipAddr.indexOf('%');
if (index > 0) {
ipAddr = ipAddr.substring(0, index);
}
return ipAddr;
}
private static boolean isReservedAddr(InetAddress inetAddr) {
if (inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress()
|| inetAddr.isLoopbackAddress()) {
return true;
}
return false;
}
}
為了支持 IPv6,Java 中增加了兩個 InetAddress 的子類:Inet4Address和 Inet6Address。一般情況下這兩個子類並不會被使用到,但是當我們需要分別處理不同的 IP 協議時就非常有用,在這我們根據Inet6Address來篩選地址。
isReservedAddr() 方法過濾了本機特殊 IP 地址,包括“LocalAddress”,“LinkLocalAddress”和“LoopbackAddress”。另一個需要注意的地方是:在 windows 平台上,取得的 IPv6 地址后面可能跟了一個百分號加數字。這里的數字是本機網絡適配器的編號。這個后綴並不是 IPv6 標准地址的一部分,可以去除。
3. IPv4/IPv6 雙環境下,網絡的選擇和測試
Java 提供了 InetAddress 的兩個擴展類以供使用:Inet4Address 和 Inet6Address,其中封裝了對於 IPv4 和 IPv6 的特殊屬性和行為。然而由於Java 的多態特性,使得程序員一般只需要使用父類 InetAddress,Java 虛擬機可以根據所封裝的 IP 地址類型的不同,在運行時選擇正確的行為邏輯。所以在多數情況下,程序員並不需要精確控制所使用的類型及其行為,一切交給 Java虛擬機即可。具體的新增類型及其新增方法,請具體參閱 Sun 公司的 JavaDoc。
在 IPv4/IPv6 雙環境中,對於使用 Java 開發的網絡應用,比較值得注意的是以下兩個 IPv6 相關的 Java 虛擬機系統屬性。
java.net.preferIPv4Stack=<\true|false>
java.net.preferIPv6Addresses=<\true|false>
程序中設置代碼如下:
System.setProperty("java.net.preferIPv6Addresses","true");
preferIPv4Stack(默認 false)表示如果存在 IPv4 和 IPv6 雙棧,Java 程序是否優先使用 IPv4 套接字。默認值是優先使用 IPv6 套接字,因為 IPv6 套接字可以與對應的 IPv4 或 IPv6 主機進行對話;相反如果優先使用 IPv4,則只不能與 IPv6 主機進行通信。
preferIPv6Addresses(默認 false)表示在查詢本地或遠端 IP 地址時,如果存在 IPv4 和 IPv6 雙地址,Java 程序是否優先返回 IPv6 地址。Java 默認返回 IPv4 地址主要是為了向后兼容,以支持舊有的 IPv4 驗證邏輯,以及舊有的僅支持 IPv4 地址的服務。
4. socket 支持 IPv6 通信示例
客戶端:
package com.text2;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.util.Enumeration;
public class Click {
public static void main(String[] args) throws IOException {
//獲取本機IPv6地址getLocalIPv6Address()方法在上面已經提到
String servernameString = getLocalIPv6Address();
System.out.println("客戶端啟動…");
System.out.println("當接收到服務器端字符為 \"OK\" 的時候, 客戶端將終止\n");
while (true) {
Socket socket = null;
try {
// 創建一個流套接字並將其連接到指定主機上的指定端口號
socket = new Socket(servernameString, 8080);
// 讀取服務器端數據
DataInputStream input = new DataInputStream(socket.getInputStream());
// 向服務器端發送數據
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
System.out.print("請輸入: \t");
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.writeUTF(str);
String ret = input.readUTF();
System.out.println("服務器端返回過來的是: " + ret);
if ("OK".equals(ret)) {
System.out.println("客戶端將關閉連接");
Thread.sleep(500);
break;
}
out.close();
input.close();
} catch (Exception e) {
System.out.println("客戶端異常:" + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
socket = null;
System.out.println("客戶端 finally 異常:" + e.getMessage());
}
}
}
}
}
public static String getLocalIPv6Address() throws IOException {
InetAddress inetAddress = null;
Enumeration<NetworkInterface> networkInterfaces =
NetworkInterface
.getNetworkInterfaces();
outer:
while (networkInterfaces.hasMoreElements()) {
Enumeration<InetAddress> inetAds =
networkInterfaces.nextElement()
.getInetAddresses();
while (inetAds.hasMoreElements()) {
inetAddress = inetAds.nextElement();
//Check if it's ipv6 address and reserved address
if (inetAddress instanceof Inet6Address
&& !isReservedAddr(inetAddress)) {
break outer;
}
}
}
String ipAddr = inetAddress.getHostAddress();
// Filter network card No
int index = ipAddr.indexOf('%');
if (index > 0) {
ipAddr = ipAddr.substring(0, index);
}
return ipAddr;
}
private static boolean isReservedAddr(InetAddress inetAddr) {
if (inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress()|| inetAddr.isLoopbackAddress()) {
return true;
}
return false;
}
}
服務器端:
package com.text2;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("服務器啟動…\n");
Server server = new Server();
server.init();
}
public void init() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
// 一旦有堵塞, 則表示服務器與客戶端獲得了連接
Socket client = serverSocket.accept();
// 處理這次連接
new HandlerThread(client);
}
} catch (Exception e) {
System.out.println("服務器異常: " + e.getMessage());
}
}
private class HandlerThread implements Runnable {
private Socket socket;
public HandlerThread(Socket client) {
socket = client;
new Thread(this).start();
}
public void run() {
try {
// 讀取客戶端數據
DataInputStream input = new DataInputStream(socket.getInputStream());
String clientInputStr = input.readUTF();
//這里要注意和客戶端輸出流的寫方法對應,否則會拋 EOFException
// 處理客戶端數據
System.out.println("客戶端發過來的內容:" + clientInputStr);
// 向客戶端回復信息
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
System.out.print("請輸入:\t");
// 發送鍵盤輸入的一行
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.writeUTF(s);
out.close();
input.close();
} catch (Exception e) {
System.out.println("服務器 run 異常: " + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
socket = null;
System.out.println("服務端 finally 異常:" + e.getMessage());
}
}
}
}
}
}