Netty自定义通信协议(粘包拆包)

Netty自定义通信协议主要是在解码器与编码器,其他的变动不大。netty入门实例:https://blog.csdn.net/zc_ad/article/details/83824911,此处将测试的demo共享出来,现在对netty没办法到用语言组织的程度,只能先将demo贡献出来,到时候学的更加深入了,会将添加上讲解。

此demo的形成的效果是:当客户端连接服务端时,会向服务端发送自定义协议消息,服务端收到客户端自定义协议消息后,会给客户端发送另一条自定义协议消息。

协议格式:

 *  数据包格式
  +——----——+——-----——+——----——+
  |协议开始标志|  长度             |   数据       |
  +——----——+——-----——+——----——+
 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
 2.传输数据的长度contentLength,int类型
 3.要传输的数据

固定值,报头:

/**
 * Created by XiChuan on 2018-11-07.
 */
public class Constant {

    public static final int HEAD = 0x76;
}

自定义协议:

/**
 * Created by XiChuan on 2018-11-07.
 */

import java.util.Arrays;

/**
 * 
 * 自己定义的协议
 *  数据包格式
 * +——----——+——-----——+——----——+
 * |协议开始标志|  长度             |   数据       |
 * +——----——+——-----——+——----——+
 * 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
 * 2.传输数据的长度contentLength,int类型
 * 3.要传输的数据
 * 
*/ public class MessageProtocol { /** * 消息的开头的信息标志 */ private int head_data = Constant.HEAD; /** * 消息的长度 */ private int contentLength; /** * 消息的内容 */ private byte[] content; /** * 用于初始化,SmartCarProtocol * * @param contentLength * 协议里面,消息数据的长度 * @param content * 协议里面,消息的数据 */ public MessageProtocol(int contentLength, byte[] content) { this.contentLength = contentLength; this.content = content; } public int getHead_data() { return head_data; } public int getContentLength() { return contentLength; } public void setContentLength(int contentLength) { this.contentLength = contentLength; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } @Override public String toString() { return "MessageProtocol [head_data=" + head_data + ", contentLength=" + contentLength + ", content=" + Arrays.toString(content) + "]"; } }

自定义编码器:

import ch.qos.logback.core.encoder.ByteArrayUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * Created by XiChuan on 2018-11-07.
 */
/**
 * 
 * 自己定义的协议
 *  数据包格式
 * +——----——+——-----——+——----——+
 * |协议开始标志|  长度             |   数据       |
 * +——----——+——-----——+——----——+
 * 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
 * 2.传输数据的长度contentLength,int类型
 * 3.要传输的数据
 * 
*/ public class MessageProtocolEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol msg, ByteBuf byteBuf) throws Exception { // 写入消息SmartCar的具体内容 // 1.写入消息的开头的信息标志(int类型) byteBuf.writeInt(msg.getHead_data()); // 2.写入消息的长度(int 类型) byteBuf.writeInt(msg.getContentLength()); // 3.写入消息的内容(byte[]类型) byteBuf.writeBytes(msg.getContent()); } }

自定义解码器:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * Created by XiChuan on 2018-11-06.
 */

/**
 * 
 * 自己定义的协议
 *  数据包格式
 * +——----——+——-----——+——----——+
 * |协议开始标志|  长度             |   数据       |
 * +——----——+——-----——+——----——+
 * 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
 * 2.传输数据的长度contentLength,int类型
 * 3.要传输的数据,长度不应该超过2048,防止socket流的攻击
 * 
*/ public class MessageProtocolDecoder extends ByteToMessageDecoder { /** *
     * 协议开始的标准head_data,int类型,占据4个字节.
     * 表示数据的长度contentLength,int类型,占据4个字节.
     * 
*/ public final int BASE_LENGTH = 4 + 4; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List out) throws Exception { // 可读长度必须大于基本长度 //System.out.println("buff的可读长度是:"+buffer.readableBytes()); if (buffer.readableBytes() >= BASE_LENGTH) { // 防止socket字节流攻击 // 防止,客户端传来的数据过大 // 因为,太大的数据,是不合理的 if (buffer.readableBytes() > 2048) { buffer.skipBytes(buffer.readableBytes()); } // 记录包头开始的index int beginReader; while (true) { // 获取包头开始的index beginReader = buffer.readerIndex(); //System.out.println("记录包头开始的index:"+beginReader); // 标记包头开始的index buffer.markReaderIndex(); // 读到了协议的开始标志,结束while循环 int head = buffer.readInt(); //System.out.println("读取的int:"+head); if (head == Constant.HEAD) { break; } // 未读到包头,略过一个字节 // 每次略过,一个字节,去读取,包头信息的开始标记 buffer.resetReaderIndex(); buffer.readByte(); // 当略过,一个字节之后, // 数据包的长度,又变得不满足 // 此时,应该结束。等待后面的数据到达 if (buffer.readableBytes() < BASE_LENGTH) { return; } } // 消息的长度 int length = buffer.readInt(); // 判断请求数据包数据是否到齐 if (buffer.readableBytes() < length) { // 还原读指针 buffer.readerIndex(beginReader); return; } // 读取data数据 byte[] data = new byte[length]; buffer.readBytes(data); /*int head = buffer.readInt(); int length = buffer.readInt(); byte[] data = new byte[length]; buffer.readBytes(data);*/ MessageProtocol protocol = new MessageProtocol(data.length, data); out.add(protocol); } } }

创建服务端:

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.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

/**
 * Created by XiChuan on 2018-11-05.
 */
public class Server {

    private int port;

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

    public void run()throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup(); //用来接收进来的连接
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //用来处理已经被接收的连接

        try {
            ServerBootstrap bootstrap = new ServerBootstrap(); //启动NIO服务的辅助启动类
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class) //服务端
                    .childHandler(new ChannelInitializer() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {

                            //心跳机制 参数:1.读空闲超时时间 2.写空闲超时时间 3.所有类型的空闲超时时间(读、写) 4.时间单位
                            //在Handler需要实现userEventTriggered方法,在出现超时事件时会被触发
                            socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(500, 0, 0,TimeUnit.SECONDS));
                            //设置解码器
                            socketChannel.pipeline().addLast("decoder", new MessageProtocolDecoder());//new ByteArrayDecoder());//new FixedLengthFrameDecoder(4));
                            //设置自定义ChannelHandler
                            socketChannel.pipeline().addLast("channelHandler", new ServerHandler());
                            //设置编码器
                            socketChannel.pipeline().addLast("encoder",new MessageProtocolEncoder());//new ByteArrayEncoder());

                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture cf = bootstrap.bind(port).sync(); //绑定端口,开始接收进来的连接
            cf.channel().closeFuture().sync(); //等待服务器socket关闭

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args)throws Exception{
        new Server(8081).run();
    }
}

创建服务端处理类:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by XiChuan on 2018-11-05.
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private AtomicInteger channelCount = new AtomicInteger(0); //通道数量

    /**
     * 读数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("read channel=" + ctx.channel() + ", total channel=" + channelCount);
        try {

            MessageProtocol message = (MessageProtocol)msg;
            System.out.println("receive client message:"+message.toString());

            String str = "Hi I am Server ...";
            MessageProtocol protocol = new MessageProtocol(str.getBytes().length, str.getBytes());
            // 当服务端完成写操作后,关闭与客户端的连接
            ctx.channel().writeAndFlush(protocol);


            //没有定义将接收的数据改为什么类型此时就会用ByteBuf,在自定义协议的时候并没有用
            if (msg instanceof ByteBuf){

                //将ascii码的二进制转换为对应的字符
                System.out.println("Buffer ");
                ByteBuf byteBuf = (ByteBuf)msg;
                System.out.println(byteBuf.toString(CharsetUtil.UTF_8));

                //转换为16进制字符串
                /*byte[] bytes = new byte[byteBuf.readableBytes()];
                byteBuf.readBytes(bytes);
                String byteStr = ByteBufUtil.hexDump(bytes);
                System.out.println(byteStr);*/

                //转换为16进制字符串
                byte[] byte2s = ByteBufUtil.getBytes(byteBuf);
                System.out.println("hex string:"+ ByteBufUtil.hexDump(byte2s));
            }
        } finally {
            // 抛弃收到的数据
            ReferenceCountUtil.release(msg);
        }
    }

    /**
     * 心跳检测的超时时会触发
     * @param ctx
     * @param evt
     * @throws Exception
     */
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            if (e.state() == IdleState.READER_IDLE) {
                System.out.println("trigger channel =" + ctx.channel());
                ctx.close();  //如果超时,关闭这个通道
            }
        } else if (evt instanceof SslHandshakeCompletionEvent) {
            System.out.println("ssl handshake done");
            //super.userEventTriggered(ctx,evt);
        }
    }

    /**
     * 当通道活动时
     * @param ctx
     * @throws Exception
     */
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        channelCount.incrementAndGet();//当有新通道连接进来时,将通道数+1
        System.out.println("active channel=" + ctx.channel() + ", total channel=" + channelCount + ", id=" + ctx.channel().id().asShortText());

    }

    /**
     * 当通道不活动时
     * @param ctx
     * @throws Exception
     */
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
       //当通道关闭时,将通道数-1
        ctx.close();
        channelCount.decrementAndGet();
        System.out.println("inactive channel,channel=" + ctx.channel() +", id=" + ctx.channel().id().asShortText());
    }

    /**
     * 异常获取
     * @param ctx
     * @param cause
     * @throws Exception
     */
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exception channel=" + ctx.channel() + " cause=" + cause); //如果不活跃关闭此通道
        ctx.close();
    }

}

创建客户端:

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.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;

/**
 * Created by XiChuan on 2018-11-05.
 */
public class Client {

    private String host;

    private int port;

    public Client(String host,int port){
        this.host = host;
        this.port = port;
    }

    public void run() throws Exception {
        // 配置客户端NIO线程组
        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 socketChannel) throws Exception {
                            socketChannel.pipeline().addLast("decoder",new MessageProtocolDecoder());//new ByteArrayDecoder());
                            socketChannel.pipeline().addLast("encoder",new MessageProtocolEncoder());//new ByteArrayEncoder());
                            socketChannel.pipeline().addLast("channelHandler",new ClientHandler()); // 处理网络IO


                        }
                    });
            // 异步链接服务器 同步等待链接成功
            ChannelFuture f = b.connect(host, port).sync();

            // 等待链接关闭
            f.channel().closeFuture().sync();

        } finally {
            group.shutdownGracefully();
            System.out.println("client release resource...");
        }

    }

    public static void main(String[] args) throws Exception {
        new Client("127.0.0.1",8081).run();

    }

}

创建客户端处理类:

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;

/**
 * Created by XiChuan on 2018-11-05.
 */
public class ClientHandler  extends ChannelInboundHandlerAdapter {

    // 客户端与服务端,连接成功的的处理
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("active channel:" + ctx.channel());
        // 发送自定义Message协议的消息
        // 要发送的信息
        String str = "I am client ...";
        // 获得要发送信息的字节数组
        byte[] content = str.getBytes();
        // 要发送信息的长度
        int contentLength = content.length;
        MessageProtocol protocol = new MessageProtocol(contentLength, content);
        System.out.println("send message:"+protocol.toString());

        Channel channel = ctx.channel();
        channel.writeAndFlush(protocol);
    }

    // 只是读数据,没有写数据的话
    // 需要自己手动的释放的消息
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("read channel:" + ctx.channel());
        try {
            MessageProtocol messageProtocol = (MessageProtocol) msg;
            System.out.println("receive server message:" + messageProtocol.toString());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

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


}

效果如下:

服务端:

Netty自定义通信协议(粘包拆包)_第1张图片

客户端:

Netty自定义通信协议(粘包拆包)_第2张图片

参考链接:

https://blog.csdn.net/zbw18297786698/article/details/53691915

你可能感兴趣的:(Netty)