概述
Netty 是由 JBOSS 提供的一个 Java 开源框架。Netty 提供异步的、基于事件驱动的网络 应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序。
Netty 是一个基于 NIO 的网络编程框架,使用 Netty 可以帮助你快速、简单的开发出一 个网络应用,相当于简化和流程化了 NIO 的开发过程。
作为当前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、 通信行业等获得了广泛的应用,知名的 Elasticsearch 、Dubbo 框架内部都采用了 Netty。
Netty 模型
Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 网络通道。 NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线 程 NioEventLoop 负责。
ChannelHandler 及其实现类
ChannelHandler 接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具 体的业务逻辑。
常用方法:
- public void channelActive(ChannelHandlerContext ctx),通道就绪事件
- public void channelRead(ChannelHandlerContext ctx,Object msg),通道读取数据事件
- public void channelReadComplete(ChannelHandlerContext ctx) ,数据读取完毕事件
- public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause),通道发生异常事件
Pipeline 和 ChannelPipeline
ChannelPipeline 是一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事 件和操作,相当于一个贯穿 Netty 的链。
常用方法:
- ChannelPipeline addFirst(ChannelHandler handler),把一个业务处理类(handler)添加到链中的第一个位置
- ChannelPipeline addLast(ChannelHandler handler),把一个业务处理类(handler)添加到链中的最后一个位置
ChannelHandlerContext
这是事件处理器上下文对象,Pipeline 链中的实际处理节点。每个处理节点 ChannelHandlerContext 中 包 含 一 个 具 体 的 事 件 处 理 器 ChannelHandler , 同 时 ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler 进行调用。
常用方法:
- ChannelFuture close(),关闭通道
- ChannelOutboundInvoker flush(),刷新
- ChannelFuture writeAndFlush(Object msg) , 将 数 据 写 到 ChannelPipeline 中 当 前 ChannelHandler 的下一个 ChannelHandler 开始处理
ChannelOption
Netty 在创建 Channel 实例后,一般都需要设置 ChannelOption 参数。ChannelOption 是 Socket 的标准参数,而非 Netty 独创的。常用的参数配置有:
-
ChannelOption.SO_BACKLOG
对应 TCP/IP 协议 listen 函数中的 backlog 参数,用来初始化服务器可连接队列大小。服 务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户 端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定 了队列的大小。 -
ChannelOption.SO_KEEPALIVE
一直保持连接活动状态。
ChannelFuture
表示 Channel 中异步 I/O 操作的结果,在 Netty 中所有的 I/O 操作都是异步的,I/O 的调 用会直接返回,调用者并不能立刻获得结果,但是可以通过 ChannelFuture 来获取 I/O 操作 的处理状态
常用方法如下所示:
- Channel channel(),返回当前正在进行 IO 操作的通道
- ChannelFuture sync(),等待异步操作执行完毕
EventLoopGroup 和其实现类 NioEventLoopGroup
EventLoopGroup 是一组 EventLoop 的抽象,Netty 为了更好的利用多核 CPU 资源,一般会有多个 EventLoop 同时工作,每个 EventLoop 维护着一个 Selector 实例。
EventLoopGroup 提供 next 接口,可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。在 Netty 服务器端编程中,我们一般都需要提供两个 EventLoopGroup,例如: BossEventLoopGroup 和 WorkerEventLoopGroup。
通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。 BossEventLoop 负责接收客户端的连接并将 SocketChannel 交给 WorkerEventLoopGroup 来进行 IO 处理。
ServerBootstrap 和 Bootstrap
ServerBootstrap 是 Netty 中的服务器端启动助手,通过它可以完成服务器端的各种配置; Bootstrap 是 Netty 中的客户端启动助手,通过它可以完成客户端的各种配置。
网络聊天室
下面是基于Netty的网络聊天室案例
ServiceHandler
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.ArrayList;
import java.util.List;
//自定义一个服务器端业务处理类
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
public static List<Channel> channels = new ArrayList<>();
@Override //通道就绪
public void channelActive(ChannelHandlerContext ctx) {
Channel inChannel=ctx.channel();
channels.add(inChannel);
System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"上线");
}
@Override //通道未就绪
public void channelInactive(ChannelHandlerContext ctx) {
Channel inChannel=ctx.channel();
channels.remove(inChannel);
System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"离线");
}
@Override //读取数据
protected void channelRead0(ChannelHandlerContext ctx, String s) {
Channel inChannel=ctx.channel();
for(Channel channel:channels){
if(channel!=inChannel){
channel.writeAndFlush("["+inChannel.remoteAddress().toString().substring(1)+"]"+" -> "+s+"\n");
}
}
}
@Override //发生异常
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
Channel incoming=ctx.channel();
System.out.println("[Server]:"+incoming.remoteAddress().toString().substring(1)+"状态异常");
ctx.close();
}
}
Service
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.Charset;
//聊天程序服务器端
public class ChatServer {
private int port; //服务器端端口号
public ChatServer(int port) {
this.port = port;
}
public void run() throws Exception {
//创建一个线程组:用来处理网络事件(接受客户端连接)
EventLoopGroup bossGroup = new NioEventLoopGroup();
//创建一个线程组:用来处理网络事件(处理通道 IO 操作)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
//设置两个线程组 EventLoopGroup
b.group(bossGroup, workerGroup)
//使用 NioServerSocketChannel 作为服务器 端通道实现
.channel(NioServerSocketChannel.class)
//设置线程队列中等待连接的个数
.option(ChannelOption.SO_BACKLOG, 128)
//保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline=ch.pipeline();
//往pipeline链中添加一个解码器
pipeline.addLast("decoder",new StringDecoder(Charset.forName("UTF-8")));
//往pipeline链中添加一个编码器
pipeline.addLast("encoder",new StringEncoder(Charset.forName("UTF-8")));
//往pipeline链中添加自定义的handler(业务处理类)
pipeline.addLast(new ChatServerHandler());
}
});
System.out.println("Netty Chat Server启动......");
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("Netty Chat Server关闭......");
}
}
public static void main(String[] args) throws Exception {
new ChatServer(9999).run();
}
}
clientHandler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
//自定义一个客户端业务处理类
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
System.out.println(s.trim());
}
}
client
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.Charset;
import java.util.Scanner;
//聊天程序客户端
public class ChatClient {
private final String host; //服务器端IP地址
private final int port; //服务器端端口号
public ChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run(){
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch){
ChannelPipeline pipeline=ch.pipeline();
//往pipeline链中添加一个解码器
pipeline.addLast("decoder",new StringDecoder(Charset.forName("UTF-8")));
//往pipeline链中添加一个编码器
pipeline.addLast("encoder",new StringEncoder(Charset.forName("UTF-8")));
//往pipeline链中添加自定义的handler(业务处理类)
pipeline.addLast(new ChatClientHandler());
}
});
ChannelFuture cf=bootstrap.connect(host,port).sync();
Channel channel=cf.channel();
System.out.println("------"+channel.localAddress().toString().substring(1)+"------");
Scanner scanner=new Scanner(System.in);
while (scanner.hasNextLine()){
String msg=scanner.nextLine();
channel.writeAndFlush(msg+"\r\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ChatClient("127.0.0.1",9999).run();
}
}
我们可以同时运行多个聊天客户端,运行效果如下图所示: