Programación de red Java (2) caso clásico [pegar y desempacar]

Desembalaje de bolsas adhesivas

Descripción general

TCP es un protocolo orientado a flujos. Los datos transmitidos por TCP en la red son una serie de datos sin ninguna línea divisoria .
La capa inferior del protocolo TCP no comprende la definición específica del negocio de la capa superior y dividirá los paquetes de acuerdo con la situación real del búfer TCP.
A nivel empresarial, se cree que TCP puede dividir un paquete completo en varios paquetes pequeños para su transmisión, o encapsular varios paquetes pequeños en un paquete de datos grande para su transmisión. Este es el llamado problema de desempaquetado de paquetes fijos de TCP. .Como se muestra arriba, habrá muchas situaciones

Análisis de causa 

Cuando el flujo de datos TCP finalmente se envía al destino, debe encapsularse en tramas Ethernet a través del protocolo Ethernet y enviarse. El tamaño mínimo de la trama de datos Ethernet es 64 bytes y el máximo es 1518 bytes. Excluyendo la parte del encabezado, la carga útil de datos es de 46 a 1500 bytes. Entonces, si la carga útil de la trama Ethernet es mayor que la MTU (predeterminada 1500 bytes), es necesario descomprimirla.

solución

Dado que la capa inferior del protocolo TCP no puede comprender los datos comerciales de la capa superior, no hay garantía de que los paquetes de datos no se divida y vuelva a ensamblar en la capa inferior, por lo que este problema solo se puede resolver mediante el diseño del Protocolo de capa de aplicación superior. Las soluciones comunes son las siguientes:

  • La longitud del mensaje es fija. El remitente y el receptor especifican una longitud de mensaje de tamaño fijo. Por ejemplo, el tamaño de cada mensaje se fija en 200 bytes. Si no es suficiente, se completarán espacios.
  • Agregue caracteres especiales para la segmentación , como el protocolo FTP
  • Personalice el protocolo y divida el mensaje en un encabezado de mensaje y un cuerpo de mensaje. El encabezado del mensaje contiene la longitud total del mensaje, de modo que el servidor pueda conocer la longitud específica de cada paquete de datos. Después de conocer los límites específicos del envío de paquetes de datos , puede resolver el problema. Problemas al pegar y desembalar.

solución neta

Es problemático resolver el problema del descompresión de paquetes fijos basado en el socket nativo de JDK o nio; como marco de comunicación de red muy poderoso, Netty proporciona una variedad de codificadores para resolver el problema de descomprimir paquetes fijos, siempre que domines su uso. bibliotecas de clases Para resolver el problema del desempaquetado de paquetes fijos, las bibliotecas de clases comunes incluyen:

  • LineBasedFrameDecoder: un decodificador basado en líneas que se utilizará como delimitadores de línea cuando se encuentren "\n" y "\r\n".
  • FixedLengthFrameDecoder: decodificador basado en longitud fija
  • DelimiterBasedFrameDecoder: decodificador de cuadros basado en delimitadores
  • ByteToMessageDecoder: un decodificador que maneja protocolos de mensajes personalizados
    • ReplayingDecoder : hereda ByteToMessageDecoder, puede ser más lento que ByteToMessageDecoder cuando la red es lenta y el formato del mensaje es complejo.

Protocolo de mensajes personalizado + codificador/decodificador para resolver el problema del desempaquetado de paquetes pegajosos

Netty utiliza un protocolo personalizado ( básicamente resuelve el problema de leer la longitud de los datos cada vez en el lado del servidor ) + códec para resolver el problema

cliente
package com.bierce.io.netty.stickingAndunpacking;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MyStickingUnpackingClientTest {
    private String host;
    private int port;
    public MyStickingUnpackingClientTest(String host, int port){
        this.host = host;
        this.port = port;
    }
    public void run(){
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new 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 MyStickingUnpackingClientHandler()); //自定义业务处理器
                        }
                    });
            ChannelFuture cf = bootstrap.connect(host, port).sync();
            Channel channel = cf.channel();
            System.out.println("北京时间-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")) + ": " + "Client"+ channel.localAddress()+ " start Successful!!!");
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new MyStickingUnpackingClientTest("127.0.0.1", 9999).run();
    }
}
class MyStickingUnpackingClientHandler extends SimpleChannelInboundHandler<MessageProtocal> {
    private int count;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 5; i++) {
            String msg = "Hello Bierce";
            byte[] data = msg.getBytes(Charset.forName("UTF-8"));
            int length = msg.getBytes(Charset.forName("UTF-8")).length;
            MessageProtocal msgProtocal = new MessageProtocal();
            msgProtocal.setData(data);
            msgProtocal.setLength(length);
            ctx.writeAndFlush(msgProtocal);
        }
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocal msg) throws Exception {
        byte[] data = msg.getData();
        int length = msg.getLength();
        System.out.println("客户端接收到服务端响应信息包括:");
        System.out.println("长度=" + length + ";内容=" + new String(data, Charset.forName("UTF-8"))  + ";消息包数量=" + (++this.count));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常消息: " + cause.getMessage());
        ctx.close();
    }
}
Servidor
package com.bierce.io.netty.stickingAndunpacking;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;
import java.nio.charset.Charset;
import java.util.UUID;
public class MyStickingUnpackingServerTest {
    private int port;
    public MyStickingUnpackingServerTest(int port){
        this.port = port;
    }
    public void run(){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
           ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .handler(new LoggingHandler(LogLevel.INFO))
                            .childHandler(new 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 MyStickingUnpackingServerHandler());
                                }
                            });
            System.out.println("服务器启动成功!");
            serverBootstrap.bind(port).sync().channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new MyStickingUnpackingServerTest(9999).run();
    }
}
class MyStickingUnpackingServerHandler extends SimpleChannelInboundHandler<MessageProtocal> {
    private int count;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocal msg) throws Exception {
        int length = msg.getLength();
        byte[] data = msg.getData();
        System.out.println();
        System.out.println("服务器接收到的信息包括:");
        System.out.println("长度=" + length + ";内容=" + new String(data, Charset.forName("UTF-8"))  + ";消息包数量=" + (++this.count));
        System.out.println("------------服务端读取成功------------");

        //回复给客户端信息
        String responseContent = UUID.randomUUID().toString();
        int responseLen = responseContent.getBytes("UTF-8").length;
        MessageProtocal msgProtocal = new MessageProtocal();
        msgProtocal.setLength(responseLen);
        msgProtocal.setData(responseContent.getBytes("UTF-8"));
        ctx.writeAndFlush(msgProtocal);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常消息:" + cause.getMessage());
        ctx.close();
    }
}
Clase de protocolo de mensaje personalizado
package com.bierce.io.netty.stickingAndunpacking;
/**
 * 自定义消息服协议
 */
public class MessageProtocal {
    private int length;
    private byte[] data;
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
    public byte[] getData() {
        return data;
    }
    public void setData(byte[] data) {
        this.data = data;
    }
}
Clase de codificador de decodificación de mensajes personalizado
package com.bierce.io.netty.stickingAndunpacking;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
public class MyMessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyMessageDecoder method called");
        int length = in.readInt();
        byte[] data = new byte[length];
        in.readBytes(data);
        MessageProtocal messageProtocal = new MessageProtocal();
        messageProtocal.setLength(length);
        messageProtocal.setData(data);
        out.add(messageProtocal);
    }
}
class MyMessageEncoder extends MessageToByteEncoder<MessageProtocal> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocal msg, ByteBuf out) throws Exception {
        System.out.println("MyMessageEncoder method called");
        out.writeInt(msg.getLength());
        out.writeBytes(msg.getData());
    }
}
representaciones

Supongo que te gusta

Origin blog.csdn.net/qq_34020761/article/details/132467653
Recomendado
Clasificación