前言
今天,我将梳理在Java网络编程中很重要的一个类InetAddress以及其相关的类NetworkInterface。在这篇文章中将会涉及:
- InetAddress
- NetworkInterface
- 具体应用范例
这里的范例将会实现一个简单的日志IP解析系统。我们将会在后面详细介绍。
InetAddress API
在此我将会直接从API入手,如果对其中的名词有何疑惑,可以先行了解一下基础概念。
需要先行了解的概念包括:
- IP,IPv4,IPv6
- 单播,多播,广播
- loop地址
- 域名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class InetAddress{ public static InetAddress getByAddress(byte[] addr) throws UnknownHostException; public static InetAddress getByAddress(String host, byte[] addr) throws UnknownHostException; public static InetAddress getByName(String host) throws UnknownHostException; public static InetAddress[] getAllByName(String host) throws UnknownHostException public static InetAddress getLoopbackAddress(); public static InetAddress getLoaclHost(); public String getHostName() public String getCanonicalHostName() public byte[] getAddress() public String getHostAddress() public boolean isAnyLocalAddress() public boolean isLoopbackAddress() public boolean isLinkLocalAddress() public boolean isSiteLocalAddress() public boolean isMulticastAddress() public boolean isMCGlobal() public boolean isMCOrgLocal() public boolean isReachable(int timeout) throws IOException public boolean isReachable(NetworkInterface interface, int ttl, int timeout) throws IOException
public boolean equals(Object o) public int hashCode() public String toString() }
|
相关参数:
networkaddress.cache.negative.ttl:失败的DNS查找被缓存的时间
networkaddress.cache.ttl:成功的DNS查找被缓存的时间
##NetworkInterface API##
NetworkInterface代表一个本地的IP地址。可以通过该接口获取所有本地地址,并根据这些地址创建InetAddress。通过NetworkInterface接口,可以获取本机配置的网络接口的名字,IP列表(包括IPV4和IPV6),网卡地址,最大传输单元(MTU),接口状态(开启/关闭)、接口网络类型(点对点网络、回环网络)等信息
1 2 3 4 5 6 7 8 9 10 11 12 13
| public final class NetworkInterface{ public static NetworkInterface getByName(String name) throws SocketException public static NetworkInterface getByInetAddress(InetAddress address) throws SocketException public static Enumeration getNetworkInterfaces() throws SocketException public Enumeration getInetAddresses() public byte[] getHardwareAddress() }
|
获取WebLog中的IP地址对应的hostName
当我们启动web应用时,我们往往会从HTTP报文中获取访客的IP并且将其记录进日志中。从而可以从日志中分析常见的访客或是不明访客。这里我们将获取一个手动生成的包含IP地址的日志文件,并且将其中的IP地址转化为hostName输出。这里我们将采用多线程实现,因为读取一条IP记录的速度远远高于从DNS服务器获取域名的速度。
首先我们使用工具类向文件中写入日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class Util {
private static final String filePath = "YOUR_FILE_PATH"; private static final String[] hostNames = new String[]{ "www.sina.com", "www.sohu.com", "www.taobao.com", "www.baidu.com", "www.qq.com", "www.163.com", "www.dzone.com", "www.github.com", "www.acmcoder.com", "www.meituan.com", "kafka.apachecn.org", "www.ibm.com", "javarevisited.blogspot.kr" }; public static void main(String[] args){ int count = 0; try (BufferedWriter bf = new BufferedWriter(new FileWriter(filePath))){ for(String host : hostNames){ InetAddress[] addresses = InetAddress.getAllByName(host); count+= addresses.length; for (InetAddress address : addresses) { bf.write(address.getHostAddress() + " " + address.getHostName()); bf.newLine(); } } System.out.println(count); } catch (IOException e) { e.printStackTrace(); } } }
|
一个处理LOG的IPAnalyser类,该类实现Callable接口,支持将内部的值返回给别的线程使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class IPAnalyser implements Callable<String>{ private String logItem; public IPAnalyser(String logItem){ this.logItem = logItem; } @Override public String call() throws Exception { if (logItem!=null){ String[] items = logItem.split(" "); InetAddress inetAddress = InetAddress.getByName(items[0]); return inetAddress.getHostAddress(); } return null; } }
|
额外使用一个线程来同步处理IPAnalyser返回的值,从而避免因为日志文件过大,日志条目全部存储在主存中,再一次性读取而带来因占用大量内存而影响性能的问题。
在这里我们使用阻塞队列实现主线程和打印线程之间的通信。但是我们需要在打印完该日志文件后,结束该打印线程,因此使用write来记录当前已经打印的日志条目数,然后在打印完成所有的之后结束该线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class IPPrinter implements Runnable { private BlockingQueue<LogEntry> queue; private static volatile int writeCount; public IPPrinter(BlockingQueue<LogEntry> queue){ this.queue = queue; }
@Override public void run() { while (true){ if (writeCount == Main.readCount){ System.out.println("写完毕"); break; } try { LogEntry logEntry = queue.take(); System.out.println(logEntry.getOrigin() + "对应的主机名为" + logEntry.getHostName().get()); writeCount++; } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
} }
|
实体类LogEntry保存原来的日志条目和解析后的日志线程返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class LogEntry { private Future<String> hostName;
private String origin;
public LogEntry(Future<String> hostName, String origin){ this.hostName = hostName; this.origin = origin; }
public void setOrigin(String origin) { this.origin = origin; }
public String getOrigin() { return origin; }
public Future<String> getHostName() { return hostName; }
public void setHostName(Future<String> hostName) { this.hostName = hostName; } }
|
最后在主线程中开启IO和相关的所有线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Main { private static final String filePath = "/Users/rale/IdeaProjects/Demo/concurrency/src/main/java/cn/deerowl/weblog_analyse/web.log"; private static final BlockingQueue<LogEntry> queue = new LinkedBlockingQueue<>();
public static volatile int readCount = 30;
public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(6); executorService.submit(new IPPrinter(queue)); try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))){ String tmp; while ((tmp = bufferedReader.readLine()) != null){ Future<String> f = executorService.submit(new IPAnalyser(tmp)); queue.put(new LogEntry(f, tmp)); }
} catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }
executorService.shutdown(); } }
|
这里要强调一下域名和主机名的关联:一个域名之下可以有多个主机名,如域名abc.com下,有主机server1和server2,其主机全名就是server1.abc.com和server2.abc.com。
参考书籍
Java Network Programming 4th Edition