Skip to content

코덱, 코덱의 구조, 기본 제공 코덱

Gump edited this page Aug 22, 2019 · 6 revisions

코덱

바이트 형태의 데이터를 애플리케이션에서 해석 가능한 데이터 포맷으로 변환하는 인코딩 과정과 분석하는 디코딩 과정이 필요

  • 인코더와 디코더
    • 수신 : XXX-encoded Stream -> Decoder -> ByteBuf
      • ChannelInboundHandler 인터페이스를 구현
      • MessageToByteDecoder : 바이트 스트림을 메세지(특정 의미를 나타내는 바이트 구조) 형태로 디코딩
      • MessageToMessageDecoder : 메세지를 다른 메세지 유형으로 디코딩
    • 송신 : ByteBuf -> Encoder -> XXX-encoded Stream
      • ChannelOutboundHandler 인터페이스를 구현
      • MessageToByteEncoder : 메세지를 바이트 스트림 형태로 인코딩
      • MessageToMessageEncoder : 메세지를 다른 메세지 형태로 인코딩
  • 추상 코덱
    • 인바운드/아웃바운드 데이터와 메세지 변환을 한 클래스에서 관리
    • ChannelInboundHandler, ChannelOutboundHandler를 모두 구현
    • ByteToMessageCodec : ByteToMessageDecoder + MessageToByteEncoder
    • MessageToMessageCodec : MessageToMessageDecoder + MessageToMessageEncoder

Netty 코덱의 실행 과정

  • 템플릿 메서드 패턴으로 구현

<Base64Encoder.java 코드>

package io.netty.handler.codec.base64;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.MessageToMessageEncoder;

import java.util.List;

// 여러 채널간에 안전하게 공유할 수 있음 (???)
@Sharable

// MessageToMessageEncoder 상속
public class Base64Encoder extends MessageToMessageEncoder<ByteBuf> {

    private final boolean breakLines;
    private final Base64Dialect dialect;

    public Base64Encoder() {
        this(true);
    }

    public Base64Encoder(boolean breakLines) {
        this(breakLines, Base64Dialect.STANDARD); // Base64Dialect.STANDARD : RFC3548
    }

    public Base64Encoder(boolean breakLines, Base64Dialect dialect) {
        if (dialect == null) {
            throw new NullPointerException("dialect");
        }

        this.breakLines = breakLines;
        this.dialect = dialect;
    }

    // encode 추상 메서드를 구현
    @Override
    protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        out.add(Base64.encode(msg, msg.readerIndex(), msg.readableBytes(), breakLines, dialect));
    }
}

<MessageToMessageEncoder.java 코드>

package io.netty.handler.codec;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.TypeParameterMatcher;

import java.util.List;

// ChannelOutboundHandlerAdapter 클래스 상속
public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {
    private final TypeParameterMatcher matcher;

    protected MessageToMessageEncoder() {
        matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I");
    }

    protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) {
        matcher = TypeParameterMatcher.get(outboundMessageType);
    }

    public boolean acceptOutboundMessage(Object msg) throws Exception {
        return matcher.match(msg);
    }

    // write 메서드를 오버라이드
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        CodecOutputList out = null;
        try {
            if (acceptOutboundMessage(msg)) {
                out = CodecOutputList.newInstance();
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                try {
                    // 코덱 구현체의 encode 메서드를 호출
                    encode(ctx, cast, out);
                } finally {
                    ReferenceCountUtil.release(cast);
                }

                if (out.isEmpty()) {
                    out.recycle();
                    out = null;

                    throw new EncoderException(
                            StringUtil.simpleClassName(this) + " must produce at least one message.");
                }
            } else {
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable t) {
            throw new EncoderException(t);
        } finally {
            if (out != null) {
                final int sizeMinusOne = out.size() - 1;
                if (sizeMinusOne == 0) {
                    ctx.write(out.get(0), promise);
                } else if (sizeMinusOne > 0) {
                    // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
                    // See https://github.com/netty/netty/issues/2525
                    ChannelPromise voidPromise = ctx.voidPromise();
                    boolean isVoidPromise = promise == voidPromise;
                    for (int i = 0; i < sizeMinusOne; i ++) {
                        ChannelPromise p;
                        if (isVoidPromise) {
                            p = voidPromise;
                        } else {
                            p = ctx.newPromise();
                        }
                        ctx.write(out.getUnsafe(i), p);
                    }
                    ctx.write(out.getUnsafe(sizeMinusOne), promise);
                }
                out.recycle();
            }
        }
    }

    // encode 메서드를 추상 메서드로 정의
    protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}

Netty의 기본 제공 코덱

  • 자주 사용되는 프로토콜의 인코더와 디코더를 기본 제공
  • netty 4.1 기준
패키지 설명
io.netty.handler.codec.base64 Base64 인코딩 데이터 송수신 지원
io.netty.handler.codec.bytes 바이트 배열 데이터 송수신 지원
io.netty.handler.codec.compression 압축 데이터 송수신 지원
io.netty.handler.codec.dns DNS 지원 (@UnstableApi)
io.netty.handler.codec.haproxy HAProxy의 프록시 프로토콜 지원
io.netty.handler.codec.http HTTP 프로토콜 지원
io.netty.handler.codec.http.cookie HTTP 프로토콜의 쿠키 송수신 지원
io.netty.handler.codec.http.cors HTTP 프로토콜의 CORS 송수신 지원
io.netty.handler.codec.http.multipart HTTP 프로토콜 multipart 송수신 지원
io.netty.handler.codec.http.websocketx 웹 소켓 지원
io.netty.handler.codec.http2 HTTP 프로토콜 지원
io.netty.handler.codec.json JSON 인코딩 데이터 수신 지원
io.netty.handler.codec.marshalling JBoss 마샬링 송수신 지원
io.netty.handler.codec.memcache Memcached 프로토콜 지원 (@UnstableApi)
io.netty.handler.codec.mqtt MQTT 프로토콜 지원
io.netty.handler.codec.protobuf Google Protocol Buffer 송수신 지원
io.netty.handler.codec.redis RESP (REdis Serialization Protocol) 지원
io.netty.handler.codec.rtsp RTSP (Real Time Streaming Protocol) 지원
io.netty.handler.codec.sctp SCTP (SCTP/IP) 지원
io.netty.handler.codec.serialization 객체 직렬화 송수신 지원
io.netty.handler.codec.smtp SMTP (Simple Mail Transfer Protocol) 프로토콜 지원
io.netty.handler.codec.socks SOCKS 지원
io.netty.handler.codec.spdy Google SPDY (SPeeDY) 지원
io.netty.handler.codec.stomp STOMP (Streaming Text Oriented Messaging Protocol) 지원
io.netty.handler.codec.string 문자열 데이터 송수신 지원
o.netty.handler.codec.xml XML 데이터 송수신 지원