Netty 시작하기 (3)

깔끔한 스레드 모델과 채널퓨처&프로미스

김대현
@hatemogi

Netty의 Promise


public void channelRead0(ChannelHandlerContext ctx, String line) {
    Promise<String> p = ctx.executor().newPromise();

    new Thread(() -> {
        try {
            Thread.sleep(5000);
            p.setSuccess("hello from " + Thread.currentThread().getName());
        } catch (InterruptedException e) { /* ignore */ }
    }).start();

    p.addListener(f ->
        System.out.println("[" + Thread.currentThread().getName() +
            "] listener got: " + f.get())
    );
}
						

대화의 예

# 서버가 보냄 클라이언트
1 HAVE:steve
2 HELO:david
3 NICK hatemogi
4 NICK:david hatemogi
5 SEND 안녕하세요
6 FROM:hatemogi 안녕하세요
7 FROM:steve 반갑습니다

src/nettystartup/h3/ChatServer.java


public final class ChatServer {
  public static void main(String[] args) throws Exception {
    NettyStartupUtil.runServer(8030, new ChannelInitializer<SocketChannel>() {
      @Override
      protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline()
          .addLast(new LineBasedFrameDecoder(1024, true, true))
          .addLast(new StringDecoder(CharsetUtil.UTF_8),
                   new StringEncoder(CharsetUtil.UTF_8))
          .addLast(new ChatMessageCodec(),
                   new LoggingHandler(LogLevel.INFO))
          .addLast(new ChatServerHandler());
      }
    });
  }
}
						
LineBasedDecoder, StringDecoder, StringDecoder는 마지막 시간에 설명

src/nettystartup/h3/ChatMessage.java


public class ChatMessage {
    public final String command;
    public final String nickname;
    public String text;

    public String toString() { ... }
    public static ChatMessage parse(String line) { ... }
    ...
}
						

src/nettystartup/h3/ChatServerHandler.java (1)


public class ChatServerHandler extends SimpleChannelInboundHandler<ChatMessage> {
    private static final ChannelGroup channels =
        new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    public void channelActive(ChannelHandlerContext ctx) {
        helo(ctx.channel());
    }

    public void channelInactive(final ChannelHandlerContext ctx) {
        channels.remove(ctx.channel());
        channels.writeAndFlush(M("LEFT", nickname(ctx)));
        nicknameProvider.release(nickname(ctx));
    }
    ... 다음 슬라이드에 계속 ...
}
						

src/nettystartup/h3/ChatServerHandler.java (2)


public class ChatServerHandler extends ... {
    ...
    public void helo(Channel ch) {
        if (nickname(ch) != null) return; // already done;
        String nick = nicknameProvider.reserve();
        if (nick == null) {
            ch.writeAndFlush(M("ERR", "sorry, no more names for you"))
              .addListener(ChannelFutureListener.CLOSE);
        } else {
            bindNickname(ch, nick);
            channels.forEach(c -> ch.write(M("HAVE", nickname(c))));
            channels.writeAndFlush(M("JOIN", nick));
            channels.add(ch);
            ch.writeAndFlush(M("HELO", nick));
        }
    }
    ...
}
						

src/nettystartup/h3/ChatServerHandler.java (3)


public class ChatServerHandler extends SimpleChannelInboundHandler<ChatMessage> {
  ... 이전 슬라이드에서 이어짐 ...
  protected void channelRead0(ChannelHandlerContext ctx, ChatMessage msg) throws Exception {
    if ("PING".equals(msg.command)) {
      // TODO: [실습3-1] PING 명령어에 대한 응답을 내보냅니다
    } else if ("QUIT".equals(msg.command)) {
      // TODO: [실습3-2] QUIT 명령어를 처리하고 BYE를 응답합니다. 연결도 끊습니다.
    } else if ("SEND".equals(msg.command)) {
      // TODO: [실습3-3] 클라이언트로부터 대화 텍스트가 왔습니다. 모든 채널에 방송합니다.
    } else if ("NICK".equals(msg.command)) {
      changeNickname(ctx, msg);
    } else {
      ctx.write(M("ERR", null, "unknown command -> " + msg.command));
    }
  }
}
						

src/nettystartup/h3/ChatMessageCodec.java

ChatMessageString간 변환 담당

class ChatMessageCodec extends MessageToMessageCodec<String, ChatMessage> {
    @Override
    protected void encode(C..H..Context ctx, ChatMessage msg, List<Object> out) throws Exception {
        out.add(msg.toString() + "\n");
    }

    @Override
    protected void decode(C..H..Context ctx, String line, List<Object> out) throws Exception {
        out.add(ChatMessage.parse(line));
    }
}