简介:
Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,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服务器和客户端的代码:
- 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适用于许多不同的网络应用场景,特别是对于高性能、并发连接和实时性要求较高的场景,例如:
- 通信服务:例如即时通讯、聊天室等。
- 实时数据传输:例如在线游戏、股票交易等。
- 网络代理:例如代理服务器、反向代理等。
- 高性能服务器:例如Web服务器、RPC服务器等。
总结:Netty是一个高性能、灵活可扩展的网络编程框架,具有异步非阻塞和零拷贝等优秀特性。它在许多实际生产环境中得到了广泛的应用,适合构建高性能的网络应用程序。开发者在使用Netty时应充分了解其特性和适用场景,合理选择和使用其中的功能,以确保项目的高效运行。