1. Java支持IPv6
在 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 地址类型的不同,在运行时选择正确的行为逻辑。
在 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());
}
}
}
}
}
}