zoukankan      html  css  js  c++  java
  • netty 解决TCP粘包与拆包问题(一)

    1.什么是TCP粘包与拆包

    首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线。当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包。得不到我们想要的效果。

    所谓粘包:当你把A,B两个数据从甲发送到乙,本想A与B单独发送,但是你却把AB一起发送了,此时AB粘在一起,就是粘包了

    所谓拆包: 如果发送数据的时候,你把A、B拆成了几份发,就是拆包了。当然数据不是你主动拆的,是TCP流自动拆的

    2.TCP粘包与拆包产生原因

    1.进行了MSS大小的TCP分段
    2.以太网帧的plyload大与MTU进行了IP分片
    3.应用程序write写入的字节大小大于套接口发送的缓冲区大小

    3.解决方法

    1.消息定长,比如把报文消息固定为500字节,不够用空格补位

    2.在包尾增加回车换行符进行分割,例如FTP协议

    3.将消息分为消息头和消息体,消息头中包含表示消息总长度的字段

    4.更复杂的应用层协议

    4.netty 普通解决方法

    这个是服务端代码

    复制代码
     1 package com.ming.netty.nio;
     2 
     3 import java.net.InetSocketAddress;
     4 
     5 import io.netty.bootstrap.ServerBootstrap;
     6 import io.netty.channel.ChannelFuture;
     7 import io.netty.channel.ChannelInitializer;
     8 import io.netty.channel.ChannelOption;
     9 import io.netty.channel.EventLoopGroup;
    10 import io.netty.channel.nio.NioEventLoopGroup;
    11 import io.netty.channel.socket.SocketChannel;
    12 import io.netty.channel.socket.nio.NioServerSocketChannel;
    13 import io.netty.handler.codec.LineBasedFrameDecoder;
    14 import io.netty.handler.codec.string.StringDecoder;
    15 
    16 public class TimeServer {
    17     
    18     
    19     public static void main(String[] args) throws Exception{
    20         new TimeServer().bind("192.168.1.102", 8400);
    21     }
    22     
    23 
    24     public void bind(String addr,int port) {
    25         //配置服务端的nio线程组
    26         EventLoopGroup boosGroup=new NioEventLoopGroup();
    27         EventLoopGroup workerGroup=new NioEventLoopGroup();
    28         try {
    29             ServerBootstrap b=new ServerBootstrap();
    30             b.group(boosGroup,workerGroup);
    31             b.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024)
    32             .childHandler(new ChildChannelHandler());
    33             //绑定端口,同步等待成功
    34             ChannelFuture f=b.bind(new InetSocketAddress(addr, port)).sync();
    35             System.out.println("启动服务器:"+f.channel().localAddress());
    36             //等等服务器端监听端口关闭
    37             f.channel().closeFuture().sync();
    38         } catch (Exception e) {
    39             // TODO: handle exception
    40         }finally{
    41             boosGroup.shutdownGracefully();
    42             workerGroup.shutdownGracefully();
    43         }
    44     }
    45     
    46     private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
    47 
    48         @Override
    49         protected void initChannel(SocketChannel ch) throws Exception {
    50             System.out.println(ch.remoteAddress());
    51             ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    52             ch.pipeline().addLast(new StringDecoder());//增加解码器
    53             ch.pipeline().addLast(new TimeServerHandler());
    54             
    55         }
    56         
    57     }
    58     
    59     
    60 }
    61 package com.ming.netty.nio;
    62 
    63 import java.nio.ByteBuffer;
    64 import java.text.SimpleDateFormat;
    65 import java.util.Date;
    66 
    67 import io.netty.buffer.ByteBuf;
    68 import io.netty.buffer.Unpooled;
    69 import io.netty.channel.ChannelHandlerAdapter;
    70 import io.netty.channel.ChannelHandlerContext;
    71 
    72 public class TimeServerHandler extends ChannelHandlerAdapter {
    73 
    74     private int counter;
    75     
    76     @Override
    77     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    78         String body=(String)msg;
    79         System.out.println("服务端收到:"+body+",次数:"+ ++counter);
    80         SimpleDateFormat  dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    81         String time=dateFormat.format(new Date());
    82         String res="来自与服务端的回应,时间:"+ time;
    83         ByteBuf resp=Unpooled.copiedBuffer(res.getBytes());
    84         ctx.writeAndFlush(resp);
    85         
    86     }
    87 
    88     
    89 
    90     @Override
    91     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    92         ctx.close();
    93     }
    94 
    95     
    96 
    97     
    98 }
    复制代码

    这个是客服端的代码

    复制代码
     1 package com.ming.netty.nio;
     2 
     3 import io.netty.bootstrap.Bootstrap;
     4 import io.netty.channel.ChannelFuture;
     5 import io.netty.channel.ChannelInitializer;
     6 import io.netty.channel.ChannelOption;
     7 import io.netty.channel.EventLoopGroup;
     8 import io.netty.channel.nio.NioEventLoopGroup;
     9 import io.netty.channel.socket.SocketChannel;
    10 import io.netty.channel.socket.nio.NioSocketChannel;
    11 import io.netty.handler.codec.LineBasedFrameDecoder;
    12 import io.netty.handler.codec.string.StringDecoder;
    13 
    14 /**
    15  * netty 客户端模拟
    16  * @author mingge
    17  *
    18  */
    19 public class TimeClient {
    20     
    21     
    22     public static void main(String[] args) throws Exception{
    23         new TimeClient().connect("192.168.1.102", 8400);
    24     }
    25 
    26     public void connect(String addr,int port) throws Exception{
    27         EventLoopGroup group=new NioEventLoopGroup();
    28         try {
    29             Bootstrap b=new Bootstrap();
    30             b.group(group).channel(NioSocketChannel.class)
    31             .option(ChannelOption.TCP_NODELAY, true)
    32             .handler(new ChannelInitializer<SocketChannel>() {
    33                 public void initChannel(SocketChannel ch) throws Exception{
    34                     ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    35                     ch.pipeline().addLast(new StringDecoder());
    36                     ch.pipeline().addLast(new TimeClientHandler());
    37                 }
    38             });
    39             ChannelFuture f=b.connect(addr,port).sync();
    40             System.out.println("连接服务器:"+f.channel().remoteAddress()+",本地地址:"+f.channel().localAddress());
    41             f.channel().closeFuture().sync();//等待客户端关闭连接
    42         } catch (Exception e) {
    43             e.printStackTrace();
    44         }finally{
    45             
    46             group.shutdownGracefully();
    47         }
    48     }
    49 }
    50 package com.ming.netty.nio;
    51 
    52 import io.netty.buffer.ByteBuf;
    53 import io.netty.buffer.Unpooled;
    54 import io.netty.channel.ChannelHandlerAdapter;
    55 import io.netty.channel.ChannelHandlerContext;
    56 
    57 public class TimeClientHandler extends ChannelHandlerAdapter {
    58     
    59     private int counter;
    60     
    61     byte[] req;
    62     
    63     public TimeClientHandler() {
    64         req=("我是请求数据哦"+System.getProperty("line.separator")).getBytes();
    65     }
    66 
    67     @Override
    68     public void channelActive(ChannelHandlerContext ctx) throws Exception {
    69         ByteBuf message=null;
    70         for(int i=0;i<100;i++){
    71             message=Unpooled.buffer(req.length);
    72             message.writeBytes(req);
    73             ctx.writeAndFlush(message);
    74         }
    75         
    76     }
    77 
    78     @Override
    79     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    80         ByteBuf buf=(ByteBuf)msg;
    81         byte[] req=new byte[buf.readableBytes()];
    82         buf.readBytes(req);
    83         String body=new String(req,"GBK");
    84         System.out.println("body:"+body+",响应次数:"+(++counter));
    85     }
    86 
    87     @Override
    88     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    89         //释放资源
    90         ctx.close();
    91     }
    92 
    93     
    94 }
    复制代码

    这次代码就是比上次的代码多了:LineBasedFrameDecoder,与StringDecoder的写法.

    LineBasedFrameDecoder的原理是它依次遍历ByteBuf中的可读字节,判断是否有" "或" ",如果有就以此为结束。它是以换行符为结束标志的解码器

    StringDecoder的原理就是将接收到的对象转换为字符串,然后接着调用后面的handler。

    LineBasedFrameDecoder+StringDecoder组合就是设计按行切换的文本解码器,被设计来支持TCP的粘包与拆包

  • 相关阅读:
    springboot @value 注解的使用
    Django时间时区问题(received a naive datetime while time zone support is active
    乐观锁与悲观锁
    Django 从入门到放弃
    根据数据库表生成 model 类
    Django CSRF攻击
    Django的orm的 数据库查询语法大全
    js常用函数、书写可读性的js、js变量声明...
    Web Worker
    css编写规范
  • 原文地址:https://www.cnblogs.com/kabi/p/6115099.html
Copyright © 2011-2022 走看看