Stay focused and work hard!
揭开 Netty 面纱
Netty
是什么?
在 Netty
的官网首页有这样一句话:
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
翻译:Netty 是一个异步事件驱动的网络应用程序框架用于快速开发可维护的高性能协议服务器和客户端
。
总结:
- 本质:网络应用程序框架。
- 实现:异步、事件驱动。
- 特性:高性能、可维护、快速开发。
- 用途:开发服务器和客户端。
Netty
结构图(摘自官网)
整体分为三部分:
- Core:Netty 的核心。
- Transport:支持的传输协议。
- Protocol Support:支持的应用层协议。
Netty 使用示例
摘自 Netty
源码!
引入 Maven
依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
Netty 实现客户端和服务端
服务端代码
/**
* Echoes back any received data from a client.
*/
public final class EchoServer {
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
// 两种设置 keepalive 风格
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(NioChannelOption.SO_KEEPALIVE, true)
// 切换到 unpooled 的方式之一
.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端处理逻辑代码
/**
* Handler implementation for the echo server.
*/
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
客户端代码
/**
* Sends one message when a connection is open and echoes back any received
* data to the server. Simply put, the echo client initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public final class EchoClient {
static final boolean SSL = System.getProperty("ssl") != null;
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
public static void main(String[] args) throws Exception {
// Configure SSL.git
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
}
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
}
}
客户端处理逻辑代码
/**
* Handler implementation for the echo client. It initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
private final ByteBuf firstMessage;
/**
* Creates a client-side handler.
*/
public EchoClientHandler() {
firstMessage = Unpooled.buffer(EchoClient.SIZE);
for (int i = 0; i < firstMessage.capacity(); i ++) {
firstMessage.writeByte((byte) i);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
Netty 实现 http 服务端
服务端代码
/**
* An HTTP server that sends back the content of the received HTTP request
* in a pretty plaintext form.
*/
public final class HttpHelloWorldServer {
public static void main(String[] args) throws Exception {
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new HttpServerCodec());
p.addLast(new HttpServerExpectContinueHandler());
p.addLast(new HttpHelloWorldServerHandler());
}
});
Channel ch = b.bind(8080).sync().channel();
System.err.println("Open your web browser and navigate to " +
"http://127.0.0.1:8080");
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端处理逻辑代码
public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> {
private static final byte[] CONTENT = "helloworld".getBytes();
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK,
Unpooled.wrappedBuffer(CONTENT));
response.headers()
.set(CONTENT_TYPE, TEXT_PLAIN)
.setInt(CONTENT_LENGTH, response.content().readableBytes());
ChannelFuture f = ctx.write(response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
为什么不直接用 JDK NIO?
当我们选择一个方案而不选择另外一个方案的时候,往往有两点原因:
- 做得更多(功能)
- 做得更好(性能)
Netty 做得更多
支持常见应用层协议
支持比如 HTTP、WebSocket、gzip
等协议,如果不支持的话,那么传输的编码和解码功能就得我们自己实现。
解决传输问题:粘包、半包现象
当数据在网络上传输的时候,难免会出现一些问题,比如粘包、半包问题,这些问题 Netty
都帮我们解决好了。
支持流量整型
在传输的过程中,如果我们需要做更多定制的功能,比如流量控制、黑白名单等功能,Netty
也帮我们做了。
完善的断连、
Idle
等异常处理
Netty
处理了各种各样乱七八糟的情况。
Netty 做得更好
Netty 做得更好之一:规避
JDK NIO Bug
(1)例子 1
官方的处理方式是暂时不修复。
而 Netty
是解决了这个问题。
(2)例子 2
官方在 JDK 12
才修复了这个 Bug
;
而 Netty
直接避开这个问题:
Netty
做得更好之二:API
更友好更强大
JDK
的NIO
一些API
不够友好,功能薄弱,比如NIO
的ByteBuffer
的设计简直是反人类,而Netty
对其进行了增强,ByteBuf
,更加友好,并且功能更加强大(比如动态扩容);- 除了
NIO
之外,也提供了一些其他增强:JDK
的ThreadLocal
->Netty
的FastThreadLocal
,在高并发场景下,性能会更好。
Netty
做得更好之三:隔离变化、屏蔽细节
- 屏蔽
JDK NIO
的实现变化:nio -> nio2(aio) -> ...,Netty
将其抽象出来,这样版本升级的时候,不用修改太多代码。 - 屏蔽
JDK NIO
的实现细节:Netty
把一些实现细节都做帮我们做好了,所以使用起来非常简单。
直接用
JDK NIO
实现的可能性
- 大概写多少行代码?
- 可能面对的问题
- 踏平多少
JDK NIO Bug
?
- 未来能维护多久?
Netty
已经维护 18 年(from 2004 ~ 2022)
为什么独选 Netty
为什么不选择
Apache Mina
?
为什么不选
Sun
的Grizzly
为什么不选
Apple SwiftNIO、ACE
等?
这些都不是 Java
语言方面的,不考虑。
为什么不选
Cindy
等?
生命周期不长。
为什么不选
Tomcat、Jetty
?
通信层功能并没有独立出来。
疑问:为什么
Tomcat
不选择Netty
?
历史原因,Tomcat
是 1990 年左右出现的,而 Netty
是 2004 年才出现的,有很多类似的例子都是因为历史原因。
Netty 的前尘往事
题外 1:废弃
Netty 5.0
原因
题外 2:与
Apache Mina
的关系
Netty 的现状与趋势
社区现状
一些典型项目
- 数据库:
Cassandra
- 大数据处理:
Spark、Hadoop
- 消息队列:
Rocket MQ
- 检索:
ElasticSearch
- 框架:
gRPC、Apche Dubbo、Spring 5
- 分布式协调器:
Zookeeper
- 工具类:
async-http-client
Netty
的趋势
- 更多流行协议的支持
- 紧跟
JDK
新功能的步伐 - 更多易用、人性化的功能
- IP 地址黑白名单、流量整形等
- 越来越多的应用使用
Netty