粘着性の問題を開梱ネッティー-4 TCPパケット

完全なコード:https://gitee.com/firewolf/java-io/tree/master/java-io/netty-02-tcppacket

問題を開梱、TCPスティックパッケージ/

TCPは、合意の「流れ」で、基本的なTCPのデジタル境界線の束ではない、いわゆるフローは、作品への接続ではない、と何の境界、彼らはサービス層の意味を知らないので、それはTCPバッファの実際の状況に基づいて行われます分割は、ビューのビジネス層で、その結果、完全なTCPパケットが大型パッケージを形成する小さな複数のパケットをパッケージ化することも、複数のパケットに可能に分割されてもよいです。
スティックパッケージおよびアンパックは、以下の発生する可能性があります
ここに画像を挿入説明
バイトの数は、サーバが固定されていない読み取るので、次のような場合が起こり得るように、クライアントがサーバに二つのパケットD1およびD2を送信すると仮定する。

  1. 2つの完全なパケットを読むために二度、サーバー、それぞれD1およびD2、スティックのパッケージと発生しません開梱。
  2. サーバーは、2つのデータパケットを読み取るには、D1およびD2がくっついて、スティックのパッケージが発生しました。
  3. パッケージの一部、別のパケットD2 D2_2の部分の第2の読み出し - サーバ倍の二つは最初の完全なパケットD1およびD2 D2_1を読み、データ・パケットを読み取ります。パッケージをスティックと発生したアンパック。
  4. サーバ回二つのパケット、最初の読み出しD1_1読み取り - D1パケット部分、パケットの完全なパッケージD1_2 D1及びD2の第二読み取り、およびアンパックスティックパッケージの発生の別の部分を、
  5. また、それは開梱パッケージを固執繰り返しも発生する可能性があります。

第二に、TCPスティックパッケージの理由は、/アンパック問題が生じます

理由は三つの主要な問題があります。

  1. 書き込みバイトサイズ書かれたアプリケーションソケットは、送信バッファのサイズよりも大きいです。
  2. TCPセグメントサイズMSSのための
  3. MTU実行IPフラグメンテーションより大きいイーサネットペイロード
    ここに画像を挿入説明

第三に、ソリューション戦略スティックパッケージTCP /開梱問題

  1. 不十分な、スペースが満たされる場合、各パケットの長さなどの固定長メッセージは、200です。
  2. 増加の終わりにキャリッジ・リターンは、TCPプロトコルのように、分割されました。
  3. 終わりに、特殊文字のセグメンテーションを追加し、キャリッジリターンラインは特殊なケースです。
  4. メッセージヘッダとメッセージ本体、全長フィールドを示すメッセージを含むメッセージ・ヘッダーにメッセージ。
  5. より複雑なアプリケーション層プロトコルを使用して、

四、TCPスティックパッケージ/開梱問題がデモ

这里简单的写一个程序,功能是客户端连接服务端之后,向服务端发送20个字符串,服务端收到后进行显示:

(一)服务端

package com.firewolf.java.io.netty.packagee.paste.origin;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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 java.util.HashMap;
import java.util.Map;

/**
 * 作者:刘兴 时间:2019/5/15
 **/
public class HelloServer {


  public HelloServer(int port) {

    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workGroup = new NioEventLoopGroup();
    try {
      ServerBootstrap bootstrap = new ServerBootstrap();
      bootstrap.group(bossGroup, workGroup)
          .channel(NioServerSocketChannel.class)
          .option(ChannelOption.SO_BACKLOG, 1024)
          .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
              socketChannel.pipeline()
                  .addLast(new MessageServerHandler());
            }
          });
      ChannelFuture future = bootstrap.bind(port).sync();
      System.out.println("启动服务端监听端口:" + port);
      future.channel().closeFuture().sync();

    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      bossGroup.shutdownGracefully();
      workGroup.shutdownGracefully();
    }

  }

  class MessageServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
      ByteBuf buf = (ByteBuf) msg;
      byte[] bytes = new byte[buf.readableBytes()];
      buf.readBytes(bytes);
      String message = new String(bytes);
      System.out.println("有客户端消息:" + message);
      buf.release();
    }
  }


  public static void main(String[] args) {
    new HelloServer(9999);
  }

}

(二)客户端

package com.firewolf.java.io.netty.packagee.paste.origin;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;

/**
 * 作者:刘兴 时间:2019/5/15
 **/
public class HelloClient {


  public HelloClient(String host, int port) {
    NioEventLoopGroup group = new NioEventLoopGroup();
    try {
      Bootstrap bootstrap = new Bootstrap();
      bootstrap.group(group)
          .channel(NioSocketChannel.class)
          .option(ChannelOption.TCP_NODELAY, true)
          .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
              socketChannel.pipeline()
                  .addLast(new MessageClientHandler());
            }
          });

      ChannelFuture f = bootstrap.connect(new InetSocketAddress(host, port)).sync();
      System.out.println("连接服务器成功-----");
      f.channel().closeFuture().sync();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      group.shutdownGracefully();
    }
  }


  class MessageClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

      //遍历写出100个字符串
      for (int i = 0; i < 20; i++) {
        String message = "helllo," + i + System.getProperty("line.separator");
        ByteBuf buf = Unpooled.buffer(message.getBytes().length);
        buf.writeBytes(message.getBytes());
        ctx.writeAndFlush(buf);
      }
    }
  }


  public static void main(String[] args) {
    new HelloClient("127.0.0.1", 9999);
  }
}

启动服务端,再启动客户端,服务端打印信息如下:

有客户端消息:helllo,0
helllo,1
helllo,2
helllo,3
helllo,4
helllo,5
helllo,6
helllo,7
helllo,8
helllo,9
helllo,10
helllo,11
helllo,12
helllo,13
helllo,14
helllo,15
helllo,16
helllo,17
helllo,18
helllo,19

我们会发现,实际上, 服务端一次接受了全部字符串,也就是说,这些包并没有正常的接受到,而是发生了粘包。

五、Netty对粘包/拆包问题解决

为了解决TCP拆包/粘包导致的半读包问题,Netty默认提供了很多编码解码器用于处理半包。

(一)LineBasedFrameDecoder

LineBasedFrameDecoder会对包进行识别,根据包里面的回车(13)换行(10)符进行切割。

1. 服务端代码

package com.firewolf.java.io.netty.packagee.paste.solve;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 作者:刘兴 时间:2019/5/15
 **/
public class HelloServerSolve {


  public HelloServerSolve(int port) {

    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workGroup = new NioEventLoopGroup();
    try {
      ServerBootstrap bootstrap = new ServerBootstrap();
      bootstrap.group(bossGroup, workGroup)
          .channel(NioServerSocketChannel.class)
          .option(ChannelOption.SO_BACKLOG, 1024)
          .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
              socketChannel.pipeline()
                  //添加编码器
                  .addLast(new LineBasedFrameDecoder(1024))
                  .addLast(new StringDecoder())
                  .addLast(new MessageServerHandler());
            }
          });
      ChannelFuture future = bootstrap.bind(port).sync();
      System.out.println("启动服务端监听端口:" + port);
      future.channel().closeFuture().sync();

    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      bossGroup.shutdownGracefully();
      workGroup.shutdownGracefully();
    }

  }

  class MessageServerHandler extends ChannelInboundHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
      //由于StringDecoder的功劳,接受消息的时候,我们可以直接转成String
      String message = (String) msg;
      System.out.println("有客户端消息:" + message);
    }
  }


  public static void main(String[] args) {
    new HelloServerSolve(9999);
  }

}

服务端有两处改动

  • 给启动类BootStrap添加了消息解码器;
  • 接受消息的时候直接使用String接受,这个是由于添加了StringDecoder具有的功能。

2.客户端代码

package com.firewolf.java.io.netty.packagee.paste.solve;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import java.net.InetSocketAddress;

/**
 * 作者:刘兴 时间:2019/5/15
 **/
public class HelloClientSolve {


  public HelloClientSolve(String host, int port) {
    NioEventLoopGroup group = new NioEventLoopGroup();
    try {
      Bootstrap bootstrap = new Bootstrap();
      bootstrap.group(group)
          .channel(NioSocketChannel.class)
          .option(ChannelOption.TCP_NODELAY, true)
          .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
              socketChannel.pipeline()
                  .addLast(new MessageClientHandler());
            }
          });

      ChannelFuture f = bootstrap.connect(new InetSocketAddress(host, port)).sync();
      System.out.println("连接服务器成功-----");
      f.channel().closeFuture().sync();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      group.shutdownGracefully();
    }
  }


  class MessageClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

      //遍历写出100个字符串
      for (int i = 0; i < 20; i++) {
        String message = "helllo," + i + System.getProperty("line.separator");
        ByteBuf buf = Unpooled.buffer(message.getBytes().length);
        buf.writeBytes(message.getBytes());
        ctx.writeAndFlush(buf);
      }
    }
  }


  public static void main(String[] args) {
    new HelloClientSolve("127.0.0.1", 9999);
  }
}

我们可以看到,客户端没有任何变化。

3.查看效果

再次启动后,打印内容如下:

有客户端消息:helllo,0
有客户端消息:helllo,1
有客户端消息:helllo,2
有客户端消息:helllo,3
有客户端消息:helllo,4
有客户端消息:helllo,5
有客户端消息:helllo,6
有客户端消息:helllo,7
有客户端消息:helllo,8
有客户端消息:helllo,9
有客户端消息:helllo,10
有客户端消息:helllo,11
有客户端消息:helllo,12
有客户端消息:helllo,13
有客户端消息:helllo,14
有客户端消息:helllo,15
有客户端消息:helllo,16
有客户端消息:helllo,17
有客户端消息:helllo,18
有客户端消息:helllo,19

可以看到,这些消息是分开了的,没有了粘包和拆包问题。
LineBasedFrameDecoder:以回车换行符作为对包进行切割;
StringDecoder:把ByteBuf转换成字符串
一般情况下,StringDecoder和LineBasedFrameDecoder会配合使用。

(二)DelimiterBasedFrameDecoder

其实DelimiterBasedFrameDecoder的功能和LineBasedFrameDecoder类似,只不过会比LineBasedFrameDecoder更加强大,支持我们自己指定用来切割的字符(串),使用方法和LineBasedFrameDecoder几乎是一模一样,这里只是简单的贴出部分代码:

1.服务端添加解码器

这里使用_$进行分割:

 socketChannel.pipeline()
                  //添加编码器
                  .addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("_$".getBytes())))

2.发送消息的时候,带上_$结尾

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

      //遍历写出100个字符串
      for (int i = 0; i < 20; i++) {
        String message = "helllo," + i + "_$";
        ByteBuf buf = Unpooled.buffer(message.getBytes().length);
        buf.writeBytes(message.getBytes());
        ctx.writeAndFlush(buf);
      }
    }

(三)FixedLengthFrameDecoder

指定された長さに応じてメッセージを復号することができるFixedLengthFrameDecoder固定長復号器。
ここで再び、単に変化(尊敬のDelimiterBasedFrameDecoder付き)コードを置きます。

1.サーバーのデコーダを追加します。

              socketChannel.pipeline()
                  //添加编码器
                  .addLast(new FixedLengthFrameDecoder(20))

ここで提供される長さ20

2.クライアント

もはや変更しない、掲載さありません

結果は以下の通りであります:

有客户端消息:helllo,0_$helllo,1_$
有客户端消息:helllo,2_$helllo,3_$
有客户端消息:helllo,4_$helllo,5_$
有客户端消息:helllo,6_$helllo,7_$
有客户端消息:helllo,8_$helllo,9_$
有客户端消息:helllo,10_$helllo,11
有客户端消息:_$helllo,12_$helllo,
有客户端消息:13_$helllo,14_$helll
有客户端消息:o,15_$helllo,16_$hel
有客户端消息:llo,17_$helllo,18_$h

私たちは、結果の文字列の長さが20で、見ることができ、かつ20未満の長さに起因する最後のものは、それが表示されません。

VIの概要

いくつかのデコーダのソースコードの上に、自分で読んで、私たちはまた、継承によって、独自のデコーダを実装することができ、すなわち、これらのデコーダは、元の取得ByteBufが処理され、ByteToMessageDecoderを継承していることがわかりますByteToMessageDecoder 。

おすすめ

転載: blog.csdn.net/mail_liuxing/article/details/90609205