Netty解决TCP的粘包和分包(二)

Netty解决TCP的粘包和分包(二)

使用LengthFieldBasedFrameDecoder解码器分包

先看一下这个类的的属性,

private final ByteOrder byteOrder; //
private final int maxFrameLength; //定义最大帧的长度
private final int lengthFieldOffset; //长度属性的起始指针(偏移量)
private final int lengthFieldLength; //长度属性的长度,即存放数据包长度的变量的的字节所占的长度
private final int lengthFieldEndOffset; //根据lengthFieldOffset和lengthFieldLength计算出来的,即就是起始偏移量+长度=结束偏移量
private final int lengthAdjustment; 
private final int initialBytesToStrip; //解码后的数据包需要跳过的头部信息的字节数
private final boolean failFast;//这个和DelimiterBasedFrameDecoder是一致的,就是如果设置成true,当发现解析的数据超过maxFrameLenght就立马报错,否则当整个帧的数据解析完后才报错
private boolean discardingTooLongFrame;//当前编码器的状态,是不是处于丢弃超长帧的状态
private long tooLongFrameLength;//当出现超长帧的时候,这个超长帧的长度
private long bytesToDiscard;//当出现超长帧的时候,丢弃的数据的字节数

实现细节http://asialee.iteye.com/blog/1784844

http://bylijinnan.iteye.com/blog/1985706

这里是根据netty里一个聊天程序改的,如下代码,

服务器端代码:

public final class SecureChatServer {

    static final int PORT = Integer
        .parseInt(System.getProperty("port", "8992"));

    public static void main(String[] args) throws Exception {
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        SslContext sslCtx = SslContext.newServerContext(ssc.certificate(),
            ssc.privateKey());

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new SecureChatServerInitializer(sslCtx));

            // sync 等待异步操作的完成(done)
            // closeFuture().sync()等待socket关闭操作的完成(done)
            b.bind(PORT).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class SecureChatServerInitializer extends
        ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public SecureChatServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(sslCtx.newHandler(ch.alloc()));

        // On top of the SSL handler, add the text line codec.
        // 4字节表示消息体的长度,服务器端解码器
        pipeline.addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());

        // and then business logic.
        pipeline.addLast(new SecureChatServerHandler());
    }
}
public class SecureChatServerHandler extends
        SimpleChannelInboundHandler<String> {

    static final ChannelGroup channels = new DefaultChannelGroup(
        GlobalEventExecutor.INSTANCE);

    @Override
    public void channelActive(final ChannelHandlerContext ctx) {
        // Once session is secured, send a greeting and register the channel to the global channel
        // list so the channel received the messages from others.
        ctx.pipeline().get(SslHandler.class).handshakeFuture()
            .addListener(new GenericFutureListener<Future<Channel>>() {
                @Override
                public void operationComplete(Future<Channel> future)
                        throws Exception {
                    String welcome = "Welcome to "
                        + InetAddress.getLocalHost().getHostName()
                        + " secure chat service!";
                    ctx.writeAndFlush(ctx.alloc().buffer()
                        .writeInt(welcome.length())
                        .writeBytes(welcome.getBytes()));
                    String message = "Your session is protected by "
                        + ctx.pipeline().get(SslHandler.class).engine()
                            .getSession().getCipherSuite() + " cipher suite.";

                    ctx.writeAndFlush(ctx.alloc().buffer()
                        .writeInt(message.length())
                        .writeBytes(message.getBytes()));

                    channels.add(ctx.channel());
                }
            });
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, String msg)
            throws Exception {
        System.out.println("[" + msg + "]");

        String returnMsg;
        // Send the received message to all channels but the current one.
        for (Channel c : channels) {
            if (c != ctx.channel()) {
                returnMsg = "[" + ctx.channel().remoteAddress() + "] " + msg;
                c.writeAndFlush(ctx.alloc().buffer()
                    .writeInt(returnMsg.length())
                    .writeBytes(returnMsg.getBytes()));
            } else {
                returnMsg = "[you] " + msg;
                c.writeAndFlush(ctx.alloc().buffer()
                    .writeInt(returnMsg.length())
                    .writeBytes(returnMsg.getBytes()));
            }
        }

        // Close the connection if the client has sent 'bye'.
        if ("bye".equals(msg.toLowerCase())) {
            ctx.close();
        }
    }

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

客户端代码:

public final class SecureChatClient {

    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer
        .parseInt(System.getProperty("port", "8992"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx = SslContext
            .newClientContext(InsecureTrustManagerFactory.INSTANCE);

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                .handler(new SecureChatClientInitializer(sslCtx));

            // Start the connection attempt.
            // sync 等待连接建立成功。因为连接在这里表现为异步操作,所以要等待连接的Future完成(done)。
            Channel ch = b.connect(HOST, PORT).sync().channel();

            // Read commands from the stdin.
            ChannelFuture lastWriteFuture = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(
                System.in));
            for (;;) {
                String line = in.readLine();
                if (line == null) {
                    break;
                }
                //获取用户的输入,然后构造消息,发送消息
                ByteBuf content = ch.alloc().buffer().writeInt(line.length())
                    .writeBytes(line.getBytes());
                // Sends the received line to the server.
                lastWriteFuture = ch.writeAndFlush(content);

                // If user typed the 'bye' command, wait until the server closes
                // the connection.
                if ("bye".equals(line.toLowerCase())) {
                    ch.closeFuture().sync();
                    break;
                }
            }

            // Wait until all messages are flushed before closing the channel.
            if (lastWriteFuture != null) {
                lastWriteFuture.sync();
            }
        } finally {
            // The connection is closed automatically on shutdown.
            group.shutdownGracefully();
        }
    }
}
public class SecureChatClientInitializer extends
        ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public SecureChatClientInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(sslCtx.newHandler(ch.alloc(), SecureChatClient.HOST,
            SecureChatClient.PORT));
        // On top of the SSL handler, add the text line codec.
        //使用该解码器解码服务器返回消息
        pipeline.addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());  //字符串编码器

        // and then business logic.
        pipeline.addLast(new SecureChatClientHandler());
    }
}
public class SecureChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void messageReceived(ChannelHandlerContext ctx, String msg) {
        System.err.println(msg);
    }

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

netty原来的example在这里,https://github.com/netty/netty/tree/master/example/src/main/java/io/netty/example/securechat

如何在这里使用LengthFieldBasedFrameDecoder解码器,如下是在服务器端解码器的配置,

// lengthFieldLength =4 字节表示实际消息体的长度
// initialBytesToStrip =4 字节表示解码消息体的时候跳过长度
pipeline.addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));

当服务器端发送消息的时候,是这样发的,

for (Channel c : channels) {
    if (c != ctx.channel()) {
        returnMsg = "[" + ctx.channel().remoteAddress() + "] " + msg;
        c.writeAndFlush(ctx.alloc().buffer()
            .writeInt(returnMsg.length())
            .writeBytes(returnMsg.getBytes()));
    } else {
        returnMsg = "[you] " + msg;
        c.writeAndFlush(ctx.alloc().buffer()
            .writeInt(returnMsg.length())
            .writeBytes(returnMsg.getBytes()));
    }
}

一定要把实际消息体的长度写入到buff中。相应的客户端的配置见上面的代码。

==================END==================

转载于:https://my.oschina.net/xinxingegeya/blog/486084

Netty中的TCP粘包和拆包问题是由于底层的TCP协议无法理解上层的业务数据而导致的。为了解决这个问题,Netty提供了几种解决方案。其中,常用的解决方案有四种[1]: 1. 固定长度的拆包器(FixedLengthFrameDecoder):将每个应用层数据包拆分成固定长度的大小。这种拆包器适用于应用层数据包长度固定的情况。 2. 行拆包器(LineBasedFrameDecoder):将每个应用层数据包以换行符作为分隔符进行分割拆分。这种拆包器适用于应用层数据包以换行符作为结束符的情况。 3. 分隔符拆包器(DelimiterBasedFrameDecoder):将每个应用层数据包通过自定义的分隔符进行分割拆分。这种拆包器适用于应用层数据包以特定分隔符作为结束标志的情况。 4. 基于数据包长度的拆包器(LengthFieldBasedFrameDecoder):将应用层数据包的长度作为接收端应用层数据包的拆分依据。根据应用层协议中包含的数据包长度进行拆包。这种拆包器适用于应用层协议中包含数据包长度的情况。 除了使用这些拆包器,还可以根据业界主流协议的解决方案来解决粘包和拆包问题[3]: 1. 消息长度固定:累计读取到长度和为定长LEN的报文后,就认为读取到了一个完整的信息。 2. 使用特殊的分隔符:将换行符或其他特殊的分隔符作为消息的结束标志。 3. 在消息头中定义长度字段:通过在消息头中定义长度字段来标识消息的总长度。 综上所述,Netty提供了多种解决方案来解决TCP粘包和拆包问题,可以根据具体的业务需求选择合适的解决方案[1][3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值