关键词:Netty NIO
学习Netty前,建议先学习Java BIO、NIO、Selector等基础知识,请戳《网络编程从BIO到NIO》《网络编程IO多路复用》
Netty在JDK NIO源码的基础上进行扩展。包括对JDK中的Channel、SocketChannel、ServerSocketChannel、ByteBuffer等都进行的重新编写。本文先对Netty中的主要类有个初步的认识。
█ 相关类
- Channel
io.netty.channel.Channel。可以将其看似JDK中的java.nio.channels.Channel。其表示一个网络Socket连接,并且具有read、write、connect、bind等IO操作。
①Channel父类继承图
②Channel子类实现图
- EventLoop
处理IO操作的事件循环器。监听是否有accept、read、write等操作。
- EventLoopGroup
一个多线程的处理IO操作的事件循环组,每一个单线程便是一个EventLoop事件循环器。EventLoopGroup继承了JDK提供的ScheduleExecutorService,具有异步定时执行功能。(如果你有Java Selector的使用经验,可以看出这里定时执行的是select方法,究竟是不是这样?后续深入源码看看)。一个EcentLoopGroup可以对应多个EventLoop,即对应多个Selector。
- ServerBootstrap、Bootstrap
ServerBootstrap是方便快速创建服务端服务的引导类,服务端代码编程从构建ServerBootstrap开始。使用其中的方法完成端口的绑定、相关连接参数的设置、相关事件处理器的设置等。
Bootstrap是快速创建客户端服务的引导类,客户端编程从构建Bootstrap开始。
- ByteBuf
用于Channel读写数据的缓冲区,相当于JDK中的java.nio.ByteBuffer。
- ChannelConfig、ChannelOption
设置Channel的相关配置,比如Socket连接超时时长等。ChannelOption类提供了预设的相关配置信息。
- ChannelFuture
Netty中所有的IO操作都是异步的,即调用IO操作方法时,方法会立即返回一个ChannelFuture对象,即使IO数据没有准备好。
- ChannelHandler
负责处理IO事件。比如接收客户端数据(read)、给客户端响应数据(write)等。可以将其看做Java Servlet中的Filter过滤器机制。
①ChannelInboundHandler
从读取数据方向开始,依次执行ChannelInboundHandler。即调用了SocketChannel的read方法之后,便会依次执行ChannelInboundHandler(按照ChannelPipeline中顺序)。
方便理解的记忆方式是此Handelr处理客户端的request,处理方向从客户端方向流向服务端方向。
抽象类ChannelInboundHandlerAdapter实现ChannelInboundHandler接口,提供基础的实现方法。
②ChannelOutboundHandler
与ChannelInboundHandler相反,朝着写数据的方向依次执行。即在调用SocketChannel的write方法之前,依次执行ChannelOutboundHandler。
方便理解的记忆方式是此Handelr处理响应客户端的response,处理方向从服务端方向流向客户端方向。
抽象类ChannelOutboundHandlerAdapter实现ChannelOutboundHandler接口,提供基础的实现方法。
- ChannelPipeline
ChannelHandler组成的链表就成了ChannelPipeline。request和response按照ChannelPipeline中的ChannelHandler顺序执行逻辑。可以将其看做Java Servlet中的FilterChain机制。
ChannelPipeline与ChannelHandler示意图:
从Socket.read()方法开始之后的ChannelInboundHandler 1按照箭头方向依次到ChannelOutboundHandler N,便组成了ChannelPipeline。(是不是和Filter的方式类似啊?)
- ChannelHandlerContext
在ChannelPipeline与ChannelHandler中传输数据的上下文。Netty将请求和响应的相关信息封装成ChannelHandlerContext对象。就像Filter中传输的对象是ServerletRequest、ServletResponse。
简单地使用Netty搭建服务端例子:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.SocketAddress;
/**
* 使用Netty搭建服务端
*/
public class MyServer {
public static void main(String[] args) {
// 创建两个线程组,bossGroup负责accept客户端的连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
// workerGroup负责处理bossGroup已经accept的Socket的操作,如read、write等。
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建Serverbootstrap
ServerBootstrap b = new ServerBootstrap();
// 设置boss和worker线程组
b.group(bossGroup, workerGroup)
// 设置使用NioServerSocketChannel类型封装所accept的SocketChannel对象
.channel(NioServerSocketChannel.class)
// 设置SocketChannel的处理器,处理SocketChannel的相关操作
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new MyServerHandler());
}
})
// 设置服务端最大支持同时连接128个客户端
.option(ChannelOption.SO_BACKLOG, 128)
// 设置与客户端的Socket保持连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口号,开始监听客户端的连接
ChannelFuture f = b.bind(8800).sync();
// 阻塞等待,直到服务端Server Socket关闭
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
/**
* 自定义ChannelHandler,处理IO操作
*
*/
public static class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = ctx.channel();
SocketAddress socketAddress = channel.remoteAddress();
System.out.println("客户端【"+socketAddress+"】发来数据:" + new String(((ByteBuf)msg).array()));
}
}
}