粘包与分包问题的出现及解决
一、粘包出现的原因
服务端与客户端没有约定好要使用的数据结构。Socket Client实际是将数据包发送到一个缓存 buffer中,通过 buffer刷到数据链路层。因服务端接收数据包时,不能断定数据包1何时结束,就有可能出现数据包2的部分数据结合数据包1发送出去,导致服务器读取数据包1时包含了数据包2的数据。这种现象称为粘包。
二、案例展示
【1】服务端代码如下,具体注释说明
【2】ServerSocketHandler处理类展示:
1 package com.server; 2 3 import io.netty.channel.ChannelHandlerContext; 4 import io.netty.channel.SimpleChannelInboundHandler; 5 6 public class ServerSocketHandler extends SimpleChannelInboundHandler<String>{ 7 8 @Override 9 protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception { 10 System.out.println(msg); 11 } 12 13 }
【3】客户端发送请求代码展示:
三、分包
数据包数据被分开一部分发送出去,服务端一次读取数据时可能读取到完整数据包的一部分,剩余部分被第二次读取。具体情况如下图展示:
四、解决办法
【方案一】:定义一个稳定的结构。
【1】包头+length+数据包:客户端代码展示:包头用来防止 socket攻击,length用来获取数据包的长度。
1 package com.server; 2 3 import java.io.IOException; 4 import java.net.Socket; 5 import java.net.UnknownHostException; 6 import java.nio.ByteBuffer; 7 8 import org.omg.CORBA.PRIVATE_MEMBER; 9 import org.omg.CORBA.PUBLIC_MEMBER; 10 11 /** 12 * @category 通过长度+数据包的方式解决粘包分包问题 13 * @author zhengzx 14 * 15 */ 16 public class Client { 17 //定义包头 18 public static int BAO = 24323455; 19 public static void main(String[] args) throws UnknownHostException, IOException { 20 //创建连接 21 Socket socket = new Socket("127.0.0.1", 10010); 22 //客户端发送的消息 23 String msg = "hello"; 24 //获取消息的字节码 25 byte[] bytes = msg.getBytes(); 26 //初始化buffer的长度:4+4表示包头长度+存放数据长度的整数的长度 27 ByteBuffer buffer = ByteBuffer.allocate(8+bytes.length); 28 //将长度和数据存入buffer中 29 buffer.putInt(BAO); 30 buffer.putInt(bytes.length); 31 buffer.put(bytes); 32 //获取缓冲区中的数据 33 byte[] array = buffer.array(); 34 //循环发送请求 35 for(int i=0;i<1000;i++){ 36 socket.getOutputStream().write(array); 37 } 38 //关闭连接 39 socket.close(); 40 } 41 }
【2】服务端:需要注意的是,添加了MyDecoder类,此类具体下面介绍
我们自己写的MyDecoder类,代码展示:(包含socket攻击的校验)
【4】服务端,处理请求的handler。
【5】结果展示(按顺序打印):
【方案二】:在消息的尾部加一些特殊字符,那么在读取数据的时候,只要读到这个特殊字符,就认为已经可以截取一个完整的数据包了,这种情况在一定的业务情况下实用。