본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Java(자바)/Java 기본

Java netty Sokect server (netty를 활용한 간단한 소켓 서버)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 9. 21.

자바에서 ServerSocket을 활용하여 일반 tcp서버를 구현합니다.

마찬가지로 netty라는 훌륭한 서버 프레임워크를 사용하면 조금 더 손쉽게 구현 할 수 있습니다.

라이브러리를 받습니다.

* maven 기준

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.52.Final</version>
</dependency>

 

* gradle 기준

// https://mvnrepository.com/artifact/io.netty/netty-all
compile group: 'io.netty', name: 'netty-all', version: '4.1.52.Final'

 

netty에서 소켓서버를 구현해야되는 방식을 간단하게 요약하여 보았습니다.

1. 서버 객체 생성

2. 채널 그룹 추가

3. 설정값 추가(ip주소, 포트 등)

4. 채널 이벤트에 대한 설정 추가

5. 서버구동

 

요청과 응답에 대한 처리를 ChannelInboundHandlerAdapter 클래스를 통해서 자유롭게 구현할 수 있습니다.

먼저 콜백행위를 정의할 인터페이스를 만들어 주었습니다.

* 이름 : MyCallBack

import io.netty.channel.ChannelHandlerContext;

public interface MyCallBack {
	
    public void read(String read);  //데이터를 수신하면 동작시킬 메소드

    public String write(String msg);  //사용자가 데이터를 조립할 메소드(msg는 받은 메시지)

    public void afterClose(ChannelHandlerContext ctx);  //종료시 동작시킬 메소드
}

 

MyCallBack이라는 인터페이스를 만들었습니다.

해당 인터페이스는 "핸들러" 라는 이름으로 불리우는 클래스에서 사용할 예정입니다.

채널에 대한 이벤트를 처리할 "핸들러"를 MyServerHandler라는 이름으로 만들었습니다.

* 이름 : MyServerHandler

import java.nio.charset.Charset;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelHandler.Sharable;

@Sharable  //중요!
public class MyServerHandler extends ChannelInboundHandlerAdapter {
	
    private static final Charset CHARSET = Charset.forName("EUC_KR");
	
    private MyCallBack writer;
	
    public MyServerHandler(MyCallBack write){
        this.writer = write;
    }

    //요청에 따른 읽기, 쓰기 이벤트 분기(인터페이스가 구현되어있지 않으면 에코)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf inBuffer = (ByteBuf) msg;
        String received = inBuffer.toString(CHARSET);
        if(writer == null){
            System.out.println("Server received : " + received);
            ctx.write(Unpooled.copiedBuffer("echo : "+received, CHARSET));
        } else {
            writer.read(received);
            String arg = writer.write(received);       	
            ctx.write(Unpooled.copiedBuffer(arg.toCharArray(), CHARSET));
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
    
    //종료 이벤트
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        if(writer != null){
            writer.afterClose(ctx);
        }
        super.channelUnregistered(ctx);
    } 
    
}

 

MyCallBack클래스를 생성자로 넣어서 동작하게 해 주었습니다.

이제 해당 "핸들러"를 사용할 서버를 작성하였습니다.

* 이름 : App

import java.net.InetSocketAddress;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;

public class App {
    private static final String HOST = "0.0.0.0";
    private static final int PORT = 12000;  //서버 포트 번호
    private static final int READ_TIME_OUT = 60;  //읽기 타임아웃 설정(클라 -> 서버)
    private static final int WRITE_TIME_OUT = 30; //쓰기 타임아웃 설정(서버 -> 클라)

    public static void main( String[] args ) {
        App app = new App();
        MyServerHandler handler = app.makeCallbackToDo();
        app.init(handler);
    }
    
    public MyServerHandler makeCallbackToDo(){
        return new MyServerHandler(new MyCallBack() {
            public void read(String read) {    //요청에 따른 처리
                System.out.println("요청들어온 메시지 입니다 : "+ read);
            }
            public String write(String msg) {   //응답할 내용
                String result = "~~보낼메시지 입니다.~~\n";
                return result;
            }			
            public void afterClose(ChannelHandlerContext ctx) {  //커넥션 끊기면 할 내용
                System.out.println("커넥션이 끊기면 동작하는 메소드 입니다.");
            }
        });	
    }

    public void init(final MyServerHandler handler){
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(group);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.localAddress(new InetSocketAddress(HOST, PORT));
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast("ReadTimeoutHandler", new ReadTimeoutHandler(READ_TIME_OUT));  
                    socketChannel.pipeline().addLast("WriteTimeoutHandler", new WriteTimeoutHandler(WRITE_TIME_OUT));  
                    socketChannel.pipeline().addLast("myHandler", handler);       
                }
            })
            .option(ChannelOption.SO_BACKLOG, 128)  //동시 접속 수
            .childOption(ChannelOption.SO_KEEPALIVE, true);  //패킷여부
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            channelFuture.channel().closeFuture().sync();
        } catch(Exception e){
            e.printStackTrace();
        } finally {
            try { group.shutdownGracefully().sync(); } catch (InterruptedException e) { e.printStackTrace(); }
        }        	
    }
     
}

 

내용이 거의다 직관적입니다.

여기서 핵심은 MyServerHandler 클래스를 잘 구현하여 데이터에 대한 행위를 지정하는 것 입니다.

위 클래스를 구동하여 보았습니다.

잘 날라가네요..

 

그리 어렵지 않게 간단하게 구현해본 것 같습니다.

이상으로 netty를 활용한 간단한 소켓 서버 구성에 대해 살펴보았습니다.

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글