Java NIO类库Selector机制解析(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Java NIO类库Selector机制解析(上)   赵锟   陈皓 http://blog.csdn.net/haoel   一、  前言   自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式。

Java NIO类库Selector机制解析(上)

 

赵锟   陈皓

http://blog.csdn.net/haoel

 

一、  前言

 

自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式。NIO的包中主要包含了这样几种抽象数据类型:

 

  • Buffer:包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。
  • Charset:它提供Unicode字符串影射到字节序列以及逆映射的操作。
  • Channels:包含socketfilepipe三种管道,都是全双工的通道。
  • Selector:多个异步I/O操作集中到一个或多个线程中(可以被看成是Unixselect()函数的面向对象版本)。

 

我的大学同学赵锟在使用NIO类库书写相关网络程序的时候,发现了一些Java异常RuntimeException,异常的报错信息让他开始了对NIOSelector进行了一些调查。当赵锟对我共享了Selector的一些底层机制的猜想和调查时候,我们觉得这是一件很有意思的事情,于是在伙同赵锟进行过一系列的调查后,我俩发现了很多有趣的事情,于是导致了这篇文章的产生。这也是为什么本文的作者署名为我们两人的原因。

 

先要说明的一点是,赵锟和我本质上都是出身于Unix/Linux/C/C++的开发人员,对于Java,这并不是我们的长处,这篇文章本质上出于对JavaSelector的好奇,因为从表面上来看Selector似乎做到了一些让我们这些C/C++出身的人比较惊奇的事情。

 

下面让我来为你讲述一下这段故事。

 

二、  故事开始 : 让C++程序员写Java程序!

 

没有严重内存问题,大量丰富的SDK类库,超容易的跨平台,除了在性能上有些微辞,C++出身的程序员从来都不会觉得Java是一件很困难的事情。当然,对于长期习惯于使用操作系统API(系统调用System Call)的C/C++程序来说,面对Java中的比较“另类”地操作系统资源的方法可能会略感困惑,但万变不离其宗,只需要对面向对象的设计模式有一定的了解,用不了多长时间,JavaSDK类库也能玩得随心所欲。

 

在使用Java进行相关网络程序的的设计时,出身C/C++的人,首先想到的框架就是多路复用,想到多路复用,Unix/Linux下马上就能让从想到select, poll, epoll系统调用。于是,在看到JavaNIO中的Selector类时必然会倍感亲切。稍加查阅一下SDK手册以及相关例程,不一会儿,一个多路复用的框架便呈现出来,随手做个单元测试,没啥问题,一切和C/C++照旧。然后告诉兄弟们,框架搞定,以后咱们就在Windows上开发及单元测试,完成后到运行环境Unix上集成测试。心中并暗自念到,跨平台就好啊,开发活动都可以跨平台了。

 

然而,好景不长,随着代码越来越多,逻辑越来越复杂。好好的框架居然在Windows上单元测试运行开始出现异常,看着Java运行异常出错的函数栈,异常居然由Selector.open()抛出,错误信息居然是Unable to establish loopback connection

 

“Selector.open()居然报loopback connection错误,凭什么?不应该啊?open的时候又没有什么loopbacksocket连接,怎么会报这个错?

 

长期使用C/C++的程序当然会对操作系统的调用非常熟悉,虽然Java的虚拟机搞的什么系统调用都不见了,但C/C++的程序员必然要比Java程序敏感许多。

 

三、  开始调查 : 怎么Java这么“傻”!

 

于是,C/C++的老鸟从SystemInternals上下载Process Explorer来查看一下究竟是什么个Loopback Connection 果然,打开java运行进程,发现有一些自己连接自己的localhostTCP/IP链接。于是另一个问题又出现了,

 

凭什么啊?为什么会有自己和自己的连接?我程序里没有自己连接自己啊,怎么可能会有这样的链接啊?而自己连接自己的端口号居然是些奇怪的端口。

 

问题变得越来越蹊跷了。难道这都是Selector.open()在做怪?难道Selector.open()要创建一个自己连接自己的链接?写个程序看看:

 

import java.nio.channels.Selector;

import java.lang.RuntimeException;

import java.lang.Thread;

public class TestSelector {

    private static final int MAXSIZE=5;

    public static final void main( String argc[] ) {

        Selector [] sels = new Selector[ MAXSIZE];

 

            try{

                forint i = 0 ;i< MAXSIZE ;++i ) {

                    sels[i] = Selector.open();

                    //sels[i].close();

                }

                Thread.sleep(30000);

            }catch( Exception ex ){

                throw new RuntimeException( ex );

            }

    }

}

 

这个程序什么也没有,就是做5Selector.open(),然后休息30秒,以便我使用Process Explorer工具来查看进程。程序编译没有问题,运行起来,在Process Explorer中看到下面的对话框:(居然有10个连接,从连接端口我们可以知道,互相连接, 如:第一个连第二个,第二个又连第一个)

 

 

 

 

 

不由得赞叹我们的Java啊,先不说这是不是一件愚蠢的事。至少可以肯定的是,Java在消耗宝贵的系统资源方面,已经可以赶的上某些蠕虫病毒了。

 

如果不信,不妨把上面程序中的那个MAXSIZE的值改成65535试试,不一会你就会发现你的程序有这样的错误了:(在我的XP机器上大约运行到2000Selector.open() 左右)

 

Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Unable to establish loopback connection

        at Test.main(Test.java:18)

Caused by: java.io.IOException: Unable to establish loopback connection

        at sun.nio.ch.PipeImpl$Initializer.run(Unknown Source)

        at java.security.AccessController.doPrivileged(Native Method)

        at sun.nio.ch.PipeImpl.<init>(Unknown Source)

        at sun.nio.ch.SelectorProviderImpl.openPipe(Unknown Source)

        at java.nio.channels.Pipe.open(Unknown Source)

        at sun.nio.ch.WindowsSelectorImpl.<init>(Unknown Source)

        at sun.nio.ch.WindowsSelectorProvider.openSelector(Unknown Source)

        at java.nio.channels.Selector.open(Unknown Source)

        at Test.main(Test.java:15)

Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect

        at sun.nio.ch.Net.connect(Native Method)

        at sun.nio.ch.SocketChannelImpl.connect(Unknown Source)

        at java.nio.channels.SocketChannel.open(Unknown Source)

        ... 9 more

 

 

四、  继续调查 : 如此跨平台

 

当然,没人像我们这么变态写出那么多的Selector.open(),但这正好可以让我们来明白Java背着大家在干什么事。上面的那些“愚蠢连接”是在Windows平台上,如果不出意外,Unix/Linux下应该也差不多吧。

 

于是我们把上面的程序放在Linux下跑了跑。使用netstat 命令,并没有看到自己和自己的Socket连接。貌似在Linux上使用了和Windows不一样的机制?!

 

如果在Linux上不建自己和自己的TCP连接的话,那么文件描述符和端口都会被省下来了,是不是也就是说我们调用65535Selector.open()的话,应该不会出现异常了。

 

可惜,在实现运行过程序当中,还是一样报错:(大约在400Selector.open()左右,还不如Windows

 

Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Too many open files

        at Test1.main(Test1.java:19)

Caused by: java.io.IOException: Too many open files

        at sun.nio.ch.IOUtil.initPipe(Native Method)

        at sun.nio.ch.EPollSelectorImpl.<init>(EPollSelectorImpl.java:49)

        at sun.nio.ch.EPollSelectorProvider.openSelector(EPollSelectorProvider.java:18)

        at java.nio.channels.Selector.open(Selector.java:209)

        at Test1.main(Test1.java:15)

 

我们发现,这个异常错误是“Too many open files”,于是我想到了使用lsof命令来查看一下打开的文件。

 

看到了有一些pipe文件,一共5对,10个(当然,管道从来都是成对的)。如下图所示。

 

 

 

 

可见,Selector.open()Linux下不用TCP连接,而是用pipe管道。看来,这个pipe管道也是自己给自己的。所以,我们可以得出下面的结论:

 

1)Windows下,Selector.open()会自己和自己建立两条TCP链接。不但消耗了两个TCP连接和端口,同时也消耗了文件描述符。

2)Linux下,Selector.open()会自己和自己建两条管道。同样消耗了两个系统的文件描述符。

 

估计,在Windows下,SunJVM之所以选择TCP连接,而不是Pipe,要么是因为性能的问题,要么是因为资源的问题。可能,Windows下的管道的性能要慢于TCP链接,也有可能是Windows下的管道所消耗的资源会比TCP链接多。这些实现的细节还有待于更为深层次的挖掘。

 

但我们至少可以了解,原来JavaSelector在不同平台上的机制。

目录
相关文章
|
1天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
14 3
|
8天前
|
存储 监控 Java
Java的NIO体系
通过本文的介绍,希望您能够深入理解Java NIO体系的核心组件、工作原理及其在高性能应用中的实际应用,并能够在实际开发中灵活运用这些知识,构建高效的Java应用程序。
26 5
|
1月前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
2月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
38 2
|
3月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
4月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
152 0
|
4月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
92 0
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
5月前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解
|
5月前
|
Java 大数据
如何在Java中进行网络编程:Socket与NIO
如何在Java中进行网络编程:Socket与NIO

推荐镜像

更多