searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Java NIO 通信基础

2024-08-29 09:42:14
4
0

Java NIO 通信基础

简介

  • JAVA NIO 三个核心组件:

    • channel
    • buffer
    • selector
  • NIO 和 OIO 的区别:

    • OIO 面向流,NIO 面向缓冲区,面向缓冲区后,读取和写入,只需要从通道中读取数据到缓冲区中,可以任意读取buffer中任意位置的数据。
    • OIO的操作是阻塞的,而NIO 的操作是非阻塞的。
    • NIO 有选择器的概念
  • Channel

    • 一个通道类似于OIO 中Input Stream 和 OutputStream 的结合体,既可以从通道中读取,也可以向通道中写入。
  • Selector 选择器

    • 与OIO 相比,使用选择器的最大的优势:系统开销小,系统不必为每个网络连接创建进程/线程,从而大大减小了系统的开销。
  • Buffer

    • 通道的读取就是将数据从通道中读取到缓冲区中;通道的写入,就是讲数据从缓冲区写入到同道中。

Buffer(缓冲)类

buffer 四个重要属性说明

属性 说明
capacity 容量,即可以容纳的最大数据量;在缓冲区创建并且不能改变
limit 上限,缓冲区中当前的数量量
position 位置,缓冲区中下一个要被读取或写的索引
mark 暂存属性,暂时报存position 的值,方便后面重复使用position 的值

allocate() 创建缓冲区

public static void main(String[] args) {
        intBuffer = IntBuffer.allocate(20);
        System.out.println("---after allocate ----");
        System.out.println("position =" + intBuffer.position());
        // 写模型下,limit 表示可以写入的数据上限
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
    }
    
 ---after allocate ----
position =0
limit =20
capacity =20

put() 写入到缓冲区

for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        System.out.println("---after allocate ----");
        System.out.println("position =" + intBuffer.position());
        // 写模型下,limit 表示可以写入的数据上限
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());


---after allocate ----
position =5
limit =20
capacity =20
  • 索引值变成了5,即第六个位置

flip() 翻转

intBuffer = IntBuffer.allocate(20);

        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        // 切换到读模式
        //    public final Buffer flip() {
        //        limit = position;
        //        position = 0;
        //        mark = -1;
        //        return this;
        //    }
        intBuffer.flip();
        System.out.println("---after allocate ----");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
        
---after allocate ----
position =0
limit =5
capacity =20

get() 从缓冲区读取

intBuffer = IntBuffer.allocate(20);

        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        // 切换到读模式
        intBuffer.flip();

        for(int i=0; i<2; i++) {
            int j = intBuffer.get();
            System.out.println("j =" +j);
        }
        System.out.println("---after allocate ----");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
        for(int i=0; i<4; i++) {
            int j = intBuffer.get();
            System.out.println("j =" +j);
        }

        System.out.println("---after allocate ----");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());

j =0
j =1
---after allocate ----
position =2
limit =5
capacity =20
j =2
j =3
j =4
// position 超过limit的值会报错
Exception in thread "main" java.nio.BufferUnderflowException
	at java.nio.Buffer.nextGetIndex(Buffer.java:500)
	at java.nio.HeapIntBuffer.get(HeapIntBuffer.java:135)
	at UseBuffer.main(UseBuffer.java:33)
  • 如果想从读模式下,切换为写模式,必须调用buffer.clear() 和 buffer.compact(),清空或压缩缓冲区

rewind() 倒带

intBuffer.rewind();

        System.out.println("---after allocate ---- read two");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
        for(int i=0; i<3; i++) {
            int j = intBuffer.get();
            System.out.println("j =" +j);
        }
        
---after allocate ---- read two
position =0
limit =5
capacity =20
  • position 重置为0
  • limit 属性保持不变,数据量还是保持一致
  • mark 被清理

Channel(通道) 类

  • FIleChannel 文件通道,用户文件的读写
    • 慢方法
fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                inChannel = fis.getChannel();
                outchannel = fos.getChannel();

                int length = -1;
                ByteBuffer buf = ByteBuffer.allocate(1024);
                //从输入通道读取到buf,buffer 默认是写模式
                while ((length = inChannel.read(buf)) != -1) {

                    //翻转buf,变成成读模式
                    buf.flip();

                    int outlength = 0;
                    //将buf写入到输出的通道
                    while ((outlength = outchannel.write(buf)) != 0) {
                        System.out.println("写入字节数:" + outlength);
                    }
                    //清除buf,变成写入模式
                    buf.clear();
            } 
         //强制刷新磁盘
         outchannel.force(true);
  • 快方法
fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                inChannel = fis.getChannel();
                outChannel = fos.getChannel();
                long size = inChannel.size();
                long pos = 0;
                long count = 0;
                while (pos < size) {
                    //每次复制最多1024个字节,没有就复制剩余的
                    count = size - pos > 1024 ? 1024 : size - pos;
                    //复制内存,偏移量pos + count长度
                    pos += outChannel.transferFrom(inChannel, pos, count);
                }

                //强制刷新磁盘
                outChannel.force(true);
  • SocketChannel 用于socket套接字TCP 连接的数据读写
FileChannel fileChannel = new FileInputStream(file).getChannel();

            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.socket().connect(
                    new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP
                            , NioDemoConfig.SOCKET_SERVER_PORT));
            socketChannel.configureBlocking(false);
            Logger.debug("Cliect 成功连接服务端");

            while (!socketChannel.finishConnect()) {
                //因为非阻塞,所以客户端需要不断的自旋、等待,或者做一些其他的事情
            }


            //发送文件名称
            ByteBuffer fileNameByteBuffer = charset.encode(destFile);
            socketChannel.write(fileNameByteBuffer);

            //发送文件长度
            ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
            buffer.putLong(file.length());

            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();


            //发送文件内容
            Logger.debug("开始传输文件");
            int length = 0;
            long progress = 0;
            while ((length = fileChannel.read(buffer)) > 0) {
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
                progress += length;
                Logger.debug("| " + (100 * progress / file.length()) + "% |");
            }

            if (length == -1) {
                IOUtil.closeQuietly(fileChannel);
                socketChannel.shutdownOutput();
                IOUtil.closeQuietly(socketChannel);
            }
            Logger.debug("======== 文件传输成功 ========");
  • ServerSocketChannel 允许我们监听TCP 连接请求,为每一个监听到请求,创建一个SocketChannel 套接字通道
  • DatagreamChannel 数据报通道,用于UDP 协议的数据读写
DatagramChannel dChannel = DatagramChannel.open();
       // 设置为非阻塞的
       dChannel.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
        Scanner scanner = new Scanner(System.in);
        Print.tcfo("UDP 客户端启动成功!");
        Print.tcfo("请输入发送内容:");
        while (scanner.hasNext()) {
            String next = scanner.next();
            buffer.put((Dateutil.getNow() + " >>" + next).getBytes());
            buffer.flip();
            // 操作三:通过DatagramChannel数据报通道发送数据
            dChannel.send(buffer,
                    new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP
                            , NioDemoConfig.SOCKET_SERVER_PORT));
            buffer.clear();
        }
        //操作四:关闭DatagramChannel数据报通道
        dChannel.close();

NIO Selector

选择器的使用流程:

  • 获取选择器实例
// 1、获取Selector选择器
        Selector selector = Selector.open();
  • 将通道注册到选择器实例
// 2、获取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 3.设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 4、绑定连接
        serverSocketChannel.bind(new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_PORT));
        Logger.info("服务器启动成功");

        // 5、将通道注册到选择器上,并注册的IO事件为:“接收新连接”
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  • 选出感兴趣的IO 就绪事件
// 6、轮询感兴趣的I/O就绪事件(选择键集合)
        while (selector.select() > 0) {
            // 7、获取选择键集合
            Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
            while (selectedKeys.hasNext()) {
                // 8、获取单个的选择键,并处理
                SelectionKey selectedKey = selectedKeys.next();

                // 9、判断key是具体的什么事件
                if (selectedKey.isAcceptable()) {
                    // 10、若选择键的IO事件是“连接就绪”事件,就获取客户端连接
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 11、切换为非阻塞模式
                    socketChannel.configureBlocking(false);
                    // 12、将该通道注册到selector选择器上
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectedKey.isReadable()) {
                    // 13、若选择键的IO事件是“可读”事件,读取数据
                    SocketChannel socketChannel = (SocketChannel) selectedKey.channel();

                    // 14、读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int length = 0;
                    while ((length = socketChannel.read(byteBuffer)) >0) {
                        byteBuffer.flip();
                        Logger.info(new String(byteBuffer.array(), 0, length));
                        byteBuffer.clear();
                    }
                    socketChannel.close();
                }
                // 15、移除选择键
                selectedKeys.remove();
            }
        }

        // 7、关闭连接
        serverSocketChannel.close();

0条评论
0 / 1000
一个正经的博主
4文章数
0粉丝数
一个正经的博主
4 文章 | 0 粉丝
一个正经的博主
4文章数
0粉丝数
一个正经的博主
4 文章 | 0 粉丝
原创

Java NIO 通信基础

2024-08-29 09:42:14
4
0

Java NIO 通信基础

简介

  • JAVA NIO 三个核心组件:

    • channel
    • buffer
    • selector
  • NIO 和 OIO 的区别:

    • OIO 面向流,NIO 面向缓冲区,面向缓冲区后,读取和写入,只需要从通道中读取数据到缓冲区中,可以任意读取buffer中任意位置的数据。
    • OIO的操作是阻塞的,而NIO 的操作是非阻塞的。
    • NIO 有选择器的概念
  • Channel

    • 一个通道类似于OIO 中Input Stream 和 OutputStream 的结合体,既可以从通道中读取,也可以向通道中写入。
  • Selector 选择器

    • 与OIO 相比,使用选择器的最大的优势:系统开销小,系统不必为每个网络连接创建进程/线程,从而大大减小了系统的开销。
  • Buffer

    • 通道的读取就是将数据从通道中读取到缓冲区中;通道的写入,就是讲数据从缓冲区写入到同道中。

Buffer(缓冲)类

buffer 四个重要属性说明

属性 说明
capacity 容量,即可以容纳的最大数据量;在缓冲区创建并且不能改变
limit 上限,缓冲区中当前的数量量
position 位置,缓冲区中下一个要被读取或写的索引
mark 暂存属性,暂时报存position 的值,方便后面重复使用position 的值

allocate() 创建缓冲区

public static void main(String[] args) {
        intBuffer = IntBuffer.allocate(20);
        System.out.println("---after allocate ----");
        System.out.println("position =" + intBuffer.position());
        // 写模型下,limit 表示可以写入的数据上限
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
    }
    
 ---after allocate ----
position =0
limit =20
capacity =20

put() 写入到缓冲区

for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        System.out.println("---after allocate ----");
        System.out.println("position =" + intBuffer.position());
        // 写模型下,limit 表示可以写入的数据上限
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());


---after allocate ----
position =5
limit =20
capacity =20
  • 索引值变成了5,即第六个位置

flip() 翻转

intBuffer = IntBuffer.allocate(20);

        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        // 切换到读模式
        //    public final Buffer flip() {
        //        limit = position;
        //        position = 0;
        //        mark = -1;
        //        return this;
        //    }
        intBuffer.flip();
        System.out.println("---after allocate ----");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
        
---after allocate ----
position =0
limit =5
capacity =20

get() 从缓冲区读取

intBuffer = IntBuffer.allocate(20);

        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        // 切换到读模式
        intBuffer.flip();

        for(int i=0; i<2; i++) {
            int j = intBuffer.get();
            System.out.println("j =" +j);
        }
        System.out.println("---after allocate ----");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
        for(int i=0; i<4; i++) {
            int j = intBuffer.get();
            System.out.println("j =" +j);
        }

        System.out.println("---after allocate ----");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());

j =0
j =1
---after allocate ----
position =2
limit =5
capacity =20
j =2
j =3
j =4
// position 超过limit的值会报错
Exception in thread "main" java.nio.BufferUnderflowException
	at java.nio.Buffer.nextGetIndex(Buffer.java:500)
	at java.nio.HeapIntBuffer.get(HeapIntBuffer.java:135)
	at UseBuffer.main(UseBuffer.java:33)
  • 如果想从读模式下,切换为写模式,必须调用buffer.clear() 和 buffer.compact(),清空或压缩缓冲区

rewind() 倒带

intBuffer.rewind();

        System.out.println("---after allocate ---- read two");
        // 读的位置变成了0
        System.out.println("position =" + intBuffer.position());
        // 读模型下,limit 可读上限值
        System.out.println("limit =" + intBuffer.limit());
        System.out.println("capacity =" + intBuffer.capacity());
        for(int i=0; i<3; i++) {
            int j = intBuffer.get();
            System.out.println("j =" +j);
        }
        
---after allocate ---- read two
position =0
limit =5
capacity =20
  • position 重置为0
  • limit 属性保持不变,数据量还是保持一致
  • mark 被清理

Channel(通道) 类

  • FIleChannel 文件通道,用户文件的读写
    • 慢方法
fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                inChannel = fis.getChannel();
                outchannel = fos.getChannel();

                int length = -1;
                ByteBuffer buf = ByteBuffer.allocate(1024);
                //从输入通道读取到buf,buffer 默认是写模式
                while ((length = inChannel.read(buf)) != -1) {

                    //翻转buf,变成成读模式
                    buf.flip();

                    int outlength = 0;
                    //将buf写入到输出的通道
                    while ((outlength = outchannel.write(buf)) != 0) {
                        System.out.println("写入字节数:" + outlength);
                    }
                    //清除buf,变成写入模式
                    buf.clear();
            } 
         //强制刷新磁盘
         outchannel.force(true);
  • 快方法
fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                inChannel = fis.getChannel();
                outChannel = fos.getChannel();
                long size = inChannel.size();
                long pos = 0;
                long count = 0;
                while (pos < size) {
                    //每次复制最多1024个字节,没有就复制剩余的
                    count = size - pos > 1024 ? 1024 : size - pos;
                    //复制内存,偏移量pos + count长度
                    pos += outChannel.transferFrom(inChannel, pos, count);
                }

                //强制刷新磁盘
                outChannel.force(true);
  • SocketChannel 用于socket套接字TCP 连接的数据读写
FileChannel fileChannel = new FileInputStream(file).getChannel();

            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.socket().connect(
                    new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP
                            , NioDemoConfig.SOCKET_SERVER_PORT));
            socketChannel.configureBlocking(false);
            Logger.debug("Cliect 成功连接服务端");

            while (!socketChannel.finishConnect()) {
                //因为非阻塞,所以客户端需要不断的自旋、等待,或者做一些其他的事情
            }


            //发送文件名称
            ByteBuffer fileNameByteBuffer = charset.encode(destFile);
            socketChannel.write(fileNameByteBuffer);

            //发送文件长度
            ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
            buffer.putLong(file.length());

            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();


            //发送文件内容
            Logger.debug("开始传输文件");
            int length = 0;
            long progress = 0;
            while ((length = fileChannel.read(buffer)) > 0) {
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
                progress += length;
                Logger.debug("| " + (100 * progress / file.length()) + "% |");
            }

            if (length == -1) {
                IOUtil.closeQuietly(fileChannel);
                socketChannel.shutdownOutput();
                IOUtil.closeQuietly(socketChannel);
            }
            Logger.debug("======== 文件传输成功 ========");
  • ServerSocketChannel 允许我们监听TCP 连接请求,为每一个监听到请求,创建一个SocketChannel 套接字通道
  • DatagreamChannel 数据报通道,用于UDP 协议的数据读写
DatagramChannel dChannel = DatagramChannel.open();
       // 设置为非阻塞的
       dChannel.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
        Scanner scanner = new Scanner(System.in);
        Print.tcfo("UDP 客户端启动成功!");
        Print.tcfo("请输入发送内容:");
        while (scanner.hasNext()) {
            String next = scanner.next();
            buffer.put((Dateutil.getNow() + " >>" + next).getBytes());
            buffer.flip();
            // 操作三:通过DatagramChannel数据报通道发送数据
            dChannel.send(buffer,
                    new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP
                            , NioDemoConfig.SOCKET_SERVER_PORT));
            buffer.clear();
        }
        //操作四:关闭DatagramChannel数据报通道
        dChannel.close();

NIO Selector

选择器的使用流程:

  • 获取选择器实例
// 1、获取Selector选择器
        Selector selector = Selector.open();
  • 将通道注册到选择器实例
// 2、获取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 3.设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 4、绑定连接
        serverSocketChannel.bind(new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_PORT));
        Logger.info("服务器启动成功");

        // 5、将通道注册到选择器上,并注册的IO事件为:“接收新连接”
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  • 选出感兴趣的IO 就绪事件
// 6、轮询感兴趣的I/O就绪事件(选择键集合)
        while (selector.select() > 0) {
            // 7、获取选择键集合
            Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
            while (selectedKeys.hasNext()) {
                // 8、获取单个的选择键,并处理
                SelectionKey selectedKey = selectedKeys.next();

                // 9、判断key是具体的什么事件
                if (selectedKey.isAcceptable()) {
                    // 10、若选择键的IO事件是“连接就绪”事件,就获取客户端连接
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 11、切换为非阻塞模式
                    socketChannel.configureBlocking(false);
                    // 12、将该通道注册到selector选择器上
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectedKey.isReadable()) {
                    // 13、若选择键的IO事件是“可读”事件,读取数据
                    SocketChannel socketChannel = (SocketChannel) selectedKey.channel();

                    // 14、读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int length = 0;
                    while ((length = socketChannel.read(byteBuffer)) >0) {
                        byteBuffer.flip();
                        Logger.info(new String(byteBuffer.array(), 0, length));
                        byteBuffer.clear();
                    }
                    socketChannel.close();
                }
                // 15、移除选择键
                selectedKeys.remove();
            }
        }

        // 7、关闭连接
        serverSocketChannel.close();

文章来自个人专栏
java 知识整理
2 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0