netty 시작하기 (2)

28
Netty 시작하기 (2) 고성능 메모리 모델과 유연한 파이프라인 김대현 @hatemogi 0

Upload: daehyun-kim

Post on 19-Jul-2015

281 views

Category:

Software


5 download

TRANSCRIPT

Page 1: Netty 시작하기 (2)

Netty 시작하기 (2)고성능 메모리 모델과 유연한 파이프라인

김대현@hatemogi

0

Page 2: Netty 시작하기 (2)

Netty 시작하기: 두번째 시간

메모리 모델과 바이트 버퍼파이프라인(ChannelPipeline) 활용ChannelInboundHandler와 아이들

실습과 예제

웹서버 개발

Page 3: Netty 시작하기 (2)

메모리 모델과 바이트 버퍼Netty에서 사용하는 ByteBuf는 별도로 관리성능 측면에서 GC부담을 최소화NIO의 ByteBuffer와 같은 역할, 성능 최적화

Page 4: Netty 시작하기 (2)

io.netty.buffer.*

java.nio.ByteBuffer와 유사커스텀 타입 개발가능복합(composite) 버퍼 도입으로 버퍼 복사를 최소화필요에 따라 버퍼 용량 "자동" 증가flip()호출 필요없음참조수 관리로 메모리 수동 해제 ­ 음?

Page 5: Netty 시작하기 (2)

ReferenceCounted

별도 메모리 풀에서 할당 / 해제최초의 참조수(refCnt)는 "1"더 참조하는 객체가 생기면 retain() 호출 ­> 1증가객체를 다 썼으면 release() 호출 ­> 1감소참조수가 0이되면 메모리 해제

public interface ReferenceCounted { int refCnt(); ReferenceCounted retain(); boolean release();}

Page 6: Netty 시작하기 (2)

retain() / release() 정책

즉, 어떤 메소드 void A(ReferenceCounted obj)가

1.  다른 메소드 B(obj)를 호출하는 경우메소드 A(obj)에서는 release()할 필요 없다메소드 B의 책임 (or 그 다음 어딘가)

2.  obj를 잘 쓰고, 별다른 메소드 호출이 없이 끝난다obj.release()를 호출 해야함!

마지막에 사용하는 메소드가 release()한다

Page 7: Netty 시작하기 (2)

연습문제: 누가 release()하나요 

public ByteBuf a(ByteBuf input) { ... return input; }public ByteBuf b(ByteBuf input) { try { output = input.alloc().directBuffer(input.readableBytes() + 1); ... return output; } finally { input.release(); }}public void c(ByteBuf input) { ... input.release(); }

public void main() { ByteBuf buf = ...; c(b(a(buf))); System.out.println(buf.refCnt());}

누가 main()의 마지막으로 release()했고, 최종 refCnt()는 얼마?

Page 8: Netty 시작하기 (2)

src/nettystartup/h1/discard/DiscardServerHandler.java

class DiscardServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; try { // discard } finally { buf.release(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }}

Page 9: Netty 시작하기 (2)

src/nettystartup/h1/echo/EchoServerHandler.java

class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }}

Page 10: Netty 시작하기 (2)

파생 버퍼 Derived Buffer

파생될 때 참조수가 증가하지 않음그러므로, 다른 메소드에 넘길 때는 retain() 필요

아래 메소드로 파생된 ByteBuf는 원래 버퍼의 참조수를 공유

public abstract class ByteBuf implements ReferenceCounted, ... { public abstract ByteBuf duplicate(); public abstract ByteBuf slice(); public abstract ByteBuf slice(int index, int length); public abstract ByteBuf order(ByteOrder endianness);}

Page 11: Netty 시작하기 (2)

ByteBufHolder

파생 버퍼와 마찬가지로 원래 버퍼와 참조수를 공유

DatagramPacket, HttpContent, WebSocketframe

Page 12: Netty 시작하기 (2)

채널 파이프라인의 활용각각의 채널에는 ChannelPipeline이 있고한 ChannelPipeline에는 ChannelHandler 여러개ChannelPipeline에 여러 ChannelHandler를 다양하게 조립해 사용

Page 13: Netty 시작하기 (2)

Channel

읽기, 쓰기, 연결(connect), 바인드(bind)등의 I/O 작업을 할 수 있는 요소 또는 네트워크 연결모든 I/O 작업은 비동기 ­> ChannelFuture

핵심 메소드

ChannelFuture addListener(GenericFutureListener<...> listener)Channel channel()boolean isSuccess();Throwable cause();ChannelFuture await()ChannelFuture sync()

Page 14: Netty 시작하기 (2)

ChannelHandler

Netty의 핵심 요소!Netty의 I/O 이벤트를 처리하는 인터페이스ChannelInboundHandlerAdapter

ChannelOutboundHandlerAdapter

"전체" 메소드

void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)void handlerAdded(ChannelHandlerContext ctx)void handlerRemoved(ChannelHandlerContext ctx)

Page 15: Netty 시작하기 (2)

ChannelPipeline

Channel에 드나드는 inbound / outbound 이벤트를 처리 처리, ChannelHandler 리스트

파이프라인 동적 변경 가능

주요 메소드

Intercepting Filter 패턴

ChannelPipeline addLast(ChannelHandler... handlers)ChannelPipeline addLast(String name, ChannelHandler handler)ChannelHandler remove(String name)<T extends ChannelHandler> T remove(Class<T> handlerType)

Page 16: Netty 시작하기 (2)

ChannelInboundHandler

ChannelInboundHandler

ChannelInboundHandlerAdapter

SimpleInboundHandler<I>

ChannelInitializer<C extends

Channel>

(채널로 들어오는) 인바운드(inbound) 이벤트를 담당하는 인터페이스

Page 17: Netty 시작하기 (2)

ChannelInboundHandler

public interface ChannelInboundHandler extends ChannelHandler { void channelRegistered(ChannelHandlerContext ctx) throws Exception; void channelActive(... ctx) throws Exception; void channelRead(... ctx, Object msg) throws Exception; void channelReadComplete(... ctx) throws Exception; void userEventTriggered(... ctx, Object evt) throws Exception; void exceptionCaught(... ctx, Throwable cause) throws Exception; ...}

Page 18: Netty 시작하기 (2)

ChannelInboundHandlerAdapter

class ChannelInboundHandlerAdapter extends ... implements ChannelInboundHandler { void channelRegistered(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelRegistered(); } void channelActive(... ctx) throws E... { ctx.fireChannelActive(); } void channelRead(... ctx, Object msg) throws E... { ctx.fireChannelRead(msg); } void channelReadComplete(... ctx) throws E... { ctx.fireChannelReadComplete(); } void exceptionCaught(... ctx, Throwable cause) throws E... { ctx.fireExceptionCaught(cause); } ...}

Page 19: Netty 시작하기 (2)

SimpleChannelInboundHandler<I>

public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter { protected abstract void channelRead0(... ctx, I msg) throws Exception; public void channelRead(C..H..Context ctx, Object msg) throws Exception { boolean release = true; try { if (acceptInboundMessage(msg)) { channelRead0(ctx, (I) msg); } else { release = false; ctx.fireChannelRead(msg); } } finally { if (autoRelease && release) { ReferenceCountUtil.release(msg); } } }}

Page 20: Netty 시작하기 (2)

ChannelInitializer<C extends Channel>

ChannelPipeline 초기화에 사용

public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter { protected abstract void initChannel(C ch) throws Exception; @Override public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { ChannelPipeline pipeline = ctx.pipeline(); ... initChannel((C) ctx.channel()); pipeline.remove(this); ctx.fireChannelRegistered(); ... }}

Page 21: Netty 시작하기 (2)

ChannelInboundHandler 요약

ChannelInboundHandler: 인터페이스ChannelInboundHandlerAdapter: 다음 핸들러에게 위임SimpleInboundHandler<I>: 특정 타입의 메시지를 처리하고 버퍼해제하거나 위임ChannelInitializer<C extends Channel>: 채널 파이프라인 초기화

Page 22: Netty 시작하기 (2)

실습: HTTP 서버 개발

HttpStaticServerHttpStaticFileHandlerHttpNotFoundHandler

GET / HTTP/1.1요청에 대해 res/h2/index.html파일 내용을 응답

Page 23: Netty 시작하기 (2)

http://localhost:8020/

Page 24: Netty 시작하기 (2)

test/nettystartup/h2/http/HttpStaticServer.java

public class HttpStaticServer { static String index = System.getProperty("user.dir") + "/res/h2/index.html";

public static void main(String[] args) throws Exception { NettyStartupUtil.runServer(8020, new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(new HttpServerCodec()); p.addLast(new HttpObjectAggregator(65536)); p.addLast(new HttpStaticFileHandler("/", index)); // TODO: [실습2-2] HttpNotFoundHandler를 써서 404응답을 처리합니다. } }); }}

Page 25: Netty 시작하기 (2)

test/nettystartup/h2/http/HttpStaticFileHandler.java

public class HttpStaticFileHandler extends SimpleChannelInboundHandler<HttpRequest> { private String path; private String filename;

public HttpStaticFileHandler(String path, String filename) { super(false); // set auto-release to false this.path = path; this.filename = filename; }

@Override protected void channelRead0(ChannelHandlerContext ctx, HttpRequest req) throws Exception { // TODO: [실습2-1] sendStaticFile메소드를 써서 구현합니다. "/" 요청이 아닌 경우에는 어떻게 할까요? }

private void sendStaticFile(ChannelHandlerContext ctx, HttpRequest req) throws IOException { ... }}

Page 26: Netty 시작하기 (2)

test/nettystartup/h2/http/HttpNotFoundHandler.java

public class HttpNotFoundHandler extends SimpleChannelInboundHandler<HttpRequest> { @Override protected void channelRead0(C..H..Context ctx, HttpRequest req) throws E.. { ByteBuf buf = Unpooled.copiedBuffer("Not Found", CharsetUtil.UTF_8); FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND, buf); res.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); if (HttpHeaders.isKeepAlive(req)) { res.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE); } res.headers().set(CONTENT_LENGTH, buf.readableBytes()); ctx.writeAndFlush(res).addListener((ChannelFuture f) -> { if (!HttpHeaders.isKeepAlive(req)) { f.channel().close(); } }); }}

Page 27: Netty 시작하기 (2)

실습 정리

기본 HttpServerCodec을 써서 간단히 HTTP 서버 구현GET / 처리와 404 위임 처리를 통해 ChannelPipeline 이해

Page 28: Netty 시작하기 (2)

다음 시간에는...

http://hatemogi.github.io/netty­startup/3.html