Java/Netty

[Netwok/Netty]07. Netty 구축을 위한 beans(3) - ChannelPipleline

양승길 2017. 10. 22. 17:18

[Network/Netty]07. Netty 구축을 위한 beans(3) - ChannelPipeline

(출처 : http://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html
           
http://ash84.net/2013/03/08/exs4j-netty-channelpipeline--ec-97-90--eb-8c-80-ed-95-9c--ec-9d-b4-ed-95-b4/)


1. ChannelPipeline
    
public abstract interface ChannelPipline extends Iterable<Map.Entry<String, ChannelHandler>>
    ChannelHandler들을 모아놓은 일련의 목록.
    
이벤트가 발생됨에 따라 내부에 등록된 ChannelHandler들을 동작하기 위한 관리자라고 볼 수 있다.


2. 동작원리

    • Preview
      입출력 이벤트는 ChannelInoundHander, ChannelOutboundHandler에 의하여 다루어진다.
      또한 그 이벤트는 ChannelHandlerContext에서 정의된 이벤트 전달 method를 호출하여 근접한 핸들러에 전달된다.
    • Inbound event
      외부로부터 무언가를 받았다는 의미인 만큼 inbound handler의 최초 시점은 Netty의 내부에 있는 thread이다.
      부르기 편하게 그냥 네티라 하겠음. 네티가 외부에서 받은 어떤 데이터를 읽었을 것이다.(SocketChannel.read(...))
      pipeline에서 inbound handler를 등록했다면, 등록된 순서대로 데이터를 처리하고 다른 inbound handler에 전달한다.
      등록된 모든 inbound event가 끝나게 되면 inbound event는 종료되고 log에 남게된다.
    • Outbound event
      최초 시점은 가장 마지막에 등록된 outbound handler다.
      자신이 외부에 무언가를 보낸다는 의미이기 때문에,
      inbound event와 달리 등록된 outbound event를 모두 거치고
      네티에서 마무리를 짓게된다.(SocketChannel.write(...))

      두 이벤트를 등록한 아래의 예제를 볼 때, 진행되는 순서는 125543이다.
      (5번의 경우 Inbound, Outbound를 모두 상속받은 bean이라 본다.)
1
2
3
4
5
6
 ChannelPipeline p = ...;
 p.addLast("1"new InboundHandlerA());
 p.addLast("2"new InboundHandlerB());
 p.addLast("3"new OutboundHandlerA());
 p.addLast("4"new OutboundHandlerB());
 p.addLast("5"new InboundOutboundHandlerX());
cs


3. 실제 서버 구현
    아래 예제에서, 
다른 내용은 다 무시하고 실제 pipeline에 등록된 내용에 집중한다.
    ByteArrayDecoder, ByteArrayEncoder는 네티에서 제공해주는 bean이고
    요청과 응답에 대한 데이터들을 
byte array로 암복호화 해주는 역할을 담당한다.

    
그러니까 등록된 handler의 로직을 대략적으로 본다면

    1. 요청 데이터 유입 - Netty
    2. byte array로 복호화 - ByteArrayDecoder
      (참고로 들어오는 모든 데이터들은 ByteBuf로 추상화 되어있음.)
    3. 필자가 작성한 EchoServerHandler 로직 수행. SimpleChannelInoundHandler를 상속받음.
    4. byte array로 암호화 - ByteArrayEncoder
    5. 요청에 대한 응답 - Netty
    6. handler로직 수행 끝.


 4. Why SimpleChannelInoundHandler?
       설명하기 앞서 해당 bean은 ChannelInboundHandler를 구현받았고 가장 추상적인 bean은 ChannelHandler다.
       
ChannelInboundHandler에서 override된 method들을 통해 이벤트들을 처리하게 될 것이다.


       주의깊게 볼 method는 channelRead0다.
       다른 bean에서도 있을지 모르겠지만 channelRead0는 SimpleChannelInoundHandler에서만 작성되어있다.
       
channelRead의 내용을 분석하면
       SimpleChannelInoundHandler의 channelRead는 
       해당 매개변수 중에 요청받은 데이터인 Object msg를 SimpleChannelInoundHandler에서 상속하였을때
       지정한 Generic과 일치하는가를 따져본 후에 
channelRead0을 진행한다.
       그렇지 아니하면 오류가 발생되어 log에 WARN이 출력된다.
       
그러나 SimpleChannelInoundHandler의 channelRead0는 요청받은 데이터의 유형이
       
애초부터 지정한 Generic을 매개변수의 유형으로 두고있다.
       따
라서 channelRead에서 진행되는 일련의 작업들을 할 필요가 없다.

        아무튼 SimpleChannelInoundHandler는 요청받은 데이터인 msg에 대한 유형 검사 작업을 할 필요가 없기 때문에
        
읽을때의 작업을 수월하게 진행 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public static void main(String[] args) {
        
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try{
            // 서버 부트스트랩 생성
            ServerBootstrap b = new ServerBootstrap();
            
            // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 서버 회선 설정 시작. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
        
            // 이벤트루프 설정
            b.group(bossGroup, workerGroup)
            // 채널입출력방식 설정
            .channel(NioServerSocketChannel.class)
            
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 서버부트스트랩의 초기화가 진행될때. 시작 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // 최초 클라이언트로부터의 연결이 들어오는 큐에 대하여 100개까지 지정.
            .option(ChannelOption.SO_BACKLOG, 100)
            // 이벤트 핸들러 설정 - 로그핸들러로 지정.
            .handler(new LoggingHandler(LogLevel.INFO))
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 서버부트스트랩의 초기화가 진행될때. 끄읕 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            
            
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 클라이언트로부터 연결이 완료된 후. 시작 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            // 새로 연결된 채널과 이벤트 핸들러 설정
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel paramC) throws Exception {
                    paramC.pipeline().addLast("1"new ByteArrayDecoder());
                    paramC.pipeline().addLast("2"new EchoServerHandler());
                    paramC.pipeline().addLast("3"new ByteArrayEncoder());
                }
            })
            // 세션 해제 여부 감지.
            .childOption(ChannelOption.SO_KEEPALIVE, true);
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 클라이언트로부터 연결이 완료된 후. 끄읕 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            
            // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 서버 회선 설정 끄읕. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
            
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
            
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
cs