nio初学记录

158 阅读3分钟

谢谢3y同学的例子(juejin.cn/post/684490…)


  • buffer
  1. 两核心方法 get() put() ,四核心属性:0 <= mark <= position <= limit <= capacity
  2. 待续...
  • channel
    • FileChannel:文件通道,用于文件的读和写
    • DatagramChannel:用于 UDP 连接的接收和发送
    • SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
    • ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求
  1. 主要配合buffer使用。
  2. 待续...
  • Selector
  1. 选择器或者叫多路复用器主要是用来搭配Socket相关的网络IO使用。
  2. 待续...

需要注意的小点

  1. 非直接缓存与直接缓存的区别及使用场景
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//非直接
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);//直接
  1. flip()方法调用的作用(读模式),buffer四大属性的变化

3. 非阻塞模式下的网络传输,主要看大文件,我的图片6M

  • 客户端
public class NoBlockClient {

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

        // 1. 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));

        // 1.1切换成非阻塞模式
        socketChannel.configureBlocking(false);

        // 2. 发送一张图片给服务端
        FileChannel fileChannel = FileChannel.open(Paths.get("C:\\nio\\a.jpg"), StandardOpenOption.READ);

        // 3.建立buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024*512);
        // 4.读取本地文件(图片),发送到服务器
        while (fileChannel.read(buffer) != -1) {
            // 在读之前都要切换成读模式
            buffer.flip();
            System.out.println(buffer.limit());
            Thread.sleep(500);
            socketChannel.write(buffer);
            // 读完切换成写模式,能让管道继续读取文件的数据
            buffer.clear();
        }

        // 5. 关闭流
        fileChannel.close();
        socketChannel.close();
    }
}
  • 服务端
public class NoBlockServer {

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

       // 1.获取通道
       ServerSocketChannel server = ServerSocketChannel.open();

       // 2.切换成非阻塞模式
       server.configureBlocking(false);

       // 3. 绑定连接
       server.bind(new InetSocketAddress(9999));

       // 4. 获取选择器
       Selector selector = Selector.open();

       // 4.1将通道注册到选择器上,指定接收“监听通道”事件
       server.register(selector, SelectionKey.OP_ACCEPT);

       // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
       while (selector.select() > 0) {
           // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
           Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

           // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
           while (iterator.hasNext()) {

               SelectionKey selectionKey = iterator.next();

               if (selectionKey.isAcceptable()) {

                   // 8. 获取客户端的链接
                   SocketChannel client = server.accept();

                   // 8.1 切换成非阻塞状态
                   client.configureBlocking(false);

                   // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
                   client.register(selector, SelectionKey.OP_READ);

               } else if (selectionKey.isReadable()) { // 读事件就绪

                   // 9. 获取当前选择器读就绪状态的通道
                   SocketChannel client = (SocketChannel) selectionKey.channel();

                   // 9.1读取数据
                   ByteBuffer buffer = ByteBuffer.allocate(1024*1024);

                   // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
                   FileChannel outChannel = FileChannel.open(Paths.get("C:\\nio\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

                   while (client.read(buffer) > 0) {
                       // 在读之前都要切换成读模式
                       buffer.flip();
System.out.println("server:"+buffer.limit());
                       outChannel.write(buffer);

                       // 读完切换成写模式,能让管道继续读取文件的数据
                       buffer.clear();
                   }


                   }

               // 接收事件就绪
               // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
               iterator.remove();
           }
       }

   }
}

运行结果如下,会发现图片大小变小了。

观察打印的客户端的buffer传输大小,System.out.println(buffer.limit());
服务端接收到的buffer大小如下:
最大的TCP数据发送窗口和接收窗口的字节恰好为131071,因此我们大概可以猜测到在传输中可能发生了粘包或拆包。客户端的缓存区大小为1024*512,也就是512kb,图片为6.5M,写入数据大于缓存区大小,因此会发生拆包。512*1024/131071恰好余数是4,可以知道为什么打印出上诉内容。服务端读数据的方法如下:

                    while (client.read(buffer) > 0) {
                        // 在读之前都要切换成读模式
                        buffer.flip();
                        System.out.println("server:"+buffer.limit());
                        outChannel.write(buffer);

                        // 读完切换成写模式,能让管道继续读取文件的数据
                        buffer.clear();
                    }

由于同时有大量的tcp传输,采用的也是非阻塞模式,因此我们最后写到文件中的内容可能会被覆盖掉,也就导致了最后的图片大小只有512kb。解决办法是采用append方式追加:

FileChannel outChannel = FileChannel.open(Paths.get("C:\\nio\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE,StandardOpenOption.APPEND);

然后通过以下方法读取:

  while (client.read(buffer) > 0) {
                        // 在读之前都要切换成读模式
                        buffer.flip();
                        while (buffer.hasRemaining())//当前位置和极限位置之间是否有元素
                        {
                            outChannel.write(buffer);
                        }
                        buffer.clear();
                    }

未完待续...