《Java网络编程》第三章:Internet地址
域名
- 域名系统(Domain Name System,DNS),将人们记忆中的主机名与计算机可以记忆的IP地址关联在一起
- 域名服务器(domain name server),是一个运行特殊DNS软件的UNIX主机,了解不同主机名和IP地址之间的映射
InetAddress类
Iaddress不可变,为线程安全的
使用
public void testInetAddress(){try {InetAddress address = InetAddress.getByName("www.baidu.com");System.out.println(address);InetAddress address1 = InetAddress.getByName("39.156.70.46");//如果没查到返回对应的点分四段地址System.out.println(address1.getHostName());InetAddress address2 = InetAddress.getLocalHost();//获取本机主机名和地址System.out.println(address2);} catch (UnknownHostException e) {throw new RuntimeException(e);}}
缓存
InetAddress类会缓存查找的结果
- 对于不成功的DNS查询只缓存10s
- 可以用系统属性networkaddress.cache.ttl和networkaddress.cache.regative.ttl分别设置成功和不成功的结果在Java缓存中保存的时间.-1表示永不过期
按IP查找
查找不到返回点分四段地址,并不会抛出UnknownHostException异常。根据主机名查询,查询不到会抛出异常
安全性问题
- DNS查询的不可靠性与延迟
- 网络依赖性:创建
InetAddress
对象时,如果传入的是主机名(而非IP地址),Java会触发DNS解析(域名到IP的转换)。这个过程依赖外部DNS服务器和网络环境。 - 性能问题:如果DNS服务器响应慢或不可用,操作会阻塞(同步调用),导致线程挂起,影响程序性能。
- 超时不可控:默认DNS查询超时时间可能较长(依赖操作系统配置),且难以通过Java API直接调整。
- 安全风险
- DNS欺骗(DNS Spoofing):攻击者可能篡改DNS响应,导致解析到恶意IP地址(例如中间人攻击)。
- 不可信的输入:如果主机名来自用户输入或外部配置,恶意用户可能注入非法主机名(如指向内部网络的域名),引发安全漏洞(如SSRF攻击)。
- 平台依赖性
- 不同系统的DNS解析行为差异:某些系统可能配置了特殊的DNS规则(如 hosts 文件、代理),导致同一主机名在不同环境中解析结果不同,引发不一致性。
- 反向解析的副作用
- 如果通过IP地址创建
InetAddress
,某些方法(如getHostName()
)可能触发反向DNS查询(IP到域名的解析),同样会遇到网络延迟和安全问题。
地址类型
- 通配地址(wildcard address)
- 回送地址(loopback address)
- IPv6本地链接地址
- IPv6本地网站地址
- 组播地址
- 全球组播地址
- 组织范围组播地址
- 网站范围组播地址
- 子网范围组播地址
- 本地接口组播地址
测试可达性
isReachable()方法
尝试使用traceroute查看指定地址是否达到。如果主机在timeout毫秒内响应,返回true
Object方法
InetAddress继承于java.lang.Object,覆写了equals(),hashCode(),toString方法
有相同IP地址的两个InetAddress都相等才会是相等的
hashCode()返回的int只根据ip地址来计算
SpamCheck
垃圾邮件发送者(spammer)。
如果想向sbl.spamhaus.org询问207.87.34.17是否为spammer,就要查找主机名17.34.87.207.sbl.spamhaus.org
public class SpamCheck {public static final String BLACKHOLE = "sbl.spamhaus.org";public static void main(String[] args) {for(String arg: args){if(isSpammer(arg)){System.out.println("Spammer: " + arg);}else{System.out.println("Not Spammer: " + arg);}}}public static boolean isSpammer(String ip){try {InetAddress address = InetAddress.getByName(ip);byte[] quad = address.getAddress();String query = BLACKHOLE;for(byte octet : quad){int unsignedByte = octet < 0 ? octet + 256 : octet;//转换为无符号数query = unsignedByte + "." + query;}InetAddress.getByName(query);//根据主机名查询,如果存在则返回IP地址,否则抛出异常return true;} catch (UnknownHostException e) {return false;}}
}
Web服务器日志文件
public static class LookupTask implements Callable<String>{private String line;public LookupTask(String line){this.line = line;}@Overridepublic String call(){try {//第一个空格之前都是IP地址,往后的内容不需要改变int index = line.indexOf(' ');String address = line.substring(0, index);String theRest = line.substring(index);//查询主机名String hostName = InetAddress.getByName(address).getHostName();return hostName + " " + theRest;} catch (Exception e) {return line;}}}public static class LogEntry{String original;Future<String> future;public LogEntry(String original, Future<String> future){this.original = original;this.future = future;}}public class PooledWeblog{private final static int NUM_THREADS = 5;public static void main(String[] args) throws IOException {ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);//存放线程池中的结果,保持日志文件原来的顺序Queue<LogEntry> results = new LinkedList<>();try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), "UTF-8"))){for(String entry = in.readLine();entry != null;entry = in.readLine()){//处理读取的一行LookupTask task = new LookupTask(entry);//提交任务Future<String> future = executor.submit(task);LogEntry result = new LogEntry(entry, future);results.add(result);}}for(LogEntry entry : results){try {System.out.println(entry.future.get());} catch (Exception e) {System.out.println(entry.original);}}executor.shutdown();}}
通过线程池来并发执行
在队列按顺序输出,可能因某个慢任务阻塞后续输出,导致队列过大。可以把输出放在一个单独的线程上面,与输入线程共享一个队列。解析输入的同时处理和显示之前的日志文件,避免队列膨胀过大。同时统计输入与输出行数,明确输出何时完成。