介绍:
netty是一个网络框架,截个简图。对比了官网和github上的介绍,还是github上的介绍比较简洁。
其实就一句话,翻译过来就是:Netty是一个事件驱动的异步网络应用程序框架。在直白点就是一个封装了nio的异步网络框架。详细介绍可以参考官网:
以下为模版示例代码的使用:
示例代码:
引入依赖:
xml
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.77.Final</version>
</dependency>
服务端启动模版:
java
public class NettyService {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
//这两个是netty自带的编码器,也可以自己实现自定义的编码器
.addLast(new StringDecoder())
.addLast(new StringEncoder())
//添加此参数后(这里是60秒),如果服务端60秒内没有响应数据,则ServerHandler类里的userEventTriggered方法会被调用
.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS))
//自定义netty数据处理类,在类中实现逻辑
.addLast("serverHandler",new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定ip和端口,sync方法会让当前线程阻塞,直到绑定操作完成
ChannelFuture future = bootstrap.bind("127.0.0.1", 8888).sync();
System.out.println("启动服务端,ip:" + "127.0.0.1" + ",端口:" + 8888);
// 获取已成功建立连接或者已绑定的Channel的实例,用于进行数据读写等后续操作。
Channel channel = future.channel();
//调用关闭逻辑,这里在服务关闭时被触发,确保资源释放完毕且程序能按预期顺序执行后续逻辑
channel.closeFuture().sync();
// 上诉两个可以这样写future.channel().closeFuture().sync();
} catch (Exception e) {
System.out.println("服务器启动错误:" + e);
} finally {
//服务停止后关闭事件循环组
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
服务端netty数据处理类,对应NettyService类中的这个对象:
java
/**
*
*继承的SimpleChannelInboundHandler类中可以是任意自定义类型,这里为方便演示就用string类型了
*/
public class ServerHandler extends SimpleChannelInboundHandler<String> {
/**
* 处理任何异常的入站或出站事件
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("执行exceptionCaught方法 --- 关闭服务端连接");
//发生异常,关闭通道
ctx.close();
}
/**
* 1
* 该方法可以设置 channel 的属性或者注册其他监听事件
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行handlerAdded方法 --- 注册监听事件或属性");
super.handlerAdded(ctx);
}
/**
* 2
* 在客户端和服务端的网络连接建立完成时触发
*
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行channelRegistered方法--成功注册服务并绑定端口连接");
super.channelRegistered(ctx);
}
/**
* 3
* 在客户端和服务端的网络连接建立完成且可以开始读写数据时触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行channelActive方法--成功建立两端连接");
super.channelActive(ctx);
}
/**
* 4
* 接收到一个新的入站数据块
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//获取客户端发送过来的消息
System.out.println("执行channelRead0方法---收到客户端" + ctx.channel().remoteAddress() + "发送的消息:" + msg);
//ctx.fireUserEventTriggered(null);
}
/**
* 5
* 收到客户端发送消息后,可以通过此方法回传客户端发送的消息
* 客户端连接中断时,该方法也会被执行
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行channelReadComplete方法---回传服务端的消息");
//接收到客户端消息后,回传消息给客户端。Unpooled是netty里的工具类,可以指定编码格式并将数据换为字节数组
ctx.writeAndFlush(Unpooled.copiedBuffer("我是服务端,滴滴滴滴", CharsetUtil.UTF_8));
}
/**
* 6
* 服务端和客户端连接时,客户端第一次发生消息后。每隔几秒时间,会被调用。
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println("执行userEventTriggered方法---自定义事件被触发");
super.userEventTriggered(ctx, evt);
}
/**
* 7
* 在客户端和服务端的网络连接断开或关闭时,通道从活动状态变为非活动状态后触发。
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行channelInactive方法---两端连接已断开");
super.channelInactive(ctx);
}
/**
* 8
* 在客户端或服务端连接时,如果有某一端断开连接,则此方法会被调用
* 此方法在handlerRemoved之前调用
*/
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行channelUnregistered方法---" + ctx.channel().localAddress() + "-服务端没有接收到任何数据");
super.channelUnregistered(ctx);
}
/**
* 9
*
* 当一个ChannelHandler实例从ChannelPipeline中移除时
* 主要用于释放资源或清理工作
* 两个方式触发这个方法:
* 1 显示调用 ctx.pipeline().remove(this)方法才会触发这个方法执行
* 2 在用户断开连接时,也会默认执行这个方法
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("执行handlerRemoved方法---" + ctx.channel().localAddress() + "-两端服务已断开连接");
super.handlerRemoved(ctx);
}
/**
* 根据数据可读变可写或可写变可读时触发
* 当 Channel 的写入能力发生变化时(例如从可写变为不可写,或者从不可写变为可写),netty 会调用该方法。
*
*/
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
super.channelWritabilityChanged(ctx);
}
}
netty处理逻辑的方法顺序如下图:
客户端通用模板:
java
public class NettyClient {
public static void main(String[] args) {
//NIO事件对象
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//获取启动类对象
Bootstrap bootstrap = new Bootstrap();
//添加到启动类里
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
//这两个是netty自带的编码器,也可以自己实现自定义的编码器
.addLast(new StringDecoder())
.addLast(new StringEncoder())
//添加此参数后(这里是60秒),如果服务端5秒内没有响应数据,则ServerHandler类里的userEventTriggered方法会被调用
.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS))
//自定义netty数据处理类,在类中实现逻辑
.addLast("clientHandler",new ClientHandler());
}
});
//连接服务端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
System.out.println("启动客户端,ip:" + "127.0.0.1" + ",端口:" + 8888);
//获取已成功建立连接的Channel,并对通道关闭进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
System.out.println("客户端启动错误:" + e);
} finally {
//服务停止后关闭事件循环组
eventExecutors.shutdownGracefully();
}
}
}
客户端netty数据处理类,对应ClientHandler类中的这个对象,因服务端已详细介绍方法细节,客户端处理类则不在赘述:
scala
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//接收服务端发送过来的消息
System.out.println("收到服务端" + ctx.channel().remoteAddress() + "的消息:" + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//发送消息到服务端
ctx.writeAndFlush(Unpooled.copiedBuffer("我是客户端,滴滴滴滴~", CharsetUtil.UTF_8));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("关闭客户端连接");
ctx.close();
}
以上处理类中,一般常用的方法有如下4个,其他方法则根据业务自行扩展:
java
/**
*
* 处理入栈消息
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
}
/**
*
* 处理异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
/**
*
* 在客户端和服务端的网络连接断开或关闭时,通道从活动状态变为非活动状态后触发。
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}
/**
*
* 在客户端和服务端的网络连接建立完成且可以开始读写数据时触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
测试
先启动服务端,再启动客户端。控制台会打印如下内容:
类结构依赖图
下图是netty的类结构树,主要关注3个类:
- ServerBootstrap(服务端启动类)
- bootstrap(客户端启动类)
- NioEventLoopGroup(线程池和事件循环的容器)
解析
Bootstrap 和 ServerBootstrap 分别用于客户端和服务器端的引导配置。它们都继承了 AbstractBootstrap 类,但它们在功能和使用场景上有所区别:
- ServerBootstrap:用来初始化和配置 Netty 服务器端,并绑定本地端口监听来自客户端的连接请求。它需要配置两个 EventLoopGroup:一个是 bossGroup,用来接收新的连接请求;另一个是 workerGroup,用于处理已经建立连接的客户端的读写事件。
- Bootstrap:用来初始化和配置客户端,创建并连接到远程服务端。它和服务端一样也需要绑定IP端口,才能自把数据发送到指定位置并被服务端进行监听处理。
- NioEventLoopGroup:可以理解为线程池和数据流转的中介。如果不设置线程数,则默认是本机CPU的2倍数量。
java
//代码示例 -- 也就是如果不设置这个4,如果不设置就是使用本机CPU * 2 的数量
NioEventLoopGroup eventExecutors = new NioEventLoopGroup(4);
以下是一段源码:
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
//判断是否是默认的,如果是则使用上诉DEFAULT_EVENT_LOOP_THREADS配置的数量
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
总结
- 本章列出了一些通用代码模板,只支持入门操作使用,这些模版小伙伴可以按实际场景进行封装。
- 本章只是简单介绍了使用示例,详细类流程解析在下一章节。
类结构解析篇(未完成)