Java NIO 通信基础介绍

简介: NIO:高性能的 Java 通信技术

Java NIO 通信基础介绍

高性能的 Java 通信,绝对离不开 Java NIO 技术,现在主流的技术框架或中间件服务器,都使 用了 Java NIO 技术,譬如:Tomcat、Jetty、Netty。

Java NIO 由以下三个核心组件组成:

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

NIO 和 OIO 的对比

在 Java 中,NIO 和 OIO 的区别,主要体现在三个方面:

1、OIO 是面向流(Stream Oriented)的,NIO 是面向缓冲区(Buffer Oriented)的。

何谓面向流,何谓面向缓冲区呢?
OIO 是面向字节流或字符流的,在一般的 OIO 操作中,我们以流式的方式顺序地从一个流中读取一个或多个字节,因此,我们不能随意地改变读取指针的位置。而在 NIO 操作中则不同,NIO 中引入了 Channel(通道)和 Buffer(缓冲区)的概念。读取和写入,只需要从通道中读取数据到缓冲区中,或将数据从缓冲区中写入到通道中。NIO 不像 OIO 那样是顺序操作,可以随意地读取 Buffer 中任意位置的数据。

2、OIO 的操作是阻塞的,而 NIO 的操作是非阻塞的。

NIO 如何做到非阻塞的呢?大家都知道,OIO 操作都是阻塞的,例如,我们调用一个 read 方法读取一个文件的内容,那么调用 read 的线程会被阻塞住,直到 read 操作完成。
而在 NIO 的非阻塞模式中,当我们调用 read 方法时,如果此时有数据,则 read 读取数据并返回;如果此时没有数据,则 read 直接返回,而不会阻塞当前线程。NIO 的非阻塞,是如何做到的呢?NIO 使用了通道和通道的多路复用技术。

3、OIO 没有选择器(Selector)概念,而 NIO 有选择器的概念。

NIO 的实现,是基于底层的选择器的系统调用。NIO 的选择器,需要底层操作系统提供支持。
而 OIO 不需要用到选择器。

使用 FileChannel 完成文件复制的实践案例

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class MyCopyFile {


    private File inFile;
    private File outFile;
    private FileInputStream fis = null;
    private FileOutputStream fos = null;
    private FileChannel fisChannel = null;
    private FileChannel fosChannel = null;

    //复制文件
    public void copyFile(String srcPath, String destPath) throws IOException {
        try {
            inFile = new File(srcPath);
            outFile = new File(destPath);
            fis = new FileInputStream(inFile);
            fos = new FileOutputStream(outFile);

            fisChannel = fis.getChannel();
            fosChannel = fos.getChannel();

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int length = -1;
            while ((length = fisChannel.read(buffer)) != -1) {
                buffer.flip();
                int outLenth = 0;
                while ((outLenth = fosChannel.write(buffer)) != 0) {
                    System.out.println("读取的字节数为:" + outLenth);
                }
                buffer.clear();
            }
            //强制刷新磁盘
            fosChannel.force(true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            fosChannel.close();
            fos.close();
            fisChannel.close();
            fis.close();
        }
    }

    public static void main(String[] args) throws IOException {
        MyCopyFiletest = new MyCopyFile();
        String s1 = "D:\\maze.txt";
        String s2 = "D:\\maze1.txt";
        MyCopyFile.copyFile(s1, s2);
    }

}

使用 DatagramChannel 数据包通道发送数据的实践案例

功能:

获取用户的输入数据,通过 DatagramChannel 数据报通道,将数据发送到远程的服务器。

客户端代码:

public class Client {

    //Client发送信息
    public void send() throws IOException {
        DatagramChannel dChannel = DatagramChannel.open();
        dChannel.configureBlocking(false);
        ByteBuffer buf = ByteBuffer.allocate(1024);
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            String s = sc.nextLine();
            buf.put(s.getBytes());
            buf.flip();
            dChannel.send(buf, new InetSocketAddress("127.0.0.1", 9999));
            buf.clear();
        }
        dChannel.close();
    }

    public static void main(String[] args) throws IOException {

        new Client().send();
    }

}

服务端代码

public class Server {

    //服务端接收 用户发来的信息
    public void receive() throws IOException {

        DatagramChannel serverChannel = DatagramChannel.open();
        //设置成非阻塞模式
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress("127.0.0.1", 9999));
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_READ);

        while (selector.select() > 0) {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (iterator.hasNext()) {
                SelectionKey next = iterator.next();
                if (next.isReadable()) {
                    SocketAddress receive = serverChannel.receive(buffer);
                    buffer.flip();
                    String s = new String(buffer.array(), 0, buffer.limit());
                    System.out.println(s);
                    buffer.clear();
                }
            }
            iterator.remove();
        }
        //关闭选择器和通道
        selector.close();
        serverChannel.close();

    }


    public static void main(String[] args) throws IOException {
        new Server().receive();
    }

}

使用 NIO 实现 Discard 服务器的实践案例

功能:

仅仅读取客户端通道的输入数据,读取完成后直接关闭客户端通道;并且读取到的数据直接抛弃掉

Discard 服务器代码:

public class SocketServerDemo {
    public void receive() throws IOException {
        //创建服务器的通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //开启选择器
        Selector selector = Selector.open();
        //绑定链接
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //将通道的某个IO事件  注册到选择器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //轮询所有就绪的IO事件
        while (selector.select() > 0) {
            //逐个获取IO事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            //逐个判断该IO事件是否为想要的
            while (iterator.hasNext()) {
                SelectionKey next = iterator.next();
                if (next.isAcceptable()) {
                    //如果为该事件为“连接就绪”事件,就获取客户端的链接
                    SocketChannel clientSocket = serverSocketChannel.accept();
                    //将客户端的链接设置为非阻塞模式
                    clientSocket.configureBlocking(false);
                    //将新的通道的可读事件,注册到选择器上
                    clientSocket.register(selector, SelectionKey.OP_READ);
                } else if (next.isReadable()) {
                    //若IO事件为“可读事件”,读取数据
                    SocketChannel clientSocket = (SocketChannel) next.channel();
                    //创建缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int length = 0;
                    //读取事件 让后丢弃
                    while ((length = clientSocket.read(buffer)) > 0) {
                        buffer.flip();
                        String s = new String(buffer.array(), 0, length);
                        System.out.println(s);
                        buffer.clear();
                    }
                    clientSocket.close();
                }
                //移除选择键
                iterator.remove();
            }
        }
        serverSocketChannel.close();
    }

    public static void main(String[] args) throws IOException {
        new SocketServerDemo().receive();
    }
}

客户端的 DiscardClient 代码:

public class SocketClientDemo {

    public void socketClient() throws IOException {
        SocketChannel clientSocket = SocketChannel.open(new InetSocketAddress(9999));
        //切换成非阻塞模式
        clientSocket.configureBlocking(false);
        //如果没有连接完成 就一直链接
        while (!clientSocket.finishConnect()){
        }
        //执行到这里说明已经连接完成了
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("Hello SocketService".getBytes());
        buffer.flip();
        clientSocket.write(buffer);
        clientSocket.shutdownInput();
        clientSocket.close();
    }



    public static void main(String[] args) throws IOException {
        new SocketClientDemo().socketClient();
    }
}

与 Java OIO 相比,Java NIO 编程大致的特点如下:
(1)在 NIO 中,服务器接收新连接的工作,是异步进行的。不像 Java 的 OIO 那样,服务器监听连接,是同步的、阻塞的。NIO 可以通过选择器(也可以说成:多路复用器),后续不断地轮询选择器的选择键集合,选择新到来的连接。
(2)在 NIO 中,SocketChannel 传输通道的读写操作都是异步的。如果没有可读写的数据,负责 IO 通信的线程不会同步等待。这样,线程就可以处理其他连接的通道;不需要像 OIO 那样,线程一直阻塞,等待所负责的连接可用为止。
(3)在 NIO 中,一个选择器线程可以同时处理成千上万个客户端连接,性能不会随着客户端的增加而线性下降。

目录
相关文章
|
8天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
8天前
|
网络协议 算法 Java
【Java网络编程】网络编程概述、UDP通信(DatagramPacket 与 DatagramSocket)
【Java网络编程】网络编程概述、UDP通信(DatagramPacket 与 DatagramSocket)
23 3
|
1天前
|
Java 调度
【JAVA学习之路 | 提高篇】线程的通信
【JAVA学习之路 | 提高篇】线程的通信
|
4天前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
28 2
|
8天前
|
监控 Java
Java一分钟之-NIO:非阻塞IO操作
【5月更文挑战第14天】Java的NIO(New IO)解决了传统BIO在高并发下的低效问题,通过非阻塞方式提高性能。NIO涉及复杂的选择器和缓冲区管理,易出现线程、内存和中断处理的误区。要避免这些问题,可以使用如Netty的NIO库,谨慎设计并发策略,并建立标准异常处理。示例展示了简单NIO服务器,接收连接并发送欢迎消息。理解NIO工作原理和最佳实践,有助于构建高效网络应用。
14 2
|
8天前
|
Java 开发者 Spring
Java一分钟之-Java网络编程基础:Socket通信
【5月更文挑战第13天】本文介绍了Java Socket编程基础知识,包括使用`ServerSocket`和`Socket`类建立连接,通过`OutputStream`和`InputStream`进行数据传输。常见问题涉及忘记关闭Socket导致的资源泄漏、网络异常处理及并发同步。理解Socket通信原理并掌握异常处理、资源管理和并发控制,能帮助开发者构建更稳定的网络应用。
33 1
|
8天前
|
缓存 Java API
Java NIO和IO之间的区别
NIO(New IO),这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
18 1
|
8天前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
|
8天前
|
存储 监控 Java
浅谈Java NIO
浅谈Java NIO
7 0
|
8天前
|
消息中间件 存储 Java
【Java NIO】那NIO为什么速度快?
是这样的,在NIO零拷贝出现之前,一个I/O操作会将同一份数据进行多次拷贝。可以看下图,一次I/O操作对数据进行了四次复制,同时来伴随两次内核态和用户态的上下文切换,众所周知上下文切换是很耗费性能的操作。
31 1
【Java NIO】那NIO为什么速度快?