SlideShare 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()했고, 최종  refCnt()는 얼마?
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

What's hot

Concurrency in action - chapter 5
Concurrency in action - chapter 5Concurrency in action - chapter 5
Concurrency in action - chapter 5
JinWoo Lee
 
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
Young Hoo Kim
 
병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임codenavy
 
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
Jaeseung Ha
 
Boost
BoostBoost
Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용
흥배 최
 
2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영
2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영
2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영
Tae Young Lee
 
Profiling - 실시간 대화식 프로파일러
Profiling - 실시간 대화식 프로파일러Profiling - 실시간 대화식 프로파일러
Profiling - 실시간 대화식 프로파일러
Heungsub Lee
 
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
내훈 정
 
Python으로 채팅 구현하기
Python으로 채팅 구현하기Python으로 채팅 구현하기
Python으로 채팅 구현하기
Tae Young Lee
 
안드로이드 멀티스레딩 입문 송형주
안드로이드 멀티스레딩 입문 송형주안드로이드 멀티스레딩 입문 송형주
안드로이드 멀티스레딩 입문 송형주
iamhjoo (송형주)
 
병렬 프로그래밍
병렬 프로그래밍병렬 프로그래밍
병렬 프로그래밍
준혁 이
 
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용 [Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
MinGeun Park
 
H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요KTH
 
[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성
NAVER D2
 
Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택
JinTaek Seo
 
Boost 라이브리와 C++11
Boost 라이브리와 C++11Boost 라이브리와 C++11
Boost 라이브리와 C++11OnGameServer
 
CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기
YEONG-CHEON YOU
 
Ipython server(Jupyter Server) 만들기
Ipython server(Jupyter Server) 만들기Ipython server(Jupyter Server) 만들기
Ipython server(Jupyter Server) 만들기
Hyun-sik Yoo
 
Nvidia architecture
Nvidia architectureNvidia architecture
Nvidia architecture
Tae Young Lee
 

What's hot (20)

Concurrency in action - chapter 5
Concurrency in action - chapter 5Concurrency in action - chapter 5
Concurrency in action - chapter 5
 
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
 
병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임
 
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
 
Boost
BoostBoost
Boost
 
Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용
 
2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영
2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영
2016317 파이썬기초_파이썬_다중설치부터_Jupyter를이용한프로그래밍_이태영
 
Profiling - 실시간 대화식 프로파일러
Profiling - 실시간 대화식 프로파일러Profiling - 실시간 대화식 프로파일러
Profiling - 실시간 대화식 프로파일러
 
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
Python으로 채팅 구현하기
Python으로 채팅 구현하기Python으로 채팅 구현하기
Python으로 채팅 구현하기
 
안드로이드 멀티스레딩 입문 송형주
안드로이드 멀티스레딩 입문 송형주안드로이드 멀티스레딩 입문 송형주
안드로이드 멀티스레딩 입문 송형주
 
병렬 프로그래밍
병렬 프로그래밍병렬 프로그래밍
병렬 프로그래밍
 
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용 [Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
 
H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요
 
[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성
 
Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택
 
Boost 라이브리와 C++11
Boost 라이브리와 C++11Boost 라이브리와 C++11
Boost 라이브리와 C++11
 
CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기
 
Ipython server(Jupyter Server) 만들기
Ipython server(Jupyter Server) 만들기Ipython server(Jupyter Server) 만들기
Ipython server(Jupyter Server) 만들기
 
Nvidia architecture
Nvidia architectureNvidia architecture
Nvidia architecture
 

Similar to Netty 시작하기 (2)

IBM JVM GC_Wh apm
IBM JVM GC_Wh apmIBM JVM GC_Wh apm
IBM JVM GC_Wh apm
엑셈
 
Mec++ chapter3,4
Mec++ chapter3,4Mec++ chapter3,4
Mec++ chapter3,4문익 장
 
Multithread pattern 소개
Multithread pattern 소개Multithread pattern 소개
Multithread pattern 소개Sunghyouk Bae
 
C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)
C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)
C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)
Dong Chan Shin
 
[2015-05월 세미나] 파이선 초심자의 Openstack
[2015-05월 세미나] 파이선 초심자의 Openstack[2015-05월 세미나] 파이선 초심자의 Openstack
[2015-05월 세미나] 파이선 초심자의 Openstack
OpenStack Korea Community
 
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
Wonha Ryu
 

Similar to Netty 시작하기 (2) (6)

IBM JVM GC_Wh apm
IBM JVM GC_Wh apmIBM JVM GC_Wh apm
IBM JVM GC_Wh apm
 
Mec++ chapter3,4
Mec++ chapter3,4Mec++ chapter3,4
Mec++ chapter3,4
 
Multithread pattern 소개
Multithread pattern 소개Multithread pattern 소개
Multithread pattern 소개
 
C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)
C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)
C# / .NET Framework로 미래 밥그릇을 챙겨보자 (Basic)
 
[2015-05월 세미나] 파이선 초심자의 Openstack
[2015-05월 세미나] 파이선 초심자의 Openstack[2015-05월 세미나] 파이선 초심자의 Openstack
[2015-05월 세미나] 파이선 초심자의 Openstack
 
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
 

More from Daehyun Kim

Netty 시작하기 (4)
Netty 시작하기 (4)Netty 시작하기 (4)
Netty 시작하기 (4)
Daehyun Kim
 
Netty 시작하기 (3)
Netty 시작하기 (3)Netty 시작하기 (3)
Netty 시작하기 (3)
Daehyun Kim
 
Netty 시작하기 (1)
Netty 시작하기 (1)Netty 시작하기 (1)
Netty 시작하기 (1)
Daehyun Kim
 
devon2013: 사내Git저장소개발사례
devon2013: 사내Git저장소개발사례devon2013: 사내Git저장소개발사례
devon2013: 사내Git저장소개발사례
Daehyun Kim
 
AWS 콘서트
AWS 콘서트AWS 콘서트
AWS 콘서트
Daehyun Kim
 

More from Daehyun Kim (6)

Netty 시작하기 (4)
Netty 시작하기 (4)Netty 시작하기 (4)
Netty 시작하기 (4)
 
Netty 시작하기 (3)
Netty 시작하기 (3)Netty 시작하기 (3)
Netty 시작하기 (3)
 
Netty 시작하기 (1)
Netty 시작하기 (1)Netty 시작하기 (1)
Netty 시작하기 (1)
 
devon2013: 사내Git저장소개발사례
devon2013: 사내Git저장소개발사례devon2013: 사내Git저장소개발사례
devon2013: 사내Git저장소개발사례
 
AWS 콘서트
AWS 콘서트AWS 콘서트
AWS 콘서트
 
Ef2011 sf
Ef2011 sfEf2011 sf
Ef2011 sf
 

Netty 시작하기 (2)