最近因为编写接口,需要使用到netty,但是我对于niio和netty一直难以入门,这次准备采用边看边记录的方法。大致计划从nio基础和通信流程过度到netty的应用。
NIO三大基础概念
缓冲区buffer
buffer是一个对象,它包含一些要写入或者要读出的对象。在NIO库中,读写数据都是用缓冲区处理的,访问NIO数据,均是通过缓冲区进行。缓冲区不仅仅是一个数组,还包括对数据的结构化访问及维护读写位置等信息。一个Buffer对象是固定数量的数据的容器。其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。缓冲区可以写满和释放。对于每个非布尔原始数据类型都有一个缓冲区类。尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节。
缓冲区的工作与通道紧密联系。通道是 I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。
参考文章https://blog.csdn.net/qq_38526573/article/details/89176469
通道channel
网络数据通过通道读取和写入,通道可以用于读、写或者二者同时进行,是全双工的,Channel相比IO中的Stream更加高效,可以异步双向传输,但是必须和buffer一起使用。通道的类图如下,前三层是定义功能的接口,后面是具体的功能类,分层用于网络读写的selectTable和用于文件操作的FileChannel
netty使用的SeverSocketChannel和SocketChannel都是网络读写selectTable的子类
参考文章https://blog.csdn.net/l18637220680/article/details/79360451
多路复用器Selector
多路复用器Selector是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以介入成千上万的客户端。
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
参考文章https://blog.csdn.net/duanduan_l/article/details/88577836
NIO序列图
NIO服务端通信序列图
NIO客户端通信序列图
Netty服务端客户端开发
netty服务端
首先创建两个NioEventLoopGroup实例,其中包含一组nio线程,专门用于网络事件的处理。实际上就是Reactor线程组,其中一个用于服务端接受客户端的连接,另一个用于进行SocketChannel的网络读写。之后创建ServerBootstrap对象,他是Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。通过调用serverBoostarp的group方法,将两个NIO线程组当作入参传递到ServerBoostrap中。接着设置创建的Channel为NioServerSocketChannel,然后配置NioServerSocketChannel的TCP参数,此处将他的backlog设置为1024,最后绑定事件I/O事件的处理类ChildChannelHandler,类似于Reactor模式的Handler类。主要用于处理网络I/O事件,例如记录日志,对消息进行编码等。
服务端启动辅助类配置完成之后,调用它的bind方法绑定监听端口,随后,调用它的同步阻塞方法sync等待绑定操作完成。完成后Netty回返回一个ChannelFuture,主要用于异步操作的通知回调。
之后调用NIO线程的shutdownGracefully进行优雅退出,它会释放跟shutdownCracefully相关联的资源。
public class JsonServer {
/**
* LOGGER
*/
private static final Logger Logger = LoggerFactory.getLogger(JsonServer.class);
private final int serverPort;
ServerBootstrap b = new ServerBootstrap();
/**
* 构造函数
* @param port
*/
public JsonServer(int port) {
this.serverPort = port;
}
public void runServer(){
//创建reactor线程组
EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
EventLoopGroup workderLoopGroup = new NioEventLoopGroup(); //使用默认cpu个数*2 的线程个数
try {
//设置reacotor线程组
b.group(bossLoopGroup, workderLoopGroup);
//设置Nio类型的channel
b.channel(NioServerSocketChannel.class); //服务器端的serverSocket
//设置监听端口
b.localAddress(serverPort); //设置监听本地地址,并且传入端口
//设置通道的参数
b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
//装配子通道流水线
b.childHandler(new ChannelInitializer<SocketChannel>() {
//有连接到达时会创建一个channel
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//pipeline管离子通道channel中的handler
//向子channel流水线添加3个handler处理器
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
ch.pipeline().addLast(new JsonMsgDecoder());
}
});
//开始绑定server
//通过sync同步方法阻塞直到绑定成功
ChannelFuture channelFuture = b.bind().sync();
Logger.info("服务器启动成功,监听端口: " + channelFuture.channel().localAddress());
//等待通道关闭的异步任务结束
//服务器监听通道一致等待通道关闭的异步任务结束
ChannelFuture closeFuture = channelFuture.channel().closeFuture();
closeFuture.sync();
} catch (Exception e) {
e.printStackTrace();
}finally {
//优雅关闭eventLoopGroup 线程
workderLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
}
}
//服务器端业务处理器
static class JsonMsgDecoder extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String json = (String) msg;
JsonMsg jsonMsg = JsonMsg.parseFromJson(json);
Logger.info("收到一个 Json 数据包 =》" + jsonMsg);
}
}
/**
* 程序执行入口
* @param args
*/
public static void main(String[] args) {
int port = 8888;
new JsonServer(port).runServer();
}
}
netty客户端
首先创建一组客户端处理I/O读写的NioEventLoopGroup线程组。之后创建NIO客户端的辅助启动类ServerBootstrap,随后进行配置。跟服务端不同的是,它的channel需要设置为NioSocketChannel,然后为其添加Handler。此处为了简单直接创建匿名内部类,实现initCHannel方法,其作用是当创建NioSocketChannel成功之后,在进行初始化时,将它的ChannelHandler设置到ChannelPipeline,用于处理网络I/O事件。
客户端启动辅助类配置完成之后,调用connect方法发起异步连接,然后调用同步方法等待连接成功。
最后,当客户端连接关闭了之后,客户端主函数退出,退出之前释放NIO线程组的资源。
public class JsonSendClient {
/**
* LOGGER
*/
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(JsonServer.class);
static String content = "!";
private int serverPort; //端口号
private String serverIp; //服务器端ip地址
Bootstrap b = new Bootstrap();
/**
* 构造函数
* @param serverIp
* @param serverPort
*/
public JsonSendClient( String serverIp, int serverPort) {
this.serverPort = serverPort;
this.serverIp = serverIp;
}
public void runClient() {
//创建reactor 线程组
EventLoopGroup workerLoopGroup = new NioEventLoopGroup();
try {
//1 设置reactor 线程组
b.group(workerLoopGroup);
//2 设置nio类型的channel
b.channel(NioSocketChannel.class);
//3 设置监听端口
b.remoteAddress(serverIp, serverPort);
//4 设置通道的参数
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
//5 装配通道流水线
b.handler(new ChannelInitializer<SocketChannel>() {
//初始化客户端channel
protected void initChannel(SocketChannel ch) throws Exception {
// 客户端channel流水线添加2个handler处理器
ch.pipeline().addLast(new LengthFieldPrepender(4));
ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
}
});
ChannelFuture f = b.connect();
f.addListener((ChannelFuture futureListener) ->
{
if (futureListener.isSuccess()) {
Logger.info("EchoClient客户端连接成功!");
} else {
Logger.info("EchoClient客户端连接失败!");
}
});
// 阻塞,直到连接完成
f.sync();
Channel channel = f.channel();
//发送 Json 字符串对象
for (int i = 0; i < 1000; i++) {
JsonMsg user = build(i, i + "->" + content);
channel.writeAndFlush(user.convertToJson());
Logger.info("发送报文:" + user.convertToJson());
}
channel.flush();
// 7 等待通道关闭的异步任务结束
// 服务监听通道会一直等待通道关闭的异步任务结束
ChannelFuture closeFuture = channel.closeFuture();
closeFuture.sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅关闭EventLoopGroup,
// 释放掉所有资源包括创建的线程
workerLoopGroup.shutdownGracefully();
}
}
public JsonMsg build(int id, String content) {
JsonMsg user = new JsonMsg();
user.setId(id);
user.setContent(content);
return user;
}
/**
* 客户端程序入口
* @param args
*/
public static void main(String[] args) {
int port = 8888;
String ip = "127.0.0.1";
new JsonSendClient(ip, port).runClient(); //创建对象,并执行客户端程序
}
}
其他netty相关的就慢慢补充吧