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