Netty基础入门——NIO【1】

Netty基础入门——NIO【1】

1 NIO

1.1 三大组件

1.1.1 Channel && Buffer
  1. Channle

channel类似于stream,是读写数据的双向通道,而stream要么是输入要么是输出

channel
buffer

常见channel:

* FileChannel
* DatagramChannel
* SocketChannel
* ServerSocketChannel
  1. Buffer

用来缓冲读写数据

常见Buffer:

* ByteBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
* CharBuffer
1.1.2 Selector

服务器设计演变过程:
①多线程版设计

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

②线程池版

线程池版
socket1
thread
socket2
thread
socket3
socket4
  • 阻塞模式下,线程仅能处理一个 socket 连接
  • 仅适合短连接场景
    ③selector版

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

selector 版
selector
thread
channel
channel
channel

调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件,这些事件发生,select 方法就会返回这些事件交给 thread 来处理

1.2 ByteBuffer

使用FileChannel来读取文件内容

@Slf4j
public class ChannelDemo1 {
    public static void main(String[] args) {
    	//twr写法
        try (RandomAccessFile file = new RandomAccessFile("data.txt", "rw")) {
            FileChannel channel = file.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(10);//缓冲区大小
            do{
                //channel读,向buffer写入
                int len = channel.read(buffer);;
                log.debug("读到字节数:{}", len);
                if(len == -1){
                    break;
                }
                //切换buffer读模式
                buffer.flip();
                while(buffer.hasRemaining()){
                    log.debug("{}", (char)buffer.get());
                }
                //切换buffer写模式
                buffer.clear();
            }while (true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
1.2.1 ByteBuffer使用步骤
  1. 向buffer写入数据,例如:channel.read(buffer)

文件获取channel - 通过channel向buffer写入

  1. 调用flip()切换至读模式
  2. 从buffer读取数据,例如:buffer.get()
  3. 调用clear()或compact()切换至写模式
  4. 重复步骤1-4
1.2.2 ByteBuffer结构
  • capacity
  • position
  • limit
  1. 初始状态
    在这里插入图片描述
  2. 写入4个字节后

写模式下,position 是写入位置,limit 等于容量

在这里插入图片描述

  1. flip动作发生,切换读写

flip 动作发生后,position 切换为读取位置,limit 切换为读取限制

在这里插入图片描述

  1. clear动作发生后
    在这里插入图片描述
  2. compact 方法,是把未读完的部分向前压缩,然后切换至写模式
    在这里插入图片描述
1.2.3 ByteBuffer常见方法
  1. allocate为ByteBuffer分配空间,其他buffer类也有该方法
Bytebuffer buf = ByteBuffer.allocate(16);
  1. 向buffer写数据
  • channel的read【从channel读取到然后向buffer写】
int readBytes = channel.read(buf);
  • buffer自己的put方法
buf.put((byte)127);
  1. 从buffer读取数据
  • 调用 channel 的 write 方法【从buf读,向channel写】
int writeBytes = channel.write(buf);
  • 调用 buffer 自己的 get 方法
byte b = buf.get();

get 方法会让 position 读指针向后走,如果想重复读取数据

* 可以调用 rewind 方法将 position 重新置为 0
* 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针
1.2.4 调试工具类
1.2.5 mark、字符与ByteBuffer互转、批量读写
  • mark 和 reset

mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置

注意

rewind 和 flip 都会清除 mark 位置

  • 字符与ByteBuffer互转

字符转ByteBuffer

ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("你好");
ByteBuffer buffer2 = Charset.forName("utf-8").encode("你好");

ByteBuffer转字符

ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("hello");
String s = StandardCharsets.UTF_8.decode(byteBuffer).toString();
//hello
System.out.println(s);
  • 批量操作byteBuffer

少搬运一次,速度更快

1. Scattering Reads(分散读取)
    public static void main(String[] args) {
        //test.txt文件内容:onetwothree
        try (FileChannel channel = new RandomAccessFile("test.txt", "rw").getChannel()) {
            //向ByteBuffer中批量写入
            ByteBuffer a = ByteBuffer.allocate(3);
            ByteBuffer b = ByteBuffer.allocate(3);
            ByteBuffer c = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{a,b,c});
            a.flip();
            b.flip();
            c.flip();
        } catch (IOException e) {
        }
    }


2.Gathering Writes(聚集写入)
   try (FileChannel channel = new RandomAccessFile("test.txt", "rw").getChannel()) {
         ByteBuffer buffer = ByteBuffer.allocate(5);
         ByteBuffer buffer2 = ByteBuffer.allocate(6);
         buffer.put("hello".getBytes(StandardCharsets.UTF_8));
         buffer2.put("你好".getBytes(StandardCharsets.UTF_8));
         //切换为读模式
         buffer.flip();
         buffer2.flip();
         channel.write(new ByteBuffer[]{buffer, buffer2});
     } catch (IOException e) {
     }

1.3 生产案例【粘包半包问题解决】

网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为

  • Hello,world\n
  • I’m zhangsan\n
  • How are you?\n

变成了下面的两个 byteBuffer (黏包,半包)

  • Hello,world\nI’m zhangsan\nHo
  • w are you?\n

现在要求你编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据

public static void main(String[] args) {
        /*原本数据:
         * Hello,world\n
         * I'm zhangsan\n
         * How are you?\n
         *
         * 特殊原因出现:粘包、半包问题
         * Hello,world\nI'm zhangsan\nHo
         * w are you?\n
         */
        ByteBuffer source = ByteBuffer.allocate(32);
        source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
        split(source);

        source.put("w are you?\nhaha!\n".getBytes());
        split(source);
    }

    //处理粘包、半包问题
    public static void split(ByteBuffer source){
        //切换为读模式
        source.flip();
        //原来的长度:Hello,world\nI'm zhangsan\nHo【粘包、半包后一条数据的长度】
        int oldLimit = source.limit();
        //limit:byteBuffer最大范围
        for(int i = 0; i < oldLimit; i++){
            if(source.get(i) == '\n'){
                //定义新ByteBuffer,内存利用率最大化【粘包、半包前真实数据长度】
                ByteBuffer tar = ByteBuffer.allocate(i + 1 - source.position());
                // 0 ~ limit【假如i为1,此时有两个数据要读,因此最大限制为2 -> i+1】
                source.limit(i + 1);
                tar.put(source);//从source读,向tar写
                ByteBufferUtil.debugAll(tar);
                //还原长度,继续向下读
                source.limit(oldLimit);
            }
        }
        //没有读完的数据,压缩
        source.compact();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值