NIO&使用NIO传输图片

相比于传统的阻塞IO,NIO提供了一种更灵活和高效的 I/O 操作方式,NIO 提供的非阻塞式的 I/O 操作,使得一个单独的线程可以管理多个通道(Channel),从而更好地处理并发连接和大量的 I/O 操作。

1. 核心组件

NIO 的核心组件包括通道(Channel)、缓冲区(Buffer)和选择器(Selector)。

(1)通道(Channel)

  • 通道是 NIO 中用于读取和写入数据的抽象。它可以连接到文件、网络套接字等输入/输出设备。
  • Java NIO 提供了不同类型的通道,包括文件通道(FileChannel)、套接字通道(SocketChannel 和 ServerSocketChannel)、Datagram 通道(DatagramChannel)等。
  • 通道与传统的流(Stream)不同,通道可以双向传输数据,而流是单向的。

(2) 缓冲区(Buffer)

  • 缓冲区是用于暂存数据的对象,它是 NIO 操作数据的基本单位。所有的数据读取和写入都是通过缓冲区进行的。
  • 缓冲区具有固定的容量,可以通过 put() 和 get() 等方法向其中写入或读取数据。
  • Java NIO 提供了不同类型的缓冲区,包括 ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 和 DoubleBuffer,每种缓冲区用于不同类型的数据。

(3) 选择器(Selector)

  • 选择器是 NIO 中用于多路复用的关键组件。它可以监听多个通道的事件,当一个或多个通道准备好进行读取或写入时,选择器将这些通道放入就绪集合(SelectionKey)中。
  • 选择器使得一个单独的线程可以有效地管理多个通道,提高了系统的性能和资源利用率。
  • 选择器是事件驱动模型的核心,它使得 NIO 可以实现非阻塞式的 I/O 操作。  

2. 与传统 IO 的区别 

(1)阻塞与非阻塞:

  • 传统的 IO 是阻塞式的,当一个 IO 操作开始后,线程会被阻塞,直到该操作完成。这意味着如果线程在进行 IO 操作时没有其他任务可执行,系统资源会被浪费。
  • NIO 使用非阻塞式 IO,意味着线程在进行 IO 操作时可以同时执行其他任务,从而提高了系统资源的利用率。

(2)通道与流: 

  • 传统的 IO 使用字节流和字符流进行数据传输,而 NIO 使用通道(Channel)和缓冲区(Buffer)。
  • 通道可以双向传输数据,而流是单向的。通道提供了更多的控制和灵活性。

(3)多路复用:

  • NIO 引入了选择器(Selector)的概念,可以同时监听多个通道的事件,当一个或多个通道准备好进行读取或写入时,选择器会通知程序。
  • 这使得一个线程可以有效地管理多个通道,处理并发连接和大量的 IO 操作,提高了系统的性能和可扩展性。

3. NIO 的优势

  • 高并发处理:NIO 可以通过较少的线程处理更多的连接,提高了系统的性能和资源利用率。

  • 灵活的数据操作:NIO 提供了更灵活和高效的数据操作方式,可以更好地处理大量的 IO 操作。

 使用NIO传输图片

客户端代码

import java.io.FileInputStream; // 导入文件输入流类,用于读取文件内容  
import java.io.IOException;     // 导入I/O异常类,用于处理可能的文件读取和网络通信异常  
import java.net.InetSocketAddress; // 导入网络地址类,用于指定服务器的IP地址和端口号  
import java.nio.ByteBuffer;       // 导入ByteBuffer类,用于在通道中传输数据  
import java.nio.channels.FileChannel; // 导入文件通道类,用于从文件中读取数据  
import java.nio.channels.SocketChannel; // 导入Socket通道类,用于网络通信  
  
public class ImageClient {  
  
    // 主函数,程序的入口点  
    public static void main(String[] args) throws IOException {  
  
        // 创建一个SocketChannel实例,并准备连接到服务器  
        // SocketChannel是NIO(非阻塞I/O)中用于网络通信的通道  
        SocketChannel socketChannel = SocketChannel.open();  
  
        // 连接到指定的服务器地址和端口,这里连接到本地机器(localhost)的8080端口  
        // InetSocketAddress是一个网络地址的封装,包含了IP地址和端口号  
        socketChannel.connect(new InetSocketAddress("localhost", 8080));  
  
        // 读取图片文件到ByteBuffer中  
        // 使用FileInputStream来读取文件,然后通过getChannel()方法获取到文件的通道(FileChannel)  
        FileInputStream fis = new FileInputStream("src/main/java/images/userAvatar.jpg");  
        FileChannel fileChannel = fis.getChannel();  
  
        // 分配一个ByteBuffer,大小为1024字节,用于临时存储从文件中读取的数据  
        // ByteBuffer是一个字节容器,可以通过它读取或写入数据到通道  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
  
        // 循环读取文件内容,直到文件结束  
        while (fileChannel.read(buffer) > 0) {  
            // 调用flip()方法,将ByteBuffer从写模式切换到读模式  
            // 在调用write()方法之前,必须先调用flip()方法  
            buffer.flip();  
  
            // 将ByteBuffer中的数据写入到SocketChannel中,发送到服务器  
            socketChannel.write(buffer);  
  
            // 调用clear()方法,清空ByteBuffer,准备下一次读取  
            // clear()方法会将position设置为0,limit设置为capacity,为下一次读取或写入做准备  
            buffer.clear();  
        }  
  
        // 关闭FileInputStream和SocketChannel,释放资源  
        fis.close();  
        socketChannel.close();  
  
    }  
}

服务端代码

import java.io.FileOutputStream; // 导入文件输出流类,用于将接收到的数据写入文件  
import java.io.IOException;     // 导入I/O异常类,用于处理可能的文件写入和网络通信异常  
import java.net.InetSocketAddress; // 导入网络地址类,用于指定服务器的IP地址和端口号  
import java.nio.ByteBuffer;       // 导入ByteBuffer类,用于在通道中传输数据  
import java.nio.channels.FileChannel; // 导入文件通道类,用于向文件中写入数据  
import java.nio.channels.ServerSocketChannel; // 导入服务器套接字通道类,用于监听和接受连接  
import java.nio.channels.SocketChannel; // 导入Socket通道类,用于网络通信  
  
public class ImageServer {  
  
    public static void main(String[] args) throws IOException {  
        // 创建一个ServerSocketChannel实例,用于监听连接请求  
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
  
        // 将ServerSocketChannel绑定到特定的端口,这里使用8080端口  
        serverSocketChannel.bind(new InetSocketAddress(8080));  
  
        // 配置ServerSocketChannel为非阻塞模式,这允许服务器同时处理多个连接  
        serverSocketChannel.configureBlocking(false);  
  
        // 无限循环,持续监听新的连接请求  
        while (true) {  
            // 尝试接受一个新的连接请求  
            // 因为设置了非阻塞模式,如果当前没有连接请求,accept()会立即返回null  
            SocketChannel socketChannel = serverSocketChannel.accept();  
  
            // 如果成功接受了一个连接请求,则进行以下操作  
            if (socketChannel != null) {  
                // 分配一个ByteBuffer,大小为1024字节,用于临时存储从SocketChannel中读取的数据  
                ByteBuffer buffer = ByteBuffer.allocate(1024);  
  
                // 创建一个FileOutputStream,用于将接收到的数据写入到文件中  
                // 这里假设文件名为"D:\\ltsServer\\text.jpg"  
                FileOutputStream fos = new FileOutputStream("D:\\ltsServer\\text.jpg");  
  
                // 获取FileOutputStream对应的FileChannel  
                FileChannel fileChannel = fos.getChannel();  
  
                // 读取SocketChannel中的数据,并写入到文件中,直到SocketChannel中没有更多数据可读  
                int bytesRead;  
                while ((bytesRead = socketChannel.read(buffer)) > 0) {  
                    // 打印从SocketChannel中读取的字节数  
                    System.out.println(bytesRead);  
  
                    // 调用flip()方法,将ByteBuffer从读模式切换到写模式  
                    buffer.flip();  
  
                    // 将ByteBuffer中的数据写入到FileChannel中,即将数据写入到文件中  
                    fileChannel.write(buffer);  
  
                    // 调用clear()方法,清空ByteBuffer,准备下一次读取  
                    buffer.clear();  
                }  
  
                // 关闭FileOutputStream和SocketChannel,释放资源  
                fos.close();  
                socketChannel.close();  
            }  
  
            // 在实际应用中,对于非阻塞模式,通常需要使用选择器(Selector)来同时处理多个通道  
            // 这里的代码只是简单示例,没有使用选择器  
        }  
    }  
}

这里再补充一段在服务端使用 选择器(Selector)监听通道的代码

private static void start() throws IOException {
        // 创建ServerSocketChannel,并设置为非阻塞模式
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(PORT));

        // 创建Selector,用于监听通道事件
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到Selector上,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 不断循环,处理通道事件
        while (true) {
            selector.select(); // 阻塞,等待通道事件发生
            for (SelectionKey key : selector.selectedKeys()) {
                if (key.isAcceptable()) { // 客户端发起连接事件
                    acceptClient(selector, serverSocketChannel);
                } else if (key.isReadable()) { // 客户端发送数据事件

                    readMessageFromClient(key);

                }
            }
            selector.selectedKeys().clear(); // 清空已处理的事件集合
        }
    }

 

 

 

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值