반응형

기존에 운영중이던 프로젝트를 고도화 시키면서 특정 서비스단에서 전달된 값을 DB에 저장하고 저장된 정보를 바로 특정 소켓 서버로 전달을 해줘야 하는 기능을 작성해야 했습니다.

이미 tomcat으로 was서버를 운영중인데 소켓 서버를 운영하는건 여러가지로 문제가 있을것이라 판단하여 별도로 빼려고 했지만, 실시간성이 좀 더 중요시 되었고, 한 곳이 망가져도 바로 재부팅으로 올려도 괜찮다는 확인을 받아 바로 소켓처리를 진행해보았습니다.

 

먼저 단순 소켓서버만 올린다면 굳이 netty까진 쓰지 않아도 됩니다. io.Socket만으로도 충분히 훌륭하게 작성 할 수 있었지만 몇몇 요구사항들을 좀 더 쉽게 구현하기 위해 netty를 공부해보고 적용해보았던 소스를 공유하고자 합니다.

 

아래는 실제로 프로젝트에 적용해본 Socket Server, Socket Client 소스들입니다.

 

Netty Server

netty사용을 위해 메이븐 추가를 먼저 해줍니다.

 

pom.xml

<!-- netty -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.0.33.Final</version>
</dependency>

먼저 Controller에@PostConstruct어노테이션을 활용하여 was 서비스 구동시netty가 동작하도록 별도의 쓰레드를 돌렸습니다.

 

NettyController.java

@Controller
public class NettyController {

    @PostConstruct
    private void start() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    new NettySocketServer(5010).run();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }

    @PreDestroy
    private void destory() {

    }
}

서비스가 구동되면 start()가 동작되면서 별도로 작성한 NettySocketServer가 동작합니다.

내부에 5010은 임시로 지정한 소켓 포트입니다.

다음은 netty 소켓 서버를 확인해보겠습니다.

 

NettySocketServer.java

public class NettySocketServer {
    private int port;

    public NettySocketServer(int port) {
        this.port = port;
    }

    public void run() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .childHandler(new ChannelInitializer<SocketChannel>() {
             @Override
             public void initChannel(SocketChannel ch) throws Exception {
                 ChannelPipeline pipeline = ch.pipeline();
                 //handler setting
                 pipeline.addLast(new NettySocketServerHandler());
             }
         })
         .option(ChannelOption.SO_BACKLOG, 128)
         .childOption(ChannelOption.SO_KEEPALIVE, true);


        try {
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

생성자를 통해 port값을 처리하고 run메소드를 통해 서버가 동작시 응답을 처리할 핸들러를 연결합니다.

다음은 핸들러입니다.

 

NettySocketServerHandler.java

@Sharable
public class NettySocketServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String readMessage = ((ByteBuf) msg).toString(Charset.forName("UTF8"));
        ctx.write(msg);
        System.out.println("message from received: " + readMessage);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

@Sharable 어노테이션을 활용하여 안전하게 데이터를 처리하도록 변경하였습니다.

 

테스트결과

톰캣을 구동하고 서비스를 올립니다.

정상적으로 netty가 동작하는지 확인하기 위해 테스트툴로 5010포트를 통해 연결하고 메시지가 출력되는지 확인하겠습니다.

 

 

Netty Client

다음은 client를 구성해보겠습니다.

 

NettySocketClient.java

public class NettySocketClient {
	
	private String msg;
	private String host;
	private int port;
	
	public NettySocketClient(String host, int port, String msg){
		this.msg = msg;
		this.host = host;
		this.port = port;
	}
	
	public void run() {
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		
		try {
			Bootstrap b = new Bootstrap();
			b.group(workerGroup);
			b.channel(NioSocketChannel.class);
			b.option(ChannelOption.SO_KEEPALIVE, true);
			b.handler(new ChannelInitializer<SocketChannel>() {
	            @Override
	            public void initChannel(SocketChannel ch) throws Exception {
	                ch.pipeline().addLast(new NettySocketClientHandler(msg));
	            }
	        });
	        
	        //client connect
			try {
				ChannelFuture f = b.connect(host, port).sync();
				f.channel().closeFuture().sync();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}finally {
			workerGroup.shutdownGracefully();
		}
	}
}

생성자를 통해 연결할 host, ip, 발신할 message를 담는 문자열 msg값을 처리합니다.

 

해당 값들을 통해 run()메소드가 동작하면 연결정보로 연결이 됩니다.

다음으로 클라이언트 핸들러를 보겠습니다.

 

NettySocketClientHandler.java

public class NettySocketClientHandler extends ChannelInboundHandlerAdapter {

	private String msg;
	
	public NettySocketClientHandler(String msg) {
		this.msg = msg;
	}
	
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ByteBuf messageBuffer = Unpooled.buffer();
		messageBuffer.writeBytes(msg.getBytes());
	
		ctx.writeAndFlush(messageBuffer);
	
		System.out.println("send message {" + msg + "}");
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		System.out.println("receive message {" + ((ByteBuf) msg).toString(Charset.defaultCharset()) +"}");
	}
	
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.close();
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println(cause);
		ctx.close();
	}
}

 

클라이언트 테스트

서버툴에서 4564포트를 열어두고 연결하여 test msg send!!!라는 문구를 발송하였습니다.

캡처와 같이 서버에 정상적으로 메시지가 수신된 것을 볼 수 있습니다.

반응형