netty的学习记录

9 篇文章 0 订阅

最近开始学习netty,自己写了个服务端的demo,包含从接收到客户端的数据流到完成业务逻辑并回发数据给客户端这一整个过程,下面开始正文。


先看一下工程目录


添加netty包

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.14.Final</version>
            <scope>compile</scope>
</dependency>


netty的一些初始化设置

public class NettyServer {
    private int port;

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

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();// 用于处理I/O操作的多线程事件循环
        EventLoopGroup workerGroup = new NioEventLoopGroup();// 线程池的数量和线程池都可以自己配置,默认的线程池数量为CPU数量 *2
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();// netty的一个帮助类,用来设置服务器
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline channelPipeline = socketChannel.pipeline();
                            channelPipeline.addLast("decoder", new PacketFrameDecoder());
                            channelPipeline.addLast("encoder", new PacketFrameEncoder());
                            channelPipeline.addLast(new NettyServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口并开启服务
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws Exception {
        System.out.println("server executed");

        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = ConfigInfo.SERVER_PORT;
        }
        new NettyServer(port).run();

    }
}

EventLoopGroup可以理解为线程池,这里初始化了两个EventLoopGroup,一个专门用于处理客户端连入事件,另一个用于处理读写事件。


 channelPipeline.addLast("decoder", new PacketFrameDecoder());
 channelPipeline.addLast("encoder", new PacketFrameEncoder());
 channelPipeline.addLast(new NettyServerHandler());

这里配置了编解码器和一个处理通道事件的类,因为netty是基于数据流的形式和客户端交互的,因此需要对数据进行编解码,在接收到数据时将其转换为string,int等,在回发时要将数据转换为byte[]


这三个类的交互顺序是PacketFrameDecoder--->NettyServerHandler--->PacketFrameEncoder


public class PacketFrameDecoder extends LengthFieldBasedFrameDecoder {
    private static final int MAX_PACKET_LENGTH = 8192 * 2;
    private static final int LENGTH_FIELD_OFFSET = 0;
    private static final int LENGTH_FIELD_LENGTH = 4;// 前几位代表报文长度,这里只能是1,2,3,4,8
    private static final int LENGTH_ADJUSTMENT = -4;
    private static final int INITIAL_BYTES_TO_STRIP = 0;

    private Logger logger = Logger.getLogger(PacketFrameDecoder.class);

    public PacketFrameDecoder() {
        super(MAX_PACKET_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        super.decode(ctx, in);
        in.resetReaderIndex();// 重置索引

        MsgHeader msgHeader = new MsgHeader();
        byte[] bytes = new byte[in.readableBytes()];
        in.readBytes(bytes);// 这里要用读的方式获取数据流,不能直接用getBytes方法,不然会出现一直可读而导致死循环的状态
        msgHeader.fromBytes(bytes);

        logger.info(msgHeader.toString());

        return msgHeader;
    }
}

MsgHeader是自定义的一个消息类,包含消息长度,消息类型,消息正文。这里的消息正文就是我们通常理解的pojo,只不过是以byte[]的形式存在。


public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    private Logger logger = Logger.getLogger(NettyServerHandler.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ServiceDispatcher serviceDispatcher = new ServiceDispatcher();
        serviceDispatcher.dispatch(ctx, (MsgHeader) msg);
        ctx.close();
    }

    /**
     * 这个方法是在客户端接入时会调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("一个客户端连入");
        super.channelActive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 当引发异常时关闭连接
        cause.printStackTrace();
        ctx.close();
    }

}

这个类主要看channelRead方法,在这里将之前解析出来的MsgHeader分发给service,这里要传入上下文,之后还要通过它来发送消息给客户端


public class ServiceDispatcher {
    private Logger logger = Logger.getLogger(ServiceDispatcher.class);

    public void dispatch(ChannelHandlerContext ctx, MsgHeader msgHeader) {
        IService service = null;
        switch (msgHeader.getType()) {
            case MsgType.REQ_LOGIN:
                logger.info("接收到了客户端登录请求");
                service = new LoginServiceImpl();
                break;
            default:
                break;
        }
        if (service != null) {
            service.handle(ctx, msgHeader);
        }
    }
}

在这里根据报文类型将数据分发给对应的service

接下来就是我们熟悉的业务逻辑啦,这里省略数据库操作

public class LoginServiceImpl implements LoginService {
    private Logger logger = Logger.getLogger(LoginServiceImpl.class);
    private Req_Login req_login;
    private Res_Login res_login;


    public void handle(ChannelHandlerContext ctx, MsgHeader msgHeader) {
        fromBytes(msgHeader.getContent());
        // 在这里执行数据库操作,比对账号密码
        if (req_login.getPassword().equals("123456") && req_login.getUsername().equals("Mike")) {
            logger.info("登录成功");
            res_login = new Res_Login(MsgType.SUCCESS);
        } else {
            logger.info("登录失败");
            res_login = new Res_Login(MsgType.FAILURE);
        }
        MsgHeader resMsg = new MsgHeader();
        byte[] resContent = toBytes();
        resMsg.setContent(resContent);
        resMsg.setType(MsgType.RES_LOGIN);
        resMsg.setLength(resContent.length + 8);
        // 响应报文组装完成,接下来发送
        logger.info("即将发送的东西是:" + msgHeader.toString() + "-->实体类:" + res_login.toString());
        final ChannelFuture channelFuture = ctx.writeAndFlush(resMsg);
        channelFuture.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                logger.info("响应报文发送成功");
                channelFuture.channel().close();
            }
        });
    }

    public byte[] toBytes() {
        if (res_login == null) {
            throw new NullPointerException("response entity is null");
        }
        return BigEndian.putInt(res_login.getErrCode());
    }

    public void fromBytes(byte[] src) {
        req_login = new Req_Login();
        int offset = 0;
        int usernameLength = BigEndian.getInt(src, offset);
        offset += 4;
        req_login.setUsername(BigEndian.getString(src, offset, usernameLength));
        offset += usernameLength;
        int passwordLength = BigEndian.getInt(src, offset);
        offset += 4;
        req_login.setPassword(BigEndian.getString(src, offset, passwordLength));
    }
}

还记MsgHeader的消息正文吗?它是数据流,因此需要把它转换为pojo我们才能操作,具体看fromBytes方法,逻辑简单这里就不赘述了。

判断完登录数据后我们该发响应报文给客户端了,通过MsgHeader进行组装报文,其中报文正文则是通过toBytes方法转换为数据流。

接着调用上下文的writeAndFlush方法将数据写入


之后数据被传到编码器的类,在这里MsgHeader自身的toBytes方法把所有数据都转换成流的形式,写入ByteBuf中,之后客户端将收到消息

public class PacketFrameEncoder extends MessageToByteEncoder<MsgHeader> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MsgHeader msgHeader, ByteBuf byteBuf) throws Exception {
        byteBuf.writeBytes(msgHeader.toBytes());
    }
}

客户端同理,导入netty包,其他操作和服务端类似

客户端可以通过ChannelFuture的引用向服务端发送消息

public class Net {
    public static void main(String[] args) throws Exception {
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline channelPipeline = ch.pipeline();
                    channelPipeline.addLast("decoder", new PacketFrameDecoder());
                    channelPipeline.addLast("encoder", new PacketFrameEncoder());
                    channelPipeline.addLast(new NetHandler());
                }
            });

            // Start the client.
            ChannelFuture f = b.connect(host, port).sync(); // (5)

            System.out.println("请输入账号和密码");

            Scanner scanner = new Scanner(System.in);
            String username = scanner.nextLine();
            String password = scanner.nextLine();
            AccountEntity entity = new AccountEntity(username, password);
            MsgHeader msgHeader = new MsgHeader();
            msgHeader.setContent(entity.toBytes());
            msgHeader.setType(MsgType.REQ_LOGIN);
            msgHeader.setLength(msgHeader.getContent().length + 8);

            f.channel().writeAndFlush(msgHeader);

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}




python023基于Python旅游景点推荐系统带vue前后端分离毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值