Netty 进阶系列之Netty入门案例学习(三)

在这里插入图片描述

1、Echo服务端代码

1.1 EchoServerHandler 处理器
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @ClassName EchoServerHandler
 * @Description TODO
 * @Date 2019/5/3 11:14
 * @Version 1.0
 **/

@ChannelHandler.Sharable
//标示一个Channel-Handler可以被多个Channel安全的共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));

        //ctx.write(Object) 方法不会使消息写入到通道上,他被缓冲在了内部,
        //你需要调用 ctx.flush() 方法来把缓冲区中数据强行输出。
        //或者你可以用更简洁的 cxt.writeAndFlush(msg) 以达到同样的目的。
        ctx.write(in);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("读完了");
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

代码解读

EchoServerHandler继承自ChannelInboundHandlerAdapte,用于对网络事件进行读写操作

下面这张结构图体现了一个总体的继承关系
在这里插入图片描述
我们发现所有的Handler都来自ChannelHandler接口

下面我们关注一些比较重要的方法

  • channelRead 读写网络数据执行的方法
  • channelReadComplete 读写网络数据结束执行的方法
  • exceptionCaught 发送异常时执行的方法
1.2 EchoServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @ClassName EchoServer
 * @Description echo服务端
 * @Date 2019/5/2 11:43
 * @Version 1.0
 **/
public class EchoServer {

    private int port;

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

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }
        new EchoServer(port).start();
    }

    private void start() throws InterruptedException {
        //1 创建线两个程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {

            //2 创建辅助工具类,用于服务器通道的一系列配置
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workGroup)   //绑定线程组
                    .channel(NioServerSocketChannel.class)  //指定NIO模型
                    .option(ChannelOption.SO_BACKLOG, 1024) //设置tcp缓冲区
                    .childHandler(new ChannelInitializer() {

                        @Override
                        protected void initChannel(Channel channel) throws Exception {
                            //3 配置具体数据接收方法的处理
                            channel.pipeline().addLast(new EchoServerHandler());
                        }
                    });

            //绑定端口,等待同步成功
            ChannelFuture cf = b.bind(port).sync();

            //等待服务器监听端口关闭
            cf.channel().closeFuture().sync();
        } finally {

            //优雅的关闭EventLoopGroup,释放线程池资源
            bossGroup.shutdownGracefully().sync();
            workGroup.shutdownGracefully().sync();

        }

    }
}

代码解读

1、NioEventLoopGroup

​ NioEventLoopGroup是个线程组,它包含了一组NIO线程,专门用于网络事件的处理,实际上它们就是Reactor线程组。这里创建两个的原因是一个用于服务端接受客户端的连接,另一个用于进行SocketChannel的网络读写。

  • shutdownGracefully:用于优雅的退出,是否服务端资源

2、 ServerBootstrap

​ ServerBootstrap是netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。

下面我们来看下里面的几个方法:

  • group 设置线程组模型,NIO线程组作为参数传入

  • channel:设置channel通道类型NioServerSocketChannel

  • option: 作用于每个新建立的channel,设置TCP连接中的一些参数,如下

    • ChannelOption.SO_BACKLOG: 存放已完成三次握手的请求的等待队列的最大长度;
    对于ChannelOption.SO_BACKLOG的解释:(❤ ω ❤)
    服务器端TCP内核维护有两个队列,我们称之为A、B队列。
    	客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),
    	服务器端接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),
    	此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到客户端发送的ACK时(第三次握手),
    	
    TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept从B队列中取出完成了三次握手的连接。A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的连接数并无影响,backlog影响的只是还没有被accept取出的连接
    
    • ChannelOption.TCP_NODELAY: 为了解决Nagle的算法问题,默认是false, 要求高实时性,有数据时马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数,就设置为false,会累积一定大小后再发送
  • childHandler: 用于对每个通道里面的数据处理

  • bind :绑定监听端口

3、 ChannelFuture

​ ChannelFutur的功能类似于JDK的java.util.concurrent.Future,主要用于异步操作的回调通知。

使用 ChannelFuture的channel().closeFuture().sync()方法进行阻塞,等待服务端链路关闭之后才继续往下执行

2、Echo客户端

2.1 EchoClientHandler
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {


    /**
     * 当从服务器接收到一条消息时被调用
     * @param channelHandlerContext
     * @param in
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) throws Exception {
        System.out.println("Client received:"+in.toString(CharsetUtil.UTF_8));
    }

    /**
     * 服务器的连接已经建立好之后将会被调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //当被知道Channel活跃时,发送一条消息
        ctx.writeAndFlush(Unpooled.copiedBuffer("这是一条netty搭建echo服务发送的消息",CharsetUtil.UTF_8));
    }

    /**
     * 发生异常时,记录异常信息同时关闭Channel
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
2.2 EchoClient
public class EchoClient {

    private int port;
    private String host;

    public EchoClient(String host, int port) {
        this.port = port;
        this.host = host;
    }

    public static void main(String[] args) throws InterruptedException {
        if (args.length != 2) {
            System.out.println("参数不对:" + EchoClient.class.getSimpleName());
        }
        new EchoClient("127.0.0.1",8080).start();
    }

    private void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception {

                            sc.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //异步绑定服务器,调用sync()方法阻塞等待直到绑定完成
            ChannelFuture channelFuture = bootstrap.connect().sync();
            //等待服务器监听,端口关闭,sync会直到绑定操作结束为止。
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 关闭EventLoopGroup,释放所有的资源
            group.shutdownGracefully().sync();
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值