I. Resumen
TCP está orientado a la conexión, al flujo y proporciona servicios altamente confiables. Tanto el extremo de recepción como el de envío (cliente y servidor) deben tener un par de sockets. Por lo tanto, para enviar varios paquetes al extremo de recepción de manera más eficaz, el remitente utiliza un método de optimización (algoritmo de Nagle). Combinar varios datos con intervalos pequeños y pequeño volumen de datos en un bloque de datos grande y luego empaquetar. Aunque esto mejora la eficiencia, es difícil para el extremo receptor distinguir el paquete de datos completo, porque la comunicación orientada al flujo no tiene un límite de protección de mensajes; dado que TCP no tiene un límite de protección de mensajes, el problema de los límites del mensaje debe tratarse en el extremo receptor, eso es lo que llamamos problemas de pegado y desempaquetado; al escribir el programa Netty, si no hay procesamiento, habrá problemas de pegado y desempaquetado.
2. Problema detallado
Tres, pegar y desempacar soluciones
- Utilice un protocolo personalizado + códec para resolver
- La clave es resolver el problema de la longitud de los datos que lee el servidor cada vez, si se resuelve este problema, el servidor no leerá más o menos datos, evitando así que se pegue y desempaquete TCP.
Cuatro, implementación de código
El cliente debe enviar 5 objetos de mensaje y el cliente envía un objeto de mensaje cada vez
Cada vez que el servidor recibe un mensaje, se descodifica en 5 veces y cada vez que se lee un mensaje, se devuelve un objeto de mensaje al cliente.
Cliente:
public class MyClient { public static void main (String [] args) arroja Exception { EventLoopGroup group = new NioEventLoopGroup (); prueba { Bootstrap bootstrap = new Bootstrap (); bootstrap.group (grupo) .channel (NioSocketChannel.class) .handler (nuevo MyClientInitializer ()); // 自 定义 一个 初始化 类 ChannelFuture channelFuture = bootstrap.connect ("localhost", 7000) .sync (); channelFuture.channel (). closeFuture (). sync (); } finalmente { grupo.shutdownGracefully (); } } }
public class MyClientHandler extiende SimpleChannelInboundHandler <MessageProtocol> { private int count; @Override public void channelActive (ChannelHandlerContext ctx) throws Exception { // Usa el cliente para enviar 10 datos "Hace frío hoy, come olla caliente" Número para (int i = 0; i <5; i ++) { String mes = "Hoy hace frío, come olla caliente"; byte [] content = mes.getBytes (Charset.forName ("utf-8")); int length = mes.getBytes (Charset.forName ("utf-8")). Length; // Crear un objeto de paquete de protocolo MessageProtocol messageProtocol = new MessageProtocol (); messageProtocol.setLen (length); messageProtocol.setContent (content); ctx.writeAndFlush (messageProtocol); } } // @Override protected void channelRead0 (ChannelHandlerContext ctx, MessageProtocol msg) arroja Exception { int len = msg.getLen (); byte [] contenido = msg.getContent (); System.out.println ("客户 端接 收到 消息 如下"); System.out.println ("长度 =" + len); System.out.println ("内容 =" + nueva Cadena (contenido, Charset.forName ("utf-8"))); System.out.println ("客户 端 接收 消息 数量 =" + (++ this.count)); } @Override public void exceptionCaught (ChannelHandlerContext ctx, Throwable cause) arroja Exception { System.out.println ("异常 消息 =" + cause.getMessage ()); ctx.close (); } }
La clase pública MyClientInitializer extiende ChannelInitializer <SocketChannel> { @Override protected void initChannel (SocketChannel ch) lanza Exception { ChannelPipeline pipeline = ch.pipeline (); pipeline.addLast (nuevo MyMessageEncoder ()); // 加入 编码 器 pipeline.addLast (new MyMessageDecoder ()); // 加入 解码 器 pipeline.addLast (new MyClientHandler ()); } }
//Servidor
public class MyServer { public static void main (String [] args) lanza Exception { EventLoopGroup bossGroup = new NioEventLoopGroup (1); EventLoopGroup workerGroup = new NioEventLoopGroup (); intente { ServerBootstrap serverBootstrap = new ServerBootstrap (); serverBootstrap.group (bossGroup, workerGroup) .channel (NioServerSocketChannel.class) .childHandler (nuevo MyServerInitializer ()); // 自 定义 一个 初始化 类 ChannelFuture channelFuture = serverBootstrap.bind (7000) .sync (); channelFuture.channel (). closeFuture (). sync (); } finalmente { bossGroup.shutdownGracefully (); workerGroup.shutdownGracefully (); } } }
// El controlador que maneja la clase pública empresarial MyServerHandler extiende SimpleChannelInboundHandler <MessageProtocol> { recuento de int privado; @Override public void exceptionCaught (ChannelHandlerContext ctx, Throwable cause) throws Exception { //cause.printStackTrace (); ctx.close (); } @ Anular el vacío protegido channelRead0 (ChannelHandlerContext ctx, MessageProtocol msg) throws Exception { // Recibir los datos y procesarlos int len = msg.getLen (); byte [] content = msg.getContent (); System.out.println (); System.out.println (); System.out.println (); System.out.println ("El servidor recibió la siguiente información"); System.out.println ("length =" + len); System.out.println ("content =" + new String (content, Charset.forName ("utf-8"))); System.out.println (" El número de paquetes de mensajes recibidos por el servidor = "+ (++ this.count)); // Mensaje de respuesta String responseContent = UUID.randomUUID (). ToString (); int responseLen = responseContent.getBytes (" utf-8 " ) .length; byte [] responseContent2 = responseContent.getBytes ("utf-8"); // Cree un paquete de protocolo MessageProtocol messageProtocol = new MessageProtocol (); messageProtocol.setLen (responseLen); messageProtocol.setContent (responseContent2); ctx. writeAndFlush (messageProtocol); } }
La clase pública MyServerInitializer extiende ChannelInitializer <SocketChannel> { @Override protected void initChannel (SocketChannel ch) lanza Exception { ChannelPipeline pipeline = ch.pipeline (); pipeline.addLast (nuevo MyMessageDecoder ()); // 解码 器 pipeline.addLast (nuevo MyMessageEncoder ()); // 编码 器 pipeline.addLast (nuevo MyServerHandler ()); } }
// 协议 包 public class MessageProtocol { private int len; // 关键 contenido de bytes privados []; public int getLen () { return len; } public void setLen (int len) { this.len = len; } public byte [] getContent () { devolver contenido; } public void setContent (byte [] contenido) { this.content = contenido; } }
// Códec
La clase pública MyMessageEncoder extiende MessageToByteEncoder <MessageProtocol> { @Override protected void encode (ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) arroja Exception { System.out.println ("MyMessageEncoder encode 方法 被 调用"); out.writeInt (msg.getLen ()); out.writeBytes (msg.getContent ()); } }
public class MyMessageDecoder extiende ReplayingDecoder <Void> { @Override protected void decode (ChannelHandlerContext ctx, ByteBuf in, List <Object> out) throws Exception { System.out.println ("Se llama a la decodificación de MyMessageDecoder"); // Necesidad de obtener binario Bytecode -> MessageProtocol paquete de datos (objeto) int length = in.readInt (); byte [] content = new byte [length]; in.readBytes (content); // encapsular en el objeto MessageProtocol, ponerlo fuera y pasar Procesamiento de negocio del siguiente controlador MessageProtocol messageProtocol = new MessageProtocol (); messageProtocol.setLen (longitud); messageProtocol.setContent (contenido); out.add (messageProtocol); } }