(1) Netty是什么?
是一个框架!一个可以让我们自己实现NIO异步事件驱动的网络应用框架,主要作用就是做网络通信的架构设计的框架,所以日常业务需求基本不会跟它直接挂钩。
它帮我们封装了TCP/UDP等协议的服务端,同时还能让我们自定义各种网络协议。
它具有并发高,封装度高,传输速度快的特点。
更简单通俗来说,如果是写业务CURD,基本用不上netty,如果你回忆起了大学时候用C写的WebSocket套接字编程做的一些简陋的聊天室/服务器,并且想用Java模仿着做一套,同时DIY一些网络传输协议,那么就可以用netty框架,它封装了很多底层的Socket编程,可以让你很轻松搭建自己的服务器端小Demo。
(2) 服务端Demo代码:
先引入对应的依赖:
我用的gradle,maven的也有
dependencies {
compile 'io.netty:netty-all:4.1.26.Final'
}
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
服务端构造类
public class DiscardSever {
// 定义端口号
private int port;
// 有参构造,配置端口号
public DiscardSever(int port) {
this.port = port;
}
public void run() throws Exception{
//bossGroup仅接收客户端连接,并且把链接分发给对应的worker
EventLoopGroup bossGroup=new NioEventLoopGroup(1);
//workerGroup作为worker,处理boss接收的连接的流量和将接收的连接注册进入这个worker
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
//ServerBootstrap负责建立服务端
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
//指定使用NioServerSocketChannel
.channel(NioServerSocketChannel.class)
//ChannelInitializer配置Channel
.childHandler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) {
// 自定义的DiscardServerHandler用于处理请求响应的业务逻辑相关代码
ch.pipeline().addLast(new DiscardServerHandler());
};
})
// 请求等待队列的最大长度
.option(ChannelOption.SO_BACKLOG, 128)
// 启用心跳机制,在双方建立连接后两个小时没有任何数据传输的情况下,心跳机制会被激活
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口
ChannelFuture f = bootstrap.bind(port).sync();
f.channel().closeFuture().sync();
}finally{
//资源释放
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Handle类
主要负责具体处理客户端消息的类,它负责进一步路由到其它方法执行业务逻辑或者直接丢弃或者直接返回给客户端
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 引用计数接口ReferenceCounted的一个实现类
ByteBuf buf = (ByteBuf) msg;
// 引用+1
buf.retain();
// 输出到客户端
ctx.writeAndFlush(msg);
// 输出到控制台
System.out.println((char) buf.readByte());
// 释放msg对象
ReferenceCountUtil.release(msg);
}
// 异常处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// channel关闭
System.out.println("Close Connect " + ctx.name());
super.channelInactive(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
// channel启动
ctx.fireChannelActive();
System.out.println("Open Connect");
}
}
主启动类
public class DiscardServerMain {
// 主启动类
public static void main(String[] args) {
try {
// 指定端口号并执行run方法
new DiscardSever(8080).run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
启动之后,我们使用cmd命令 telnet localhost 8080 即可链接到这个服务端,之后输入的字符和数字都会被接收到打印到服务端控制台并返回给客户端.
(3)关于netty中对象的生命周期
io.netty.util.IllegalReferenceCountException: refCnt: 0 问题
netty中,对象的生命周期使用引用计数器控制,对象初始化时候的引用计数为1,调用一次release就减一,所以当某个对象的计数为0却还要访问该对象时候就会出现IllegalReferenceCountException异常,Ctx.write();Ctx.flush();方法底层也有调用release方法针对ctx.write(XXX);的内容进行引用-1
同时,如果我们如果不希望buf对象引用计数变为0导致不可用,例如我们希望返回客户端后,控制台再打印输出一下,那么我们就可以使用buf.retain();方法来手动维持buf的引用为1;
为了验证,我们可以通过buf.refCnt();方法来查看buf对象的引用计数是否为0
可以看出当先write,再ctx.flush()之后,buf的引用就为0了(单独flush()是不会减少引用的,必须配合write),所以再使用buf对象做操作,就会出现IllegalReferenceCountException,如果要输出的话,直接把最后一句话移动到flush操作之前即可,因为输出并不会有release方法清除引用~
但是把输出System.out.println()提前到flush之前,就会导致ctx.write()没有返回值给客户端,具体原因我查了很久也没结论,以后搞懂了会补充上来
如果想既控制台打印,也要返回给客户端,就要先flush,再System.out.println(),但是像上面说的,先flush会导致引用减一,如果要保证System.out.println()不报错,请加buf.retain();这个方法会帮我们引用+1;下面试试看:
还有另外一种写法,writeAndFlush(msg);这样它就会自己输出到客户端,然后引用-1;