NIO三大组件
Channel和Buffer
Channel是读写数据的双向通道,可以从Channel将数据读入Buffer,也可以将Buffer的数据写入Channel,而Java中的Stream,如InputStream、OutputStream要么输入,要么输出,Channel比Stream更加底层
常见的Channel
- FileChannel
- DatagramChannel,UDP式的数据传输通道
- SocketChannel,TCP式的,可用于服务端和客户端
- ServerSocketChannel,TCP式的,专用于服务端
常见的Buffer
- ByteBuffer,以字节为单位存储数据的Buffer,是抽象类,下面是实现类
- MappedByteBuffer
- DirectByteBuffer
- HeapByteBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
- CharBuffer
Selector
多线程设计服务器
需求:每个服务器可能同时需要处理来自多个客户端的请求,这就需要使用多线程
处理:每个客户端与服务端的连接开一个线程处理数据传输,如下图所示
存在的问题:
1.CPU能同时开启的线程数是有限的,如16核CPU最多开启16个线程,也就是能够同时处理的连接也就16个,如果有新的连接就会被阻塞
2.每个线程会占用一定的内存(记忆状态,名词变量等等),线程一多内存占用就会很高
3.线程上下文切换成本高,一旦线程被换下,它的状态是需要被记住的,以便下次处理该线程的时候继续上次的进度,因此这样会有许多线程的状态需要被记住
这种多线程设计只适合少连接的情况
线程池设计服务器
需求:多线程版本线程数量不受管束,容易超出内存
处理:线程池限制最高线程数,由于最高线程数被限制,那么一旦连接数过多,每个线程就要处理多条连接,但是同一时刻线程只能处理一条连接,其他连接会被阻塞,线程池的工作模式如下:
缺点:
1.不管正在被处理的连接干不干事,线程都会被占用,其他线程只能等待,这个模式被称为阻塞模式,也就是每个线程仅能处理一个socket连接
2.仅适合短链接,http请求下服务端返回响应,连接就可以被断开了,当下线程便可继续处理其他线程,这被称为短链接,非常适合线程池模式
总结:线程池模式下,线程利用率不高
Selector设计服务器
需求:如果连接不进行任何读写事件,就不占用线程,线程可以处理其他发出读写事件的连接
注意:在Selector中,Channel就相当于一次Socket连接
处理:Selector负责监听所有连接,一旦有连接发出读写事件,就通知Thread进行处理,如果有多个线程同时发出读写事件,就轮流处理;模式图如下:
适用多连接和流量低的场景,如果一次连接的流量过高,比如某个连接需要处理大量的数据,这样就要耗费大量的时间,在轮流处理模式下其他连接其实就相当与被阻塞了,甚至可能永远不会被处理到,比如在当前连接处理当中,有新增了需要处理的连接,这样等待处理的连接队伍就会越来越长;之所以适合多连接是因为Selector模式就是为了在多连接下提高线程的利用率
ByteBuffer的基本使用
读取本地文件,并在控制台打印内容
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
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class Demo {
public static void main(String[] args) {
// 1.文件输入流获取FileChannel 或 2.RandomAccessFile获取
try (FileChannel fileChannel = new FileInputStream("E:\\Netty\\NIO-demo1\\src\\main\\resources\\file1.txt").getChannel()) {
// 准备ByteBuffer缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
while (true) {
// Channel数据写入ByteBuffer
int count = fileChannel.read(byteBuffer);
if (count < 0) {
break;
}
// 开启读模式
byteBuffer.flip();
// 打印数据
while (byteBuffer.hasRemaining()) {
byte b = byteBuffer.get();
System.out.println((char) b);
}
// 切换写模式
byteBuffer.clear();
}
} catch (IOException e) {
}
}
}
ByteBuffer正确使用姿势
1.文件输入流获取Channel
2.准备ByteBuffer缓冲区,Channel数据写入缓冲区(ByteBuffer刚创建默认为写模式)
3.ByteBuffer开启读模式,byteBuffer.flip()
4.byteBuffer读出数据,如byteBuffer.get()
5.如果要继续写入byteBuffer,请开启写模式,byteBuffer.clear(),或byteBuffer.compact()