什么是netty?
Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github上的独立项目。
Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络 IO 程序。Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下>的大量数据持续传输的应用。
Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
要透彻理解Netty , 需要先学习 NIO , 这样我们才能阅读 Netty 的源码
Reactor模式
基本原理:
传统的BIO模型会造成大量线程的浪费
针对传统BIO的缺点,reactor解决方案:
- 基于I/O复用模型,多个连接公用一个阻塞对象,应用程序只需要在一个阻塞对象等待,当某个连接有新的数据可以处理时,操作系统通知应用程序,程序从阻塞状态返回,开始处理业务
- 基于线程池服用,不必为每个连接创建线程,一个线程可以处理多个业务
reactor核心组成:
- Reactor:负责监听和分发事件
Handlers :负责处理程序执行I/O事件要完成的实际事件
架构图:
Netty模型简单图解
- BossGroup线程维护Selector,只关注Accecpt
- 当接受到Accept事件,获取对应的SocketChannel,封装成NIOSocketChannel并注册到Woker线程,并由Woker线程维护
- Woker线程监听到Selector中的通道发生了自己感兴趣的事件后,就进行处理
- Woker线程出了Handler进行处理事件
进阶版图解
- Netty抽象出两组线程池,BossGroup专门负责接收客户端的连接,WorkGroup负责网络的读写
- BossGourp和WorkGroup类型都是NIOEventLoopGroup
- NioEventLoopGroup相当于一个事件循环组,这个族中含有多个事件循环,每一个事件循环都是NioEventLoop
- NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其上的socket的网络通讯
每个Boss NIOEventLoop执行步有三步
- 轮询 accept事件
- 处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个woker NIOEventLoop上的selector,
- 处理任务队列的任务,即runAllTasks
每个Woker NIOEventLoop循环执行的步骤:
- 轮询 read,write事件,
- 处理I/O事件,即read,write事件,在对应的NioSocketChannel处理
- 处理任务队列的任务,即runAllTasks
Netty入门Demo:
需求
服务端代码:
public class server {
public static void main(String[] args) throws InterruptedException {
//创建BossGroup和WorkerGroup
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap=new ServerBootstrap();
//使用链式编程进行设置
bootstrap.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//使用NioServerSocketChannel,作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128)//设置线程队列的连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道测试对象
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new serverHandler());//自定义的处理器
}
});
System.out.println("....server is ready");
ChannelFuture future = bootstrap.bind(6668).sync();
//对关闭通道进行监听,只有关闭事件的时候次才会关闭
future.channel().closeFuture().sync();
}
}
服务端handler:
//自定义Handler
public class serverHandler extends SimpleChannelInboundHandler {
@Override
//channelHandlerContext ctx:含有管道pipeline,通道,地址等
//object o--客户端发送的数据
protected void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception {
System.out.println("server ctx="+ctx);
//将msg转成一个ByteBufer
ByteBuf buf=(ByteBuf)o;
System.out.println("客户端发送的消息是:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:"+ctx.channel().remoteAddress());
}
@Override
//数据读取完毕
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓存,并刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端~!",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端代码:
public class client {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventExecutors=new NioEventLoopGroup();
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClientHandler());//加入自己的处理其
}
});
System.out.println("-----客户端Ok!!!");
//
ChannelFuture future=bootstrap.connect("127.0.0.1",6668).sync();
future.channel().closeFuture().sync();
}
}
客户端handler:`
public class ClientHandler extends SimpleChannelInboundHandler {
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client "+ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server:im client", CharsetUtil.UTF_8));
}
//当通道有事件时,会触发
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf)msg;
System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}