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 中,一个选择器线程可以同时处理成千上万个客户端连接,性能不会随着客户端的增加而线性下降。

目录
相关文章
|
15天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
1月前
|
存储 Java 数据处理
|
1月前
|
Java API
java中IO与NIO有什么不同
java中IO与NIO有什么不同
|
5天前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
|
2月前
|
移动开发 编解码 网络协议
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
|
2月前
|
网络协议 Java Linux
用Java来实现BIO和NIO模型的HTTP服务器(二) NIO的实现
用Java来实现BIO和NIO模型的HTTP服务器(二) NIO的实现
|
2月前
|
编解码 网络协议 Java
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
|
2月前
|
并行计算 Java API
深入理解Java多线程编程:创建、状态管理、同步与通信
深入理解Java多线程编程:创建、状态管理、同步与通信
|
3月前
|
Java 应用服务中间件 Linux
java中的NIO,BIO,AIO
java中的NIO,BIO,AIO
17 0
|
3月前
|
设计模式 网络协议 Java
Java NIO 网络编程 | Netty前期知识(二)
Java NIO 网络编程 | Netty前期知识(二)
77 0

热门文章

最新文章