一.什么是Netty
查看官网netty.io/ 可以看到netty的简介
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.
Netty——是一个异步事件驱动的网络应用程序框架。 Netty 的主要目的是构建基于 NIO(或者是 NIO.2)的高性能协议服务器/客户端
二.为什么使用netty
如果使用原生jdk的话,
1)NIO 的类库和 API 繁杂,使用麻烦:你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。
2)需要具备其他的额外技能做铺垫:例如熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的 NIO 程序。
3)可靠性能力补齐,开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等。NIO 编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大。
4)JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。
三.使用JDK的NIO完成socket通信
原生的nio主要是三大件,分别是Selector,Channel和ByteBuffer,原理图
service端开启一个线程运行selector, 等待client端的接入,当监听到client接入时,在TCP三次握手后,会建立对应的通道(既channel),然后把channel注册到这个Selector上,并注册关注的事件是"读/写事件",当client需要发送数据时,把数据封装成ByteBuffer,写入channel中,selector感应到channel中的"读/写事件"后,就会处理请求,处理后把需要返回的数据同样封装成ByteBuffer写进channel,传递给client端,代码如下
1)service端
//创建Selector
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket socket = serverSocketChannel.socket();
socket.bind(new InetSocketAddress(9999));
/**
* 把channel注册到selector'上,注册为"连接"
*/
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int numbers = selector.select();
System.out.println("numbers:" + numbers);
Set<SelectionKey> keys = selector.selectedKeys();
System.out.println("SelectionKey:" + keys);
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
/** 获得连接*/
if (selectionKey.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = channel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
iterator.remove();/**必须移除*/
System.out.println("获得客户端连接:" + socketChannel);
}
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
int byteRead = 0;
final ByteBuffer byteBuffer = ByteBuffer.allocate(5);
while (true) {
final int read = socketChannel.read(byteBuffer);
if (read == 0) {
break;
}
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteRead += read;
}
System.out.println("读取:" + byteRead + ",来源于:" + socketChannel);
iterator.remove();/**必须移除*/
}
}
}
2.clinet端代码
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
final Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 6666));
System.out.println("客户端发送连接请求"+ LocalDateTime.now());
while (true) {
selector.select();
final Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("客户端监听到事件"+ LocalDateTime.now());
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isConnectable()) {
//连接
final SocketChannel client = (SocketChannel) selectionKey.channel();
if (client.isConnectionPending()) {
client.finishConnect();
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(("client connect:" + LocalDateTime.now()).getBytes());
byteBuffer.flip();
client.write(byteBuffer);
ExecutorService executorService = newSingleThreadExecutor(defaultThreadFactory());
executorService.submit(() -> {
while (true) {
final InputStreamReader inputStreamReader = new InputStreamReader(System.in);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
try {
final String readLine = bufferedReader.readLine();
byteBuffer.clear();
byteBuffer.put(readLine.getBytes());
byteBuffer.flip();
client.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
final SocketChannel client = (SocketChannel) selectionKey.channel();
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
final int cout = client.read(byteBuffer);
if (cout > 0) {
final String receivemsg = new String(byteBuffer.array(), 0, cout);
System.out.println("收到的数据:" + receivemsg);
}
}
}
selectionKeys.clear();
}
如上是JDK原生的NIO网络编程,可以看到需要自己数量掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。对新手很不友好,所以再次基础上,netty实现了对jdk的nio的封装,并加入了自己的一些优化,让开发着更容易上手使用
四.注意
netty仅仅是封装JDK原生的NIO这一块,并不是自己开发,本质上还是使用JDK的NIO这一套
五.Netty初使用
1-1.服务端-启动类
public class MyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
//bossGroup.setIoRatio(20); 设置io比例为20%,即非io是io的四倍,执行非io的时间也是io的时间的四倍
EventLoopGroup workGroup = new NioEventLoopGroup(1);
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 源码解析
serverBootstrap
.group(bossGroup, workGroup)
.option(ChannelOption.SO_BACKLOG, 1)
.channel(NioServerSocketChannel.class)
.handler(new MyServerHandler())
/**
* todo 这里需要我们自定义编写处理类,处理netty中的数据
*/
.childHandler(new MyTcpServerInitializer());
try {
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
1-2服务端-自定义ChannelInitializer--MyTcpServerInitializer
public class MyTcpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
System.out.println("netty 执行MyTcpServerInitializer的initChannel方法,socketChannel:" + socketChannel.toString());
//心跳
// pipeline.addLast("idleStateHandler", new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("decode", new MyDecode());
pipeline.addLast("encode", new MyEncode());
//在这里添加具体的handler
pipeline.addLast(new MyTcpServerHandler());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
1-3.添加具体的handler处理类
public class MyTcpServerHandler extends SimpleChannelInboundHandler<Person> {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Person person) throws Exception {
System.out.println("服务器端接收的数据" + person);
ctx.writeAndFlush(person);
/**
* 给客户端回数据
*/
final ByteBuf byteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString(), CharsetUtil.UTF_8);
ctx.writeAndFlush(byteBuf);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
}
2-1. 客户端-启动类
public class TestTcpClient {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
/**
* todo 这里需要我们自定义编写处理类,处理netty中的数据
*/
.handler(new MyTcpClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899);
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
eventLoopGroup.shutdownGracefully();
}
}
}
2-2.客户端--自定义ChannelInitializer--MyTcpClientInitializer
public class MyTcpClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//添加具体handler处理类
pipeline.addLast(new MyTcpClinetHandler());
}
}
2-3.客户端--自定义handler
public class MyTcpClinetHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//todo 发送数据
final ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
byteBuf.writeShort(4);
byteBuf.writeBytes(("pqs" + 1).getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(byteBuf);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println(System.currentTimeMillis() + "==>channelRead0");
short length = msg.readShort();
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer);
String message = new String(buffer, CharsetUtil.UTF_8);
System.out.println("client 接收消息" + message);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println(System.currentTimeMillis() + "==>channelReadComplete");
super.channelReadComplete(ctx);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(System.currentTimeMillis() + "==>channelRegistered");
super.channelRegistered(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(System.currentTimeMillis() + "==>channelUnregistered");
super.channelUnregistered(ctx);
}
}
六.Netty核心模块
通过对 "Netty初使用"中可以看到,netty主要有以下极大部分
- BootStrap
- BossGroup/WorkGroup
- Channel
- ChannelHandler
- ChannelPipline
- EventLoop
- EventLoopGroup 这几大核心部分,可以用下图说明
BootStrap
Netty 应用程序通过设置 bootstrap(引导)类的开始,该类提供了一个 用于应用程序网络层配置的容器。有两种类型的引导:一种用于客户端(简单地称为 Bootstrap),而另一种 (ServerBootstrap)用于服务器。 引导类作为netty承上启下作用,能添加 BossGroup/WorkGroup,设置Channel,添加ChannelHandler,并绑定端口/连接服务端,进而启动netty进程
channel
基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提 供的原语。在基于 Java 的网络编程中,其基本的构造是 class Socket。Netty 的 Channel 接 口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。此外,Channel 也是拥有许多 预定义的、专门化实现的广泛类层次结构的channel类。
ChannelHandler
ChannelHandler 支持很多协议,并且提供用于数据处理的容器。我们已经知道 ChannelHandler 由特定事件触发。 ChannelHandler 可用于几乎所有的动作,包括将一个对象转为字节(或相反),执行过程中抛出的异常处理。
常用的一个接口是 ChannelInboundHandler,这个类型接收到入站事件(包括接收到的数据)可以处理应用程序逻辑。当你需要提供响应时,你也可以从 ChannelInboundHandler 冲刷数据。一句话,业务逻辑经常存活于一个或者多个 ChannelInboundHandler。
ChannelPipline
ChannelPipeline 提供了一个容器给 ChannelHandler 链并提供了一个API 用于管理沿着链入站和出站事件的流动。每个 Channel 都有自己的ChannelPipeline,当 Channel 创建时自动创建的。 ChannelHandler 是如何安装在 ChannelPipeline? 主要是实现了ChannelHandler 的抽象 ChannelInitializer。ChannelInitializer子类 通过 ServerBootstrap 进行注册。当它的方法 initChannel() 被调用时,这个对象将安装自定义的 ChannelHandler 集到 pipeline。当这个操作完成时,ChannelInitializer 子类则 从 ChannelPipeline 自动删除自身。
EventLoop
EventLoop是netty处理数据的核心,每一个EventLoop里都有一个Selector和taskQueue, 通过启动一个线程,不停的做三件事
- 1.获取select中的的事件
- 2.处理这些事件
- 3.处理taskQueue中的任务
EventLoopGroup
是EventLoop的集合,每个EventLoopGroup中包含一个或多个EventLoop
七.总结
本篇文章主要简要介绍Netty是什么?怎么用?并通过传统NIO编程和Netty编程,熟悉Netty的基本用法,并列举Netty中常用组件,在接下来文章中会深入源码,逐步深入解析各个模块的原理.