netty-startup



netty-startup

44 97


netty-startup

Netty 기초 프로그래밍 튜터리얼

On Github hatemogi / netty-startup

Netty 시작하기 (1)

고성능 네트워크 애플리케이션 프레임워크

김대현@hatemogi

김대현

프리랜서 서버 개발자

  • 2016, Akka 기반 서버 개발
  • 2015, Netty 기반 서버 개발
  • 2013, 사내 Redis SaaS 개발팀장, 160+ VM
  • 2010, 클라우드, 네트워크 파일동기화 프로토콜 설계
  • 2010, MyPeople, 네트워크 아키텍쳐 설계
  • 2009, Cafe 채팅 부하분산 서버 개발
  • 2004, Cafe 한줄메모장 개발, 일일 3억 PV

지금 보시는 발표 자료는...

http://hatemogi.github.io/netty-startup

위 주소에서 직접 볼 수 있고, [SPC]를 누르면 다음 슬라이드로, [Shift-SPC]를 누르면 이전 슬라이드로 이동합니다.

주의: 커서키로 이동하면 슬라이드를 건너뛰는 경우도 있습니다.

과정

Netty의 기본 설명 문제 풀이 예제 이해 / 실습 개발

목표

  • Event-Driven, 비동기 I/O 이해
  • Netty로 개발 경험
  • (조만간) 실무에 응용

이 과정의 목표입니다. Netty의 기본을 설명하며, 중간중간 문제풀이로 이해를 돕고, 직접 예제를 개발해보는 과정을 통해, 오늘 4시간을 잘 보내고나면, "Event-Driven 서버 개발이 무엇인지, 비동기 I/O는 무엇인지 알겠다", "나도 Netty로 개발해봤다", "어쩌면 실무에 응용해볼 수 있겠다"는 생각이 드셨으면 좋겠습니다.

준비사항

5시간 진행 주제

비동기 개념, 기본 인터페이스(Channel) 정리 고성능 메모리 모델과 유연한 파이프라인 깔끔한 스레드 모델, Future/Promise 활용 풍부한 코덱과 WebSocket

예제와 실습

첫 Netty 서버: Discard 서버, Echo 서버 HTTP 서버를 직접 만들어 보자 텍스트 프로토콜 기반의 채팅 서버를 만들자 웹소켓을 써서 채팅 서버를 웹환경으로!

매시간 직접 작성하셔야 하는 (간단한) 코드가 있습니다.

매 시간 구조

설명 (~30분) 예제 설명 / 데모 (~10분) pair 실습 (~30분)
  • 총 9개 문제 풀이

다음의 공통점

왜 Netty를 쓸까?

  • 고성능
  • 쓰기 편하다 (유연하고 쉽다)

얼마나 빠른가? netty servlet

Netty가 고성능인 이유

  • Non-blocking Asynchronous 처리가 기본
  • 적은 스레드로 많은 요청을 처리
  • GC부하를 최소화하는 Zero-copy ByteBuf 지원

Netty가 편한 이유

  • 각종 네트워크 프로토콜 (코덱) 기본 지원
  • 필요한 부분을 쉽게 조립해 쓸 수 있는 구조
  • 멀티스레딩 처리 없이도...

Netty 버전

이 자료와 실습은 4.1.6.Final을 기준으로 합니다.

  • 4.1.6.Final ‐ 14-Oct-2016 (Stable, Recommended)
  • 4.0.42.Final ‐ 14-Oct-2016 (Stable)
  • 3.10.6.Final ‐ 29-Jun-2016 (Stable)

동기와 비동기

  • Synchronous vs. Asynchronous
  • Blocking vs. Non-blocking

우리말로 하자면...

  • 요청하고 결과를 계속 기다리기
  • 완료되면 알려달라고 요청하고 다른 일 하기

고객센터에 전화를 했는데...

모든 상담원이 통화중입니다.

연결될 때까지 기다리기 전화해달라는 기록 남기고 끊기

동기 or 비동기

  • 커피 주문
  • 지하철 이용, 자가 운전
  • 업무요청 메일, 업무문의 전화
  • 팀장의 업무지시

동기 (Synchronous) 코드

Blocking call

// 동기 방식
void greeting(Context ctx) {
    String req = ctx.readLine();
    ctx.write("안녕, " + req);
    System.out.println("완료");
}

비동기 (Asynchronous) 코드

Non-blocking call

// 비동기 방식
void greeting(Context ctx) {
    ctx.readLine().done(line -> {
        ctx.write("안녕, " + req);
    });
    System.out.println("완료");
}

Event-Driven Programming

In computer programming, event-driven programming is a programming paradigm in which the flow of the program is determined by events such as user actions (mouse clicks, key presses), sensor outputs, or messages from other programs/threads. Event-driven programming is the dominant paradigm used in graphical user interfaces and other applications (e.g. JavaScript web applications) that are centered on performing certain actions in response to user input. Wikipedia

사건 기반 프로그래밍

사건 기반 프로그래밍(영어: Event-driven programming; EDP)은 비주얼 베이직과 같이, 사용자의 명령·마우스 클릭·다른 프로그램의 메시지·키보드 글쇠 입력 등의 ‘사건’에 따라, 제어 흐름이 결정되어 일을 하도록 하게끔 만들어진 프로그래밍 언어 방식을 뜻한다.위키백과

등잔 밑의 Event-Driven 코드

$("button").on("click", function(event) {
    console.log(event);
    alert("클릭");
});
  • 자바스크립트
  • iOS 앱
  • Android 앱
  • 데스크탑 애플리케이션

True or False

  • 비동기와 Event-driven은 같은 뜻이다?
  • Event-Driven은 데스크탑앱 클라이언트에만 적합하다?
  • 비동기식 싱글스레드에서는 Blocking Call을 할 수 없다?

업무요청시

  • 업무 요청하고 옆에서 기다리고 있으면?
  • 업무 요청하고 돌아갔다가, 나중에 다시 와서 확인하면?
  • 업무 요청하면서, "다되면 저한테 알려주세요"하면?

Netty는 Async Event-Driven

어? 그럼 그냥 Java의 NIO를 쓰면 안되나요

  • 됩니다만...

Netty의 핵심 인터페이스

  • Channel
  • ChannelFuture
  • ChannelHandler
  • ChannelHandlerContext
  • ChannelPipeline
  • EventLoop

Channel

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

핵심 메소드

ChannelFuture   write(Object obj)
ChannelFuture   flush(Object obj)
ChannelFuture   writeAndFlush(Object obj)
ChannelFuture   closeFuture()
ChannelPipeline pipeline()
SocketAddress   remoteAddress()

ChannelFuture

  • Channel의 I/O 작업의 결과
  • ChannelFutureListener를 등록 결과에 따른 작업

핵심 메소드

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

ChannelHandler

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

전체 메소드

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

ChannelHandlerContext

  • ChannelHandler는 ChannelHandlerContext를 통해
    • 다음 ChannelHandler에게 이벤트를 넘기거나,
    • 동적으로 ChannelPipeline을 변경할 수 있음 - [실습4]

핵심 메소드

Channel               channel()
ChannelPipeline       pipeline()
ChannelFuture         write(Object msg)
ChannelHandlerContext fireChannelActive(Object msg)
ChannelHandlerContext fireChannelRead(Object msg)

ChannelPipeline

  • Channel에 드나드는 inbound / outbound 이벤트를 처리
  • Intercepting Filter 패턴 처리, ChannelHandler 리스트
  • 두번째 시간에 상세 설명

주요 메소드

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

EventLoop

  • 등록된 Channel들의 모든 I/O 작업을 처리
  • 구현체 NioEventLoopGroup를 주로 사용

주요 메소드

boolean                inEventLoop()
<T> Future<T>          submit(Callable<T> task)
<V> Promise<V>         newPromise()
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

Channel관련 인터페이스 전체 구조

예제와 실습용 프로젝트

https://github.com/hatemogi/netty-startup

Git clone

git clone https://github.com/hatemogi/netty-startup

처음에는 실패하는 유닛테스트가 준비돼 있고, 실습문제를 모두 풀면 유닛 테스트가 모두 통과돼야합니다.

Netty 4.1 JavaDoc

첫 예제: DiscardServer

src/nettystartup/h1/discard

  • DiscardServer.java
  • DiscardServerHandler.java

src/nettystartup/h1/discard/DiscardServer.java

public final class DiscardServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new DiscardServerHandler());
            ChannelFuture f = b.bind(8010).sync();
            System.err.println("Ready for 0.0.0.0:8010");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}
						

src/nettystartup/h1/discard/DiscardServerHandler.java

class DiscardServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(C..H..Context ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        try {
            // discard
        } finally {
            buf.release(); // 이 부분은 두번째 시간에 설명합니다.
        }
    }
    @Override
    public void exceptionCaught(C..H..Context ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
						

DiscardServer 테스트

telnet localhost 8010

Trying ::1...
Connected to localhost.
Escape character is '^]'.
helo
test

^]
telnet> close
Connection closed.

Netty와 유닛테스팅

  • EmbeddedChannel을 활용
EmbeddedChannel ch =
    new EmbeddedChannel(new HandlerToBeTested());

EmbeddedChannel

public class EmbeddedChannel extends AbstractChannel {
    public EmbeddedChannel(ChannelHandler... handlers) {}

    public Object readInbound() { ... }
    public Object readOutbound() { ... }
    public boolean writeInbound(Object... msgs) { ... }
    public boolean writeOutbound(Object... msgs) { ... }

    public void checkException() { ... }
}

test/.../h1/discard/DiscardServerHandlerTest.java

public class DiscardServerHandlerTest {
    @Test
    public void discard() {
        String m = "discard test\n";
        EmbeddedChannel ch = new EmbeddedChannel(new DiscardServerHandler());
        ByteBuf in = Unpooled.wrappedBuffer(m.getBytes());
        ch.writeInbound(in);
        ByteBuf r = ch.readOutbound();
        assertThat(r, nullValue());
    }
}
						

첫번째 실습: EchoServer

  • EchoServer
  • EchoServerHandler
  • EchoServerHandlerTest

src/nettystartup/h1/echo/EchoServer.java

public final class EchoServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class);
            // TODO: [실습 1-1] 이 부분을 채워서 EchoServerHandler를 등록합니다
            ChannelFuture f = b.bind(8020).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}
						

src/nettystartup/h1/echo/EchoServerHandler.java

class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // TODO: [실습1-2] 받은대로 응답하는 코드를 한 줄 작성합니다. release는 필요하지 않습니다.
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
						

test/nettystartup/h1/echo/EchoServerHandlerTest.java

public class EchoServerHandlerTest {
    @Test
    public void echo() {
        String m = "echo test\n";
        EmbeddedChannel ch = new EmbeddedChannel(new EchoServerHandler());
        ByteBuf in = Unpooled.copiedBuffer(m, CharsetUtil.UTF_8);
        ch.writeInbound(in);
        ByteBuf r = (ByteBuf)ch.readOutbound();
        releaseLater(r);
        assertThat("응답이 없습니다", r, notNullValue());
        assertThat("참조수는 1이어야 합니다",r.refCnt(), is(1));
        assertThat("수신한 텍스트가 결과로 와야합니다", r.toString(CharsetUtil.UTF_8), equalTo(m));
    }
}
						

실습 정리

  • Netty로 네트워크 서버를 만들어 봤습니다.
  • ChannelHandler의 기초를 익혔습니다.
  • 메모리 모델은 다음 시간에 설명합니다.

참고자료

다음 시간에는...

http://hatemogi.github.io/netty-startup/2.html

0
Netty 시작하기 (1) 고성능 네트워크 애플리케이션 프레임워크 김대현 @hatemogi