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

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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

关于TCP的粘包和分包:http://my.oschina.net/xinxingegeya/blog/484824

Netty分包

分包的解决办法:

1、消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。

2、包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。

3、将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段

4、更复杂的自定义应用层协议

而在netty提供了两个解码器,可以进行分包的操作,这两个解码器分别是:

  • DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)

  • FixedLengthFrameDecoder(使用定长的报文来分包)

来看一下这两种解码器是如何进行分包的。


DelimiterBasedFrameDecoder分包

先看一个实例,一个netty的example,github地址

https://github.com/netty/netty/tree/master/example/src/main/java/io/netty/example/securechat

只是其中的一个设置编解码器的类。

package com.usoft.chat;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;

/**
 * Creates a newly configured {@link ChannelPipeline} for a new channel.
 */
public class SecureChatServerInitializer extends
        ChannelInitializer {

    private final SslContext sslCtx;

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

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

        // Add SSL handler first to encrypt and decrypt everything.
        // In this example, we use a bogus certificate in the server side
        // and accept any invalid certificates in the client side.
        // You will need something more complicated to identify both
        // and server in the real world.
        pipeline.addLast(sslCtx.newHandler(ch.alloc()));

        // On top of the SSL handler, add the text line codec.
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters
            .lineDelimiter()));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());

        // and then business logic.
        pipeline.addLast(new SecureChatServerHandler());
    }
}

其中这行代码就是设置解码器来分包的,

 pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));

如下是这个类的属性字段,

private final ByteBuf[] delimiters;  // 分包的分隔符数组
private final int maxFrameLength; //报文(帧)的最大的长度
private final boolean stripDelimiter;  // 是否除去分隔符(如果数据包中含有分隔符,不影响)
private final boolean failFast; // 为true是说发现读到的数据已经超过了maxFrameLength了,立即报TooLongFrameException,如果为false就是读完整个帧数据后再报
private boolean discardingTooLongFrame; //是否抛弃超长的帧
private int tooLongFrameLength; //就是说出现了超长帧,那这个帧的长度到底是多少,就是这个长度,一般来说是在发现当前buffer的可读数据超过最大帧时候进行设置

其实这个例子都不会造成粘包,因为客户端的每次输入然后回车都会使客户端进行一次writeAndFlush。这里只不过是演示了 DelimiterBasedFrameDecoder的用法。同时使用这个解码器也可以实现分包。


FixedLengthFrameDecoder分包

实例如下,服务器端代码,

package com.usoft.demo1;

import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 定长解码 服务器端
 * 
 * @author xwalker
 */
public class Server {

    public static void main(String[] args) throws Exception {
        int port = 8000;
        new Server().bind(port);
    }

    public void bind(int port) throws Exception {
        //接收客户端连接用
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //处理网络读写事件
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //配置服务器启动类
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .handler(new LoggingHandler(LogLevel.INFO))//配置日志输出
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel ch)
                            throws Exception {
                        ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//设置定长解码器 长度设置为30
                        ch.pipeline().addLast(new StringDecoder());//设置字符串解码器 自动将报文转为字符串
                        ch.pipeline().addLast(new Serverhandler());//处理网络IO 处理器
                    }
                });
            //绑定端口 等待绑定成功
            ChannelFuture f = b.bind(port).sync();
            //等待服务器退出
            f.channel().closeFuture().sync();
        } finally {
            //释放线程资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}
package com.usoft.demo1;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

/**
 * 服务器handler
 *
 * @author xwalker
 */
public class Serverhandler extends ChannelHandlerAdapter {
    private static final String MESSAGE = "It greatly simplifies and streamlines network programming such as TCP and UDP socket server.";
    int counter = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("接收客户端msg:[" + msg + "]");
        ByteBuf echo = Unpooled.copiedBuffer(MESSAGE.getBytes());
        ctx.writeAndFlush(echo);
    }

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

}

客户端代码,

package com.usoft.demo1;

import io.netty.bootstrap.Bootstrap;
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.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
 * 客户端 
 * @author xwalker
 *
 */
public class Client {
    /**
     * 链接服务器
     * @param port
     * @param host
     * @throws Exception
     */
    public void connect(int port,String host)throws Exception{
        //网络事件处理线程组
        EventLoopGroup group=new NioEventLoopGroup();
        try{
        //配置客户端启动类
        Bootstrap b=new Bootstrap();
        b.group(group).channel(NioSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)//设置封包 使用一次大数据的写操作,而不是多次小数据的写操作
        .handler(new ChannelInitializer() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//设置定长解码器
                ch.pipeline().addLast(new StringDecoder());//设置字符串解码器
                ch.pipeline().addLast(new ClientHandler());//设置客户端网络IO处理器
            }
        });
        //连接服务器 同步等待成功
        ChannelFuture f=b.connect(host,port).sync();
        //同步等待客户端通道关闭
        f.channel().closeFuture().sync();
        }finally{
            //释放线程组资源
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception {
        int port=8000;
        new Client().connect(port, "127.0.0.1");
 
    }
 
}
package com.usoft.demo1;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

/**
 * 客户端处理器
 * 
 * @author xwalker
 */
public class ClientHandler extends ChannelHandlerAdapter {
    private static final String MESSAGE = "Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.";

    public ClientHandler() {
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(MESSAGE.getBytes()));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("接收服务器响应msg:[" + msg + "]");
    }

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

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

服务器端打印结果:

receive client msg:[Netty is a NIO client server f]
receive client msg:[ramework which enables quick a]
receive client msg:[nd easy development of network]
receive client msg:[ applications such as protocol]

客户端打印结果:

receive sever msg:[It greatly simplifies and stre]
receive sever msg:[amlines network programming su]
receive sever msg:[ch as TCP and UDP socket serve]
receive sever msg:[r.It greatly simplifies and st]
receive sever msg:[reamlines network programming ]
receive sever msg:[such as TCP and UDP socket ser]
receive sever msg:[ver.It greatly simplifies and ]
receive sever msg:[streamlines network programmin]
receive sever msg:[g such as TCP and UDP socket s]
receive sever msg:[erver.It greatly simplifies an]
receive sever msg:[d streamlines network programm]
receive sever msg:[ing such as TCP and UDP socket]

客户端需要和服务器端约定每个包的大小为定长的,这样服务器端才可以根据这个规则来分包。这里也是演示了FixedLengthFrameDecoder解码器的用法。

参考和引用:

http://my.oschina.net/imhoodoo/blog/357290

http://asialee.iteye.com/blog/1783842

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

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

你可能感兴趣的:(Netty解决TCP的粘包和分包(一))