Netty框架初步学习

初步看了一下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的数组传输方式也值得一提:

Netty源码之ByteBuf(一) - 简书 (jianshu.com) Mark一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值