Netty
Netty是一个异步的,基于事件驱动的NIO网络应用框架。主要针对在TCP协议下,面向Client端的高并发应用,或者Peer-to-Peer场景下的大数据持续传输的应用。
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
GitHub:https://github.com/netty/netty
线程模型
-
传统阻塞I/O服务模型
特点:采用阻塞I/O模式获取输入的数据,每个连接都需要独立的线程完成数据的输入、业务处理、数据返回。
瓶颈:当并发数很大时,会创建大量的线程,导致CPU上下文频繁切换,占用系统资源。连接创建后,如果当前线程暂时没有数据可读时,该线程会阻塞在Read操作上,造成线程资源浪费。
-
Reactor模型
根据Reactor的数量和处理资源池线程数量的不同,有3种典型的实现。
基于I/O复用模型:多个连接公用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞态返回。
基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。
-
单Reactor单线程
-
单Reactor多线程
-
主从Reactor多线程
-
-
Netty线程模型(基于主从Reactor多线程模型,其中主从Reactor多线程模型有多个Reactor)
Netty抽象出两组线程池BoosGroup负责接收客户端的连接,WorkGroup负责网络的读写。
BoosGroup
和WorkGroup
类型都是NioEventLoopGroup
,相当于一个事件循环组,组中含有多个事件循环,每个事件循环是NioEventGroup
。NioEventLoop
表示一个不断循环的执行处理任务的线程,每个NioEventLoop
都有一个Selector
,用于监听绑定在其上的Socket
的网络通信。NioEventLoopGroup
可以有多个线程,即可以含有多个NioEventLoop
。- 每个
Boos NioEventLoop
执行- 轮训accept事件
- 处理accept事件,与clinet建立连接,生成
NioSocketChannel
,并将其注册到某个Worker NioEventLoop
上的selector - 处理任务队列,即runAllTasks
- 每个
Worker NioEventLoop
执行- 轮询read/write事件,即在对应的
NoiSocketChannel
处理I/O事件 - 处理任务队列,即runAllTasks
- 轮询read/write事件,即在对应的
每个
Worker NioEventLoop
处理业务是,会使用PipeLine
,其中维护了多种处理器 - 每个
入门案例
Server端
package org.study.netty.simple.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Netty 服务端
* @author harlan
*/
public class Server {
public static void main(String[] args) throws InterruptedException {
//创建 BossGroup 和 WorkGroup
//BoosGroup处理连接
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
//WorkGroup处理业务
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
//创建服务端的启动对象,并配置启动参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
//设置线程组
serverBootstrap.group(boosGroup, workGroup)
//设置通道类型
.channel(NioServerSocketChannel.class)
//设置线程队列等待连接个数
.option(ChannelOption.SO_BACKLOG, 128)
//设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
//设置PipeLine处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
//创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//向PipeLine设置处理器
ch.pipeline().addLast(new ServerHandler());
}
});
System.out.println("Server is ready");
//绑定端口并进行同步处理
ChannelFuture cf = serverBootstrap.bind(19999).sync();
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
//关闭线程组
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
Server Handler
package org.study.netty.simple.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
/**
* 自定义 Handler 需继承 Netty 规定好的某个 HandlerAdapter
* @author harlan
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取数据
* @param ctx 通道上下文(管道、通道)
* @param msg 数据
* @throws Exception 异常
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("server ctx::" + ctx);
//将msg转为ByteBuf
ByteBuf buf = (ByteBuf) msg;
System.out.println("client msg::" + buf.toString(StandardCharsets.UTF_8));
System.out.println("client ip::" + ctx.channel().remoteAddress());
}
/**
* 读取数据完毕
* @param ctx 通道上下文
* @throws Exception 异常
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓存并刷新
//对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Client", StandardCharsets.UTF_8));
}
/**
* 异常处理
* @param ctx 通道上下文
* @param cause 异常
* @throws Exception 异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭通道
ctx.close();
}
}
Clinet端
package org.study.netty.simple.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* Netty 客户端
* @author harlan
*/
public class Client {
public static void main(String[] args) throws InterruptedException {
//客户端需要一个事件循环组
EventLoopGroup loopGroup = new NioEventLoopGroup();
try {
//创建客户端启动对象
Bootstrap bootstrap = new Bootstrap();
//设置参数
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
}
});
System.out.println("Client is ready");
//启动客户端
ChannelFuture cf = bootstrap.connect(new InetSocketAddress(19999)).sync();
//关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
//关闭线程组
loopGroup.shutdownGracefully();
}
}
}
Client Handler
package org.study.netty.simple.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
/**
* @author harlan
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
/**
* 通道就绪时触发
* @param ctx 通道上下文
* @throws Exception 异常
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client ctx::" + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Server", StandardCharsets.UTF_8));
}
/**
* 当通道有读取事件时触发
* @param ctx 通道上下文
* @param msg 数据
* @throws Exception 异常
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("server msg::" + buf.toString(StandardCharsets.UTF_8));
System.out.println("server ip::" + ctx.channel().remoteAddress());
}
/**
* 异常处理
* @param ctx 通道上下文
* @param cause 异常
* @throws Exception 异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
Netty 核心组件
BootStrap & ServerBootStrap
BootStrap
意思是引导,一个Netty
应用通常由一个BootStrap
开始,主要作用是配置整个Netty
程序,串联各个组件,Netty
中BootStrap
类是客户端程序的引导类,ServerBootStrap
是服务端启动引导类。
Channel
Channel
是Netty
网络通信的组件,能够用于执行网络I/O操作。
通过Channel
可以获得当前网络连接的通道状态,网络连接的配置参数。
Channel
提供异步的网络I/O操作,异步调用意味着任何I/O调用都将立即返回,并且不保证在调用结束时所请求的I/O操作已完成。
调用立即返回一个ChannelFuture
实例,通过注册监听器到ChannelFuture
上,可以在I/O操作成功、失败、取消时回调通知调用方。
Selector
Netty
基于Selector
对象实现I/O多路复用,通过Selector
一个线程可以监听多个连接的Channel
事件。当向一个Selector
中注册Channel
后,Selector
内部的机制就可以自动不断地查询这些注册的Channel
是否有已就绪的I/O事件。