Java NIO系列教程二

简介: ​ 今天主要是为大家详细的介绍了常见的各种Channel以及他们的用法,本文编写了大量的案例还需要大家认真的去实践以后才能真正的掌握住。介绍完Channel那么下一篇文章我们就可以为大家介绍Buffer和Selector的具体使用了。

NIO详解

一、其他Channel

1. Socket通道

​ 所有的 socket 通道类(DatagramChannel、
SocketChannel 和 ServerSocketChannel)都继承了位于 java.nio.channels.spi 包中的 AbstractSelectableChannel。这意味着我们可以用一个 Selector 对象来执行socket 通道的就绪选择

  • 非阻塞模式中,有更多的伸缩性和灵活性。使用socket,可以用一个线程或者多个线程就可以管理成百上千个socket,管理功能强大,而且没有性能损失,可以通过 Selector监听多个通道
  • DatagramChannel 和 SocketChannel 实现定义读和写功能的接口而ServerSocketChannel 不实现。ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel 对象,它本身从不传输数据。究其代码是因为它实现的接口比较少
  • socket通道可以被重复使用,socket不可以被重复使用
  • 设置socket的通道模式为阻塞还是非阻塞,可以通过AbstractSelectableChannel.java 中实现的 configureBlocking()方法。传递参数值为 true 则设为阻塞模式,参数值为 false 值设为非阻塞模式

2. ServerSocketChannel

  • 本身不传入数据,主要是为了监听,可以在非阻塞模式下运行没有bind()绑定方法,通过对等的socket来绑定端口并进行监听
  • ServerSocketChannel的对象.socket().bind();
  • 有accept()方法,返回SocketChannel 类型,对象为空则没有链接,反之则。可以在非阻塞下运行

其它 Socket 的 accept()方法会阻塞返回一个 Socket 对象。如果
ServerSocketChannel 以非阻塞模式被调用,当没有传入连接在等待时,ServerSocketChannel.accept( )会立即返回 null。正是这种检查连接而不阻塞的能力实现了可伸缩性并降低了复杂性。可选择性也因此得到实现。我们可以使用一个选择器实例来注册 ServerSocketChannel 对象以实现新连接到达时自动通知的功能

具体的代码思路步骤为:

  • 打开ServerSocketChannel
  • 绑定端口号
  • 设置非阻塞模式configureBlocking(false);
  • 监听连接,通过accept
  • 将其buffer归为0指针rewind(),并且写入buffer
  • 关闭 ServerSocketChannel

具体通过代码演示演示代码代码如下:

public class ServerSocketChannelDemo {
   

    public static void main(String[] args) throws Exception {
   
        //端口号
        int port = 8888;

        //buffer
        ByteBuffer buffer = ByteBuffer.wrap("hello opencoder".getBytes());

        //ServerSocketChannel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定
        ssc.socket().bind(new InetSocketAddress(port));

        //设置非阻塞模式
        ssc.configureBlocking(false);

        //监听有新链接传入
        while(true) {
   
            System.out.println("Waiting for connections");
            SocketChannel sc = ssc.accept();
            if(sc == null) {
    //没有链接传入
                System.out.println("null");
                Thread.sleep(2000);
            } else {
   
                System.out.println("Incoming connection from: " + sc.socket().getRemoteSocketAddress());
                buffer.rewind(); //指针0
                sc.write(buffer);
                sc.close();
            }
        }
    }
}

通过浏览器点击127.0.0.1:8888,就会出现监听的提示

3. SocketChannel

  • 用于连接到TCP的套接字
  • 实现多路复用
  • 面向缓冲区

主要的特征有:
(1)对于已经存在的 socket 不能创建 SocketChannel
(2)SocketChannel 中提供的 open 接口创建的 Channel 并没有进行网络级联,需要使用 connect 接口连接到指定地址
(3)未进行连接的 SocketChannle 执行 I/O 操作时,会抛出
NotYetConnectedException
(4)SocketChannel 支持两种 I/O 模式:阻塞式和非阻塞式
(5)SocketChannel 支持异步关闭。如果 SocketChannel 在一个线程上 read 阻塞,另一个线程对该 SocketChannel 调用 shutdownInput,则读阻塞的线程将返回-1 表示没有读取任何数据;如果 SocketChannel 在一个线程上 write 阻塞,另一个线程对该SocketChannel 调用 shutdownWrite,则写阻塞的线程将抛出AsynchronousCloseException
(6)SocketChannel 支持设定参数
    SO_SNDBUF 套接字发送缓冲区大小
    SO_RCVBUF 套接字接收缓冲区大小
    SO_KEEPALIVE 保活连接
    O_REUSEADDR 复用地址
    SO_LINGER 有数据传输时延缓关闭 Channel (只有在非阻塞模式下有用)
    TCP_NODELAY 禁用 Nagle 算法

部分代码解释

  • 创建 SocketChannel

      SocketChannel socketChannel = SocketChannel.open(new 
      InetSocketAddress("www.baidu.com", 80));
      //或者
      SocketChannel socketChanne2 = SocketChannel.open();
      socketChanne2.connect(new InetSocketAddress("www.baidu.com", 80));
    
  • 连接校验

      socketChannel.isOpen(); // 测试 SocketChannel 是否为 open 状态
      socketChannel.isConnected(); //测试 SocketChannel 是否已经被连接
      socketChannel.isConnectionPending(); //测试 SocketChannel 是否正在进行
      连接
      socketChannel.finishConnect(); //校验正在进行套接字连接的 SocketChannel
      是否已经完成连接
    
  • 读写模式(有阻塞和非阻塞)

    设置 SocketChannel 的读写模式。false 表示非阻塞,true 表示阻塞

      socketChannel.configureBlocking(false);
    
  • 读写相关代码

      //阻塞读
      SocketChannel socketChannel = SocketChannel.open(
       new InetSocketAddress("www.baidu.com", 80));
      ByteBuffer byteBuffer = ByteBuffer.allocate(16);
      socketChannel.read(byteBuffer);
      socketChannel.close();
      System.out.println("read over");
      //非阻塞读
      SocketChannel socketChannel = SocketChannel.open(
       new InetSocketAddress("www.baidu.com", 80));
      socketChannel.configureBlocking(false);
      ByteBuffer byteBuffer = ByteBuffer.allocate(16);
      socketChannel.read(byteBuffer);
      socketChannel.close();
      System.out.println("read over");
    

完整代码如下:

public class SocketChannelDemo {
   

    public static void main(String[] args) throws Exception {
   
        //创建SocketChannel
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));
//        SocketChannel socketChanne2 = SocketChannel.open();
//        socketChanne2.connect(new InetSocketAddress("www.baidu.com", 80));
        //设置阻塞和非阻塞
        socketChannel.configureBlocking(false);
        //读操作
        ByteBuffer byteBuffer = ByteBuffer.allocate(16);
        socketChannel.read(byteBuffer);
        socketChannel.close();
        System.out.println("read over");
    }
}

4. DatagramChannel

  • SocketChannel 对应 Socket,ServerSocketChannel 对应ServerSocket
  • DatagramChannel 对应的是 DatagramSocket 对象,DatagramChannel (如UDP/IP)是无连接的,可以发送单独的数据报给不同的目的地址。同样,可以接收来自任意地址的数据包。

部分代码解释

  • 打开 DatagramChannel

    DatagramChannel server = DatagramChannel.open();
    server.socket().bind(new InetSocketAddress(10086));
    
  • 接收数据

    ByteBuffer receiveBuffer = ByteBuffer.allocate(64);
    receiveBuffer.clear();
    SocketAddress receiveAddr = server.receive(receiveBuffer);
    
  • 发送数据

    DatagramChannel server = DatagramChannel.open();
    ByteBuffer sendBuffer = ByteBuffer.wrap("client send".getBytes());
    server.send(sendBuffer, new InetSocketAddress("127.0.0.1",10086));
    
  • 连接

    read()和 write()只有在 connect()后才能使用,否则会抛异常。

    client.connect(new InetSocketAddress("127.0.0.1",10086));
    int readSize= client.read(sendBuffer);
    server.write(sendBuffer);
    

    具体发送的完整代码

    //发送的实现
    @Test
    public void sendDatagram() throws Exception {
         
        //打开 DatagramChannel
        DatagramChannel sendChannel = DatagramChannel.open();
        InetSocketAddress sendAddress =
                new InetSocketAddress("127.0.0.1",9999);
    
        //发送
        while(true) {
         
            ByteBuffer buffer = ByteBuffer.wrap("发送数据".getBytes("UTF-8"));
            sendChannel.send(buffer,sendAddress);
            System.out.println("已经完成发送");
            Thread.sleep(1000);
        }
    }
    

    具体接收的完整代码

    接收代码的时候其缓冲区一开始要先清除,在将其接收之后缓冲区的数据读写转换在将其输出注意此处的输出语句要用toString()

    //接收的实现
    @Test
    public void receiveDatagram() throws Exception {
         
        //打开DatagramChannel
        DatagramChannel receiveChannel = DatagramChannel.open();
        InetSocketAddress receiveAddress = new InetSocketAddress(9999);
        //绑定
        receiveChannel.bind(receiveAddress);
        //buffer
        ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
        //接收
        while(true) {
         
            receiveBuffer.clear();
            SocketAddress socketAddress = receiveChannel.receive(receiveBuffer);
            receiveBuffer.flip();
            System.out.println(socketAddress.toString());
            System.out.println(Charset.forName("UTF-8").decode(receiveBuffer));
        }
    }
    

5. Scatter/Gather

以下两种方式都是按照顺序读取或者写入:

​ 分散(scatter)从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer中。

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = {
    header, body };
channel.read(bufferArray);

​ 聚集(gather)写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个Channel

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = {
    header, body };
channel.write(bufferArray);

他们的使用方式跟其他的Channel基本都是一样的这里就不在赘述了,大家可以按照之前的使用方式自行学一下这两种方式的使用。

总结

​ 今天主要是为大家详细的介绍了常见的各种Channel以及他们的用法,本文编写了大量的案例还需要大家认真的去实践以后才能真正的掌握住。介绍完Channel那么下一篇文章我们就可以为大家介绍Buffer和Selector的具体使用了。

目录
相关文章
|
7月前
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
1033 3
|
6月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
418 10
|
5月前
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
432 0
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
621 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
8月前
|
缓存 安全 Java
Java 并发新特性实战教程之核心特性详解与项目实战
本教程深入解析Java 8至Java 19并发编程新特性,涵盖CompletableFuture异步编程、StampedLock读写锁、Flow API响应式流、VarHandle内存访问及结构化并发等核心技术。结合电商订单处理、缓存系统、实时数据流、高性能计数器与用户资料聚合等实战案例,帮助开发者高效构建高并发、低延迟、易维护的Java应用。适合中高级Java开发者提升并发编程能力。
311 0
|
9月前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
1070 2
|
消息中间件 Java 数据库
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
13181 5
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
Java 数据库连接 数据处理
探究Java异常处理【保姆级教程】
Java 异常处理是确保程序稳健运行的关键机制。它通过捕获和处理运行时错误,避免程序崩溃。Java 的异常体系以 `Throwable` 为基础,分为 `Error` 和 `Exception`。前者表示严重错误,后者可细分为受检和非受检异常。常见的异常处理方式包括 `try-catch-finally`、`throws` 和 `throw` 关键字。此外,还可以自定义异常类以满足特定需求。最佳实践包括捕获具体异常、合理使用 `finally` 块和谨慎抛出异常。掌握这些技巧能显著提升程序的健壮性和可靠性。
265 4
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
879 26