tcp自动粘包拆包和自定义协议

TCP是面向连接的,面向流的,提供高可靠性服务。收发两端都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化方法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的。这就需要我们对消息边界进行处理,也就是粘包拆包问题。

例:

在客户端的handler重写:

public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i=0;i<10;i++){
ByteBuf byteBuf = Unpooled.copiedBuffer("hello,server" + i, CharsetUtil.UTF_8);
ctx.writeAndFlush(byteBuf);

}
}
当连接建立后,会连续发送十条消息,
再在服务端的handler重写:
private int count;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {

byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
//buffer转成字符串
String s = new String(bytes, CharsetUtil.UTF_8);
System.out.println(s);
System.out.println(++this.count);
//回送一个随机id
ByteBuf byteBuf1 = Unpooled.copiedBuffer(UUID.randomUUID().toString(), CharsetUtil.UTF_8);
channelHandlerContext.writeAndFlush(byteBuf1);
}
定义一个count记录读取次数,运行,结果:

 

 发送的十条消息被当成一条进行读取,进行了自动粘包。

为了防止自动粘包,我们需要使用自定义协议 + 编解码器 来解决

关键就是要解决 服务器端每次读取数据长度的问题, 这个问题解决,就不会出现服务器多读或少读数据的问题,从而避免的TCP 粘包、拆包 。

例: 协议包:

//协议包
public class MessageProtocol {
private int len;
private byte[] content;

public int getLen() {
return len;
}

public void setLen(int len) {
this.len = len;
}

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

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

}
解码器:
ublic class MyMessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
System.out.println("decoder被调用");
//需要将获取到的二进制字节码转成MyMessageProtocol数据包(对象)
//进行解码
int len = byteBuf.readInt();
byte[] bytes = new byte[len];
byteBuf.readBytes(bytes);
//封装成MessageProtocol对象 放入out 传给下一个handler进行业务处理
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(len);
messageProtocol.setContent(bytes);
list.add(messageProtocol);

}
编码器:
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
System.out.println("encoder被调用");
byteBuf.writeInt(messageProtocol.getLen());
byteBuf.writeBytes(messageProtocol.getContent());
}
}
ClientHandler:
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//客户端发送10条数据
for (int i=0;i<10;i++){
String msg="74120";
byte[] bytes = msg.getBytes(CharsetUtil.UTF_8);
int length = msg.getBytes(CharsetUtil.UTF_8).length;
//创建协议包
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setContent(bytes);
messageProtocol.setLen(length);
ctx.writeAndFlush(messageProtocol);
}
}

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

@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
int len=messageProtocol.getLen();
byte[] content = messageProtocol.getContent();
System.out.println("长度:"+len);
System.out.println("内容:"+new String(content,CharsetUtil.UTF_8));
System.out.println(++count);
}
}
ServerHandler:
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;


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

@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
int len=messageProtocol.getLen();
byte[] content=messageProtocol.getContent();
System.out.println("服务器接收到信息"+len);
System.out.println("内容"+new String(content,CharsetUtil.UTF_8));
System.out.println("接收到消息包数量:"+(++count));
//回复消息
String response=UUID.randomUUID().toString();
int responseLen=response.getBytes("utf-8").length;
byte[] bytes = response.getBytes("utf-8");
//构建一个协议包
MessageProtocol messageProtocol1 = new MessageProtocol();
messageProtocol1.setContent(bytes);
messageProtocol1.setLen(responseLen);
channelHandlerContext.writeAndFlush(messageProtocol1);

}
}



 



posted @ 2020-09-06 17:04  第十八使徒  阅读(490)  评论(0)    收藏  举报