1.Netty框架入门

Netty框架入门

首先声明在学习的过程中参考了netty5的user guide。在此非常感谢netty的贡献者社区。在本篇博客中,将会基于netty5实现最简单的读取和发送数据的功能。代码中有一些自己的理解,以注释形式给出。
参考手册地址:http://ifeve.com/netty5-user-guide/

客户端代码

package netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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;

/**
 * @Auther: ;李泽
 * @Date: 2019/3/4 22:05
 * @Description:
 */
public class NettyClient {
    public static void main(String[] args) throws InterruptedException, UnsupportedEncodingException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //这里面的是接受服务器端反馈时,才会触发的handler
                        socketChannel.pipeline().addLast(new ClientHandler());
                    }
                });
        ChannelFuture channelFuture1 = bootstrap.connect("127.0.0.1",8080).sync();
        //客户端发送数据,借用netty的buffer转化工具
        channelFuture1.channel().writeAndFlush(Unpooled.copiedBuffer("李泽".getBytes("utf-8")));

        //可以理解为阻塞在这
        channelFuture1.channel().closeFuture().sync();
        eventLoopGroup.shutdownGracefully();
    }
}

客户端业务handler

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

/**
 * @Auther: ;李泽
 * @Date: 2019/3/4 22:05
 * @Description:
 */
public class ClientHandler extends ChannelHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //super.channelRead(ctx, msg);
        try {
            //获得反馈过来的信息,转化为bytebuffer,这个对象是netty封装的,不是原生的。
            ByteBuf buffer = (ByteBuf)msg;
            //创建一个空间,从buffer中拿出数据
            byte[] bytes = new byte[buffer.readableBytes()];
            buffer.readBytes(bytes);
            //打印输出
            String resp = new String(bytes,"utf-8");
            System.out.println("resp = " + resp);
            //buffer.release();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
}

服务端代码

package netty;

import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;

/**
 * @Auther: ;李泽
 * @Date: 2019/3/4 22:04
 * @Description:
 */
public class NettyServer {
    private int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        /*
        NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,Netty提供了许多不同的EventLoopGroup的实现用来处理不同传
        输协议。在这个例子中我们实现了一个服务端的应用,因此会有2个NioEventLoopGroup会被使用。第一个经常被叫做‘boss’,
        用来接收进来的连接。第二个经常被叫做‘worker’,用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信
        息注册到‘worker’上。如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的
        实现,并且可以通过构造函数来配置他们的关系。
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /*
            ServerBootstrap 是一个启动NIO服务的辅助启动类。你可以在这个服务中直接使用Channel,但是这会是一个复杂的处
            理过程,在很多情况下你并不需要这样做。
             */
            ServerBootstrap b = new ServerBootstrap();
            //给启动器配置两个线程组
            b.group(bossGroup, workerGroup)
                    //这里我们指定使用NioServerSocketChannel类来举例说明一个新的Channel如何接收进来的连接。
                    .channel(NioServerSocketChannel.class)
                    /*
                    这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。ChannelInitializer是一个特殊的处理类,
                    他的目的是帮助使用者配置一个新的Channel。也许你想通过增加一些处理类比如ServerHandler来配置一
                    个新的Channel或者其对应的ChannelPipeline来实现你的网络程序。当你的程序变的复杂时,可能你会增加更多
                    的处理类到pipline上,然后提取这些匿名类到最顶层的类上。
                     */
                    .childHandler(new ChannelInitializer <SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ServerHandler());
                }
            })
            /*
            你可以设置这里指定的通道实现的配置参数。我们正在写一个TCP/IP的服务端,因此我们被允许设置socket的参
            数选项比如tcpNoDelay和keepAlive。
             */
            /*
              SO_BACKLOG参数解释:服务器端的内核模块维护有两个队列,我们称之为A,B。
              客户端向服务器端connect的时候,会发送带有SYN标志的包,称之为第一次握手。
              服务器收到客户端发来的SYN时,向客户端发送SYN_ACk确认,称之为第二次握手。
              此时TCP内核模块把客户端连接加入到A队列中,然后服务器收到客户端发来的ACK时,称之为第三次握手
              TCP内核把客户端连接从A队列移动到B队列,连接完成,应用程序的accept被触发,产生返回值。
              也就是说accept从B队列中取出完成三次握手的连接。
              A,B的队列的长度之和是BACKLOG,若AB队列之和大于BACKLOG时,新连接会被tcp内核拒绝。
              所以,如果BACKLOG过小,可能会出现accept速度跟不上,AB满了,导致新连接无法接入。
              需要注意的是:BACKLOG对支持的连接数并无影响,影响的只是还没被accept取出的连接。
             */
             .option(ChannelOption.SO_BACKLOG, 128)
             /*
             你关注过option()和childOption()吗?option()是提供给NioServerSocketChannel用来接收进来的连接。childOption()
             是提供给由父管道ServerChannel接收到的连接,在这个例子中也是NioServerSocketChannel。
              */
             /*
               保持连接不断开
              */
              .option(ChannelOption.SO_KEEPALIVE, true);

            // 绑定并且启动服务来接受到来的请求
            ChannelFuture f = b.bind(port).sync(); // (7)

            // 在服务关闭之前都会等待
            // 在这个例子中这是不会发生的, 但是你也可以写上这个来做到完美
            // 关闭你的服务.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new NettyServer(port).run();
    }
}

服务端业务handler

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;


/**
 * @Auther: ;李泽
 * @Date: 2019/3/4 22:05
 * @Description:  继承自ChannelHandlerAdapter,这个类实现了ChannelHandler接口,ChannelHandler提供了许多事件处理
 *                的接口方法,然后你可以覆盖这些方法。现在仅仅只需要继承ChannelHandlerAdapter类而不是你自己去实
 *                现接口方法。
 */
public class ServerHandler extends ChannelHandlerAdapter {
    /**
     * 功能描述: exceptionCaught()事件处理方法是当出现Throwable对象才会被调用,即当Netty由于IO错误或者处理器在处理事件
     *           时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的channel给关闭掉。然而这个方法的处
     *           理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。
     *
     * @auther: 李泽
     * @date: 2019/3/4 22:24
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
    /**
     * 功能描述: 这里我们覆盖了chanelRead()事件处理方法。每当从客户端收到新的数据时,这个方法会在收到消息时被调用,
     *           这个例子中,收到的消息的类型是ByteBuf
     *
     * @auther: 李泽
     * @date: 2019/3/4 22:23
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //super.channelRead(ctx, msg);  不注释掉会报错
        /*
           介绍一个读取事件的最佳实践:
           ByteBuf in = (ByteBuf) msg;
           try {
                // Do something with msg
                如果单纯用网络助手测试的话 发16进制,可以在这打印一下:
                    while (in.isReadable()) { // (1)
                        System.out.print((char) in.readByte());
                        System.out.flush();
                    }
                  等价于:System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))
            } finally {
                ReferenceCountUtil.release(msg);
            }
         */
         try {

             //获得发送过来的信息,转化为bytebuffer,这个对象是netty封装的,不是原生的。
             ByteBuf bufferRead = (ByteBuf)msg;
             //复制一份,用来响应数据
             ByteBuf bufferWrite = bufferRead.copy();
             //创建一个空间,从buffer中拿出数据
             byte[] bytes = new byte[bufferRead.readableBytes()];
             bufferRead.readBytes(bytes);
             //打印输出
             String req = new String(bytes,"utf-8");
             System.out.println("req = " + req);
             //下面的finally来释放
             //bufferRead.release();
             //给个反馈,遵循echo响应协议,发啥反馈啥,
             ctx.writeAndFlush(bufferWrite);
             //不需要释放,因为冲刷之后,自动释放了
             //bufferWrite.release();
          } finally {
             //如果上面的业务出现了反馈的代码,就不需要释放空间,冲刷了之后,就自动释放了
             ReferenceCountUtil.release(msg);
          }
    }
}

关于bytebuf对象的释放问题

  1. 对于单纯读的逻辑,读完了之后要最终使用 ReferenceCountUtil.release(msg)来释放。
  2. 对于写的逻辑,用到的bytebuf对象,netty会自动处理掉,不用手动release(),相关方法:ctx.writeAndFlush(bufferWrite)
  3. 对一个msg对象又要读取,又要写出,最好是复制一份。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值