跟着视频学习netty;这个资源地址 【https://www.bilibili.com/video/BV11X4y1K7VZ?p=10】 这个可学学网络基础还行,后边的内容丝毫不连贯,差评

跟着学netty的,看看尚硅谷的课程吧 【https://www.bilibili.com/video/BV1DJ411m7NR?p=2】,这个课程的笔记有人上传了;
笔记:【https://blog.csdn.net/youth_lql/category_10959696.html 目录
https://blog.csdn.net/Youth_lql/article/details/115524052 第1弹 nio等基础
https://blog.csdn.net/Youth_lql/article/details/115734142 第2弹 netty服务器客户端示例
https://blog.csdn.net/Youth_lql/article/details/116015820 第3弹 编解码器+协议+粘包等】


netty: 异步!基于事件驱动! 的网路应用框架!
有一点需要注意 nio 对应的是网络编程,

1 nio

1 三大核心


NIO 有三大核心部分:Channel(通道,对应BIO的socket)、Buffer(缓冲区)、Selector(选择器)
image.png
这是一个NIO的简易图
image.png
这是nio三大组件的结构图

0 三大组件的关系

  1. 每个channel都对应一个buffer
  2. 一个selector对应一个线程; 一个线程可以对应多个channel(channel就理解成“链接”、socket的概念就行)
  3. 程序切换到哪个channel是由事件(event)决定的; event是一个很重要的概念;Sector会根据不同的事件,在各个通道上来回的切换
  4. Buffer就是一个内存块,底层是一个数组;
  5. 数据的读取、写入时通过buffer;这个和BIO是不同的,BIO中要么是输入流要么是输出流,不能双向;但是NIO中的buffer可以读也可以写,需要flip方法切换channel

    1 Channel 通道

    通道的状态会发生变化,一但有变化,就会通知selector;然后selector去处理
    可以粗俗的 将channel理解为流或者链接;
    我们常用的channel 有 FileChannel (用于文件的读写) DatagramChannel(用于UDP数据的读写) ServerSocketChannelSocketChannel (用于 TCP的数据的读写)

    1 FileChannel 🌟🌟🌟🌟🌟

    主要用于对本地文件进行IO操作
  • public int read(ByteBuffer dst): 从通道读取数据到缓冲区中 🌟🌟🌟🌟🌟🌟🌟
  • public int write(ByteBuffer src): 把缓冲区的数据写到通道中🌟🌟🌟🌟🌟🌟
  • public long transferFrom(ReadableByteChannel src,long position,long count): 从目标通道中复制数据到当前通道中
  • public long transferTo(long position,long count,WritableByteChannel target): 把数据从当前通道赋值给目标通道

2 Buffer 缓冲区

buffer相关的一些基本概念和操作、方法什么的之前写过一点;
1 nio基础,浅识ByteBuffer —> IoBuffer —> mina
当时是为了用mina网络应用框架才学习了一下相关的操作api;这里不在单独写了,到时候看这个文章吧;

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图:【后面举例说明】
Buffer 是一个顶级抽象类,他的子类有 IntBuffer DoubleBuffer LongBuffer等,每个基本类型都有一个对应的buffer类,但是最长用的还是ByteBuffer(字节数据buffer),我们网络上传输的数据都是二进制数据,所以ByteBuffer很常用;

3 Selector 选择器

Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
image.png
对于上图的解读:

  1. 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel。
  2. Selector 进行监听 select 方法,返回有事件发生的通道的个数。
  3. 将 socketChannel 注册到 Selector 上,register(Selector sel, int ops),一个 Selector 上可以注册多个 SocketChannel。
  4. 注册后返回一个 SelectionKey,会和该 Selector 关联(集合)。
  5. 进一步得到各个 SelectionKey(有事件发生)。
  6. 在通过 SelectionKey 反向获取 SocketChannel,方法 channel()。
  7. 可以通过得到的 channel,完成业务处理。

解读过程变成代码:

  1. package com.binc.testspring.study.nio;
  2. import org.junit.Test;
  3. import java.io.IOException;
  4. import java.net.InetSocketAddress;
  5. import java.nio.ByteBuffer;
  6. import java.nio.channels.SelectionKey;
  7. import java.nio.channels.Selector;
  8. import java.nio.channels.ServerSocketChannel;
  9. import java.nio.channels.SocketChannel;
  10. import java.util.Iterator;
  11. import java.util.Set;
  12. /**
  13. * FileName: Test2
  14. * Autho: binC
  15. * Date: 2022/4/14 17:49
  16. */
  17. public class Test2 {
  18. /**
  19. * 服务端
  20. *
  21. * @param args
  22. * @throws Exception
  23. */
  24. public static void main(String[] args) throws Exception {
  25. //创建ServerSocketChannel -> ServerSocket
  26. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  27. //serverSocketChannel绑定一个端口6666, 在服务器端监听
  28. serverSocketChannel.socket().bind(new InetSocketAddress(6666));
  29. //设置为非阻塞
  30. serverSocketChannel.configureBlocking(false);
  31. //得到一个Selecor对象
  32. Selector selector = Selector.open();
  33. /*
  34. * 注意:我们从所有的流程图中看到的都是将socketChannel注册到selector中去;
  35. * 实际上我们隐藏的一个操作时,在创建了服务端的serverSocketChannel 也需要注册到selector中去
  36. * */
  37. //把 serverSocketChannel 注册到 selector 关心 事件为 OP_ACCEPT pos_1
  38. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  39. System.out.println("注册后的selectionkey 数量=" + selector.keys().size()); // 1
  40. //循环等待客户端连接
  41. while (true) {
  42. //这里我们等待1秒,如果没有事件(我们现在这个程序关注的事件是 serverSocketChannel的连接(OP_ACCEPT)事件)发生, 直接返回
  43. if (selector.select(1000) == 0) { //没有事件发生
  44. System.out.println("服务器等待了1秒,无连接");
  45. continue;
  46. }
  47. //如果返回的>0, 就获取到相关的 selectionKey集合
  48. //1.如果返回的>0, 表示已经获取到关注的事件
  49. //2. selector.selectedKeys() 返回关注事件的集合
  50. // 通过 selectionKeys 反向获取通道
  51. Set<SelectionKey> selectionKeys = selector.selectedKeys();
  52. System.out.println("selectionKeys 数量 = " + selectionKeys.size());
  53. //遍历 Set<SelectionKey>, 使用迭代器遍历
  54. Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
  55. while (keyIterator.hasNext()) {
  56. //获取到SelectionKey
  57. SelectionKey key = keyIterator.next();
  58. //根据key 对应的通道发生的事件做相应处理
  59. if (key.isAcceptable()) { //如果是 OP_ACCEPT, 有新的客户端连接
  60. //该该客户端生成一个 SocketChannel
  61. SocketChannel socketChannel = serverSocketChannel.accept();
  62. System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());
  63. //将 SocketChannel 设置为非阻塞
  64. socketChannel.configureBlocking(false);
  65. //将socketChannel 注册到selector, 关注事件为 OP_READ, 同时给socketChannel
  66. //关联一个Buffer
  67. socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
  68. System.out.println("客户端连接后 ,注册的selectionkey 数量=" + selector.keys().size()); //2,3,4..
  69. }
  70. if (key.isReadable()) { //发生 OP_READ
  71. //通过key 反向获取到对应channel
  72. SocketChannel channel = (SocketChannel) key.channel();
  73. //获取到该channel关联的buffer
  74. ByteBuffer buffer = (ByteBuffer) key.attachment(); // 得到与之关联的共享数据 即buffer
  75. channel.read(buffer);
  76. System.out.println("form 客户端 " + new String(buffer.array()));
  77. }
  78. //手动从集合中移动当前的selectionKey, 防止重复操作
  79. keyIterator.remove();
  80. }
  81. }
  82. }
  83. /**
  84. * 客户端
  85. * @throws IOException
  86. */
  87. @Test
  88. public void test() throws IOException {
  89. //得到一个网络通道
  90. SocketChannel socketChannel = SocketChannel.open();
  91. //设置非阻塞
  92. socketChannel.configureBlocking(false);
  93. //提供服务器端的ip 和 端口
  94. InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
  95. //连接服务器
  96. if (!socketChannel.connect(inetSocketAddress)) {
  97. // 客户端这是成非阻塞了, 建立链接是需要时间的所以很容易走到这里; 我们尝试不启动服务端,直接启动给客户端,程序就一直在这里执行别的逻辑;
  98. while (!socketChannel.finishConnect()) {
  99. System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作..");
  100. }
  101. }
  102. //...如果连接成功,就发送数据
  103. String str = "hello, 尚硅谷~";
  104. //Wraps a byte array into a buffer
  105. ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
  106. //发送数据,将 buffer 数据写入 channel
  107. socketChannel.write(buffer);
  108. System.in.read();
  109. }
  110. }

selector api

open() 获取一个selector select() 阻塞 直到获取到有事件发生 select(int time) 阻塞time毫秒,然后返回 selectNow() 非阻塞,立马返回 keys() 获取到所有注册到selector的selectionKey的集合 selectorKeys() 手动获取一下有事件发生的key;

selectorKey api

key.selector() 得到selectorKey与之关联的selector对象 key.channel() 得到与之关联的channel key.attachment() 得到与之关联的共享数据 即: channel key.interestOps(int ops) 设置或者改变监听事件

serverSocketChannel api

ServerSocketChannel 在服务器端监听新的客户端socket连接

open() 得到一个ServerSocketChannel 通道 bind() 设置服务器端口 configureBlocking(Boolean b) 设置阻塞或者非阻塞模式 nio编程中通常都是设置成false;否则就不用nio了 accept(*) 接受一个链接,返回代表这个链接的通道对象 即 socketChannel register(selector,int) 将当前channel注册到selector 并这是监听事件

socketChannel api

SocketChannel ,网络IO通道,具体负责进行读写操作; nio把缓冲区的数据写入到通道,或者把通道的数据读到缓冲区

  • public int read(ByteBuffer dst): 从通道读取数据到缓冲区中 🌟🌟🌟🌟🌟🌟🌟
  • public int write(ByteBuffer src): 把缓冲区的数据写到通道中🌟🌟🌟🌟🌟🌟

open 得到一个socketChannel 通道 configureBlocking(Boolean b) 设置阻塞或者非阻塞模式 nio编程中通常都是设置成false;否则就不用nio了 connect(SocketAddres addr) 链接服务器 finnishConnect 如果上边方法链接失败,接下来通过该方法完成链接操作; write(buffer) 往通道写数据 read(buffer) 从通道里数据 register(selector,int) 将当前channel注册到selector 并这是监听事件 close 关闭通道


2 netty

这部分的资料:https://blog.csdn.net/Youth_lql/article/details/115734142
nio已经能够完成对同步非阻塞编程的开发了,为什么还要有netty呢,其实就是原生代码nio 编程很复杂,并且粘包 半包等问题得自己解决,

1原生 NIO 存在的问题

  1. NIO 的类库和 API 繁杂,使用麻烦:需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
  2. 需要具备其他的额外技能:要熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的 NIO 程序。
  3. 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。
  4. JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU100%。直到 JDK1.7 版本该问题仍旧存在,没有被根本解决。

因为有这些问题,所以引出了对nio的封装的框架;netty就是用的nio做的进一步封装;这个封装过程还是涉及到了reactor模型;

2 reactor模型

  1. 单reactor单线程模型
  2. 单reactor 多线程模型
  3. 主从reactor多线程模型

netty就是一句的 主从reactor多线程模型; ngix也是用的这个模型;

3