Netty基本介绍!

官网:https://netty.io/

Netty 是一个基于 JAVA NIO 类库的异步通信框架

用于创建异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性的网络客户端和服务器端

Netty主要针对在 TCP协议下,面向Clients端的高并发应用。

  • 或者Peer-to-Peer场景下的 大量数据持续传输 的应用。

Netty本质是一个 NIO框架,适用于 服务器通讯 相关的多种应用场景。

在这里插入图片描述

原生NIO存在的问题

NIO 的类库和 API 繁杂,使用麻烦。

要熟悉 Java 多线程编程

  • 因为 NIO 编程涉及到 Reactor 模式
    • 必须对多线程和网络编程非常熟悉,才能编写出高质量的 NIO 程序。

开发工作量和难度都非常大:

  • 例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。

JDK NIO 的 Bug:

  • 例如臭名昭著的 Epoll Bug:它会导致 Selector 空轮询,最终导致 CPU 100%。
    • 直到 JDK 1.7 版本该问题仍旧存在,没有被根本解决。

应用场景

互联网行业:

  • 在 分布式系统中,各个节点之间需要 远程服务调用,高性能的 RPC 框架必不可少
  • Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用

典型的应用有:

  • 阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信
  • Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信

游戏行业

  • Netty 作为高性能的基础通信组件,提供了 TCP/UDPHTTP 协议栈

    • 方便定制和开发私有协议栈,账号登录服务器
  • 地图服务器之间可以方便的通过 Netty 进行高性能的通信

Netty案例

搭建 Server 端服务器:

  • 创建 ServerBootstrap 对象。
  • ServerBootstrap配置eventLoopGroup
    • BossGroup : 处理连接请求,用于创建 Channel 转发给 WorkerGroup。
    • workerGroup : 处理已建立的连接。
  • 配置服务器处理的 Channel 连接的类型 ,包括 NIO 类型 , EPOLL 类型等。
  • 配置整个处理流程的 ChannelHandler,用于特定的处理。
  • Server 端 bind() 一个端口 , 同时阻塞等待绑定完成。
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 第一步 :准备后续处理业务的 Handler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端连接会触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("S2 : 服务端建立连接,触发 Active .....");
}

/**
* 客户端发消息会触发
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器收到消息: " + msg.toString());
// 接收到消息后,返回一条消息给客户端
ctx.write(msg + "World!");
ctx.flush();
}

/**
* 发生异常触发
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}


// 第二步 :创建 Initializer 用于初始化 Channel
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加编解码
socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
// 此处将 Handler 绑定到 ChannelPipeline 中
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}

// 第三步 : 准备好 Netty 客户端
public class NettyServer extends Thread {

public void run() {

// S1 : 准备 Boss 管理线程组 和 Worker 工作线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup(200);

// S2 : 绑定 Group , 绑定渠道 , 绑定 Handler
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer());

try {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8090);

// S3 : 绑定对应端口和地址,用于后续阻塞监听
ChannelFuture future = bootstrap.bind(socketAddress).sync();
System.out.println("S1 : 服务端构建完成 .....");
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//关闭主线程组
bossGroup.shutdownGracefully();
//关闭工作线程组
workGroup.shutdownGracefully();
}
}
}

搭建客户端:

  • 不同于 Server ,Client 端只需要一个 EventLoopGroup
  • 创建 Bootstrap ,绑定 Group / channel / handler
  • 通过 writeAndFlush 发送消息。
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
49
50
51
52
53
54
55
56
57
58
59
60
61
// 第一步 :准备客户端的 Handler 接收消息
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("S2 : 客户端建立连接,触发 Active .....");
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客户端收到消息: " + msg.toString());
}

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


// 第二步 :创建 Initializer 用于初始化 Channel
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("decoder", new StringDecoder());
socketChannel.pipeline().addLast("encoder", new StringEncoder());
// 此处将 Handler 绑定到 ChannelPipeline 中
socketChannel.pipeline().addLast(new NettyClientHandler());
}
}


// 第三步 : 准备启动类
public class NettyClient extends Thread {

public void run() {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new NettyClientInitializer());
try {

ChannelFuture future = bootstrap.connect("127.0.0.1", 8090).sync();
System.out.println("S2 : 客户端构建完成 .....");

for (int i = 0; i < 10; i++) {
String message = "第" + i + "条消息 , " + "Hello ";
future.channel().writeAndFlush(message);
Thread.sleep(1000); // 暂停一秒钟
}

future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}

发起请求:

  • 通过2个线程模拟消息的发送和接收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DemoService {

public static void main(String[] args) throws InterruptedException {
System.out.println("Start :开始整个 Netty 搭建流程");

// S1 : 分别开2个线程模拟 Server 和 Client 的开启
NettyServer server = new NettyServer();
server.start();

NettyClient client = new NettyClient();
client.start();
}

}