Netty 概览

摘要:
Netty 特性、示例

Why Netty

Java 原生 NIO 的缺点

上一篇文章 NIO 概览 里已经说过了,直接转过来:Java 原生 NIO 编程的缺点

NIO 框架选型

我听说过的 NIO 框架有下面几种:
Mina
Netty
Grizzly(Sun GlassFish Application Server 底层NIO框架)
Tomcat NIO
xSocket(已停止维护)

但是都不熟悉,就没法拿测试数据来对比性能、稳定性等等指标。不过有一点是毫无疑问的,Netty 应用面是最广的。Netty 官网的 Adopters 上,基本包含了国内外说的上号的互联网公司。

选它准没错。

Netty

Netty 官网介绍

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

‘Quick and easy’ doesn’t mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

Netty components
netty-components

Design

  • Unified API for various transport types - blocking and non-blocking socket
  • Based on a flexible and extensible event model which allows clear separation of concerns
  • Highly customizable thread model - single thread, one or more thread pools such as SEDA (Staged Event-Driven Architecture)
  • True connectionless datagram socket support (since 3.1)

Performance

  • Better throughput, lower latency
  • Less resource consumption
  • Minimized unnecessary memory copy

用官方的话来说就是:Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。
简言之,Netty封装了JDK的NIO,让你用得更爽。

  • asynchronous - 异步,不仅仅是异步 IO(NIO,读写不阻塞线程),且所有 IO 操作都是异步进行的(Future、Promise)
  • event-driven - 事件驱动:事件产生,事件处理,事件流转 ChannelPipeline、ChannelHandlerContext、ChannelHandler
  • high performance - Reactor 模型(multiple reactor threads),并发编程,避免切换线程 EventLoop
  • protocol - 实现了各种协议,当然包括底层的粘包、拆包 ByteToMessageDecoder
  • Zero-copy - 不是系统层面的内核态(Kernel-space) 与 用户态(User-space) 之间的来回拷贝。Netty的 Zero-coyp 完全是在用户态(Java 层面)的, Zero-copy 更多的是说明尽量减少拷贝。

Netty 示例

EchoServer

EchoServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public final class EchoServer {

private static final int PORT = Integer.parseInt(System.getProperty("port", "7777"));

public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBoot = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
// log just for dev/test env, NOT product env
.handler(Commons.LOGGING_HANDLER_INFO)
.childHandler(new EchoServerChannelInitializer());

// Bind and start to accept incoming connections.
ChannelFuture channelFuture = serverBoot.bind("127.0.0.1", PORT).sync();
System.out.println("EchoServer 启动成功. port=" + PORT);

// Wait until the server socket is closed.
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
System.out.println("EchoServer 已关闭. port=" + PORT);
}
}

private static final EventLoopGroup BUSINESS_EVENT_LOOP_GROUP = new DefaultEventLoopGroup(10);
private static final EchoServerHandler ECHO_SERVER_HANDLER = new EchoServerHandler();

private static class EchoServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
// Decoders
.addLast("frameDecoder", new DelimiterBasedFrameDecoder(Commons.MAX_FRAME_LENGTH, Delimiters.lineDelimiter()))
.addLast("stringDecoder", Commons.STRING_DECODER)
// Encoder
.addLast("stringEncoder", Commons.STRING_ENCODER)
// log just for dev/test env, NOT product env
.addLast(Commons.LOGGING_HANDLER_INFO)
// business logic ChannelHandler
.addLast(BUSINESS_EVENT_LOOP_GROUP, "echoServerHandler", ECHO_SERVER_HANDLER);
}
}
}

EchoServerHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.printf("%s - %s\n", Thread.currentThread().getName(), msg);
ctx.write(msg + "\n");
}

@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();
}
}

终端上创建 3 个 client

1
nc 127.0.0.1 7777

EchoServer 打印日志如下,可以找到 Multiple Reactor Threads 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[INFO ][2019-02-15 16:36:28,704][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b] REGISTERED
[INFO ][2019-02-15 16:36:28,714][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b] BIND: /127.0.0.1:7777
EchoServer 启动成功. port=7777
[INFO ][2019-02-15 16:36:28,718][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] ACTIVE
[INFO ][2019-02-15 16:36:32,679][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] READ: [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929]
[INFO ][2019-02-15 16:36:32,680][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] READ COMPLETE
[INFO ][2019-02-15 16:36:32,693][nioEventLoopGroup-4-1](AbstractInternalLogger.log:150) - [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929] REGISTERED
[INFO ][2019-02-15 16:36:32,693][nioEventLoopGroup-4-1](AbstractInternalLogger.log:150) - [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929] ACTIVE
[INFO ][2019-02-15 16:36:43,486][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] READ: [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931]
[INFO ][2019-02-15 16:36:43,486][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] READ COMPLETE
[INFO ][2019-02-15 16:36:43,488][nioEventLoopGroup-4-2](AbstractInternalLogger.log:150) - [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931] REGISTERED
[INFO ][2019-02-15 16:36:43,488][nioEventLoopGroup-4-2](AbstractInternalLogger.log:150) - [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931] ACTIVE
[INFO ][2019-02-15 16:36:45,252][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] READ: [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932]
[INFO ][2019-02-15 16:36:45,252][nioEventLoopGroup-3-1](AbstractInternalLogger.log:150) - [id: 0xb265683b, L:/127.0.0.1:7777] READ COMPLETE
[INFO ][2019-02-15 16:36:45,254][nioEventLoopGroup-4-3](AbstractInternalLogger.log:150) - [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932] REGISTERED
[INFO ][2019-02-15 16:36:45,254][nioEventLoopGroup-4-3](AbstractInternalLogger.log:150) - [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932] ACTIVE
[INFO ][2019-02-15 16:36:49,127][nioEventLoopGroup-4-3](AbstractInternalLogger.log:150) - [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932] READ: nihao
defaultEventLoopGroup-2-3 - nihao
[INFO ][2019-02-15 16:36:49,128][nioEventLoopGroup-4-3](AbstractInternalLogger.log:150) - [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932] READ COMPLETE
[INFO ][2019-02-15 16:36:49,129][nioEventLoopGroup-4-3](AbstractInternalLogger.log:150) - [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932] WRITE: nihao

[INFO ][2019-02-15 16:36:49,131][nioEventLoopGroup-4-3](AbstractInternalLogger.log:150) - [id: 0xa5fb3d3c, L:/127.0.0.1:7777 - R:/127.0.0.1:59932] FLUSH
[INFO ][2019-02-15 16:36:55,344][nioEventLoopGroup-4-2](AbstractInternalLogger.log:150) - [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931] READ: 好的
defaultEventLoopGroup-2-2 - 好的
[INFO ][2019-02-15 16:36:55,345][nioEventLoopGroup-4-2](AbstractInternalLogger.log:150) - [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931] READ COMPLETE
[INFO ][2019-02-15 16:36:55,345][nioEventLoopGroup-4-2](AbstractInternalLogger.log:150) - [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931] WRITE: 好的

[INFO ][2019-02-15 16:36:55,346][nioEventLoopGroup-4-2](AbstractInternalLogger.log:150) - [id: 0xd7e4804f, L:/127.0.0.1:7777 - R:/127.0.0.1:59931] FLUSH
[INFO ][2019-02-15 16:36:59,188][nioEventLoopGroup-4-1](AbstractInternalLogger.log:150) - [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929] READ: 很好
defaultEventLoopGroup-2-1 - 很好
[INFO ][2019-02-15 16:36:59,188][nioEventLoopGroup-4-1](AbstractInternalLogger.log:150) - [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929] READ COMPLETE
[INFO ][2019-02-15 16:36:59,188][nioEventLoopGroup-4-1](AbstractInternalLogger.log:150) - [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929] WRITE: 很好

[INFO ][2019-02-15 16:36:59,189][nioEventLoopGroup-4-1](AbstractInternalLogger.log:150) - [id: 0xa8747d46, L:/127.0.0.1:7777 - R:/127.0.0.1:59929] FLUSH

EchoClient

EchoClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public final class EchoClient {

static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "7777"));

public static void main(String[] args) throws Exception {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap boot = new Bootstrap()
.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new EchoClientInitializer());

// connect server
ChannelFuture channelFuture = boot.connect(HOST, PORT).sync();
Channel channel = channelFuture.channel();
System.out.printf("EchoClient [%s] 已连接上 EchoServer [%s].\n",
channelFuture.channel().localAddress(), channel.remoteAddress());

// Wait until the connection is closed.
channel.closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
System.out.println("EchoClient 已关闭.");
}
}

private static final EchoClientHandler Echo_CLIENT_HANDLER = new EchoClientHandler();

private static class EchoClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
// Decoders
.addLast("frameDecoder", new DelimiterBasedFrameDecoder(Commons.MAX_FRAME_LENGTH, Delimiters.lineDelimiter()))
.addLast("stringDecoder", Commons.STRING_DECODER)
// Encoder
.addLast("stringEncoder", Commons.STRING_ENCODER)
// log just for dev/test env, NOT product env
.addLast(Commons.LOGGING_HANDLER_INFO)
// Then you can use a String instead of a ByteBuf as a message in your own ChannelHandler
.addLast("echoClientHandler", Echo_CLIENT_HANDLER);
}
}
}

EchoClientHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Sharable
public class EchoClientHandler extends ChannelInboundHandlerAdapter {

@Override
public void channelActive(ChannelHandlerContext ctx) {
String firstMessage = "hello!\n";
ByteBuf byteBuf = Unpooled.copiedBuffer(firstMessage.getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(byteBuf);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.printf("%s - %s\n", Thread.currentThread().getName(), msg);
try {
Thread.sleep(1 * 1000L);
} catch (InterruptedException e) {
}
ctx.write(msg + "\n");
}

@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();
}
}

参考

本文参考了互联网上大家的分享,就不一一列举,在此一并谢过。
也希望本文,能对大家有所帮助,若有错误,还请谅解、指正。