Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers & clients.
-
开发门槛低:API 使用简单;
-
定制能力强:可以通过 ChannelHandler对通信框架进行灵活地扩展;
-
Handler强大:预置了多种编解码器,支持多种主流协议,对传输中粘包和拆包有现成解决方案,有完善的断连,idle(心跳检测)等异常处理;
-
高性能:与其他业界主流的 NIO 框架对比,Netty 的综合性能最优;
-
社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时更多的新功能会加入;经历了大规模的商业应用考验,质量有验证;
开发门槛低
BIO vs NIO vs AIO
package app.tmp; public class Server { //定义一个循环接收客户端的Socket连接请求。初始化一个线程池对象 private static ExecutorService poolHandler = new ThreadPoolExecutor(5, 5, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)); public static void main(String[] args) { try { // 注册端口 ServerSocket ss = new ServerSocket(9999); while (true) { Socket socket = ss.accept(); // 把Socket封装成一个任务对象交给线程池处理 Runnable target = new ServerRunnable(socket); poolHandler.execute(target); } } catch (IOException e) { e.printStackTrace(); } } public static class ServerRunnable implements Runnable { private Socket socket; public ServerRunnable(Socket socket) { this.socket = socket; } @Override public void run() { // 处理接收的客户端Socket通信需求 try { InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String msg; while ((msg = br.readLine()) != null) { //处理数据的粘包拆包 //对完整的数据包进行解码操作 //得到客户端消息 //触发各种统计类事件如心跳检测 信息统计 //处理客户端的消息 //得到响应消息 //对响应消息进行编码 } } catch (IOException e) { e.printStackTrace(); //处理网络断开事件 //处理其他异常事件 } } } }
NIOServer
public class Server { public static void main(String[] args) throws IOException { //获取ServerSocketChannel ServerSocketChannel ssChannel = ServerSocketChannel.open(); //设置非阻塞模式 ssChannel.configureBlocking(false); //绑定端口 ssChannel.bind(new InetSocketAddress(9999)); //获取选择器 Selector selector = Selector.open(); //将ServerSocketChannel注册到选择器上,并且监听建立连接事件 ssChannel.register(selector, SelectionKey.OP_ACCEPT); // 使用Selector选择器轮询已经就绪好的事件 while (selector.select() > 0) { // 获取选择器就绪事件 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); //遍历事件 while (it.hasNext()) { SelectionKey sk = it.next(); //判断事件类型 if (sk.isAcceptable()) { // 获取客户端channel SocketChannel channel = ssChannel.accept(); //切换非阻塞模式 channel.configureBlocking(false); //将该channel注册到选择器上 channel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { //获取channel SocketChannel sChannel = (SocketChannel) sk.channel(); //读取网络数据 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { //处理数据的粘包拆包 //对完整的数据包进行解码操作 //得到客户端消息 //触发各种统计类事件如心跳检测 信息统计 } }else if(sk.isWritable()){ //得到响应消息 //对响应消息进行编码 } // 15.取消选择键SelectionKey it.remove(); } } } }
用Netty进行网络编程时代码是这样的:
public class NettyServer { public static void main(String[] args) throws Exception{ //设置接受网络连接线程池 NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); //设置处理网络除连接外所有事件线程的线程池 NioEventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class)//设置Channel类型 .option(ChannelOption.SO_BACKLOG,1024) .handler(new ChannelInitializer<ServerSocketChannel>() { @Override protected void initChannel(ServerSocketChannel ch) throws Exception { //设置处理网络连接的Handler ch.pipeline().addLast("serverBindHandler", new NettyBindHandler(NettyTcpServer.this,serverStreamLifecycleListeners)); } }) .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline() .addLast("protocolHandler", new NettyProtocolHandler())//设置编解码器 .addLast("serverIdleHandler", new IdleStateHandler(0, 0, serverIdleTimeInSeconds))//设置心跳检测 .addLast("serverHandler",new NettyServerStreamHandler(NettyTcpServer.this, false, serverStreamLifecycleListeners, serverStreamMessageListeners));//设置业务处理逻辑 } }); ChannelFuture channelFuture = serverBootstrap.bind(9000).sync(); channelFuture.channel().closeFuture().sync(); } }
BIO和NIO编程在网络事件发生后都需要进行处理数据的粘包拆包、对完整的数据包进行解码、触发各种统计类事件、对响应消息进行编码、监听各种网络异常、需要对底层网络和通信协议有一定的了解。
定制能力强
通过ChannelHandler灵活扩展
-
假如每个handler都会把读事件向下一个InboundHandler类型的节点进行传递,此时的调用链路为head->A->B->C->tail;
-
假如B业务handler处理数据后不把读事件继续向下传递,此时B可以在自己内部选择不向下一个节点传递读事件.此时调用链路变为head->A->B;
-
假如每个handler都会把读事件向下一个OutboundHandler类型的节点进行传递,当C业务handler发送响应数据时此时调用链路为C->B->head;
-
假如业务B是参数校验的的headler,当校验失败就响应客户端.此时调用的链路为B->head;
Handler强大
编解码
TCP粘包拆包
编解码Handler
-
LineBasedFrameDecoder(回车换行分包); -
DelimiterBasedFrameDecoder(特殊分隔符分包); -
FixedLengthFrameDecoder(固定长度报文来分包)
-
编解码字符串的StringEncoder和StringDecoder; -
用于处理HTTP协议编解码HttpObjectDecoder和HttpObjectEncoder; -
用于处理protobuf编解码ProtobufVarint32FrameDecoder和ProtobufDecoder;
支持多种主流协议
idle(心跳检测)
-
readerIdleTimeSeconds: 读取空闲时间,即从上次读取数据到现在的秒数。
-
writerIdleTimeSeconds: 写入空闲时间,即从上次写入数据到现在的秒数。
-
allIdleTimeSeconds: 读写都空闲的时间,即从上次读写数据到现在的秒数。
高性能
一次网络通信都发生了什么
1.客户端确定需要发送的数据;
2.数据从程序到系统然后通过网卡发送;
3.服务端收到读事件后把数据从系统读取到应用中;
Netty怎么提高通信性能
-
数据在系统中的存储Netty使用ByteBuf内存池来减少申请内存耗时;
-
数据在用户态到内核态的传输Netty采用了内存零拷贝(减少用户态到内核态的两次拷贝)来减少传输耗时;
-
数据在网络中的传输 数据传输时间取决于数据传输速度、数据包数量。对于传输速度无法优化,Netty内置了许多编码器,可以选择对数据压缩比较好的解码器来减少数据包的数量,以此来减少耗时;
-
服务端对网络事件的处理Netty采用主次Reactor多线程模型来加快对网络事件的处理,传统的BIO不能支持太多网络连接以及对系统资源使用率比较低。传统的NIO当网络连接数超多时网络事件得不到快速响应,造成大量客户端进行重试。
-
客户端消息处理上,Netty采用了无锁串行化设计思想结合volatile的大量使用;通过读写锁提升并发性能来大大缩短了消息处理时间。
整体结构
-
Core核心层:核心层里Netty最精华的部分,它提供了底层网络通信的通用抽象和实现,包括事件模型、通用API、支持零拷贝的ByteBuf 等;
-
Protocol Support 协议支持层:协议支持层基本上覆盖了主流协议的编解码实现,Netty 丰富的协议支持降低了用户的开发成本;
-
Transport Service 传输服务层:传输服务层提供了网络传输能力的定义和实现方法。它支持 Socket、HTTP 隧道等传输方式。Netty 对 TCP、UDP 等数据传输做了抽象和封装,让开发者可以更聚焦在业务逻辑实现上,而不必关系底层数据传输的细节;
逻辑架构
网络通信层
-
Netty服务端:程序启动时会生成一个ServerBootstrap对象,该对象会生成一个NioServerSocketChannel来监听某一个端口的建立网络连接的事件,当网络连接建立后会监听网络连接的各种事件并通知到事件调度层。
-
Netty客户端:程序启动的时会生成一个Bootstrap对象,该对象会生成一个NioSocketChannel来与服务端建立网络连接的事件,当网络连接建立后会监听网络连接的各种事件并通知到事件调度层。
事件调度层
-
EventLoop 负责处理 I/O 事件和调度任务。每一个NioEventLoop内部都有唯一一个Selector,通过这个Selector可以对注册的channel进行网络事件的读取;NioEventLoop 还有一个内部的任务队列,可以用来提交 Runnable 任务。这些任务会在 NioEventLoop 的线程上下文中执行,确保了任务的顺序执行。 -
EventLoopGroup 本质是一个线程池,负责管理EventLoop。其主要作用有从线程池挑选一个EventLoop进行channe的注册或者提交一个任务、关闭不再使用的 Channel、释放 Selector 和其他相关资源确保应用程序的干净退出。
服务编排层
-
ChannelHandler主要分为两类,InboundHandler和OutboundHandler
-
InboundHandler用于处理从网络流入(inbound)的数据和事件。帮助我们处理接收的数据、连接建立、断开等事件。核心方法有channelActive(网络连接建立成功时会调用)、channelInactive(网络连接断连时调用)、channelRead(接受到数据时调用)、exceptionCaught(如果在处理事件或数据时发生异常,该方法会被调用。可以在这里捕获并处理异常,防止应用程序崩溃。)
-
OutboundHandler用于处理从应用程序流向网络的出站(outbound)操作,如写入数据,发起连接,关闭连接等核心方法有。
write(写入数据到channel时调用),flush(将缓冲区所有未写入数据立即发送出去),connect(尝试建立网络连接时调用)。
-
ChannelHandlerContext是handler与Netty内部机制交互的主要方式,它使得 handler 可以在不直接访问其他handler的情况下,协同处理I/O事件和数据。同时也是每个ChannelHandler在处理事件时的上下文环境,可以获取到Pipeline、Channel、Allocator等对象。
-
ChannelPipeline Netty中的关键组件,是一个处理网络I/O事件和数据的有序链表。ChannelPipeline负责将入站(inbound)和出站(outbound)事件分发给链中的各个ChannelHandler,实现了事件驱动的网络编程模型。每个ChannelHandler都有一个唯一的ChannelHandlerContext,用于与ChannelPipeline交互。
运行流程
1.服务端启动的时把ServerSocketChannel注册到boss EventLoopGroup中某一个EventLoop上,暂时把这个EventLoop叫做server EventLoop;
2.当 serverEventLoop中监听到有建立网络连接的事件后会把底层的SocketChannel和serverSocketChannel封装成为NioSocketChannel;
3.开始把自定义的ChannelHandler加载到NioSocketChannel 里的pipeline中,然后把该NioSocketChannel注册到worker EventLoopGroup中某一个EventLoop上,暂时把这个EventLoop叫做worker EventLoop;
4.worker EventLoop开始监听NioSocketChannel上所有网络事件;
0 条评论