第9章TCPスティッキングとアンパックおよびソリューション

9.1 TCPの固定と展開の基本

  1. TCPは接続指向でストリーム指向であり、信頼性の高いサービスを提供します。送信側と受信側(クライアントとサーバー)には1組のソケットが必要なので、複数のパケットを受信側に送信するには、送信側で最適化メソッド(Nagleアルゴリズム)を使用します小さな間隔と小さなデータ量のデータを大きなデータブロックに結合し、それをカプセル化します。これにより効率は向上しますが、しかし、受信側が完全なデータパケットを区別することは困難です。ストリーム指向の通信にはメッセージ保護境界がないため

  2. TCPにはメッセージ保護境界がないため、メッセージ境界の問題は受信側で処理する必要があります。これを、スティッキングとアンパックと呼びます。

  3. TCPのスティッキングとアンパックの概略図
    ここに画像の説明を挿入

図の説明:

クライアントが2つのデータパケットD1およびD2をサーバーに送信するとします。サーバーが一度に読み取るバイト数は不明であるため、次の4つの状況が考えられます。

  1. サーバーは、2つの独立したデータパケットを2回、つまりD1とD2で読み取ります。

  2. サーバーは一度に2つのパケットを受信し、D1とD2はTCPスティッキーパケットと呼ばれる一緒に接着されます。

  3. サーバーはデータパケットを2回読み取り、最初はD1およびD2パケットの完全なコンテンツを読み取り、2回目はD2パケットの残りのコンテンツを読み取ります。これはTCPアンパックと呼ばれます。

  4. サーバーはデータパケットを2回読み取ります。1回目はD1パッケージのD1_1部分を読み取り、2回目はD1パッケージの残りの部分D1_2と完全なD2パッケージを読み取ります。

9.2 TCPスティッキングとアンパックの例

Nettyプログラムを作成するときに、処理がない場合、貼り付けと展開の問題が発生します。
具体的な例を見てください

public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf>{
    private int count;//每个Channel有各自的ChannelHandler

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

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {

        byte[] buffer = new byte[msg.readableBytes()];
        msg.readBytes(buffer);

        //将buffer转成字符串
        String message = new String(buffer, Charset.forName("utf-8"));

        System.out.println("服务器接收到数据 " + message);
        System.out.println("服务器接收到消息量=" + (++this.count));//服务端收到消息的次数

        //服务器回送数据给客户端, 回送一个随机id ,
        ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString() 
        + " ", Charset.forName("utf-8"));
        ctx.writeAndFlush(responseByteBuf);
    }
}

public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private int count;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //使用客户端发送10条数据 hello,server 编号
        for(int i= 0; i< 10; ++i) {
            ByteBuf buffer = Unpooled.copiedBuffer("hello,server " + i, Charset.forName("utf-8"));
            ctx.writeAndFlush(buffer);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] buffer = new byte[msg.readableBytes()];
        msg.readBytes(buffer);

        String message = new String(buffer, Charset.forName("utf-8"));
        System.out.println("客户端接收到消息=" + message);
        System.out.println("客户端接收消息数量=" + (++this.count));

    }

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

表示:
サーバー側がメッセージを6回受信サーバー側がメッセージを
ここに画像の説明を挿入
7回受信
ここに画像の説明を挿入

9.3 TCPスティッキングおよびアンパックソリューション

  1. 使用する カスタムプロトコル+コーデック 解決する

  2. 重要なのは、サーバーが毎回読み取るデータの長さの問題を解決することです。この問題が解決されれば、サーバーが多かれ少なかれデータを読み取ることによる問題は発生せず、TCPのスティッキングとアンパックが回避されます。

9.4特定の例を見てください:

  1. クライアントは5つのメッセージオブジェクトを送信する必要があり、クライアントは一度に1つのメッセージオブジェクトを送信します

  2. サーバーはメッセージを受信するたびに5回デコードし、メッセージを読み取るたびにメッセージオブジェクトをクライアントに返信します。
    ここに画像の説明を挿入

//Server
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new MyMessageDecoder());//解码器
        pipeline.addLast(new MyMessageEncoder());//编码器
        pipeline.addLast(new MyServerHandler());
    }
}

public class MyMessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyMessageDecoder decode 被调用");
        //需要将得到二进制字节码-> MessageProtocol 数据包(对象)
        int length = in.readInt();

        byte[] content = new byte[length];
        in.readBytes(content);

        //封装成 MessageProtocol 对象,放入 out, 传递下一个handler业务处理
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(length);
        messageProtocol.setContent(content);

        out.add(messageProtocol);
    }
}

public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        System.out.println("MyMessageEncoder encode 方法被调用");
        out.writeInt(msg.getLen());
        out.writeBytes(msg.getContent());
    }
}

//处理业务的handler
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol>{//MessageProtocol接收
    private int count;

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

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {

        //接收到数据,并处理    第一部分
        int len = msg.getLen();
        byte[] content = msg.getContent();

        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println("服务器接收到信息如下");
        System.out.println("长度=" + len);
        System.out.println("内容=" + new String(content, Charset.forName("utf-8")));

        System.out.println("服务器接收到消息包数量=" + (++this.count));




        //回复消息  第二部分

        String responseContent = UUID.randomUUID().toString();
        int responseLen = responseContent.getBytes("utf-8").length;
        byte[]  responseContent2 = responseContent.getBytes("utf-8");
        //构建一个协议包
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(responseLen);
        messageProtocol.setContent(responseContent2);

        ctx.writeAndFlush(messageProtocol);
    }
}

//Client
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new MyMessageEncoder()); //加入编码器
        pipeline.addLast(new MyMessageDecoder()); //加入解码器
        pipeline.addLast(new MyClientHandler());
    }
}

public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        System.out.println("MyMessageEncoder encode 方法被调用");
        out.writeInt(msg.getLen());
        out.writeBytes(msg.getContent());
    }
}

public class MyMessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyMessageDecoder decode 被调用");
        //需要将得到二进制字节码-> MessageProtocol 数据包(对象)
        int length = in.readInt();

        byte[] content = new byte[length];
        in.readBytes(content);

        //封装成 MessageProtocol 对象,放入 out, 传递下一个handler业务处理
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(length);
        messageProtocol.setContent(content);

        out.add(messageProtocol);
    }
}

public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {

    private int count;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //使用客户端发送10条数据 "今天天气冷,吃火锅" 编号

        for(int i = 0; i< 5; i++) {
            String mes = "今天天气冷,吃火锅";
            byte[] content = mes.getBytes(Charset.forName("utf-8"));
            int length = mes.getBytes(Charset.forName("utf-8")).length;

            //创建协议包对象
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLen(length);
            messageProtocol.setContent(content);
            ctx.writeAndFlush(messageProtocol);

        }

    }

//    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {

        int len = msg.getLen();
        byte[] content = msg.getContent();

        System.out.println("客户端接收到消息如下");
        System.out.println("长度=" + len);
        System.out.println("内容=" + new String(content, Charset.forName("utf-8")));

        System.out.println("客户端接收消息数量=" + (++this.count));

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常消息=" + cause.getMessage());
        ctx.close();
    }
}

元の記事を138件公開 賞賛3 訪問7224

おすすめ

転載: blog.csdn.net/weixin_43719015/article/details/105306403
おすすめ