Directorio de artículos
1. Descripción general del códec
1. Descripción general del códec
Cuando Netty envía o recibe un mensaje, se produce una conversión de datos. Los mensajes entrantes se decodifican (se convierten de bytes a otro formato, como objetos java); los mensajes salientes se codifican en bytes.
Netty proporciona una serie de códecs prácticos, todos los cuales implementan la interfaz ChannelInboundHadnler o ChannelOutboundHandler. En estas clases, el método channelRead ha sido anulado. Tomando la entrada como ejemplo, se llamará a este método para cada mensaje leído del canal de entrada. Posteriormente, llamará al método decode() proporcionado por el decodificador para decodificar y reenviar los bytes decodificados al siguiente ChannelInboundHandler en ChannelPipeline.
2. Diagrama de relación de clases de codificador
Todos los codificadores heredan la clase abstracta MessageToByteEncoder y necesitan reescribir su método de codificación.
Descubrimos que el codificador hereda ChannelOutboundHandlerAdapter, lo que indica que solo se realizará la operación de salida.
3. Diagrama de relación de clase de decodificador
Todos los decodificadores heredan la clase abstracta ByteToMessageDecoder y necesitan reescribir su método de decodificación.
Descubrimos que el decodificador hereda ChannelInboundHandlerAdapter, lo que significa que se ejecutará la operación de entrada.
2. Tome el códec como ejemplo para comprender la entrada y la salida
1. Lado del servidor
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class MyServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();//一会下断点
//入站的handler进行解码 MyByteToLongDecoder
pipeline.addLast(new MyByteToLongDecoder());
//出站的handler进行编码
pipeline.addLast(new MyLongToByteEncoder());
//自定义的handler 处理业务逻辑
pipeline.addLast(new MyServerHandler());
System.out.println("end");
}
}); //自定义初始化pipeline
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MyServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("从客户端" + ctx.channel().remoteAddress() + " 读取到long " + msg);
//给客户端发送一个long
ctx.writeAndFlush(98765L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2. Lado del cliente
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class MyClient {
public static void main(String[] args) throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入一个出站的handler 对数据进行一个编码
pipeline.addLast(new MyLongToByteEncoder());
//这时一个入站的解码器(入站handler )
pipeline.addLast(new MyByteToLongDecoder());
//加入一个自定义的handler , 处理业务
pipeline.addLast(new MyClientHandler());
}
}); //自定义一个初始化类
ChannelFuture channelFuture = bootstrap.connect("localhost", 7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MyClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("服务器的ip=" + ctx.channel().remoteAddress());
System.out.println("收到服务器消息=" + msg);
}
//重写channelActive 发送数据
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("MyClientHandler 发送数据");
ctx.writeAndFlush(123456L); //发送的是一个long
}
}
3. Códec
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MyLongToByteEncoder extends MessageToByteEncoder<Long> {
//编码方法
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("MyLongToByteEncoder encode 被调用");
System.out.println("msg=" + msg);
out.writeLong(msg);
}
}
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MyByteToLongDecoder extends ByteToMessageDecoder {
/**
*
* decode方法 会根据接收的数据,被调用多次, 直到确定没有新的元素被添加到list
* , 或者是ByteBuf 没有更多的可读字节为止
* 如果list out 不为空,就会将list的内容传递给下一个 channelinboundhandler处理, 该处理器的方法也会被调用多次
*
* @param ctx 上下文对象
* @param in 入站的 ByteBuf
* @param out List 集合,将解码后的数据传给下一个handler
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder 被调用");
//因为 long 8个字节, 需要判断有8个字节,才能读取一个long
if(in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
3. Ejecutar y ver los resultados
lado del cliente:
MyClientHandler envía datos
La codificación MyLongToByteEncoder se llama
msg=123456
MyByteToLongDecoder se llama
servidor ip=localhost/127.0.0.1:7000
mensaje del servidor recibido=98765
lado del servidor:
Se llamó a MyByteToLongDecoder para
leer desde client/127.0.0.1:58478 a long 123456
Se llamó a la codificación MyLongToByteEncoder con
msg=98765
De acuerdo con nuestros resultados de ejecución, simplemente podemos sacar la conclusión de que el proceso de ejecución es más o menos así:
4. Asuntos que requieren atención
El códec solo puede manejar los datos del tipo especificado. Si el tipo de datos no coincide, el códec se omitirá, pero los datos no se perderán.
Por ejemplo, si el cliente ctx.writeAndFlush(Unpooled.copiedBuffer(“abcdabcdabcdabcd”, CharsetUtil.UTF_8)) envía una cadena de 16 bits, aunque se ejecutará el método de codificación del codificador, no se codificará. Por lo tanto, cuando escribimos Encoder, debemos prestar atención a la consistencia del tipo de datos entrantes y el tipo de datos procesados.
Echemos un vistazo al método de escritura de MessageToByteEncoder:
// io.netty.handler.codec.MessageToByteEncoder#write
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
if (acceptOutboundMessage(msg)) {
//判断当前msg 是不是应该处理的类型,如果是就处理,不是就跳过encode
@SuppressWarnings("unchecked")
I cast = (I) msg;
buf = allocateBuffer(ctx, cast, preferDirect);
try {
encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(cast);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
if (buf != null) {
buf.release();
}
}
}
3. Otros códecs integrados de Netty
Netty incluye muchos códecs, que suelen ser suficientes para nuestro trabajo diario:
1, Decodificador de reproducción
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
public class MyByteToLongDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder2 被调用");
//在 ReplayingDecoder 不需要判断数据是否足够读取,内部会进行处理判断
out.add(in.readLong());
}
}
ReplayingDecoder es fácil de usar, pero también tiene algunas limitaciones:
1. No todas las operaciones de ByteBuf son compatibles.Si se llama a un método no compatible, se lanzará una UnsupportedOperationException.
2. ReplayingDecoder puede ser un poco más lento que ByteToMessageDecoder en algunos casos. Por ejemplo, cuando la red es lenta y el formato del mensaje es complejo, el mensaje se dividirá en varios fragmentos y la velocidad disminuirá.
2. Otros codificadores
LineBasedFrameDecoder: esta clase también se usa dentro de Netty, que usa caracteres de control de fin de línea (\n o \r\n) como delimitadores para analizar datos.
DelimiterBasedFrameDecoder: use caracteres especiales personalizados como delimitadores de mensajes.
HttpObiectDecoder: un decodificador para datos HTTP.
LengthFieldBasedFrameDecoder: identifique el mensaje de paquete completo especificando la longitud, de modo que los paquetes fijos y los mensajes de medio paquete se puedan procesar automáticamente.
Los codificadores y decodificadores vienen en pares.
3. El códec incorporado maneja el problema de pegar y desempacar
Cuatro soluciones para que Netty resuelva problemas pegajosos y de desembalaje