- NIO简单介
NIO 非阻塞IO模型,基于缓冲区(Buffer)读写数据,读写后的数据通过通道(Channel)进行传输,采用选择器(Selector)管理多个通道从而实现高并发。
核心组件:1. Buffer 为一个内存数组作为数据容器,代替传统的InputStream和OutputStream;2. Channel 双向数据传输;3. Selector 监听多通道区别于传统的单线程管理多连接 - 传统的IO模型
阻塞、非阻塞、多路复用、信号驱动、异步IO - 简单的NIO服务器接收客户端TCP请求
java
public void start(int port) throws IOException {
//创建一个选择器管理通道
Selector selector = Selector.open();
//创建通道并注册到选择器(可创建多个通道)
createChannel(selector, port);
while (true) {
//阻塞,等到有事件发生时继续执行
selector.select();
//处理事件
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
//新连接到达
if (key.isAcceptable()) {
ServerSocketChannel socketChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = socketChannel.accept();
channel.configureBlocking(false);
//注册新的读取事件
channel.register(selector, SelectionKey.OP_READ);
continue;
}
//可读取数据
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
//定义读取数据的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据到缓冲
int bytes = channel.read(buffer);
//客户端关闭连接
if (bytes == -1) {
channel.close();
continue;
}
//切换为读取模式
buffer.flip();
String message = new String(buffer.array(), 0, bytes);
System.out.println("接收到消息");
//回显消息给客户端
channel.write(ByteBuffer.wrap(message.getBytes()));
}
}
}
}
java
private void createChannel(Selector selector, int port) throws IOException {
//创建通道并绑定端口
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.bind(new InetSocketAddress(port));
//设置为非阻塞
socketChannel.configureBlocking(false);
//将通道注册到选择器
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
- Netty基于NIO的异步事件驱动的网络应用框架
特性:异步和事件驱动、高性能(高效的线程模型和缓冲区机制)、易使用和灵活可扩展
涉及到 channel 、callback(事件发生时的回调)、Future和Promise(异步计算)、EventLoop(事件循环)、Pipeline和ChannelHandler管道和处理器、bootstrap引导类
java
public static void start(int port) {
// 创建两个EventLoopGroup事件循环组,一个用于接收客户端连接,一个用于处理客户端数据
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
start(bossGroup, workerGroup, port);
} catch (Exception e) {
log.warn("Netty服务【{}】发生异常", port, e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
java
private static void start(EventLoopGroup bossGroup, EventLoopGroup workerGroup, int port) throws InterruptedException {
//创建服务器启动对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
//parentGroup:这个事件组用于接收客户端的连接请求。它会创建一个ServerChannel,用于监听并接收客户端的连接。一旦有连接请求到达,parentGroup就会将连接分配给childGroup中的一个EventLoop来处理。
//childGroup:这个事件组用于处理接收到的连接的数据。它会创建一个或多个Channel,用于与客户端进行通信。每个Channel都有一个关联的EventLoop,用于处理该Channel的I/O操作。childGroup会负责管理这些Channel和EventLoop,包括事件循环、线程分配、I/O操作等。
//通过使用两个不同的事件组,可以实现多线程处理客户端连接和数据的模型。parentGroup负责接收连接请求并将其分配给childGroup处理,childGroup负责处理具体的连接和数据操作,从而提高服务器的并发性能。
serverBootstrap.group(bossGroup, workerGroup)
//.option(ChannelOption)可设置其它各种参数
//服务器通道实现采用NioServerSocketChannel
.channel(NioServerSocketChannel.class)
//针对服务端的所有连接(不论已连接或是创建连接)
.handler(new LoggingHandler())
//针对已接收到的连接的处理器配置
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
//添加自定义解码器,如继承ByteToMessageCodec用于处理各种业务逻辑,解码器按添加的先后顺寻执行
//ByteToMessageCodec主要用于解码字节数据,将其转换为消息对象,常用于解决粘包和拆包问题
//ChannelInboundHandlerAdapter主要用于处理各种事件,如接收到数据、连接建立、异常处理等,同时为响应请求数据可以用用channel的writeAndFlush将数据写入并刷新网路
socketChannel.pipeline().addLast(new IotDataDecoder(), new IotBusinessDecoder());
}
});
//以下两行代码用于确保服务器在绑定端口后一直保持运行状态,直到接收到关闭信号
//绑定服务器端口并启动服务 sync()使当前线程阻塞直到绑定服务器,绑定多个端口直接重复以下两行代码
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//等待服务器关闭
channelFuture.channel().closeFuture().sync();
}
- 通过NIO将数据流写入到本地文件
java
private void write(InputStream stream, String targetPath) throws IOException {
//创建通道
FileChannel outChannel = FileChannel.open(Paths.get(targetPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
ReadableByteChannel inChannel = Channels.newChannel(stream);
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(2048);
//写入和读取
while (inChannel.read(buffer) != -1) {
//调整缓冲区的位置以便读取,将位置调整到0
buffer.flip();
outChannel.write(buffer);
//将缓冲区未读取的位置移动到起始位置
buffer.compact();
}
buffer.flip();
//判断是否有剩余内容
while (buffer.hasRemaining()) {
outChannel.write(buffer);
}
}