ݺߣ

ݺߣShare a Scribd company logo
Netty 시작하기 (2)
고성능 메모리 모델과 유연한 파이프라인
김대현
@hatemogi
0
Netty 시작하기: 두번째 시간
메모리 모델과 바이트 버퍼
파이프라인( ChannelPipeline) 활
용
ChannelInboundHandler와 아이들
실습과 예제
웹서버 개
발
메모리 모델과 바이트 버퍼
Netty에서 사용하는  ByteBuf는 별도로 관리
성능 측면에서 GC부담을 최소화
NIO의  ByteBuffer와 같은 역할, 성능 최적
화
io.netty.buffer.*
java.nio.ByteBuffer와 유사
커스텀 타입 개발가능
복합(composite) 버퍼 도입으로 버퍼 복사를 최소화
필요에 따라 버퍼 용량 "자동" 증가
flip()호출 필요없음
참조수 관리로 메모리 수동 해제 ­ 음?
ReferenceCounted
별도 메모리 풀에서 할당 / 해제
최초의  참조수(refCnt)는 "1"
더 참조하는 객체가 생기면  retain() 호출 ­> 1증가
객체를 다 썼으면  release() 호출 ­> 1감소
참조수가 0이되면 메모리 해제
publicinterfaceReferenceCounted{
intrefCnt();
ReferenceCountedretain();
booleanrelease();
}
retain() / release() 정책
즉, 어떤 메소드  voidA(ReferenceCountedobj)가
1.  다른 메소드  B(obj)를 호출하는 경우
메소드  A(obj)에서는  release()할 필요 없다
메소드  B의 책임 (or 그 다음 어딘가)
2.  obj를 잘 쓰고, 별다른 메소드 호출이 없이 끝난다
obj.release()를 호출 해야함!
마지막에 사용하는 메소드가  release()한다
연습문제: 누가 release()하나요 
publicByteBufa(ByteBufinput){...returninput;}
publicByteBufb(ByteBufinput){
try{
output=input.alloc().directBuffer(input.readableBytes()+1);
...returnoutput;
}finally{input.release();}
}
publicvoidc(ByteBufinput){...input.release();}
publicvoidmain(){
ByteBufbuf=...;
c(b(a(buf)));
System.out.println(buf.refCnt());
}
누가  main()의 마지막으로  release()했고, 최종  ڰԳ() 얼마?
src/nettystartup/h1/discard/DiscardServerHandler.java
classDiscardServerHandlerextendsChannelInboundHandlerAdapter{
@Override
publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{
ByteBufbuf=(ByteBuf)msg;
try{
//discard
}finally{
buf.release();
}
}
@Override
publicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){
cause.printStackTrace();
ctx.close();
}
}
src/nettystartup/h1/echo/EchoServerHandler.java
classEchoServerHandlerextendsChannelInboundHandlerAdapter{
@Override
publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg){
ctx.write(msg);
}
@Override
publicvoidchannelReadComplete(ChannelHandlerContextctx){
ctx.flush();
}
@Override
publicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){
cause.printStackTrace();
ctx.close();
}
}
파생 버퍼 Derived Buffer
파생될 때 참조수가 증가하지 않음
그러므로, 다른 메소드에 넘길 때는  retain() 필요
아래 메소드로 파생된  ByteBuf는 원래 버퍼의 참조수를 공유
publicabstractclassByteBufimplementsReferenceCounted,...{
publicabstractByteBufduplicate();
publicabstractByteBufslice();
publicabstractByteBufslice(intindex,intlength);
publicabstractByteBuforder(ByteOrderendianness);
}
ByteBufHolder
파생 버퍼와 마찬가지로 원래 버퍼와 참조수를 공유
DatagramPacket, HttpContent, WebSocketframe
채널 파이프라인의 활용
각각의 채널에는  ChannelPipeline이 있고
한  ChannelPipeline에는  ChannelHandler 여러개
ChannelPipeline에 여러  ChannelHandler를 다양하게 조립해 사용
Channel
읽기, 쓰기, 연결(connect), 바인드(bind)등의 I/O 작업을 할 수 있는 요소 또
는 네트워크 연결
모든 I/O 작업은 비동기 ­>  ChannelFuture
핵심 메소드
ChannelFutureaddListener(GenericFutureListener<...>listener)
Channel channel()
boolean isSuccess();
Throwable cause();
ChannelFutureawait()
ChannelFuturesync()
ChannelHandler
Netty의 핵심 요소!
Netty의 I/O 이벤트를 처리하는 인터페이스
ChannelInboundHandlerAdapter
ChannelOutboundHandlerAdapter
"전체" 메소드
voidexceptionCaught(ChannelHandlerContextctx,Throwablecause)
voidhandlerAdded(ChannelHandlerContextctx)
voidhandlerRemoved(ChannelHandlerContextctx)
ChannelPipeline
Channel에 드나드는 inbound / outbound 이벤트를 처리
 처리,  ChannelHandler 리스트
파이프라인 동적 변경 가능
주요 메소드
Intercepting Filter 패턴
ChannelPipelineaddLast(ChannelHandler...handlers)
ChannelPipelineaddLast(Stringname,ChannelHandlerhandler)
ChannelHandler remove(Stringname)
<TextendsChannelHandler>Tremove(Class<T>handlerType)
ChannelInboundHandler
ChannelInboundHandler
ChannelInboundHandlerAdapter
SimpleInboundHandler<I>
ChannelInitializer<Cextends
Channel>
(채널로 들어오는) 인바운드(inbound) 이벤트를 담당하는 인터페이스
ChannelInboundHandler
publicinterfaceChannelInboundHandlerextendsChannelHandler{
voidchannelRegistered(ChannelHandlerContextctx)throwsException;
voidchannelActive(...ctx)throwsException;
voidchannelRead(...ctx,Objectmsg)throwsException;
voidchannelReadComplete(...ctx)throwsException;
voiduserEventTriggered(...ctx,Objectevt)throwsException;
voidexceptionCaught(...ctx,Throwablecause)throwsException;
...
}
ChannelInboundHandlerAdapter
classChannelInboundHandlerAdapterextends...implementsChannelInboundHandler{
voidchannelRegistered(ChannelHandlerContextctx)throwsException{
ctx.fireChannelRegistered();
}
voidchannelActive(...ctx)throwsE...{
ctx.fireChannelActive();
}
voidchannelRead(...ctx,Objectmsg)throwsE...{
ctx.fireChannelRead(msg);
}
voidchannelReadComplete(...ctx)throwsE...{
ctx.fireChannelReadComplete();
}
voidexceptionCaught(...ctx,Throwablecause)throwsE...{
ctx.fireExceptionCaught(cause);
}
...
}
SimpleChannelInboundHandler<I>
publicabstractclassSimpleChannelInboundHandler<I>extendsChannelInboundHandlerAdapter{
protectedabstractvoidchannelRead0(...ctx,Imsg)throwsException;
publicvoidchannelRead(C..H..Contextctx,Objectmsg)throwsException{
booleanrelease=true;
try{
if(acceptInboundMessage(msg)){
channelRead0(ctx,(I)msg);
}else{
release=false;
ctx.fireChannelRead(msg);
}
}finally{
if(autoRelease&&release){ReferenceCountUtil.release(msg);}
}
}
}
ChannelInitializer<C extends Channel>
ChannelPipeline 초기화에 사용
publicabstractclassChannelInitializer<CextendsChannel>
extendsChannelInboundHandlerAdapter{
protectedabstractvoidinitChannel(Cch)throwsException;
@Override
publicfinalvoidchannelRegistered(ChannelHandlerContextctx)throwsException{
ChannelPipelinepipeline=ctx.pipeline();
...
initChannel((C)ctx.channel());
pipeline.remove(this);
ctx.fireChannelRegistered();
...
}
}
ChannelInboundHandler 요약
ChannelInboundHandler: 인터페이스
ChannelInboundHandlerAdapter: 다음 핸들러에게 위임
SimpleInboundHandler<I>: 특정 타입의 메시지를 처리하고 버퍼해제하거나 위
임
ChannelInitializer<CextendsChannel>: 채널 파이프라인 초기화
실습: HTTP 서버 개발
HttpStaticServer
HttpStaticFileHandler
HttpNotFoundHandler
GET/HTTP/1.1요청에 대해  res/h2/index.html파일 내용을 응답
http://localhost:8020/
test/nettystartup/h2/http/HttpStaticServer.java
publicclassHttpStaticServer{
staticStringindex=System.getProperty("user.dir")+"/res/h2/index.html";
publicstaticvoidmain(String[]args)throwsException{
NettyStartupUtil.runServer(8020,newChannelInitializer<SocketChannel>(){
@Override
publicvoidinitChannel(SocketChannelch){
ChannelPipelinep=ch.pipeline();
p.addLast(newHttpServerCodec());
p.addLast(newHttpObjectAggregator(65536));
p.addLast(newHttpStaticFileHandler("/",index));
//TODO:[실습2-2]HttpNotFoundHandler를 써서 404응답을 처리합니다.
}
});
}
}
test/nettystartup/h2/http/HttpStaticFileHandler.java
publicclassHttpStaticFileHandlerextendsSimpleChannelInboundHandler<HttpRequest>{
privateStringpath;
privateStringfilename;
publicHttpStaticFileHandler(Stringpath,Stringfilename){
super(false);//setauto-releasetofalse
this.path=path;
this.filename=filename;
}
@Override
protectedvoidchannelRead0(ChannelHandlerContextctx,HttpRequestreq)throwsException{
//TODO:[실습2-1]sendStaticFile메소드를 써서 구현합니다."/"요청이 아닌 경우에는 어떻게 할까요?
}
privatevoidsendStaticFile(ChannelHandlerContextctx,HttpRequestreq)throwsIOException{
...
}
}
test/nettystartup/h2/http/HttpNotFoundHandler.java
publicclassHttpNotFoundHandlerextendsSimpleChannelInboundHandler<HttpRequest>{
@Override
protectedvoidchannelRead0(C..H..Contextctx,HttpRequestreq)throwsE..{
ByteBufbuf=Unpooled.copiedBuffer("NotFound",CharsetUtil.UTF_8);
FullHttpResponseres=newDefaultFullHttpResponse(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((ChannelFuturef)->{
if(!HttpHeaders.isKeepAlive(req)){
f.channel().close();
}
});
}
}
실습 정리
기본  HttpServerCodec을 써서 간단히 HTTP 서버 구현
GET/ 처리와  404 위임 처리를 통해 ChannelPipeline 이해
다음 시간에는...
http://hatemogi.github.io/netty­startup/3.html

More Related Content

Netty 시작하기 (2)