Netty系列整体栏目
内容 | 链接地址 |
---|---|
【一】深入理解网络通信基本原理和tcp/ip协议 | https://zhenghuisheng.blog.csdn.net/article/details/136359640 |
【二】深入理解Socket本质和BIO | https://zhenghuisheng.blog.csdn.net/article/details/136549478 |
【三】深入理解NIO的基本原理和底层实现 | https://zhenghuisheng.blog.csdn.net/article/details/138451491 |
【四】深入理解反应堆模式的种类和具体实现 | https://zhenghuisheng.blog.csdn.net/article/details/140113199 |
【五】深入理解直接内存与零拷贝 | https://zhenghuisheng.blog.csdn.net/article/details/140721001 |
【六】select、poll和epoll多路复用的区别 | https://zhenghuisheng.blog.csdn.net/article/details/140795733 |
【七】深入理解和使用Netty中组件 | https://zhenghuisheng.blog.csdn.net/article/details/141166098 |
深入理解和使用Netty中组件
- 一、netty的初步了解和使用
- 1. netty基本组件初步了解
- 2. netty的基本使用
- 2.1. netty服务端实现
- 2.2 netty客户端编写
- 2.3 调试
一、netty的初步了解和使用
在了解netty组件之前,最好先查看一下本人之前的netty文章,结合nio以及前面的socket,对了解netty有很大的帮助,因为netty基于nio。
1. netty基本组件初步了解
BootStrap: netty中的主启动类,类似于Main方法。在客户端中用BootStrap,在服务端中用ServerBootStrap
Channel:用于封装Socket的实现类,类似于nio中的SocketChannel和ServerSocketChannel
EventLoop:表示的是事件循环,类似于那个nio中反应堆模式核心组件的selector,用于事件注册和循环。在这里可以理解为一个线程,用于做循环处理的。那么EventLoopGroup就是一个线程组,用于管理EventLoop
事件:在官网中已经对netty做出过解释,netty是一个异步事件驱动的网络框架。也就是说在netty中,所有的请求和响应以及要做的事情等都被统一的定义成事件,如读取数据,写数据等。事件又分为两种,分别是入站事件和出站事件
ChannelHandler:ChannelHandle就是用于事件的触发器,根据不同的事件触发不同的动作然后做出对应的响应。如在发送数据时,需要对数据进行加密和压缩的动作,因此分别可以给加密一个事件,给压缩一个事件。可以通过ChannelHandle中的方法,定制化一个具体动作的事件
ChannelPipeline:上面讲解到了加密和压缩两个事件,为了满足这两个事件都被触发,因此就引入了一个ChannelPipeline 的管道,可以将所有的事件阻塞。就是一个简单的责任链模式,当事件在这个 channelPipeline 中执行到哪个ChannelHandle时,则触发对应的动作。
ChannelFuture:依旧是加密和压缩两个事件,需要先将数据加密,后将数据压缩,为了保证事件实现异步的同时,也需要实现事件的顺序性,因此引入了这个ChannelFuture,和线程中的Future是一样的,通过get方法阻塞拿到上一个异步事件的结果
2. netty的基本使用
通过上面在了解了netty的基本组件之后,接下来通过netty内部的一些接口和类,实现一个简单的客户端和服务端之间的通信。在使用netty之前,先说一下以下使用netty的版本,版本为 4.1.42.Fanal
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
2.1. netty服务端实现
首先定义一个NettyServer类,定义服务端这边的端口为8888,然后定义一个处理循环事件的事件组EventLoopGroup,以及定义一个 ServerBootstrap 服务端这边的主启动类,然后主启动类绑定这个事件组,并将封装socket的类定义为 NioServerSocketChannel ,这样就实现了一个基础的netty服务端。
然后netty服务端需要加入对应的事件和处理对应的事件,需要将全部的Handler事件作为子事件加入,并且将对应的handler事件加入到pipeline 中,后面会通过这个EventLoopGrop 事件处理器中的线程通过责任链的模式去处理这些事件。每一个Handler可以交给开发人员去定制化,因此后续自定义实现了一个 NettyServerChannelHandler 的事件。最后需要注意的是,在绑定和获取结果都需要通过阻塞,因为netty底层实现的接口基本是基于异步的。
package com.ruoyi.web.controller.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
private static Integer port = 8888;
public static void main(String[] args) {
// 创建自定义事件组,一个线程循环的处理事件,类似与nio的selector
EventLoopGroup loopGroup = new NioEventLoopGroup();
try{
//创建服务端主启动类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(loopGroup) //绑定组
.channel(NioServerSocketChannel.class)
.localAddress(port) //绑定端口
.childHandler(new ChannelInitializer<SocketChannel>() { //初始化channel,将事件加入
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerChannelHandler()); //将事件加入到管道中
}
});
//完成绑定,内部如果异步实现bind,因此需要阻塞拿到返回结果
ChannelFuture future = bootstrap.bind().sync();
//关闭future时也需要阻塞,内部也采用的是异步操作
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
//处理中断异常
loopGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
接下来就是服务端的定制化Handler事件,用于服务端具体的处理事件的动作。如下面这个 NettyServerChannelHandler 类,实现了 ChannelInboundHandlerAdapter 的抽象类,其父类就是一个公共的 ChannelHandler ,然后入通过具体的适配器模式,提供一个可以直接使用的类
然后在这个适配类中,里面有着各种具体的实现方法。最主要的就是这些注册事件,读事件,写事件等
接下来主要看看这个 NettyServerChannelHandler 实现类,在该类中继承了ChannelInboundHandlerAdapter抽象类,并且实现了一个read读事件的方法。在接收到客户端的数据之后,将接收到的数据打印出来即可。最后直接关闭此次连接,中断三次握手,将该连接作为短连接
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
//适配器类,用于处理具体的事件
@Slf4j
public class NettyServerChannelHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
log.info("接收到的数据为:" + byteBuf.toString(CharsetUtil.UTF_8));
super.channelRead(ctx, msg);
//短连接,直接关闭
ctx.close();
}
}
2.2 netty客户端编写
其代码编写和服务端基本相同,只有部分代码不同。首先就是客户端需要连接服务端,需要知道服务端的主机和端口号,需要主动的去绑定对应的地址,其次就是客户端这边使用的是BootStrap启动类,还有就是 服务端使用的childHandler客户端只需要具体的handler,最后就是在异步获取服务端使用的是bind阻塞,客户端使用的是connet阻塞。由于直接是在本地开发,所以直接设置这个主机为127.0.0.1本地的即可,端口为上面服务端设置的端口8888
public class NettyClient {
private static Integer port = 8888;
private static String host = "127.0.0.1";
public static void main(String[] args) {
// 创建自定义事件组,一个线程循环的处理事件,类似与nio的selector
EventLoopGroup loopGroup = new NioEventLoopGroup();
try{
//客户端只需要用bootStrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port)) //和服务器不一样,这里只需要连接服务器地址即可
.handler(new ChannelInitializer<SocketChannel>() { //和服务端不同,服务端使用的childHandler客户端只需要具体的handler即可
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientChannelHandler());
}
});
//完成绑定,内部如果异步实现bind,因此需要阻塞拿到返回结果
ChannelFuture future = bootstrap.connect().sync();
//关闭future时也需要阻塞,内部也采用的是异步操作
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}
}
}
接下来就是服务端这边的事件,由于服务端那边写的是接收消息,那么在客户端这些就写一个发送消息的,即重写一个 channelActive 方法,然后发送一条数据
public class NettyClientChannelHandler extends SimpleChannelInboundHandler {
protected NettyClientChannelHandler() {
super();
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
}
//tcp三次握手成功之后,触发的事件,可以用于做具体的业务需求
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty", CharsetUtil.UTF_8));
}
}
2.3 调试
在编写完上面的代码之后,先启动服务端的main方法,然后再启动客户端去连接客户端。可以看到服务端这边成功的接收到了数据
[nioEventLoopGroup-2-2] INFO c.r.w.c.n.NettyServerChannelHandler - [channelRead,22] 接收到的数据为:Hello Netty
服务端中的代码和客户端的代码很像,不仅仅是在这,在所有用了netty的组件中,底层都是这样子写的流程都是那一套,都需要主启动类BootStrap或者ServerBootStrap,然后执行事件的任务组EventGroupEvent,然后事件Handler,责任链模式执行事件的管道Pipeline,任务结果Future。有了组件之后,就能完成一个简单又高效的通信组件了