Netty笔记(1)—— Netty基础入门

Netty入门案例

Server端

package com.wy.netty.simple;

import com.wy.netty.simple.messagepack.MsgPackEncoder;
import com.wy.netty.simple.messagepack.MsgpackDecoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
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.sctp.nio.NioSctpServerChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

/**
 * @ClassName NettyServer
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:02
 */

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        //两个group的
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            //使用固定长度的解码器
//                            ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
                            //使用自定义符合进行分割
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                            //使用换行符进行分割
//                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("...服务器 is ready...");

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();

            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}
package com.wy.netty.simple;

import com.wy.netty.simple.pojo.User;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
import org.msgpack.MessagePack;
import org.msgpack.template.Templates;

/**
 * @ClassName NettyServerHandler
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:19
 */

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    private int count = 1;

    //读取数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String buf = (String) msg;
        System.out.println("客户端发送消息是: " + buf + " 本次是第:" + count++ + "次");
        ctx.writeAndFlush(Unpooled.copiedBuffer(buf + "$_", CharsetUtil.UTF_8));
    }


    //处理异常,一般需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

Client端

package com.wy.netty.simple;

import com.wy.netty.simple.messagepack.MsgPackEncoder;
import com.wy.netty.simple.messagepack.MsgpackDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
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 io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

/**
 * @ClassName NettyClient
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:41
 */

public class NettyClient {

    public static void main(String[] args) throws InterruptedException {
        //客户端需要一个事件循环即可
        EventLoopGroup 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) {
                            //使用自定义符合进行分割
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                            //使用换行符
//                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            ch.pipeline().addLast(new StringDecoder());
                            //加入自己的处理器
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            System.out.println("...客户端 is ok...");
            //启动客户端连接服务器
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

}
package com.wy.netty.simple;

import com.wy.netty.simple.pojo.User;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @ClassName NettyClientHandler
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:50
 */

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    private int count = 1;


    //当通道就绪时,就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 100; i++) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello ,server" + "$_", CharsetUtil.UTF_8));
        }
    }

    //当通道有读取事件时,会触发此方法
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String buf = (String) msg;
        System.out.println("服务器回复消息: " + buf + " 本次是第:" + count++ + "次");
    }

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

TCP粘包/拆包问题

TCP是个"流"协议,所谓流,就是没有界限的一串数据。可以想象河里的水流,他们是连成一片,没有界限的。TCP底层并不清楚业务数据的含义,它会根据缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP分为多个包发送,也可能把多个小包封装成一个大包发送。
在这里插入图片描述

粘包/拆包产生的原因

  1. 应用程序write写入的字节大小大于套接口发送缓冲区的大小
  2. 进行MSS(最大报文程度)大小的TCP分段
  3. 以太网帧的payload大于MTU进行IP分片

粘包/拆包的解决策略

  1. 消息定长,比如按照每个报文200字节,不够补空格的方式
  2. 在包尾增加回车换行符进行分割,例如FTP协议
  3. 将消息分为消息头和消息体,消息头中包含表示消息总长度或者消息体长度的字段。通常的设计思路为消息头的第一个字段使用int32来表示消息的总程度
  4. 使用更复杂的应用层协议

Netty解决粘包/拆包问题

LineBasedFrameDecoder和StringDecoder

在入门案例中已经有如下代码。

ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());

使用LineBasedFrameDecoderStringDecoder可以解决粘包/拆包问题。

首先解释LineBasedFrameDecoder:是以换行符未结束标志的解码器,它的工作原理是一次遍历ByteBuf中的可读字节,判断是否有"\n"或者"\r\n",如果有,就以此位置为结束位置。并且可以设置最大长度,笔者设置为1024,若超出最大长度后仍然没有发现换行符,就会抛出异常。

StringDecoder:会将接受到的对象装换为字符串,然后继续调用后面的Handler。

分隔符解码器

Netty还提供了分隔符解码器DelimiterBasedFrameDecoder,它可以支持使用自定义字符,进行报文分割。

使用实例如下

ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));

定长解码器

Netty提供定长解码器FixedLengthFrameDecoder可以完成对定长消息的解码

使用实例如下

//使用固定长度的解码器
ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值