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. Channel 在 NIO 中是一个接口 public interface Channel extends Closeable{}
  4. 常用的 Channel 类有:**FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel**。【ServerSocketChanne 类似 ServerSocket、SocketChannel 类似 Socket】
  5. FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写。

Selector

  1. Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。
  2. Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
  3. 只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
  4. 避免了多线程之间的上下文切换导致的开销
  1. Netty 的 IO 线程 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