Netty
Redis,Zookeper,Netty,游戏服务器等其实底层就是I/O通讯程序(C/S架构)
Client与Server之间进行IO通讯。
1、netty的优势、底层逻辑
Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于NIO的,它封装了jdk的NIO,让我们使用起来更加方法灵活。
Netty的特点:
高并发: Netty 是一款基于NIO 开发的网络通 信框架,对比于BIO ,他的并发性能得到了很大提高。
传输快: Netty 的传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了 更高效率的传输。
封装好: Netty 封装了 NIO 操作的很多细节,提供了易于使用调用接口。
Netty的优势:
对NIO进行了大量的优化和封装,使用起来更加方便和高效,同时可扩展性很强,将NIO的连接建立、数据接收都封装到框架里面,以及使用NIO编程的一些异常处理
使用简单:封装了 NIO 的很多细节,使用更简单。
功能强大:预置了多种编解码功能,支持多种主流协议。
定制能力强:可以通过 ChannelHandler 对通信框架进行灵活地扩展。
性能高:通过与其他业界主流的 NIO 框架对比, Netty 的综合性能优。
稳定: Netty 修复了已经发现的所有 NIO 的 bug ,让开发人员可以专注于业务 本身。
社区活跃: Netty 是活跃的开源项目,版本迭代周期短,bug 修复速度快。
Netty 的应用场景:
阿里分布式服务框架 Dubbo ,默认使用 Netty 作为基础通信组件,还有 RocketMQ 也是使用 Netty 作为通讯的基础。
Netty的底层逻辑:
Netty底层主要是基于JAVA NIO的非阻塞事件驱动模型以及多路复用器实现的。
在底层,Netty通过以下几个核心组件实现了高性能的网络通信:
Channel(通道):Channel是Netty的核心抽象,它表示一个开放的连接,可以执行读取、写入和关闭等操作。Netty中的Channel提供了异步的I/O操作和事件通知机制。
EventLoop(事件循环):EventLoop是Netty中的事件处理机制,它负责处理和分发事件,以及执行对应的I/O操作。每个Channel都关联了一个EventLoop,它负责处理该Channel上的所有事件。
ChannelPipeline(通道管道):ChannelPipeline是Netty中的处理器链,它由一系列的处理器组成,用于处理、转换或拦截事件和数据。每个Channel都有自己的ChannelPipeline,事件在Pipeline中依次经过处理器进行处理。
ChannelHandler(通道处理器):ChannelHandler是Netty中的处理器,用于执行实际的业务逻辑。它可以处理事件、读取和写入数据,以及修改ChannelPipeline。
ByteBuf(字节缓冲区):ByteBuf是Netty中的字节容器,它提供了高效的字节操作方法,用于读取和写入数据。
Netty的底层实现利用了Java NIO的非阻塞I/O模型,通过Selector轮询事件,将I/O操作异步化并交给EventLoop处理。这种事件驱动的模型使得Netty能够处理大量并发连接,同时提供低延迟和高吞吐量的网络通信
2、netty线程模型
对于服务器端而言有两个线程组,Boss线程组和Worker线程组。其中Boss线程组一般只开启一条线程(除非一个Netty服务同时监听多个端口),Worker线程数默认是CPU核数的两倍。Boss线程主要监听SocketChannel的OP_ACCEPT事件和客户端的连接。
当Boss线程监听到有SocketChannel连接接入时,会把SocketChannel包装成NioSocketChannel,并注册到Worker线程的Selector中,同时监听其OP_WRITE和OP_READ事件。
当Worker线程监听到某个SocketChannel有就绪的读IO事件时,就会进行以下操作:
1、向内存池中分配内存,读取IO数据流
2、将读取后的ByteBuf传递给解码器Handler进行解码,若能解码出完整的请求数据包,就会把请求数据包交给业务逻辑处理Handler
3、经过业务逻辑处理Handler后,在返回响应结果前,交给编码器进行数据加工
4、最终写到缓冲区,并由IO Worker线程将缓冲区的数据输出到网络中并传输给客户端
3、netty实现一个小服务:客户端发消息,服务端可以返回对话
Netty 客户端
package com.example.demo.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
static final String HOST = "localhost";
static final int PORT = 8000;
public static void main(String[] args) throws InterruptedException {
//客户端启动
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap
// 指定线程模型
.group(group)
// 指定IO类型为NIO
.channel(NioSocketChannel.class)
// 连接的超时时间,如果超过这个时间,仍未连接到服务端,则表示连接失败
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
// IO处理逻辑
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new NettyClientHandler());
}
});
//3、与服务器建立连接
bootstrap.connect(HOST,PORT);
}
}
Netty 服务端
package com.example.demo.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyServer {
public static void main(String[] args) {
// 1、引导服务端的启动
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 用于监听端口,接收新连接的线程组
NioEventLoopGroup boss = new NioEventLoopGroup();
// 表示处理每一个连接的数据读写的线程组
NioEventLoopGroup worker = new NioEventLoopGroup();
//2、初始化
serverBootstrap.group(boss, worker)
// 指定IO模型为NIO
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
// 定义后面每一个连接的数据读写
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new NettyServerHandler());
}
});
//3、绑定端口
serverBootstrap.bind(8000);
}
}
自定义ClientHandler
package com.example.demo.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端连接服务器完成就会触发该方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx){
System.out.println(new Date() + ": 客户端发送数据给服务端");
ctx.channel().writeAndFlush(getByteBuf(ctx));
}
/**
* 读取服务端发送的数据
* @param ctx 上下文对象,含有通道管道
* @param msg 客户端发送的数据
* @throws Exception
*/
@Override
// 不管服务端还是客户端,收到数据后都会调用channelRead()方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(new Date() + ": 客户端收到服务端的数据 -> " + byteBuf.toString(StandardCharsets.UTF_8));
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
ByteBuf buffer = ctx.alloc().buffer();
byte[] bytes = "hello world".getBytes(StandardCharsets.UTF_8);
buffer.writeBytes(bytes);
return buffer;
}
}
自定义ServerHandler
package com.example.demo.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* 自定义Handler需要继承netty规定好的某个HandlerAdapter(规范)
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端连接服务器完成就会触发该方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx){
System.out.println("服务端与客户端连接通道建立完成");
}
/**
* 读取客户端发送的数据
* @param ctx 上下文对象,含有通道管道
* @param msg 客户端发送的数据
* @throws Exception
*/
@Override
// 不管服务端还是客户端,收到数据后都会调用channelRead()方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(new Date() + ": 服务端收到客户端数据 -> " + byteBuf.toString(StandardCharsets.UTF_8));
// 服务端将读到的数据返回客户端
System.out.println(new Date() + ": 服务端发送数据给客户端");
ctx.channel().writeAndFlush(getByteBuf(ctx));
}
/**
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx){
}
//写数据
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
byte[] bytes = "hello world from server!".getBytes(StandardCharsets.UTF_8);
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(bytes);
System.out.println(buffer.toString(StandardCharsets.UTF_8));
return buffer;
}
}
NIO、BIO、AIO
● BIO 就是传统的IO,同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
● NIO 是同步非阻塞的,服务器端用一个线程处理多个连接(多个客户端),客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理,适合大量并发请求的场景
● AIO 就是NIO的升级版,通过操作系统的回调通知去让线程处理事件,异步非阻塞,适合超大并发请求的场景
● IO多路复用是一种同步IO模型(NIO),允许单线程去同时监听多个文件描述符,一旦文件描述符就绪就会通知程序去处理
常见的有select、poll、epoll
○ select
■ 基于数组实现,每次调用都进行遍历,最大连接数有上限
○ poll
■ 通过链表实现,每次调用都进行遍历,最大连接数无上限
○ epoll
■ 通过哈希表实现,通过事件通知,每当有IO事件就绪,系统注册的回调函数就会被调用,最大连接数无上限
BIO (阻塞IO)
● BIO 就是传统的IO,同步阻塞,服务器实现模式为(一个客户端)一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
NIO (非阻塞IO)
● NIO 是同步非阻塞的,服务器端用一个线程处理多个连接(多个客户端),客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理,适合大量并发请求的场景
简单NIO
缺点:
1、每次建立连接都会遍历整个channelList
2、非阻塞的IO,程序会空转,占用CPU资源