侧边栏壁纸
  • 累计撰写 27 篇文章
  • 累计创建 9 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

netty-NIO基础篇

仓鼠
2024-03-04 / 0 评论 / 0 点赞 / 33 阅读 / 6491 字 / 正在检测是否收录...

NIO 基础

NIO (New lO)也有人称之为java non-blocking lO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。

三大组件

1.1 Channel (通道)

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。相比较直接对数组的操作,Buffer APl更加容易操作和管理。

常见channel

FileChannel
DatagramChannel
socketChannel
ServerSocketChannel

1.2 Buffer (内存缓冲区)

Java NIO的通道类似流,但又有些不同:既可以从通道中读取数据,又可以写数据到通道。但流的(input或output)读写通常是单向的。通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步地读写。

graph LR
channel --> buffer
buffer --> channel

常见Buffer

ByteBuffer
MappedByteBuffer
DirectByteBuffer
HeapByteBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer

1.3 Selector (选择器)

Selector是一个ava NIO组件,可以能够检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接,提高效率.

多线程版设计

graph TD
subgraph 多线程版
t1(thread) --> s1(socket1)
t2(thread) --> s2(socket2)
t3(thread) --> s3(socket3)
end

⚠️ 多线程版缺点

  • 内存占用高
  • 线程上下文切换成本高
  • 只适合连接数少的场景

线程池版设计

graph TD;
subgraph 线程池版
t4(thread) --> s4(socket1);
t5(thread) --> s5(socket2);
t4(thread) -.-> s6(socket3);
t5(thread) -.-> s7(socket4);
end;

⚠️ 线程池版缺点

  • 阻塞模式下,线程仅能处理一个 socket 连接
  • 仅适合短连接场景

selector 版设计

selector 的作用就是配合一个线程来管理多个 channel,获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,不会让线程吊死在一个 channel 上。适合连接数特别多,但流量低的场景(low traffic)

graph TD
subgraph selector 版
thread --> selector
selector --> c1(channel)
selector --> c2(channel)
selector --> c3(channel)
end

每个channel都会对应一个 Buffer一个线程对应Selector ,一个Selector对应多个channel(连接)程序切换到哪个channel是由事件决定的Selector 会根据不同的事件,在各个通道上切换Buffer 就是一个内存块,底层是一个数组数据的读取写入是通过 Buffer完成的,BlO中要么是输入流,或者是输出流,不能双向,但是NIO的Buffer是可以读也可以写。

Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到lO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。简而言之,Channel负责传输,Buffer负责存取数据

2. ByteBuffer

使用 FileChannel 来读取文件内容

@Slf4j
public class ChannelDemo1 {
    public static void main(String[] args) {
    	String fileUri = "C:/1.txt";
        try (RandomAccessFile file = new RandomAccessFile(fileUri, "rw")) {
            FileChannel channel = file.getChannel();
            //ByteBuffer buffer = ByteBuffer.allocate(10); // 堆内存读写
            ByteBuffer buffer = ByteBuffer.allocateDirect(10); //直接内存读写, 少一次用户态的拷贝,不受GC 影响。 netty底层采用此方法
            do {
                int len = channel.read(buffer);
                if (len == -1) {
                    break;
                }
                // 读模式
                buffer.flip();
                while(buffer.hasRemaining()) {
                    system.out.print(buffer.get())
                }
                // 写模式
                buffer.clear();
            } while (true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

new RandomAccessFile(fileUri, "rw") rw代表读写权限
FileInputStream 获取的 channel 只能读 channel.read(buffer)
FileOutputStream 获取的 channel 只能写 channel.write(buffer)
ByteBuffer.allocate(10); // 堆内存读写 JVM处理 受GC的影响,有 JVM 调度管理
ByteBuffer.allocateDirect(10); //直接操作系统函数内存读写, 少一次用户态的拷贝,不受GC 影响。 netty底层采用此方法。 ❗使用切记回收,以防止内存泄漏

上文中读写模式解析

初始结构

图片-1670897537305-xmgk.png

写入模式 abcd 字符串

图片-1670897581514.png

此时此刻,position 已经到下标第4。如果直接读取,则数据想走后一直为 NULL。需要切换读写模式,将position重新切换到下标为0

flip 切换读模式 = 切换 position 和 limit下标

图片-1670897790891.png

compact 切换到写模式

图片-1670898038870.png

clear 动作,恢复到初始结构

图片-1670897537305.png

get() 读取当前position值,position 下标 后移
get(i) 读取当前position值,position 下标不变
flip 读取当前position值,position 下标 变为0 limit改为position。
rewind 读取当前position值,position 下标 变为0。
mark 做一个标记,记录 position 位置(rewind的增强)
reset 是将 position 重置到 mark 的位置(rewind的增强)

粘包和半包

粘包问题发生在 TCP/IP 协议中,因为 TCP 是面向连接的传输协议,它是以“流”的形式传输数据的,而“流”数据是没有明确的开始和结尾边界的,所以就会出现粘包问题。

所谓的粘包问题是指数据在传输时,在一条消息中读取到了另一条消息的部分数据,这种现象就叫做粘包。比如发送了两条消息,分别为“ABC”和“DEF”,那么正常情况下接收端也应该收到两条消息“ABC”和“DEF”,但接收端却收到的是“ABCD”,像这种情况就叫做粘包,如下图所示:

图片-1670923072919.png
半包问题是指接收端只收到了部分数据,而非完整的数据的情况就叫做半包。比如发送了一条消息是“ABC”,而接收端却收到的是“AB”和“C”两条信息,这种情况就叫做半包,如下图所示:

图片-1670923084477.png
半包问题可以用:compact

0

评论区