Netty-Netty的底层实现:JavaNIO

Netty框架 - 前传

跟着韩顺平老师学完了Netty,深感这种底层框架使用起来必须得有很好的计算机网络的知识。

果然,转码的路上是不能避免学习基础课程的。

Netty简介

总结一句话, Netty是一个异步的,基于事件驱动的网络应用框架,用来开发高性能、高可靠性的网络IO程序。

Netty的应用场景也比较广泛,比如说:

  • RPC框架中往往会使用Netty来作为基础的通信组件。Dubbo,Hadoop···
  • 游戏行业会使用Netty来搭建服务器之间的通信。

I/O模型

如上文所说,Netty是一种网络IO程序。实际上,Netty的底层就是基于JAVA NIO实现的。

那么 NIO是什么呢?

Java中共支持了3种I/O模型:BIO,NIO,NIO2.0 ( 也称AIO )

  • BIO

    • 即 Blocking IO
    • 是传统的阻塞型IO。服务器会为每一个连接创建一个线程。当这个连接不做任何事情时,就会造成线程资源的浪费
  • NIO

    • 即 Non-Blocking IO
    • 同步非阻塞。服务器上每一个线程会处理多个请求,这种实现依赖于IO多路复用(Selector)。多路复用器使用轮询的方式处理I/O请求。
  • AIO

    • 即Asynchronized IO
    • 异步非阻塞。AIO引入了异步通道的概念,采用Proactor模式。有效的请求才启动线程。适用于连接数较多且连接时间较长的应用。

简单说说NIO

为什么要用NIO,BIO哪不行了?

BIO也就是Blocking-IO,指的是阻塞性IO。在连接数小的时候,BIO结合线程池是可以应付的过来的。但是,当请求数很大的时候,BIO就完全没有办法胜任了。因为在BIO中,每个请求都需要创建一个独立的线程,从而和客户端直接建立Socket连接,然后在这个线程上执行读写操作。当并发数大的时候,大量的线程都被创建用来建立连接,但也不一定马上进行读写操作,而是阻塞在那里,等待客户端发送请求。此时,线程资源就形成了极大的浪费。若线程池中所有线程都被使用,那么新的连接请求也无法完成,导致不可用。

NIO怎么就行了?

NIO是面向缓冲区,或面向块编程的。数据会读取到一个稍后处理的缓冲区中,需要的时候可以在缓冲区中前后移动,这样就提供了处理过程中的灵活性。

NIO是非阻塞的。具体表现就是,客户端与服务端建立的连接,需要从一个通道发送请求或读取数据,但只在通道中有数据或有请求的时候,才安排线程去执行任务。如果没有,线程也不会阻塞在那里,而是可以去做别的事情。那么如此,在NIO中,一个线程处理多个连接的任务就可以实现了,正所谓IO多路复用。当有10000个请求过来,使用NIO可能只需要100个线程就能同时处理,而BIO就一定得分配10000个线程才能同时处理。

比较NIO & BIO

  1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。
  2. BIO 是阻塞的,NIO 则是非阻塞的。
  3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
  4. Buffer和Channel之间的数据流向是双向的

NIO三大组件 - Channel | Selector | Buffer

Buffer

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer

  1. Buffer是一个抽象类,继承该类的有ByteBuffer,LongBuffer等。
  2. 在Buffer类里定义了4个重要的属性,mark,position,limit,capacity
    1. mark:标记
    2. position:下一个要被读或写的元素的索引位置,为下次读写做准备
    3. limit:表示缓冲区的当前终点,不能超过这个位置进行读写。limit可以修改。
    4. capacity:可以容纳的最大数据量,缓冲区创建时被设定,不能改变。

Channel

  1. NIO的通道类似于流,但有些区别如下:
    • 通道可以同时进行读写,而流只能读或者只能写
    • 通道可以实现异步读写数据
    • 通道可以从缓冲读数据,也可以写数据到缓冲:
  2. BIO 中的 Stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
  3. ChannelNIO 中是一个接口 public interface Channel extends Closeable{}
  4. 常用的 Channel 类有:**FileChannelDatagramChannelServerSocketChannelSocketChannel**。【ServerSocketChanne 类似 ServerSocketSocketChannel 类似 Socket
  5. FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannelSocketChannel 用于 TCP 的数据读写。

Selector

  1. JavaNIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。
  2. Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
  3. 只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
  4. 避免了多线程之间的上下文切换导致的开销
  1. NettyIO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。
  2. 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
  3. 线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
  4. 由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
  5. 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

NIO网络编程原理图

img
  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,完成业务处理。

实例 - NIO网络编程

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.kun98.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
public static void main(String[] args) throws IOException {

//创建一个ServerSocketChannel,用来创建ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();


//创建一个Selector对象
Selector selector = Selector.open();

//绑定端口,让服务器监听
serverSocketChannel.bind(new InetSocketAddress(6666));

serverSocketChannel.configureBlocking(false);

//注册serverSocketChannel到Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
if (selector.select(1000) == 0) {
System.out.println("服务器等待一秒,无连接");
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
//有客户端来连接
if (key.isAcceptable()) {
//生成SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();

System.out.println("客户端连接成功,socketChannel: " + socketChannel.hashCode());
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}

if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
channel.read(byteBuffer);
System.out.println("from client: " + new String(byteBuffer.array()).trim());
}
//最后要移除这个key,因为已经处理完了
keyIterator.remove();
}

}

}

}


Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.kun98.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

public class NIOClient {

public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false);

InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 6666);

if(!socketChannel.connect(socketAddress)){
while(!socketChannel.finishConnect()){
System.out.println("连接需要时间,客户端不阻塞");
}
}

String str = "hello,kun98!";
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8));

socketChannel.write(byteBuffer);
System.in.read();
}
}


Netty-Netty的底层实现:JavaNIO
http://kun98-liu.gihub.io/2022/08/18/Netty-Java-NIO/
Author
Liu Jiankun
Posted on
August 18, 2022
Licensed under