从I/O多路复用到Netty,还要跨过Java NIO包(一)

简介: 从I/O多路复用到Netty,还要跨过Java NIO包(一)

1.先来看一个Java NIO服务端的例子


上一篇文章我们已经了解了I/O多路复用的实现形式。
就是多个的进程的IO可以注册到一个复用器(selector)上,然后用一个进程调用select,select会监听所有注册进来的IO。

NIO包做了对应的实现。如下图所示。

64.png


有一个统一的selector负责监听所有的Channel。这些channel中只要有一个有IO动作,就可以通过Selector.select()方法检测到,并且使用selectedKeys得到这些有IO的channel,然后对它们调用相应的IO操作。


我们来个简单的demo做一下演示。如何使用NIO中三个核心组件(Buffer缓冲区、Channel通道、Selector选择器)来编写一个服务端程序。


public class NioDemo {
    public static void main(String[] args) {
        try {
            //1.创建channel
            ServerSocketChannel socketChannel1 = ServerSocketChannel.open();
            //设置为非阻塞模式,默认是阻塞的
            socketChannel1.configureBlocking(false);
            socketChannel1.socket().bind(new InetSocketAddress("127.0.0.1", 8811));
            ServerSocketChannel socketChannel2 = ServerSocketChannel.open();
            socketChannel2.configureBlocking(false);
            socketChannel2.socket().bind(new InetSocketAddress("127.0.0.1", 8822));
            //2.创建selector,并将channel1和channel2进行注册。
            Selector selector = Selector.open();
            socketChannel1.register(selector, SelectionKey.OP_ACCEPT);
            socketChannel2.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                //3.一直阻塞直到有至少有一个通道准备就绪
                int readChannelCount = selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                //4.轮训已经就绪的通道
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    //5.判断准备就绪的事件类型,并作相应处理
                    if (key.isAcceptable()) {
                        // 创建新的连接,并且把连接注册到selector上,并且声明这个channel只对读操作感兴趣。
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer readBuff = ByteBuffer.allocate(1024);
                        socketChannel.read(readBuff);
                        readBuff.flip();
                        System.out.println("received : " + new String(readBuff.array()));
                        socketChannel.close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


通过这个代码示例,我们能清楚地了解如何用Java NIO包实现一个服务端:


  • 1)创建channel1和channel2,分别监听特定端口。
  • 2)创建selector,并将channel1和channel2进行注册。
  • 3)selector.select()一直阻塞,直到有至少有一个通道准备就绪。
  • 4)轮训已经就绪的通道
  • 5)并根据事件类型做出相应的响应动作。


程序启动后,会一直阻塞在selector.select()。
通过浏览器调用localhost:8811 或者 localhost:8822就能触发我们的服务端代码了。


2.Java NIO包如何实现I/O多路复用模型


上文演示的Java NIO服务端已经比较清楚地展示了使用NIO编写服务端程序的过程。


那这个过程中如何实现了I/O多路复用的呢?


我们得深入看下selector的实现。


//2.创建selector,并将channel1和channel2进行注册。
Selector selector = Selector.open();


从open这里开始吧。

65.png


这里用了一个SelectorProvider来创建selector。


进入SelectorProvider.provider(),看到具体的provider是由
sun.nio.ch.DefaultSelectorProvider创建的,对应的方法是:

66.png


咦?原来不同的操作系统会提供不同的provider对象。这里包括了PollSelectorProvider、EPollSelectorProvide等。


名字是不是有点眼熟?


没错,跟我们上一篇文章分析过的I/O多路复用的不同实现方式poll/epoll有关。


我们选择默认的


sun.nio.ch.PollSelectorProvider往下看看。

67.png


OK,找到了实现类PollSelectorImpl。


然后,通过以下调用:

68.png


找到最终的native方法poll0。

69.png


是不是仍然很眼熟?


没错!跟我们上一篇文章分析过的poll函数是一致的。


int poll (struct pollfd *fds, unsigned int nfds, int timeout);


绕了这么久,到最后,还是找到了我们聊过I/O多路复用的 poll 实现。


至此,我们终于把Java NIO和 I/O多路复用模型串联起来了。


Java NIO包使用selector,实现了I/O多路复用模型。


同时,在不同的操作系统中,会有不同的poll/epoll选择。



目录
相关文章
|
17天前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
2月前
|
编解码 网络协议 开发者
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
|
2月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
59 1
|
3月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
5天前
|
Java API 数据处理
Java 包(package)的作用详解
在 Java 中,包(package)用于组织和管理类与接口,具有多项关键作用:1)系统化组织代码,便于理解和维护;2)提供命名空间,避免类名冲突;3)支持访问控制,如 public、protected、默认和 private,增强封装性;4)提升代码可维护性,实现模块化开发;5)简化导入机制,使代码更简洁;6)促进模块化编程,提高代码重用率;7)管理第三方库,避免命名冲突;8)支持 API 设计,便于功能调用;9)配合自动化构建工具,优化项目管理;10)促进团队协作,明确模块归属。合理运用包能显著提升代码质量和开发效率。
|
5天前
|
Java 数据安全/隐私保护
Java 包(package)的使用详解
Java中的包(`package`)用于组织类和接口,避免类名冲突并控制访问权限,提升代码的可维护性和可重用性。通过`package`关键字定义包,创建相应目录结构即可实现。包可通过`import`语句导入,支持导入具体类或整个包。Java提供多种访问权限修饰符(`public`、`protected`、`default`、`private`),以及丰富的标准库包(如`java.lang`、`java.util`等)。合理的包命名和使用对大型项目的开发至关重要。
|
2月前
|
Java Maven 数据库
Java 包(package)
Java 包(package)
31 1
|
2月前
|
Java
Java应用结构规范问题之在biz层的convert包实现转换的问题如何解决
Java应用结构规范问题之在biz层的convert包实现转换的问题如何解决
|
2月前
|
Java
Java 基础语法-面试题(54-63道)(数组+类+包)
Java 基础语法-面试题(54-63道)(数组+类+包)
37 16
|
2月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
69 0
下一篇
无影云桌面