资讯专栏INFORMATION COLUMN

重拾Java Network Programming(二)InetAddress

daryl / 1654人阅读

摘要:前言今天,我将梳理在网络编程中很重要的一个类以及其相关的类。这类主机通常不需要外部互联网服务,仅有主机间相互通讯的需求。可以通过该接口获取所有本地地址,并根据这些地址创建。在这里我们使用阻塞队列实现主线程和打印线程之间的通信。

前言

今天,我将梳理在Java网络编程中很重要的一个类InetAddress以及其相关的类NetworkInterface。在这篇文章中将会涉及:

InetAddress

NetworkInterface

具体应用范例

这里的范例将会实现一个简单的日志IP解析系统。我们将会在后面详细介绍。

InetAddress API

在此我将会直接从API入手,如果对其中的名词有何疑惑,可以先行了解一下基础概念。
需要先行了解的概念包括:

IP,IPv4,IPv6

单播,多播,广播

loop地址

域名

public class InetAddress{
    //私有化构造器,我们只能通过其提供的静态工厂方法来获取一个实例
    //同时我们也不可以修改内部的IP地址或是域名
    //这种Immutable的方式确保了其线程安全性
    
    //这里需要注意java没有正整数型,因此我们需要对开头为1的二进制数进行转义,如下
    //byte[] address = {107, 23, (byte) 216, (byte) 196};
    public static InetAddress getByAddress(byte[] addr) throws UnknownHostException;
    //该方法不会查询DNS来确保域名和IP地址相符
    //这里有一个比较有意思的在于如果你希望寻找家用设备如打印机等的局域网地址,则可以通过遍历254个可能的局域网地址来找到它,而不需要将其硬编码进代码中
    public static InetAddress getByAddress(String host, byte[] addr) throws UnknownHostException;
    //根据域名获取一个实例,该加载为懒加载,即除非到必要的时候,否则将不会启动DNS查询
    public static InetAddress getByName(String host) throws UnknownHostException;
    
    //获取该域名对应的所有IP地址(即一个域名可以映射到多个IP地址)
    public static InetAddress[] getAllByName(String host)
                                  throws UnknownHostException
    
    //获取环回地址,通常为172.0.0.1                              
    public static InetAddress getLoopbackAddress();
    //获取本机的IP地址,如果本机没有联网,则返回环回地址
    public static InetAddress getLoaclHost();
    
    //获取主机名
    public String getHostName()
    //获取主机别名
    public String getCanonicalHostName() 
    //获取主机的IP地址
    //该方法可以用来判断是IPv4地址还是IPv6地址
    public byte[] getAddress()
    //获取String类型的IP地址
    public String getHostAddress()
    
    //是否是一个和本地主机相关的地址
    //包括本机IP,loopback,wifi地址等
    public boolean isAnyLocalAddress() 
    //是否是环回地址
    public boolean isLoopbackAddress() 
    //是否是链路本地地址
    //链路本地地址(Link-local address)是计算机网络中一类特殊的地址,
    //它仅供于在网段,或广播域中的主机相互通信使用。
    //这类主机通常不需要外部互联网服务,仅有主机间相互通讯的需求。
    public boolean isLinkLocalAddress() 
    //是否落入这三个网段
    //10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
    public boolean isSiteLocalAddress() 
    //是否是广播地址
    public boolean isMulticastAddress() 
    //是否是全球通用的广播地址
    public boolean isMCGlobal()
    //是否是企业专属的多播地址
    public boolean isMCOrgLocal()
    
    //判断当前节点是否可以访问目标节点
    //通常使用ICMP报文实现
    public boolean isReachable(int timeout) throws IOException
    public boolean isReachable(NetworkInterface interface, int ttl, int timeout) throws IOException

    //只要两个对象的IP地址相等,则二者相等(与域名无关)
    public boolean equals(Object o) 
    //仅根据IP地址计算hashCode
    public int hashCode()
    //格式为 “域名/IP地址”
    public String toString()
}

相关参数:
networkaddress.cache.negative.ttl:失败的DNS查找被缓存的时间
networkaddress.cache.ttl:成功的DNS查找被缓存的时间

NetworkInterface API

NetworkInterface代表一个本地的IP地址。可以通过该接口获取所有本地地址,并根据这些地址创建InetAddress。通过NetworkInterface接口,可以获取本机配置的网络接口的名字,IP列表(包括IPV4和IPV6),网卡地址,最大传输单元(MTU),接口状态(开启/关闭)、接口网络类型(点对点网络、回环网络)等信息

public final class NetworkInterface{
    //根据名称获取网络接口
    //该方法依赖于底层平台
    public static NetworkInterface getByName(String name) throws SocketException
    //根据IP地址获得对应的网络接口
    public static NetworkInterface getByInetAddress(InetAddress address) throws SocketException
    //获取所有的网络接口
    public static Enumeration getNetworkInterfaces() throws SocketException
    //获取该接口之下所有的IP地址
    public Enumeration getInetAddresses()
    //获取MAC地址
    public byte[] getHardwareAddress()
}
获取WebLog中的IP地址对应的hostName

当我们启动web应用时,我们往往会从HTTP报文中获取访客的IP并且将其记录进日志中。从而可以从日志中分析常见的访客或是不明访客。这里我们将获取一个手动生成的包含IP地址的日志文件,并且将其中的IP地址转化为hostName输出。这里我们将采用多线程实现,因为读取一条IP记录的速度远远高于从DNS服务器获取域名的速度。

首先我们使用工具类向文件中写入日志:

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接口,支持将内部的值返回给别的线程使用:

public class IPAnalyser implements Callable{
    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来记录当前已经打印的日志条目数,然后在打印完成所有的之后结束该线程。

public class IPPrinter implements Runnable {
    private BlockingQueue queue;
    private static volatile int writeCount;
    public IPPrinter(BlockingQueue 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保存原来的日志条目和解析后的日志线程返回:

public class LogEntry {
    private Future hostName;

    private String origin;

    public LogEntry(Future hostName, String origin){
        this.hostName = hostName;
        this.origin = origin;
    }

    public void setOrigin(String origin) {
        this.origin = origin;
    }

    public String getOrigin() {
        return origin;
    }

    public Future getHostName() {
        return hostName;
    }

    public void setHostName(Future hostName) {
        this.hostName = hostName;
    }
}

最后在主线程中开启IO和相关的所有线程:

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 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 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


想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76366.html

相关文章

  • 重拾Java Network Programming(一)IO流

    摘要:不同类型的流入,往往对应于不同类型的流数据。所以通常会将字节缓存到一定数量后再发送。如果是,则将两个标记都抛弃并且将之前的内容作为一行返回。因此二者陷入死锁。因此推出了和类。 前言 最近在重拾Java网络编程,想要了解一些JAVA语言基本的实现,这里记录一下学习的过程。 阅读之前,你需要知道 网络节点(node):位于网络上的互相连通的设备,通常为计算机,也可以是打印机,网桥,路由器等...

    Lycheeee 评论0 收藏0
  • 重拾Java Network Programming(四)URLConnection & C

    摘要:从而一方面减少了响应时间,另一方面减少了服务器的压力。表明响应只能被单个用户缓存,不能作为共享缓存即代理服务器不能缓存它。这种情况称为服务器再验证。否则会返回响应。 前言 本文将根据最近所学的Java网络编程实现一个简单的基于URL的缓存。本文将涉及如下内容: HTTP协议 HTTP协议中与缓存相关的内容 URLConnection 和 HTTPURLConnection Respo...

    Guakin_Huang 评论0 收藏0
  • 重拾Java Network Programming(四)URLConnection & C

    摘要:从而一方面减少了响应时间,另一方面减少了服务器的压力。表明响应只能被单个用户缓存,不能作为共享缓存即代理服务器不能缓存它。这种情况称为服务器再验证。否则会返回响应。 前言 本文将根据最近所学的Java网络编程实现一个简单的基于URL的缓存。本文将涉及如下内容: HTTP协议 HTTP协议中与缓存相关的内容 URLConnection 和 HTTPURLConnection Respo...

    魏明 评论0 收藏0
  • JAVA网络程序设计基础(笔记)

    摘要:三端口与套接字端口指一台计算机只有单一的连接到网络的物理连接,所以的数据都通过此连接对内对外送达特定的计算机,这就是端口。三程序设计由上面可知基于的信息传递速度更快。接收数据包使用创建数据包套接字,绑定指定端口。 服务器 网络 客户机 第一部分 一.局域网与因特网 服务器是指提供信息的计算机或程序,...

    PAMPANG 评论0 收藏0
  • Java网络编程-你是GG还是MM?

    摘要:网络层主要将从下层接收到的数据进行地址例的封装与解封装。会话层通过传输层端口号传输端口与接收端口建立数据传输的通路。 第六阶段 网络编程 每一台计算机通过网络连接起来,达到了数据互动的效果,而网络编程所解决的问题就是如何让程序与程序之间实现数据的通讯与互动在吗?你是GG还是MM? (一) 网络模型概述 (1) 两大模型 网络模型一般是指: OSI(Open System Inter...

    Shihira 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<