Netty——02 入门案例讲解

一、前言

本文内容是参考《netty权威指南 第二版》写的。之前我一直看不懂netty的方法调用,很懵逼。看了这本书理解多了。在这里记录一下学习过程。

二、导入依赖

注意:这个版本是比较旧的版本。不同版本方法的调用可能有较大差入,但是核心的内容都差不多。
在这里插入图片描述
建议你也用这个版本的netty依赖,不然有些方法可能无法调用(被废弃或者迁移到其他类中了)

 <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha2</version>
    <!--注意 《netty权威指南 第二版》 用的是5.0.0.Alpha1.如果出现异常就换成1吧--> 
</dependency>

三、服务端

提示1:netty所有IO都是异步的。

netty中所有的IO都是异步IO,也就是说所有的IO都是立即返回的,返回的时候,IO可能还没有结束,所以需要返回一个ChannelFuture,当IO有结果之后,会去通知ChannelFuture,这样就可以取出结果了。
提示2:netty底层使用了Reactor(响应式框架,也就是非阻塞)
在响应式编程中,主线程执行的是建立通道的代码,主线程很快执行完,通道就建好了。此时只是一个空的通道,根本就没有数据。 在数据到来时,由工作线程执行每个节点的逻辑代码来处理数据,然后把数据传入下一个节点,如此反复直至结束。 所以,在写响应式代码的时候,心里一定要默念着,我所做的事情就是建立一条数据通道,在通道上指定的位置插入适合的逻辑处理代码。同时还要切记,主线程执行完时,只是建立了通道,并没有数据。
推荐阅读:https://www.cnblogs.com/lixinjie/p/step-into-reactive-programing-in-an-hour.html
package Netty01;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import javafx.event.EventTarget;

import java.util.Date;

/**
 * @author hs
 * @date 2021/11/29 17:11
 * 参考:https://blog.csdn.net/m0_45406092/article/details/103995125?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link
 * https://blog.csdn.net/m0_45406092/article/details/103985092
 * 
 * 时间服务端(功能:客户端发送消息体内容为:"QUERY TIME ORDER"的请求时,服务端响应返回现在的时间)
 */
public class TimeServer {
    public void bind(int port) throws Exception {
        //配置服务端的nio线程组,它包含了一组NIO 线程,专门用于网络事件的处理,事实上他们就是Reactor线程组。
        // 这里创建两个的原因是一个用于服务端接受客户端的连接,另外一个用于进行SocketChannel的网络读写。
        //因为bossGroup仅接收客户端连接,不做复杂的逻辑处理,为了尽可能减少资源的占用,取值越小越好
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(1);

        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)//传入两个线程组
                    .channel(NioServerSocketChannel.class)//设置channel为NioServerSocketChannel
                    .option(ChannelOption.SO_BACKLOG,1024)//配置NioServerSocketChannel的TCP参数
                    .childHandler(new ChildChannelHandler());//最后绑定I/O事件处理类ChildChannelHandler,主要用于处理网络IO事件,例如记录日志、对消息进行解码等
            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();
            //等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            System.out.println("123");
            //退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{

        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            //增加这两个handler 解决TCP的粘包和拆包
            // 搭配下面这两个解码器(管道处理器),pipeline作为netty的数据输送管道,该管道中存放了众多的handler
            socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
            socketChannel.pipeline().addLast(new StringDecoder());

            //自定义管道处理器
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args!=null && args.length>0){
            try{
                port = Integer.valueOf(args[0]);
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
        new TimeServer().bind(port);
    }
}
class TimeServerHandler extends ChannelHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf)msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("当前时间是:"+body);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString():"BAD ORDER";
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

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



四、客户端

package Netty01;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;


import java.util.logging.Logger;


/**
 * @author hs
 * @date 2021/11/30 10:11
 * 客户端 从服务端获取现在的时间
 */
public class TimeClient {
    public void connect(int port , String host) throws Exception {
        //配置客户端NIO线程组,它包含了一组NIO 线程,专门用于网络事件的处理,事实上他们就是Reactor线程组。
        NioEventLoopGroup group = new NioEventLoopGroup();
        try{
            //客户端辅助启动器,目的是为了降低开发复杂性
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)//传入一个线程组
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            //增加这两个handler 解决TCP的粘包和拆包
                            // 搭配下面这两个解码器(管道处理器),pipeline作为netty的数据输送管道,该管道中存放了众多的handler
                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            ch.pipeline().addLast(new StringDecoder());

                            //自定义管道处理器
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            //发起异步连接操作
            ChannelFuture f = bootstrap.connect(host,port).sync();

            //等待客户端链路关闭
            f.channel().closeFuture().sync();
        } catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            System.out.println("clent 123");
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args)  {
        int port = 8080;
        if (args!=null&&args.length>0){
            try {
                port = Integer.valueOf(args[0]);
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
        System.out.println("启动");
        try {
            new TimeClient().connect(port,"127.0.0.1");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class TimeClientHandler extends ChannelHandlerAdapter{
    private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
    private final ByteBuf firstMessage;

    public TimeClientHandler() {
        byte[] req = "QUERY TIME ORDER".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf)msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("Now is" + body);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.warning("Unexpected exception from downstream:"+cause.getMessage());
        ctx.close();
    }
}


五、核心代码

在这里插入图片描述

六、handler与childHandler

区别:

  1. ServerBootstrap有handler()和childHandler()这两个方法,Bootstrap只有handler()。原因ServerBootstrap有两个NIO线程组NioEventLoopGroup,一个负责接受客户端的连接,另外一个用于进行SocketChannel的网络读写
  2. handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行。

七、如何解决TCP的粘包和拆包

LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf中的可读字节,判断看是否有“n”或者“\rln”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。
StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的Handler。LineBasedFrameDecoder + StringDecoder组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包
程序的运行结果完全符合预期,说明通过使用LineBasedFrameDecoder和 StringDecoder成功解决了TCP粘包导致的读半包问题。对于使用者来说,只要将支持半包解码的Handler添加到ChannelPipeline中即可,不需要写额外的代码,用户使用起来非常简单

服务端和客户端都需要添加,注意handler的顺序。
在这里插入图片描述
还要一些其他的解码器。可以使用时可以百度一下。

  1. DelimiterBasedFrameDecoder
    在这里插入图片描述
  2. FixedLengthFrameDecoder
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一个基于Java的高性能网络编程框架,它提供了一种简单而强大的方式来处理网络通信。在Netty中,异常处理是非常重要的一部分,可以帮助我们及时发现和解决网络通信中的问题。下面是一个Netty异常处理的案例: 1. 异常捕获和处理: 在Netty中,可以通过实现ChannelHandler的exceptionCaught方法来捕获和处理异常。当有异常发生时,Netty会自动调用该方法,并将异常传递给它。在该方法中,我们可以根据具体的异常类型进行相应的处理,例如记录日志、关闭连接等。 ```java public class MyHandler extends ChannelInboundHandlerAdapter { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 异常处理逻辑 if (cause instanceof IOException) { // 处理IO异常 // ... } else if (cause instanceof IllegalArgumentException) { // 处理参数异常 // ... } else { // 其他异常处理 // ... } // 关闭连接 ctx.close(); } } ``` 2. 异常传播: 在Netty中,异常可以在ChannelPipeline中传播。当一个ChannelHandler抛出异常时,它会被传递给ChannelPipeline中的下一个ChannelHandler,直到被处理或者到达Pipeline的末尾。这种机制可以让我们在不同的Handler中对异常进行处理,从而实现更加灵活的异常处理策略。 3. 异常日志记录: 在Netty中,可以使用日志框架来记录异常信息,以便后续的排查和分析。常用的日志框架有Log4j、Logback等。通过配置日志级别和输出格式,可以将异常信息记录到日志文件中,方便后续的查看和分析。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值