Language/Java

Netty 프로젝트 시작하기-server

park_juyoung 2018. 12. 16. 16:24

ChatServerInitializer.java 

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
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;
 
public class ChatServerInitializer extends ChannelInitializer<SocketChannel>{
    
    private final SslContext sslCtx;
    
    public ChatServerInitializer(SslContext sslCtx) {
        this.sslCtx=sslCtx;
    }
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //클라이언트 소켓 채널이 생성될 떄 호출.
        
        //Netty 내부에서 할당한 빈 채널 파이프라인 가져오기
        ChannelPipeline pipeline=socketChannel.pipeline();
        
        pipeline.addLast(sslCtx.newHandler(socketChannel.alloc()));
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192,Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());
        //파이프라인에 이벤트 핸들러 등록
        pipeline.addLast(new ChatServerHandler());
        
        
    }
    
 
}
 
cs



채널 인바운드 이벤트

  • 연결 상대가 어떤 동작을 취했을 때 발생하는 이벤트
  • 채널 활성화, 데이터 수신 등
  • Bottom-Up 식으로 동작하기 때문에 가장 먼저 등록한 Handler에서부터 마지막에 등록한 Handler 순서로 동작

이벤트 순서

  1. channelRegistered
    1. 채널이 이벤트루프에 등록되었을 때
    2. 서버는 최초 서버 소켓 채널 등록, 매 연결마다 클라이언트 소켓 채널 등록 이렇게 두번 발생
    3. 클라이언트는 connect() 호출 때마다 발생
  2. channelActive
    1. 채널 입출력 준비 완료
    2. 연결 직후 한번 수행하는 작업에 유용
  3. channelRead
    1. 데이터가 수신되었음
    2. ByteBuf 객체로 전달됨
  4. channelReadComplete
    1. 데이터 수신이 완료되었음
    2. 소켓 채널에 더 이상 읽을 데이터가 없을 때 발생
  5. channelInactive
    1. 채널이 비활성화되었음
    2. 입출력 작업 불가
  6. channelUnregistered
    1. 채널이 이벤트 루프에서 제거되었음
    2. 채널 이벤트 처리 불가


ChatServerHandler.java

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
 
@Sharable //ChannelHandler를 여러 채널간에 안전하게 공유 할수 있음을 나타냄
public class ChatServerHandler extends ChannelInboundHandlerAdapter{
    private static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
 
    
    
    
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //채널 입출력 준비 완료 사용자가 들어왔을때.
        System.out.println("ChannelActive");
        Channel incoming=ctx.channel();
        for(Channel channel:channelGroup) {
            channel.write("[SERVER]-"+incoming.remoteAddress()+"has joined!\n");
        }
        channelGroup.add(incoming);
    }
    
    
 
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //사용자가 나갔을 때 기존 사용자에게 알림.
        System.out.println("ChannelInactive");
        Channel incoming=ctx.channel();
        for(Channel channel:channelGroup) {
            channel.write("[SERVER] - " + incoming.remoteAddress() + "has left!\n");
        }
        channelGroup.remove(incoming);
    }
 
 
 
    /*
     * 메시지가 들어올 때마다 호출되는 함수. 여기서는 수신한 데이터를 모두 처리하기위해 재정의.
     * 
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
 
        String message=null;
        message=(String)msg;
        System.out.println("channelRead of [SERVER]"+message);
        Channel incoming=ctx.channel();
        for(Channel channel:channelGroup) {
            if(channel!=incoming) {
                //메시지 전달.
                channel.writeAndFlush("[" + incoming.remoteAddress() + "]" + message + "\n");
            }
        }
        if("bye".equals(message.toLowerCase())) {
            ctx.close();
        }
    }
    
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
 
    
    
}
 
cs

Netty 부트스트랩

  • 애플리케이션의 각종 동작, 설정 지정 헬퍼

구조

  • 전송 계층 (소켓 모드 및 IO 종류)
  • 이벤트 루프 (싱글 쓰레드, 멀티 쓰레드)
  • 채널 파이프라인 설정
  • 소켓 주소와 포트
  • 소켓 옵션


ChatServer.java

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
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
 
public class ChatServer {
    private final int port;
    
    public ChatServer(int port) {
        super();
        this.port=port;
    }
    
    public static void main(String[] args)throws Exception{
        new ChatServer(5001).run();
    }
    
    public void run() throws Exception{
         SelfSignedCertificate ssc = new SelfSignedCertificate();
            SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
                .build();
 
        EventLoopGroup bossGroup=new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap =new ServerBootstrap();
            //부트스트랩에 쓰레드 등록첫번째는 부모고 두번째는 자식. 부모는 외부에서 들어오는 클라이언트 연결을 받고 자식은 연결된 클라이언트 소켓을 바탕으로 데이터 입출력 및 이벤트 처리를 담당
             bootstrap.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)//부모 쓰레드가 사용할 네트워크 입출력 모드 설정
             .handler(new LoggingHandler(LogLevel.INFO)) //서버 소켓 채널에서 발생한 이벤트에 대한 로그 출력
             .childHandler(new ChatServerInitializer(sslCtx));//자식 쓰레드의 초기화 방법 설정
             
             //서버를 비동기식으로 바인딩후 채널의 closeFuture을 얻고 완료될때까지 현재 스레드를 블록킹.
             bootstrap.bind(port).sync().channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
 
cs


'Language > Java' 카테고리의 다른 글

RxJava란? - 기본 구조  (5) 2019.01.26
JDBC- MariaDB와 Java연동  (0) 2019.01.02
Netty 특징과 아키텍처  (0) 2018.12.16
Netty Codec Framework  (0) 2018.12.15
TCP/IP 소켓 통신이란?  (3) 2018.12.15