初步看了一下Netty的代码构成,发现还是挺有意思的。
先看看如下几段代码:
服务端
package ServerNetty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* @author PinkFloyd
*/
public class ServerNetty {
static final int PORT = Integer.parseInt(System.getProperty("port", "9999"));
public void runServer() {
//Group:群组,Loop:循环,Event:事件
//Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的,注册Channel,管理他们的生命周期。
//NioEventLoopGroup是一个处理I/O操作的多线程事件循环
//bossGroup作为boss,接收传入连接
//因为bossGroup仅接收客户端连接,不做复杂的逻辑处理,为了尽可能减少资源的占用,取值越小越好
// 用来接收进来的连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 用来处理已经被接收的连接,一旦bossGroup接收到连接,就会把连接信息注册到workerGroup上
EventLoopGroup workerGroup = new NioEventLoopGroup();
final ServerNettyHandler serverHandler = new ServerNettyHandler();
try{
// 引导类,用于配置参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 设置引导类的相关参数
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// 设置线程队列等待连接的个数
.option(ChannelOption.SO_BACKLOG, 128 )
//保持连接
.childOption(ChannelOption.SO_KEEPALIVE,true)
// 设置这样做好的好处就是禁用nagle算法
// Nagle算法试图减少TCP包的数量和结构性开销, 将多个较小的包组合成较大的包进行发送.
// 但这不是重点, 关键是这个算法受TCP延迟确认影响, 会导致相继两次向连接发送请求包,
// 读数据时会有一个最多达500毫秒的延时.
.childOption(ChannelOption.TCP_NODELAY, true)
//给我们的 WorkerGroup 的 EventLoop 对应的管道设置处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new IdleStateHandler(3,5,7, TimeUnit.MINUTES));
socketChannel.pipeline().addLast(serverHandler);
}
});
System.out.println("server 开启--------------");
// 通过绑定一个端口并实现同步,生成一个 ChannelFuture 对象
// 启动并绑定 channel
ChannelFuture future = serverBootstrap.bind(PORT).sync();
// Wait until the server socket is closed,
// In this example, this does not happen,
// but you can do that to gracefully shut down your server.
// sync()会同步等待连接操作结果,用户线程将在此wait(),直到连接操作完成之后,线程被notify(),用户代码继续执行
// closeFuture()当Channel关闭时返回一个ChannelFuture,用于链路检测
future.channel().closeFuture().sync();
}
catch (Exception e){
e.printStackTrace();
}
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new ServerNetty().runServer();
}
}
package ServerNetty;
import Common.Message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
public class ServerNettyHandler extends ChannelInboundHandlerAdapter {
// 读取数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try{
ByteBuf bb = (ByteBuf) msg;
int len = bb.readableBytes();
byte[] bytes = new byte[len];
bb.readBytes(bytes);
String value = new String(bytes);
//打印客户端传递过来的值
System.out.println("server 接收到客户端的请求: " + value);
//返回给客户端一个字符串来自服务器的响应
ByteBuf result = Unpooled.copiedBuffer("来自服务器的响应".getBytes());
ctx.writeAndFlush(result);
System.out.println("已发送服务器响应");
}
finally {
ReferenceCountUtil.release(msg);
}
}
// 数据读取完毕的处理
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.err.println("服务端读取数据完毕");
}
// 出现异常的处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
System.err.println("server 读取数据出现异常");
ctx.close();
}
}
客户端
package ClientNetty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import Common.Message;
public class ClientNetty {
// 要请求的服务器的ip地址
private final String ip;
// 服务器的端口
private int port;
public ClientNetty(String ip, int port){
this.ip = ip;
this.port = port;
}
// 请求端主题
private void action() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
try{
Bootstrap bs = new Bootstrap();
bs
.group(bossGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// marshalling 序列化对象的解码
// socketChannel.pipeline().addLast(MarshallingCodefactory.buildDecoder());
// marshalling 序列化对象的编码
// socketChannel.pipeline().addLast(MarshallingCodefactory.buildEncoder());
// 处理来自服务端的响应信息
socketChannel.pipeline().addLast(new ClientNettyHandler());
}
});
// 客户端开启
ChannelFuture cf = bs.connect(ip, port).sync();
Message msg = new Message();
String reqStr = "我是客户端请求1saioduiasoudioasuiasouaisoaiouasioasiduaio";
msg.setContent(reqStr);
// 发送客户端的请求
cf.channel().writeAndFlush(Unpooled.copiedBuffer(reqStr, Charset.forName("utf-8")));
// 等待直到连接中断
cf.channel().closeFuture().sync();
}
finally {
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws UnsupportedEncodingException, InterruptedException {
new ClientNetty("localhost",9999 ).action();
}
}
package ClientNetty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import java.nio.charset.Charset;
/**
* @author PinkFloyd
*/
public class ClientNettyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx,msg);
try {
ByteBuf bb = (ByteBuf)msg;
byte[] respByte = new byte[bb.readableBytes()];
bb.readBytes(respByte);
String respStr = new String(respByte, Charset.forName("utf-8"));
System.out.println("client--收到响应:" + respStr);
// 直接转成对象
// handlerObject(ctx, msg);
} finally{
System.out.println("Done");
// 必须释放msg数据->似乎ChannelInboundHandlerAdapter会自动释放?
// SimpleChannelInboundHandler作为Handler处理事务,
// 使用AbstractChannelInboundHandler是不会主动释放内容的,这个时候需要你自己手动释放一次。
// ReferenceCountUtil.release(msg);
}
}
// 数据读取完毕的处理
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.err.println("客户端读取数据完毕");
}
// 出现异常的处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
System.err.println("client 读取数据出现异常");
ctx.close();
}
}
启动服务端和客户端之后,服务端消息接收正常,但是客户端却出现了如下的报错:
io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1464)
at io.netty.buffer.AbstractByteBuf.checkReadableBytes0(AbstractByteBuf.java:1448)
at io.netty.buffer.AbstractByteBuf.checkReadableBytes(AbstractByteBuf.java:1434)
at io.netty.buffer.AbstractByteBuf.readBytes(AbstractByteBuf.java:903)
at io.netty.buffer.AbstractByteBuf.readBytes(AbstractByteBuf.java:911)
at ClientNetty.ClientNettyHandler.channelRead(ClientNettyHandler.java:20)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1421)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:697)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:632)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:549)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:511)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:832)
分析了一下应该是bb为空,向上分析一下,bb是强制转化成ByteBuf的msg。
那么再针对msg分析,发现我们在重写channelRead的时候调用了super.channelRead(ctx,msg)
super.channelRead(ctx,msg);
向上追踪
ctx.fireChannelRead(msg);
fireChannelRead(msg)
(17条消息) netty中的发送有序与channel使用_silver9886的专栏-CSDN博客_firechannelread
在这篇文章中,我了解了:
这个代码在ctx.fireChannelRead(msg);的时候,最终会调用到tail的inboundhandle。tail inboundhandle是netty默认的最后一个处理msg的handle。将msg进行realse。那么当 ctx.flush();的时候,msg已经被释放掉,再读取msg,就会报错。
所以如果直接写 ctx.write(msg);复用msg的时候,坚决不能释放msg的引用。那么msg的引用什么时间会被释放呢?
当flush调用成功后,真正写入channel以后,DefaultChannelPipeline$HeadContext,最终会调用ChannelOutboundBuffer的
remove方法,将已经写入的内容从待写的链表中删除。
————————————————
版权声明:本文为CSDN博主「silver9886」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/silver9886/article/details/81475512
答案一目了然
另外我觉得Netty的数组传输方式也值得一提: