Netty框架介绍

简介:

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCPUDP的socket服务开发。

“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTPSMTPHTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性

核心组件:

Netty是一个强大的网络编程框架,其主要组件包括Channel、EventLoop、ChannelHandler和ChannelPipeline。以下是对每个组件的详细介绍:

1. **Channel(通道)**:
   - Channel是Netty网络编程的基本概念,表示一个网络连接,可以用于读取和写入数据。
   - 它提供了异步的I/O操作,支持非阻塞的网络通信,从而实现了高并发和低延迟。
   - Channel可以绑定到一个EventLoop,所有的I/O事件都由绑定的EventLoop处理。

2. **EventLoop(事件循环)**:
   - EventLoop是Netty的核心组件,负责处理I/O事件,并驱动Channel的数据读写。
   - 每个Channel都会与一个EventLoop绑定,所有的I/O事件都由绑定的EventLoop处理,从而实现了单线程处理I/O事件,避免了线程之间的竞争和锁的使用。
   - EventLoop采用异步非阻塞的方式,可以处理大量的并发连接而不需要创建额外的线程,从而提高了并发性能和吞吐量。

3. **ChannelHandler(通道处理器)**:
   - ChannelHandler是Netty的事件处理器,用于处理Channel的各种事件,例如连接事件、数据读写事件等。
   - 它是Netty网络编程的核心组件,负责实际的数据处理和业务逻辑。
   - ChannelHandler可以添加到ChannelPipeline中,形成处理器链,按顺序依次处理事件。
   - Netty提供了许多预定义的ChannelHandler,也可以自定义实现以满足特定的业务需求。

4. **ChannelPipeline(通道处理器链)**:
   - ChannelPipeline是ChannelHandler的容器,用于处理Channel的入站和出站事件。
   - 它是Netty的另一个核心组件,负责管理和调度所有添加到ChannelPipeline中的ChannelHandler。
   - ChannelPipeline是一个双向链表,包含了一系列的ChannelHandler,用于处理不同类型的事件。
   - 当有数据读写事件发生时,ChannelPipeline中的每个ChannelHandler将按顺序依次处理事件,形成了处理器链。

5.**ByteBuf (Netty的数据容器,提供了高效的数据读写操作) **

通过合理配置Channel、EventLoop、ChannelHandler和ChannelPipeline,开发者可以构建高性能、灵活可扩展的网络应用程序。Netty的主要组件提供了丰富的功能和灵活的扩展性,适用于多种网络应用场景。

Netty具体使用示例:

下面是一个使用Netty的简单Java代码示例,展示了如何创建一个Echo服务器和客户端。Echo服务器接收客户端发送的消息并将其原样回复给客户端。

首先,确保你已经添加了Netty的依赖到项目中。在Maven项目中,可以添加以下依赖到pom.xml文件:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.0.Final</version> <!-- 替换为你所使用的Netty版本 -->
</dependency>

接下来,我们创建Echo服务器和客户端的代码:

  1. Echo服务器(EchoServer.java)
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class EchoServer {
    private static final int PORT = 8888;

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new EchoServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            serverBootstrap.bind(PORT).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

class EchoServerHandler extends io.netty.channel.ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String message = (String) msg;
        System.out.println("Received message: " + message);
        ctx.writeAndFlush(message);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Echo客户端(EchoClient.java)

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class EchoClient {
    private static final String HOST = "localhost";
    private static final int PORT = 8888;

    public static void main(String[] args) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new StringEncoder());
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect(HOST, PORT).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

class EchoClientHandler extends io.netty.channel.ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("Hello, Netty!");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String message = (String) msg;
        System.out.println("Received from server: " + message);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

以上代码演示了一个简单的Echo服务器和客户端。当客户端发送消息给服务器时,服务器会将接收到的消息原样回复给客户端。这里的Echo服务器和客户端都使用了Netty的API,通过StringDecoder和StringEncoder来处理字符串消息的解码和编码。在实际应用中,可以根据具体的业务需求进行相应的处理和定制。

Netty如何解决空轮询bug:

若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%。

这个臭名昭著的epoll bug,是 JDK NIO的BUG,官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7、JDK1.8版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。该BUG以及与该BUG相关的问题单可以参见以下链接内容:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933
Netty的解决办法总览:
1、对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。
2、重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。
 

Netty的高性能原因:

Netty在高性能网络编程中采用了两个重要的优化技术:零拷贝和同步非阻塞模型。下面我将详细解读这两个原理:

## 1. 零拷贝(Zero-Copy):

在传统的数据传输过程中,通常涉及从一个缓冲区(例如Java的ByteBuffer)拷贝数据到另一个缓冲区(例如操作系统的内核缓冲区)中,然后再拷贝到网络设备中进行发送。这样的数据拷贝操作会涉及多次内存复制,增加了CPU的负担和数据传输的延迟。

Netty采用了零拷贝技术来避免不必要的数据拷贝。具体来说,它使用了两种方式实现零拷贝:

### 1.1. 使用Direct Memory(直接内存):

Netty的ByteBuf默认使用Direct Memory(直接内存)来存储数据。Direct Memory是JVM外部的堆外内存,它由操作系统直接管理。在数据传输时,Netty直接将Direct Memory中的数据发送到网络设备,避免了数据从Java堆内存到Direct Memory的拷贝。

### 1.2. 使用Composite ByteBuf:

Netty支持Composite ByteBuf,它是多个ByteBuf的组合,形成一个逻辑上的复合缓冲区。使用Composite ByteBuf时,多个ByteBuf的数据可以在逻辑上连续存储,避免了数据在物理上的合并和拷贝,从而减少了数据合并的开销。

通过零拷贝技术,Netty在数据传输过程中减少了不必要的数据拷贝,提高了数据传输的效率和性能。

## 2. 同步非阻塞模型:

传统的网络编程通常采用同步阻塞模型,即每个I/O操作都会阻塞当前线程,直到操作完成。在高并发环境下,这种模型会导致大量的线程被阻塞,增加了线程的上下文切换开销,降低了系统的性能。

Netty采用了同步非阻塞模型,也称为异步非阻塞模型。在同步非阻塞模型中,网络I/O操作是异步的,不会阻塞当前线程。Netty通过EventLoopGroup和EventLoop来实现异步处理。EventLoopGroup包含了多个EventLoop,每个EventLoop负责处理一组连接上的I/O事件。当有I/O事件发生时,EventLoop会异步地通知相应的ChannelHandler进行处理,而不需要创建额外的线程。

采用同步非阻塞模型,Netty可以处理大量的并发连接而不需要创建大量的线程,减少了线程的上下文切换开销,提高了系统的并发性能和吞吐量。

## 3. 高级特性:

3.1. 内存池化:

Netty实现了内存池化机制,通过ByteBuf池来重用缓冲区,减少了内存的分配和回收开销。内存池化可以有效地降低内存碎片的产生,并且能够提高内存分配和回收的性能。这在高并发场景下非常重要,可以减少内存管理的开销,提高系统的性能。

3.2. 引用计数:

Netty的ByteBuf采用了引用计数的机制来管理内存的释放。每个ByteBuf都会有一个引用计数,当引用计数减为0时,ByteBuf的内存会被释放。引用计数机制确保了内存的正确释放,避免了内存泄漏的问题。

3.3. Composite ByteBuf:

Netty支持Composite ByteBuf,它是多个ByteBuf的组合,形成一个逻辑上的复合缓冲区。Composite ByteBuf可以在逻辑上连续存储多个ByteBuf的数据,避免了数据在物理上的合并和拷贝,从而减少了数据合并的开销。这在处理大量小数据块时非常高效。

3.4. 对象池化:

除了内存池化之外,Netty还支持对象池化。通过使用对象池,可以重用一些常用对象,如ByteBuf、ChannelHandlerContext等,避免了对象的频繁创建和销毁,减少了GC压力,提高了系统的性能和可靠性。

Netty的应用场景

Netty适用于许多不同的网络应用场景,特别是对于高性能、并发连接和实时性要求较高的场景,例如:

  1. 通信服务:例如即时通讯、聊天室等。
  2. 实时数据传输:例如在线游戏、股票交易等。
  3. 网络代理:例如代理服务器、反向代理等。
  4. 高性能服务器:例如Web服务器、RPC服务器等。

总结:Netty是一个高性能、灵活可扩展的网络编程框架,具有异步非阻塞和零拷贝等优秀特性。它在许多实际生产环境中得到了广泛的应用,适合构建高性能的网络应用程序。开发者在使用Netty时应充分了解其特性和适用场景,合理选择和使用其中的功能,以确保项目的高效运行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值