자바 네트워크 소녀 네티( 정경석 저 / 이희승 감수, 한빛미디어 ) 를 읽고 정리합니다.
📘2부 4장. 네티 상세 - 채널 파이프라인과 코덱
🔹코덱
동영상 압축 알고리즘을 코덱이라고 부른다
원본파일을 압축하는 것을 인코딩, 압축된 파일을 원본으로 추출하는 과정을 디코딩이라고 한다.
이 과정을 네티의 소켓 채널로 옮기면 인코더는 인바운드, 디코더는 아웃바운드가 된다.
🔸코덱의 구조
인바운드와 아웃바운드에 해당하는 이벤트는 ChannelInboundHandler 와 ChannelOutboundHandler 인터페이스로 각각 인코더와 디코더라고 부른다.
데이터를 전송할 때 인코더를 사용해 패킷으로 변환하고 데이터를 수신할 떄는 디코더를 사용해 패킷을 데이터로 변환한다.
🔸코덱의 실행과정
네티의 코덱은 템플릿 메서드 패턴으로 작성되어있다. 템플릿 메서드 패턴의 상위 구현체에서 메서드의 실행 순서만을 지정하고 수행될 메서드의 구현은 하위 구현체로 위임한다.
public class Base64Encoder extends MessageToMessageEncoder<ByteBuf> {
...}
public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {
...
@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(ctx, cast, out); //
} catch (Throwable th) {
ReferenceCountUtil.safeRelease(cast);
PlatformDependent.throwException(th);
}
ReferenceCountUtil.release(cast);
if (out.isEmpty()) {
throw new EncoderException(
StringUtil.simpleClassName(this) + " must produce at least one message.");
}
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
...
}
}
protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}
Base64Encoder는 MessageToMessageEncoder를 상속받고 있고, MessageToMessageEncoder는 ChannelOutboundHandlerAdapter 를 상속받고 있다.
write 이벤트 메서드는 ChannelOutboundHandlerAdapter 에 구현되어있고, Base64Encoder클래스가 네티의 채널 파이프라인에 등록되어있을때 채널에 데이터를 기록하면 ChannelOutboundHandlerAdapter 의 write 이벤트 메서드가 수행된다.
write 이벤트 메서드는 Base64Encoder 클래스에 구현된 encode 메서드를 호출하게 된다.
🔸기본 제공 코덱
base64 코덱
base64 인코딩 데이터에 대한 송수신을 지원하는 코덱이다.
+) base64 : 8비트 이진 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자로 이루어진 일련의 문자열로 바꾸는 인코딩
byte 코덱
바이트 배열 데이터에 대한 송수신 지원
compression 코덱
송수신 데이터의 압축을 지원하는 코덱
http 코덱
HTTP 프로토콜을 지원하는 코덱
HTTP 코덱의 세부 구현체로 HTTP 프로토콜의 1) CORS송수신을 지원하는 CORS 코덱, 2) 파일 송수신과 같은 multipart 요청과 응답을 지원하는 multipart 코덱, 3) 웹 소켓 프로토콜의 데이터 송수신을 지원하는 websocketx 코덱이 있다.
malshalling 코덱
마셸링이란 객체를 네트워크를 통해서 송신 가능한 형태로 변환하는 과정이다. (네트워크를 통해 수신한 데이터를 객체로 변환하는 과정을 언마샬링이라고 한다.)
protobuf 코덱
구글의 프로토콜 버퍼를 사용한 데이터 송수신을 지원하는 코덱이다.
rtsp 코덱
오디오나 비디오 같은 실시간 데이터 전달을 위해서 특수하게 만들어진 애플리케이션 레벨의 프로토콜
sctp 코덱
tcp가 아닌 sctp 전송계층을 사용하는 코덱
spdy 코덱
구글의 SPDY 프로토콜을 지원하는 코덱
string 코덱
문자열의 송수신을 지원하는 코덱, 주로 텔넷 프로토콜이나 채팅서버의 프로토콜에 이용된다.
serialization 코덱
자바의 객체를 네트워크로 전송할 수 있도록 직렬화, 역직렬화를 지원하는 코덱이다.
🔸사용자 정의 코덱
예제를 통해 HTTP 코덱을 사용하는 방법과 사용자 정의 코덱을 작성하는 방법을 보자
public class HttpHelloWorldServer {
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080"));
public static void main(String[] args) throws CertificateException, SSLException {
final SslContext sslContext;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
//sslContext = SslContext.newServerContext(ssc.certificate()); => deprecated
sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
ssc.privateKey();
} else {
sslContext = null;
}
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpHelloWorldServerInitializer(sslContext));
Channel ch = b.bind(8888).sync().channel();
System.err.println("Open your web browser and navigate to " +
(SSL ? "https" : "http") + "://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
- SO_BACKLOG: 서버가 대기하는 연결 수를 설정한다. 여기서는 1024로 설정되어 있다.
- handler(new LoggingHandler(LogLevel.INFO)): 서버에서 발생하는 모든 이벤트를 로그로 기록한다. INFO 레벨로 로그가 출력된다.
- childHandler(new HttpHelloWorldServerInitializer(sslContext)): 클라이언트와 연결될 때마다 실행될 초기화 핸들러를 설정한다. HttpHelloWorldServerInitializer는 클라이언트와의 연결을 초기화하는 역할을 한다. SSL을 사용할 경우 sslContext를 전달한다.
public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslContext;
public HttpHelloWorldServerInitializer(SslContext sslContext) {
this.sslContext = sslContext;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslContext != null) {
p.addLast(sslContext.newHandler(ch.alloc()));
}
p.addLast(new HttpServerCodec());
p.addLast(new HttpHelloWorldServerHandler());
}
}
p.addLast(new HttpServerCodec()); : 네티가 제공하는 HTTP 서버 코덱이다. 인바운드와 아웃바운드 핸들러를 모두 구현해야한다.
public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder>{...}
HttpServerCodec의 생성자에서 HttpRequestDecoder와 HttpResponseEncoder를 모두 생성한다.
HttpServerCodec 는 HTTP 요청과 응답을 디코딩(수신) 하고, 인코딩(송신) 하는 역할을 한다. 간단한 웹서버를 생성하는데 사용되는 코덱으로서 1) 수신된 ByteBuf 객체를 HttpRequest와 HttpContent 객체로 변환하고 2) HttpResponse 객체를 ByteBuf 객체로 인코딩하여 송신한다.
p.addLast(new HttpHelloWorldServerHandler()); : HttpServerCodec이 요청을 HttpRequest 객체로 변환하고 나면, HttpHelloWorldServerHandler가 수신한 HttpRequest 객체를 처리하여 응답을 생성하는 역할을 한다.
public class HttpHelloWorldServerHandler extends **ChannelInboundHandlerAdapter** {
private static final byte[] CONTENT = {'H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D'};
private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type");
private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length");
private static final AsciiString CONNECTION = new AsciiString("Connection");
private static final AsciiString KEEP_ALIVE = new AsciiString("Keep-alive");
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
if (HttpUtil.is100ContinueExpected((HttpMessage) req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
boolean keepAlive = HttpUtil.isKeepAlive((HttpMessage) req);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, KEEP_ALIVE);
ctx.write(response);
}
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- HttpServerCodec 으로부터 수신된 channelRead 이벤트를 처리하기 위해 ChannelInboundHandlerAdapter 를 상속받는다.
- 웹브라우저로부터 데이터가 모두 수신되었을떄(channelReadComplete) 버퍼의 내용을 웹 브라우저로 전송한다.
- msg instanceof HttpRequest : 수신된 객체를 확인하고 response 를 생성해 헤더와 메시지를 저장한다.
'Netty' 카테고리의 다른 글
[Netty] 네티 바이트 버퍼 (0) | 2025.03.27 |
---|---|
[Netty] 네티의 이벤트 루프와 비동기 I/O 처리 (1) | 2025.03.26 |
[Netty] 네티의 이벤트 실행, 채널 파이프라인, 이벤트 핸들러 (0) | 2025.03.23 |
[Netty] 부트스트랩이란, 부트스트랩 구조 및 설정(ServerBootStrap, BootStrap) (0) | 2025.03.22 |
[Netty] 네티의 주요특징 - 이벤트 기반 프로그래밍 (1) | 2025.03.15 |