IO与NIO的区别

IO与NIO的区别及其应用案例

Java中的IO(Input/Output)和NIO(New Input/Output)是用于处理数据传输的两种不同的API。它们在设计理念、实现方式和适用场景上有显著的差异。本文将详细介绍IO与NIO的区别,并通过实际案例说明如何应用NIO。

IO与NIO的区别

1. 阻塞与非阻塞

  • IO(阻塞IO,BIO):传统IO以阻塞模式工作,当一个线程进行读写操作时,该线程会被阻塞,直到操作完成。每个连接需要一个线程来处理,多个连接需要多个线程。

    try (InputStream inputStream = socket.getInputStream()) {
        byte[] buffer = new byte[1024];
        int bytesRead = inputStream.read(buffer); // 阻塞,直到有数据可读
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • NIO(非阻塞IO):NIO以非阻塞模式工作,允许一个线程处理多个连接,可以在数据未准备好时返回,而不是阻塞等待。通过选择器(Selector)机制,一个线程可以管理多个通道(Channel)。

    try (Selector selector = Selector.open();
         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
        while (true) {
            selector.select(); // 阻塞,直到有事件发生
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
    
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    client.read(buffer); // 非阻塞
                }
                iterator.remove();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

2. 基本单元

  • IO:以流(Stream)为基本操作单元,数据一个字节一个字节地传输。

    InputStream inputStream = new FileInputStream("data.txt");
    int data = inputStream.read();
    while (data != -1) {
        System.out.print((char) data);
        data = inputStream.read();
    }
    inputStream.close();
    
  • NIO:以块(Block)为基本操作单元,数据以块的形式进行传输,效率更高。

    RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
    FileChannel channel = file.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(48);
    int bytesRead = channel.read(buffer);
    while (bytesRead != -1) {
        buffer.flip();
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        buffer.clear();
        bytesRead = channel.read(buffer);
    }
    file.close();
    

3. 选择器机制

  • IO:没有选择器机制,每个连接都需要一个线程来处理。

  • NIO:引入了选择器(Selector)机制,一个线程可以管理多个通道(Channel),大大提高了资源利用率和处理效率。

    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.configureBlocking(false);
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    

4. 可扩展性

  • IO:每个连接需要一个线程处理,高并发场景下,创建和管理大量线程的开销非常大,扩展性较差。

  • NIO:通过非阻塞I/O和选择器机制,一个线程可以处理多个连接,减少了线程开销,提高了系统的扩展性和性能。

5. 使用场景

  • IO:适用于连接数量较少、处理逻辑复杂或需要简单实现的场景,例如小型应用、脚本工具等。

  • NIO:适用于高并发、大数据量传输或实时性要求高的场景,例如高性能服务器、聊天室、实时数据处理系统等。

NIO的经典应用案例:Netty Echo 服务器

Netty是基于NIO的高性能网络应用框架,简化了NIO编程,提供了高级特性。下面是使用Netty实现的Echo服务器和客户端的示例。

服务器端代码:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class NettyEchoServer {

    private final int port;

    public NettyEchoServer(int port) {
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new NettyEchoServerHandler());
                    }
                });

            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        new NettyEchoServer(port).start();
    }
}

服务器处理器代码:

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class NettyEchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Server received: " + msg);
        ctx.write(msg); // 将消息写回客户端
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush(); // 刷新消息
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close(); // 关闭连接
    }
}

客户端代码:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyEchoClient {

    private final String host;
    private final int port;

    public NettyEchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new NettyEchoClientHandler());
                    }
                });

            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new NettyEchoClient("localhost", 8080).start();
    }
}

客户端处理器代码:

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class NettyEchoClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush("Hello, Netty!");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Client received: " + msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.print

StackTrace();
        ctx.close(); // 关闭连接
    }
}

解释

  1. 服务器端

    • ServerBootstrap:引导服务器的启动。
    • NioEventLoopGroup:处理I/O操作的线程池,bossGroup接受连接,workerGroup处理连接。
    • NioServerSocketChannel:对应于NIO中的ServerSocketChannel
    • ChannelInitializer:初始化新连接的通道,添加处理器。
    • NettyEchoServerHandler:业务处理器,实现了消息的接收和回送。
  2. 客户端

    • Bootstrap:引导客户端的启动。
    • NioSocketChannel:对应于NIO中的SocketChannel
    • NettyEchoClientHandler:客户端业务处理器,实现了消息的发送和接收。

Netty通过这些类和机制,简化了NIO编程,使得实现高性能的网络应用变得更加容易和高效。

总结

IO和NIO在Java中提供了不同的I/O处理模型,适用于不同的应用场景。NIO通过非阻塞I/O和选择器机制,可以高效地处理高并发和大数据量的网络通信。Netty作为基于NIO的高性能网络应用框架,进一步简化了NIO的编程,使得开发高性能网络应用更加便捷。通过实际案例,可以清楚地看到NIO和Netty在处理高并发网络通信中的优势。

posted on 2024-05-27 19:00  滚动的蛋  阅读(321)  评论(0编辑  收藏  举报

导航