天天看點

Netty——自定義協定解決TCP粘包拆包問題什麼是TCP粘包拆包自定義協定解決拆包粘包問題

什麼是TCP粘包拆包

簡單的來講,就是多次TCP請求傳遞的msg,由于TCP的請求沒有隔離,造成服務端接收到消息不知道哪些位元組是屬于同一個請求的。

拆包:某一次TCP請求的msg被拆開讀取

粘包:某一次TCP請求的msg包含了其他請求的位元組

粘包拆包問題執行個體

問題示範就不貼全部代碼了

用戶端Handler

package com.leolee.netty.demo.unpackAdherepackage;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.Charset;

/**
 * @ClassName MyClientHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private int count;

    //連續發送10條資訊,測試服務端接受的消息的拆包粘包情況
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        for (int i = 0; i < 10; i++) {
            ByteBuf byteBuf = Unpooled.copiedBuffer("hello server " + i + "|", Charset.forName("utf-8"));
            channel.writeAndFlush(byteBuf);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);

        String message = new String(bytes, Charset.forName("utf-8"));
        System.out.println("用戶端接收到的資料:");
        System.out.println(message);
        System.out.println("用戶端接收到資料量:" + ++this.count);
    }
}
           

服務端Handler

package com.leolee.netty.demo.unpackAdherepackage;

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

import java.nio.charset.Charset;
import java.util.UUID;

/**
 * @ClassName MyServerHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private int count;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {

        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);

        String message = new String(bytes, Charset.forName("utf-8"));
        System.out.println("服務端接收到的資料:");
        System.out.println(message);
        System.out.println("服務端接收到資料量:" + ++this.count);


        ByteBuf byteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString() + "\n", Charset.forName("utf-8"));
        ctx.writeAndFlush(byteBuf);
    }
}
           

拆包粘包現象:

服務端接收到的資料:
hello server 0|
服務端接收到資料量:1
服務端接收到的資料:
hello server 1|
服務端接收到資料量:2
服務端接收到的資料:
hello server 2|hello server 3|hello server 4|hello server 5|
服務端接收到資料量:3
服務端接收到的資料:
hello server 6|hello server 7|
服務端接收到資料量:4
服務端接收到的資料:
hello server 8|hello server 9|
服務端接收到資料量:5
           

自定義協定解決拆包粘包問題

自定義協定:

package com.leolee.netty.demo.unpackAdherepackage.protocol;

/**
 * @ClassName MessageProtocol
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MessageProtocol {

    private int length;//讀取的長度

    private byte[] content;

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }
}
           

自定義編碼器:

package com.leolee.netty.demo.unpackAdherepackage.protocol;

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

/**
 * @ClassName MessageEncoder
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        System.out.println("自定義編碼器被調用");
        out.writeInt(msg.getLength());
        out.writeBytes(msg.getContent());
    }
}
           

自定義解碼器:

package com.leolee.netty.demo.unpackAdherepackage.protocol;

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

import java.util.List;

/**
 * @ClassName MessageDecoder
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MessageDecoder extends ReplayingDecoder<Void> {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("自定義解碼器被調用");
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes);

        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLength(length);
        messageProtocol.setContent(bytes);
        out.add(messageProtocol);
    }
}
           

用戶端代碼修改:增加編碼器

package com.leolee.netty.demo.unpackAdherepackage;

import com.leolee.netty.demo.unpackAdherepackage.protocol.MessageEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/**
 * @ClassName MyClientInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new MessageEncoder());//加入自定義編碼器
        pipeline.addLast(new MyClientHandler());
    }
}
           

用戶端handler

package com.leolee.netty.demo.unpackAdherepackage;

import com.leolee.netty.demo.unpackAdherepackage.protocol.MessageProtocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.Charset;

/**
 * @ClassName MyClientHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private int count;

    //連續發送10條資訊,測試服務端接受的消息的拆包粘包情況
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        for (int i = 0; i < 10; i++) {
            String message = "hello server " + i + "|";
            byte[] bytes = message.getBytes(Charset.forName("utf-8"));
            int length = message.getBytes(Charset.forName("utf-8")).length;

            MessageProtocol messageProtocal = new MessageProtocol();
            messageProtocal.setLength(length);
            messageProtocal.setContent(bytes);
            channel.writeAndFlush(messageProtocal);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);

        String message = new String(bytes, Charset.forName("utf-8"));
        System.out.println("用戶端接收到的資料:");
        System.out.println(message);
        System.out.println("用戶端接收到資料量:" + ++this.count);
    }
}
           

服務端代碼修改:增加解碼器

package com.leolee.netty.demo.unpackAdherepackage;

import com.leolee.netty.demo.unpackAdherepackage.protocol.MessageDecoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/**
 * @ClassName MyServerInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new MessageDecoder());
        pipeline.addLast(new MyServerHandler());
    }
}
           

服務端handler:

package com.leolee.netty.demo.unpackAdherepackage;

import com.leolee.netty.demo.unpackAdherepackage.protocol.MessageProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.Charset;

/**
 * @ClassName MyServerHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/22
 * @Version V1.0
 **/
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {

    private int count;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {

        int length = msg.getLength();
        byte[] content = msg.getContent();
        System.out.println("服務端接收到的資料:");
        System.out.println(new String(content, Charset.forName("utf-8")));
        System.out.println("服務端接收到資料量:" + ++this.count);
    }
}
           

測試:

自定義解碼器被調用
服務端接收到的資料:
hello server 0|
服務端接收到資料量:1
自定義解碼器被調用
服務端接收到的資料:
hello server 1|
服務端接收到資料量:2
自定義解碼器被調用
服務端接收到的資料:
hello server 2|
服務端接收到資料量:3
自定義解碼器被調用
服務端接收到的資料:
hello server 3|
服務端接收到資料量:4
自定義解碼器被調用
服務端接收到的資料:
hello server 4|
服務端接收到資料量:5
自定義解碼器被調用
服務端接收到的資料:
hello server 5|
服務端接收到資料量:6
自定義解碼器被調用
服務端接收到的資料:
hello server 6|
服務端接收到資料量:7
自定義解碼器被調用
服務端接收到的資料:
hello server 7|
服務端接收到資料量:8
自定義解碼器被調用
服務端接收到的資料:
hello server 8|
服務端接收到資料量:9
自定義解碼器被調用
服務端接收到的資料:
hello server 9|
服務端接收到資料量:10
           

繼續閱讀