动态每日更新算法题,想要学习的可以关注一下一起学习
第一章 初识NIO(同步非阻塞的I/O模型)(Netty第一步)
Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
为什么使用Netty
NIO的主要问题是:
Netty的优点
传统的阻塞IO模型为了解决高并发和非阻塞的问题,进而产生了Reactor主从模式,而netty就是在此基础上演变而来,如果不了解阻塞模型以及Reactor可以前往顶部跳转之前的文章。
需要特别说明的就是不管是BossGroup还是WorkGroup每一个都可以包含无数个NioEventLoop,但是每一个NioEventLoop都会对应一个线程,如果无限制的创建会导致cpu资源耗尽。对于一般业务来说,BossGroup只会创建一个NioEventLoop,因为在BossGroup中它只负责socket的连接并且会不断的阻塞等待下一个连接,所以一个即可。而对于workGroup来说,它是负责大并发的业务处理,包括读写等任务,所以如果不指定数量,默认为cpu的核数×2,这意味着将会启用所有资源。
在我的上一篇文章其实讲过Socket和Channel的区别,其实对于这两个是最容易混淆的,我也特地去看了Netty的源码,对于Channel源码给的解释是:
总结起来就是一个名词:网络socket,可以说这解释说了和没说一样,博主查阅大量资料得以总结在之前的文章,这里也可以再说一遍,socket是网络连接的入口,当然这个入口也可以用来传输信息等,但是原生的socket是阻塞的IO操作,对于Channel来说更像是一种包装后的socket,在netty中用它来传输数据,而socket更像是一种入口。
task任务队列顾名思义和RabbitMQ一样,用来启动定时任务或者非阻塞业务,目的就是为了在处理复杂业务时,程序不应该阻塞等待业务处理完,而是进行下去,因为Netty本身是能够全双工消息处理,如果在某个地方阻塞那么还是与传统阻塞IO存在同样问题,所以为了解决阻塞等待,于是便有了task。
与task任务队列不同的是netty中的其他异步处理还包括通道channel的开启关闭,以及callback函数,例如连接开启和关闭,在连接开启后会返回一个Future对象(有可能连接没有完成因为网络等问题),这个对象会记录该连接的所有信息,在返回Future之后,程序会直接向下执行,而不是阻塞等待连接,更像是一种监控。当然业务处理同理。
Selector可以理解为一个注册中心,如果说有什么像它,我想nacos可以很好的诠释它,注册后的连接将会分配给NioEventLoop。
顾名思义Handler就是一个处理器,它就是用来处理所有的业务操作,包括IO操作等,但是仅负责于此,并不处理连接操作。
每一个workGroup拿到了由BossGroup分配到的连接,都会分发给一个空闲的NioEventLoop,然后这个空闲的NioEventLoop会将这个连接中的数据放入到PipeLine中,而PipeLine的作用就是处理业务数据,因为PipeLine中包含了很多的Handler。而在示意图中我们看到所有的Handler都是由HandlerContext包裹,这个HandlerContext就是上下文对象,顾名思义就是记录信息,包括当前的pipeline、channel、ip地址以及IO的数据等等
这是专门操作缓冲区的类,但也不是唯一的,使用平常的ByteBuf也是可以的。
对于任意一端包括服务端和客户端,都有入栈和出栈事件,这也是相对而言的。当有数据需要输出,那么经过Handler编码之后输出到Channel中这就是出栈事件,相反如果从channel中读入数据解码这就是入栈事件。
如图所示,在网络中传输就是二进制的传输,那么编解码自然少不了。
解决方案:
由于底层TCP是无法理解上层业务数据,所以在底层是无法保证数据包不被拆分和重组的,所以只能通过上层应用协议栈设计来解决
(1)消息定长,例如每个报文的大小固定长度200字节,不够空位补空格
(2)在包尾增加回车换行符进行分割,例如FTP协议
(3)将消息分为消息头和消息体,消息头中包含表示消息总长度的字段
(4)更复杂的应用层协议。
Handler
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = Logger.getLogger(TimeClientHandler.class.getName());
private int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8").substring(0, req.length - System.getProperty("line.separator").length());
// 每收到一条消息计数器就加1, 理论上应该接收到100条
System.out.println("The time server receive order: " + body + "; the counter is : "+ (++counter));
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
new Date(System.currentTimeMillis()).toString():"BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.warning("Unexpected exception from downstream: " + cause.getMessage());
ctx.close();
}
}
Server
public class TimeServer {
public static final Logger log = LoggerFactory.getLogger(TimeServer.class);
public static void main(String[] args) throws Exception {
new TimeServer().bind();
}
public void bind() throws Exception {
// NIO 线程组
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture f = bootstrap.bind(NettyConstant.REMOTE_IP, NettyConstant.REMOTE_PORT).sync();
log.info("Time server[{}] start success", NettyConstant.REMOTE_IP + ": " + NettyConstant.REMOTE_PORT);
// 等待所有服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
ClientHander
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = Logger.getLogger(TimeClientHandler.class.getName());
private int counter;
private byte[] req;
public TimeClientHandler() {
req = ("QUERY TIME ORDER" + System.getProperty("line.separator"))
.getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = null;
// 循环发送100条消息,每发送一条刷新一次,服务端理论上接收到100条查询时间指令的请求
for (int i = 0; i < 100; i++) {
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
// 客户端每接收到服务端一条应答消息之后,计数器就加1,理论上应该有100条服务端日志
System.out.println("Now is: " + body + "; the current is "+ (++counter));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.warning("Unexpected exception from downstream: " + cause.getMessage());
ctx.close();
}
}
Client
public class TimeClient {
public static final Logger log = LoggerFactory.getLogger(TimeClient.class);
public static void main(String[] args) throws Exception {
new TimeClient().connect(NettyConstant.REMOTE_IP, NettyConstant.REMOTE_PORT);
}
public void connect(final String host, final int port) throws Exception {
// NIO 线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = bootstrap.connect(host, port).sync();
// 等待所有服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
group.shutdownGracefully();
}
}
}