实现Netty Hello World 步骤
首先导入netty得依赖是必须的,我这次的helloworld只自定义客户端和服务端的处理器,引入ChannelInboundHandlerAdapter入栈handler适配器,简单业务部分,服务端会触发对于的生命周期方法,并打印到控制台,还会响应客户端一些信息,就这么多了,开始上代码。
1、导入netty依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.66.Final</version> <!-- 请替换为最新版本 -->
</dependency>
2、服务端代码展示
创建一个NettyServer 的类
主要部分有创建两个事件循环组,bossLoopGroup 和workerLoopGroup ,Netty使用EventLoopGroup来管理EventLoop线程
还需要创建一个服务端的启动对象ServerBootstrap ,这是一个链式编程的类,可以设置参数 通道类型、服务端处理器类型、连接客户端处理器类型
netty服务端主要由以下几个核心组件构成
Netty使用EventLoopGroup来管理EventLoop线程
Channel是Netty中的网络通信组件
Handler是Netty中处理网络事件的逻辑组件
Bootstrap是Netty中用于初始化和配置客户端或服务器端的启动类。
最后返回Future,Netty的IO操作是异步的,返回Future对象,开发者可以通过Future来获取操作的结果。
public class NettyServer {
public static void main(String[] args) {
// 创建Reactor
// 用来管理channel 监听事件 ,是无限循环的事件组(线程池)
EventLoopGroup bossLoopGroup = new NioEventLoopGroup();
EventLoopGroup workerLoopGroup = new NioEventLoopGroup();
// 服务端的启动对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 设置相关参数 这是一个链式编程
serverBootstrap.group(bossLoopGroup,workerLoopGroup)
// 声明通道类型
.channel(NioServerSocketChannel.class)
// 设置处理器 我这里设置了netty提供的Handler 处理器
.handler(new LoggingHandler(LogLevel.INFO))
// 定义客户连接端处理器的使用
// ChannelInitializer 通道处理化
// 可以自定义通道初始化器,如实现编码解码器时
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
// 需要处理的是客户端通道
// 通道代表的是 连接的角色 管道代表的是 处理业务的逻辑管理
// 管道相当与一个链表, 将不同的处理器连接起来,管理的是处理器的顺序
ch.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("服务端初始化完成");
// 启动需要设置端口 还需要设置是异步启动
try {
// 设置异步的future
ChannelFuture future = serverBootstrap.bind(8888).sync();
// 将关闭的通道也设置成异步的
// 阻塞finally 中的代码
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 优雅关闭
bossLoopGroup.shutdownGracefully();
workerLoopGroup.shutdownGracefully();
}
}
}
细分步骤
(1) 创建主从事件循环组
// 创建Reactor
// 用来管理channel 监听事件 ,是无限循环的事件组(线程池)
EventLoopGroup bossLoopGroup = new NioEventLoopGroup();
EventLoopGroup workerLoopGroup = new NioEventLoopGroup();
(2) 创建服务启动对象
// 服务端的启动对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 设置相关参数 这是一个链式编程
serverBootstrap.group(bossLoopGroup,workerLoopGroup)
// 声明通道类型
.channel(NioServerSocketChannel.class)
// 设置处理器 我这里设置了netty提供的Handler 处理器
.handler(new LoggingHandler(LogLevel.INFO))
// 定义客户连接端处理器的使用
// ChannelInitializer 通道处理化
// 可以自定义通道初始化器,如实现编码解码器时
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
// 需要处理的是客户端通道
// 通道代表的是 连接的角色 管道代表的是 处理业务的逻辑管理
// 管道相当与一个链表, 将不同的处理器连接起来,管理的是处理器的顺序
ch.pipeline().addLast(new NettyServerHandler());
}
});
(3) 设置服务器启动端口和关闭线程池也就是事件循环组
// 启动需要设置端口 还需要设置是异步启动
try {
// 设置异步的future
ChannelFuture future = serverBootstrap.bind(8888).sync();
// 将关闭的通道也设置成异步的
// 阻塞finally 中的代码
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 优雅关闭
bossLoopGroup.shutdownGracefully();
workerLoopGroup.shutdownGracefully();
}
自定义处理器
上面的NettyServerHandler类处理器下面定义了
需要继承ChannelInboundHandlerAdapter 还注意到还有一个SimpleChannelInboundHandler的子类,也可以直接继承,也能达到相同的目的。
handler的逻辑如下
a) 继承ChannelInboundHandlerAdapter, 此为netty提供的适配器
b) 重写其中的方法,channelActive 、channelRead、channelReadComplete,分别对应于通道创建、读事件发生、读事件完成三个时间点。
c) 方法的参数有一个 ChannelHandlerContext ,是处理器的上下文,除了获取通道和管道外,可以调用writeAndFlush() 直接写入数据
// 基础 ChannelInboundHandlerAdapter 入栈handler 相当于适配器
// 提供 不同时期使用的方法 ctrl + O 实现父类方法
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// 通道被启用 刚刚建立连接 的时候触发
// 业务使用一般 发送欢迎消息
/**
*
* @param ctx 通道处理器上下文
* @throws Exception
* 通道被启用 刚刚建立连接 的时候触发
* 可以整合使用过程中需要的参数 如: 通道channel 管道pipeline
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 直接写入字符串 底层还是获取通道--建立缓冲区 ---写入数据---缓冲区写入通道
System.out.println("channelActive done");
ctx.writeAndFlush("Welcome to Netty Server");
}
/**
*
* @param ctx 通道处理器上下文
* @param msg 读取的消息数据
* @throws Exception
* 读取消息时触发的方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
System.out.println("客户端消息:"+buf.toString(CharsetUtil.UTF_8));
// 获取客户端地址
System.out.println("客户端地址:"+ctx.channel().remoteAddress());
// 可以使用引用计数工具类ReferenceCountUtil的方式来释放ByteBuf
// 后面后使用SimpleChannelInboundHandler.channelRead0()方法
ReferenceCountUtil.release(msg);
}
/**
*
* @param ctx 通道处理器上下文
* @throws Exception
* 数据读取完成时触发的事件
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// Unpooled是 ByteBuf 和 String 之间方便转换的工具
ctx.writeAndFlush(Unpooled.copiedBuffer("hello client ",CharsetUtil.UTF_8));
}
}
3、客户端代码展示
客户端代码就相对比较简单,和服务器代码差不多,就些许不同比如事件循环组只需要一个就行
创建一个NettyClient 类
public class NettyClient {
public static void main(String[] args) {
// 客户端事件循环组 只需要一个
EventLoopGroup group = new NioEventLoopGroup();
// 客户端启动器
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("客户端初始化完成了");
try {
// 设置异步的future 需要连接的服务端参数
ChannelFuture future = bootstrap.connect("127.0.0.1", 8888).sync();
// 将关闭的通道也设置成异步的
// 阻塞finally 中的代码
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
}
同样的配方客户端自定义了处理器NettyClientHandler,接收服务端信息和响应服务端
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
*
* @param ctx 通道处理器上下文
* @throws Exception
* 通道被启用 刚刚建立连接 的时候触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(" client channelActive done ");
ctx.writeAndFlush(Unpooled.copiedBuffer("你好服务端,我是客户端", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(" client channelRead done ");
ByteBuf buf = (ByteBuf)msg;
System.out.println("server msg:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务端地址:"+ctx.channel().remoteAddress());
}
}
结语:本文中主要是本人通过看视频学习总结的,有错误的地方,还请指出。