Java网络编程(7) - 什么是TCP粘包、拆包和解决?什么Netty编解码技术?主流的序列化框架有哪些?Netty的JBOSS的Marshalling详解案例?

TCP粘包、拆包问题

      熟悉TCP编程的可能都知道,无论是服务端还是客户端,当进行数据读取或者发送数据的时候,都需要考虑TCP底层的粘包和拆包机制的问题。

      TCP是一个流协议,流是没有界限的数据,是连成一片的,没有分界线。TCP底层并不理解上层的业务数据具体的含义,它会根据TCP缓冲区的实际情况进行包的划分,也就是说,在业务上,一个完整的包可能会被TCP分成多个包进行发送,也可能把多个小包封装成一个大的数据包发送出去,这就是所谓的TCP粘包、拆包问题。

 

TCP粘包和拆包 - 问题产生的常见原因

      1、应用程序Write写入的字节大小大于套接字发送的缓冲区的大小。

      2、进行MSS大小的TCP分段

      3、以太网帧的payload大于MTU进行IP分片。

 

TCP粘包和拆包 - 常见解决方案

      1、消息定长:例如每个包的大小固定为200个字节,不够用空格补。

      2、特殊字符:在包尾部增加特殊字符进行分割,例如加回车等,在实际中经常使用的是这种。

      3、自定义:将消息分为消息头和消息体,消息头中包含消息总长度的字段,然后进行业务逻辑的处理,但是要考虑安全等问题。

 

TCP粘包和拆包模拟 – 消息定长与特殊字符解决方案(FixedLengthFrameDecoder与DelimiterBasedFrameDecoder)

/**

 * Tcp拆包、粘包客户端

 */

public class N02TcpUnpacketAndNotUnServer {

    public static void main(String[] args) throws InterruptedException {

        

         //用于客户端链接

         EventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();

         //用于传输数据

         EventLoopGroup dataEventLoopGroup = new NioEventLoopGroup();

 

         //数据启动

        ServerBootstrap serverBootstrap = new ServerBootstrap();

         serverBootstrap.group(clientEventLoopGroup, dataEventLoopGroup) //设置链接和数据处理

                          .channel(NioServerSocketChannel.class) //设置模式

                          .option(ChannelOption.SO_BACKLOG, 1024) //TCP缓冲区

                          .option(ChannelOption.SO_SNDBUF, 32 * 1024) //发送数据缓冲区

                          .option(ChannelOption.SO_RCVBUF, 32 * 1024) //接受数据缓冲区

                          .childHandler(new ChannelInitializer() { //启动初始化类

                               @Override

                               protected void initChannel(SocketChannel ch) throws Exception {

                                   //3、自定义分隔符的方式解决粘包拆包问题

                                   //注意:设置顺序

//                                 ByteBuf byteBuf = Unpooled.copiedBuffer("_".getBytes());

//                                 ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, byteBuf));

                                  

                                   //5、定长数据包

                                   //注意:设置顺序

                                   ch.pipeline().addLast(new FixedLengthFrameDecoder(3));

                                  

                                   ch.pipeline().addLast(new StringDecoder()); //设置字符串解码类

                                   ch.pipeline().addLast(new TcpUServerHandler()); //设置处理类

                               }

                          });

        

         //绑定链接

         ChannelFuture channelFuture = serverBootstrap.bind(1111).sync();

         channelFuture.channel().closeFuture().sync();

        

         //关闭

         clientEventLoopGroup.shutdownGracefully();

         dataEventLoopGroup.shutdownGracefully();

    }

}

class TcpUServerHandler extends ChannelHandlerAdapter {

   

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

         System.out.println("服务端:活跃...");

    }

   

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

         //在上面时,已经设置了编解码器就是String类型了

         String data = (String) msg;

         System.out.println("服务端:" + data);

         //响应数据

         ctx.writeAndFlush(Unpooled.copiedBuffer("你好,客户端".getBytes()));

    }

   

    @Override

    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

         System.out.println("服务端:读取完成");

    }

   

    @Override

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

         cause.printStackTrace();

    }

}

/**

 * Tcp拆包、粘包客户端

 */

public class N02TcpUnpacketAndNotUnClient {

    public static void main(String[] args) throws InterruptedException {

         //创建链接

         EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        

         //启动类

         Bootstrap bootstrap = new Bootstrap();

         bootstrap.group(eventLoopGroup) //启动类

                          .channel(NioSocketChannel.class) //设置模式

                          .handler(new ChannelInitializer() { //初始化

                               @Override

                               protected void initChannel(SocketChannel ch) throws Exception {

                                   //3、自定义分隔符的方式解决粘包拆包问题

//                                 ByteBuf byteBuf = Unpooled.copiedBuffer("_".getBytes());

//                                 ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));

                                  

                                   //5、定长数据包

                                   ch.pipeline().addLast(new FixedLengthFrameDecoder(3));

                                  

                                   ch.pipeline().addLast(new StringDecoder()); //编解码器

                                   ch.pipeline().addLast(new TcpUnClientHandler()); //数据处理类

                               }

                          });

                         

         //绑定地址和端口

         ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 1111).sync();

        

         /*

          * 1、模拟粘包、拆包问题

          * 会发现服务端收到的数据是链接在一起的:111111222222333333

          *

          * 2、添加一个Thread.sleep(100);模拟延迟发送

          * 会发现服务端接收的数据是一行一行的,似乎每个数据包都是相隔开的。

          * 问题在实际的应用中,不可能这样去停止等待的,效率太低了。

          *

          * Netty提供了2个类用于解决这个粘包、拆包问题:

          * 自定义分隔符:DelimiterBasedFrameDecoder

          * 定长数据包:FixedLengthFrameDecoder

          */

//       channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("111111".getBytes()));

//       Thread.sleep(100);

//       channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("222222".getBytes()));

//       Thread.sleep(100);

//       channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("333333".getBytes()));

        

         //4、模拟自定义分隔符,解决粘包、拆包问题

//       channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("aaa_".getBytes()));

//       channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("bbb_".getBytes()));

        

         //6、模拟定长数据包,解决粘包、拆包问题

        channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("aaaaaaabbbbbbbccccccc".getBytes()));

        

         //阻塞

         channelFuture.channel().closeFuture().sync();

        

         //关闭

         eventLoopGroup.shutdownGracefully();

    }

}

/**

 * 数据处理

 */

class TcpUnClientHandler extends ChannelHandlerAdapter {

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

         System.out.println("客户端:活跃状态");

    }

   

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

         try {

             //上面设置了编解码器

             String data = (String) msg;

             System.out.println("客户端:" + data);

         } finally {

             ReferenceCountUtil.release(msg);

         }

    }

   

    @Override

    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

         System.out.println("客户端:数据读取完成");

    }

   

    @Override

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

         cause.printStackTrace();

    }

}

 

 

Netty编解码技术(序列化) (JBOSS的Marshalling案例)

      其实就是Java序列化技术,序列化的目的就有两个,一是网络传输、而是对象持久化。

      虽然可以使用Java进行对象序列化,Netty去传输,但是Java序列化的硬伤太多了,且明显,比如没法跨语音,序列化后码流太大,序列化性能低等等。

 

主流的序列化框架区别

      JBOSS的Marshalling:对JDK默认的序列化框架进行了优化,但又保持了跟java.io.Serializable接口的兼容,同时增加了一些可选的参数和附加特性。

      Google的Protobuf:基于Protobuf协议,序列号后码流小,性能高,依赖于工具生成代码,支持语音也比较少,但是在性能要求高的RPC调用,而且具有很好的跨防火墙的访问属性,适合应用程对象持久化。

      基于Protobuf的Kyro(Spark):只支持java语言。

      MessagePack框架:高效的二进制序列化格式。

      AvroHadoop的一个子项目,解决了JSON的冗长和没有IDL的问题,对于习惯于静态类型语音的用户不直观,适用于Hadoop中的Hive这些做持久化数据格式。

 

JBOSS的Marshalling序列化案例

/**

 * 压缩工具类

 */

public class N03GzipUtils {

    public static void main(String[] args) throws Exception {

         String inPath = "C:\\Users\\EDZ\\Desktop\\1.gif";

        

         //读取文件

         FileInputStream fileInputStream = new FileInputStream(new File(inPath));

         byte [] data = new byte[fileInputStream.available()];

         fileInputStream.read(data);

         fileInputStream.close();

         System.out.println("文件原始大小:" + data.length);

        

         byte[] gzipData = N03GzipUtils.gzip(data);

         System.out.println("压缩大小:" + gzipData.length);

        

         byte[] ungzipData = N03GzipUtils.ungzip(gzipData);

         System.out.println("解压缩大小:" + ungzipData.length);

        

         //写出文件

         String outPath = "C:\\Users\\EDZ\\Desktop\\2.gif";

         FileOutputStream fileOutputStream = new FileOutputStream(outPath);

         fileOutputStream.write(ungzipData);

         fileOutputStream.close();

    }

   

    /**

     * 压缩

     */

    public static byte[] gzip(byte[] data) throws Exception {

         //创建二进制流写出

         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

         GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);

         gzipOutputStream.write(data);

         gzipOutputStream.finish();

         gzipOutputStream.close();

        

         //读取到的数据

         byte[] byteArray = byteArrayOutputStream.toByteArray();

         byteArrayOutputStream.close();

         return byteArray;

    }

   

    /**

     * 解压

     */

    public static byte[] ungzip(byte[] data) throws Exception {

         //创建二进制流写出

         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);

         GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);

         byte [] buf = new byte[1024];

         int num = -1;

        

         //写出

         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

         while ((num = gzipInputStream.read(buf, 0, buf.length)) != -1) {

             byteArrayOutputStream.write(buf,0,num);

         }

        gzipInputStream.close();

         byteArrayInputStream.close();

        

         byte [] rData = byteArrayOutputStream.toByteArray();

         byteArrayOutputStream.flush();

         byteArrayOutputStream.close();

         return rData;

    }

}

import org.jboss.marshalling.MarshallerFactory;

import org.jboss.marshalling.Marshalling;

import org.jboss.marshalling.MarshallingConfiguration;

 

import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;

import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;

import io.netty.handler.codec.marshalling.MarshallerProvider;

import io.netty.handler.codec.marshalling.MarshallingDecoder;

import io.netty.handler.codec.marshalling.MarshallingEncoder;

 

/**

 * 序列化编解码器:

 * JBossMarshalling

 * GoogleProtobuf

 * 基于ProtobufKyro

 * MessagePack框架

 */

public class N03MarshallingDecoderEncoderUtils {

 

    public final static String TAG = "serial";

   

    /**

     * 解码器:将二进制序列化为java对象

     */

    public static MarshallingDecoder buildDecoder() {

         //通过Marshalling工具类获取到实例对象,serial标识创建的是java的序列化工厂对象

         MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory(TAG);

        

         //创建MarshallingConfiguration对象,并配置版本号

         MarshallingConfiguration marshallingConfiguration = new MarshallingConfiguration();

         marshallingConfiguration.setVersion(5);

        

         //创建Provider

         DefaultUnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, marshallingConfiguration);

        

         //构建Netty的解码对象,并配置provider和单个消息序列化的最大长度

         return new MarshallingDecoder(provider, 1024 * 1024);

    }

   

    /**

     * 编码器:将对象序列化为二进制数组

     */

    public static MarshallingEncoder buildEncoder() {

         //序列化java工厂类

         MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory(TAG);

        

         //创建配置类

         MarshallingConfiguration marshallingConfiguration = new MarshallingConfiguration();

         marshallingConfiguration.setVersion(5);

        

         //创建供应者

         MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, marshallingConfiguration);

        

         //构建NettyMarshallingEncoder对象

         MarshallingEncoder marshallingEncoder = new MarshallingEncoder(provider);

         return marshallingEncoder;

    }

}

import java.io.Serializable;

 

/**

 * 请求数据

 */

public class N03Request implements Serializable {

 

    private static final long serialVersionUID = -7633763905125419996L;

 

    private String data;

   

    private byte[] file;

 

    public String getData() {

         return data;

    }

 

    public void setData(String data) {

         this.data = data;

    }

 

    public byte[] getFile() {

         return file;

    }

 

    public void setFile(byte[] file) {

         this.file = file;

    }

}

 

import java.io.Serializable;

 

/**

 * 响应数据

 */

public class N03Response implements Serializable {

 

    private static final long serialVersionUID = -6704864849770619898L;

 

    private String data;

 

    public String getData() {

         return data;

    }

 

    public void setData(String data) {

         this.data = data;

    }

}

 

import java.io.FileOutputStream;

import java.util.Random;

 

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.ChannelFuture;

import io.netty.channel.ChannelHandlerAdapter;

import io.netty.channel.ChannelHandlerContext;

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.logging.LogLevel;

import io.netty.handler.logging.LoggingHandler;

 

/**

 * 序列化的编解码Api,服务端

 */

public class N03SerialServer {

    public static void main(String[] args) throws InterruptedException {

         //用于客户端链接

         EventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();

         //用于数据读写

         EventLoopGroup dataEventLoopGroup = new NioEventLoopGroup();

        

         //创建启动类

         ServerBootstrap serverBootstrap = new ServerBootstrap();

         serverBootstrap.group(clientEventLoopGroup, dataEventLoopGroup) //配置链接类

                          .channel(NioServerSocketChannel.class) //设置模式

                          .option(ChannelOption.SO_BACKLOG, 1024) //TCP缓冲区

                          .handler(new LoggingHandler(LogLevel.INFO)) //日志级别

                          .childHandler(new ChannelInitializer() { //吃实话

                               @Override

                               protected void initChannel(SocketChannel ch) throws Exception {

                                   //编解码器

                                   ch.pipeline().addLast(N03MarshallingDecoderEncoderUtils.buildDecoder());

                                   ch.pipeline().addLast(N03MarshallingDecoderEncoderUtils.buildEncoder());

                                   //处理类

                                   ch.pipeline().addLast(new SServerHandler());

                               }

                          });

        

         //绑定端口

         ChannelFuture channelFuture = serverBootstrap.bind(1111).sync();

         channelFuture.channel().closeFuture().sync();

        

         //关闭

         clientEventLoopGroup.shutdownGracefully();

         dataEventLoopGroup.shutdownGracefully();

    }

}

class SServerHandler extends ChannelHandlerAdapter {

   

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

         System.out.println("服务端:活跃");

    }

   

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

         System.out.println("---------------------");

         //已经设置了对象的序列化编解码器

         N03Request request = (N03Request) msg;

         System.out.println("服务端:" + request.getData());

        

         byte[] ungzipData = N03GzipUtils.ungzip(request.getFile());

        

         //写出文件

         String outPath = "C:\\Users\\EDZ\\Desktop\\" + new Random().nextInt() + ".gif";

         FileOutputStream fileOutputStream = new FileOutputStream(outPath);

         fileOutputStream.write(ungzipData);

         fileOutputStream.close();

        

         //响应客户端

         N03Response response = new N03Response();

         response.setData("已收到文件,长度是:" + ungzipData.length);

         ctx.writeAndFlush(response);

    }

   

    @Override

    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

         System.out.println("服务端:读取完毕");

    }

   

    @Override

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

         cause.printStackTrace();

    }

}

import java.io.FileInputStream;

 

import io.netty.bootstrap.Bootstrap;

import io.netty.channel.ChannelFuture;

import io.netty.channel.ChannelHandlerAdapter;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInitializer;

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.util.ReferenceCountUtil;

 

/**

 * 序列化的编解码Api,客户端

 */

public class N03SerialClient {

    public static void main(String[] args) throws Exception {

         //链接服务端

         EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        

         //创建启动类

         Bootstrap bootstrap = new Bootstrap();

         bootstrap.group(eventLoopGroup) //绑定

                      .channel(NioSocketChannel.class) //设置模式

                      .handler(new ChannelInitializer() { //初始化

                          @Override

                          protected void initChannel(SocketChannel ch) throws Exception {

                               //编解码器

                              ch.pipeline().addLast(N03MarshallingDecoderEncoderUtils.buildDecoder());

                              ch.pipeline().addLast(N03MarshallingDecoderEncoderUtils.buildEncoder());

                               //处理类

                               ch.pipeline().addLast(new SClientHandler());

                          }

                      });

        

         ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",1111).sync();

        

         //读取文件数据

         String inPath = "C:\\Users\\EDZ\\Desktop\\1.gif";

         FileInputStream fileInputStream = new FileInputStream(inPath);

         byte [] fileData = new byte[fileInputStream.available()];

         fileInputStream.read(fileData);

         fileInputStream.close();

         N03Request request = new N03Request();

         request.setData("你好,服务端!");

         request.setFile(N03GzipUtils.gzip(fileData));

         //写出数据

         channelFuture.channel().writeAndFlush(request);

        

         System.out.println("==================");

        

         //阻塞,关闭

         channelFuture.channel().closeFuture().sync();

         eventLoopGroup.shutdownGracefully();

        

    }

}

class SClientHandler extends ChannelHandlerAdapter {

   

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

         System.out.println("客户端:活跃");

    }

   

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

         try {

             N03Response response = (N03Response) msg;

             System.out.println("客户端:" + response.getData());

         } finally {

             ReferenceCountUtil.release(msg);

         }

    }

   

    @Override

    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

         System.out.println("客户端:读取完成");

    }

   

    @Override

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

         cause.printStackTrace();

    }

}

 

 

你可能感兴趣的:(Java,什么是TCP粘包,拆包,Netty粘包和拆包,什么Netty编解码技术?,主流的序列化框架有哪些?,Marshalling编解码)