본문 바로가기
Old Posts/Java

[Java] 간단한 Netty Client 예제 코드

by A6K 2021. 5. 16.

앞서 '[Java] Netty 프레임워크 소개'에서 Netty 프레임워크에 대해서 간단하게 알아봤다. 백문이 불여일견이라고 실제로 동작하는 Netty 애플리케이션 코드를 보고 눈으로 확인하는게 더 중요할 수도 있다.

이번 포스트에서는 지난번 포스트에서 구현한 서버에 문자열을 전송하는 클라이언트를 Netty 프레임워크로 구현해보겠다. (관련글 : [Java] 간단한 Netty Server 예제)

우선 메인 클래스다

package SimpleNettyServer;

import java.net.InetSocketAddress;
import java.util.Scanner;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;

public class Client {
    private static final int SERVER_PORT = 11011;
    private final String host;
    private final int port;

    private Channel serverChannel;
    private EventLoopGroup eventLoopGroup;

    public Client(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void connect() throws InterruptedException {
        eventLoopGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("client"));

        Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup);

        bootstrap.channel(NioSocketChannel.class);
        bootstrap.remoteAddress(new InetSocketAddress(host, port));
        bootstrap.handler(new ClientInitializer());

        serverChannel = bootstrap.connect().sync().channel();
    }

    private void start() throws InterruptedException {
        Scanner scanner = new Scanner(System.in);

        String message;
        ChannelFuture future;

        while(true) {
            // 사용자 입력
            message = scanner.nextLine();

            // Server로 전송
            future = serverChannel.writeAndFlush(message.concat("\n"));

            if("quit".equals(message)){
                serverChannel.closeFuture().sync();
                break;
            }
        }

        // 종료되기 전 모든 메시지가 flush 될때까지 기다림
        if(future != null){
            future.sync();
        }
    }

    public void close() {
        eventLoopGroup.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {
        Client client = new Client("127.0.0.1", SERVER_PORT);

        try {
            client.connect();
            client.start();
        } finally {
            client.close();
        }
    }

}

채널 파이프라인을 생성해주는 Initializer 코드도 작성한다.

package SimpleNettyServer;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class ClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new LineBasedFrameDecoder(65536));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new ClientHandler());
    }
}

서버로부터 응답을 받아 화면에 출력해줄 handler도 구현한다.

package SimpleNettyServer;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class ClientHandler extends SimpleChannelInboundHandler {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        System.out.println((String)msg);
    }

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

Netty TCP 클라이언트

Client 생성도 서버와 비슷하다

  • EventLoopGroup 생성
  • Bootstrap 생성 및 설정
  • ChannelInitializer 생성
  • 클라이언트 시작

EventLoopGroup 생성

eventLoopGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("client"));

서버와 마찬가지로 NIO를 사용하기 위해 EventLoopGroup을 생성한다. 좀 다른 점은 클라이언트라서 서버소켓에 listen하기 위한 boss 그룹은 없다는 점이다.

Bootstrap 생성 및 설정

bootstrap.channel(NioSocketChannel.class);
bootstrap.remoteAddress(new InetSocketAddress(host, port));
bootstrap.handler(new ClientInitializer());

클라이언트 생성을 위해서 마찬가지로 bootstrap 설정들을 해준다. remoteAddress() 메소드로 접속할 서버 소켓의 주소와 포트를 입력해준다. handler() 메소드로 ClientInitializer()를 넘겨준다.

serverChannel = bootstrap.connect().sync().channel();

connect() 메소드로 서버 소켓에 연결을 하고 sync() 메소드로 기다린다.

ChannelInitializer 생성

@Override
protected void initChannel(SocketChannel ch) {
	ChannelPipeline pipeline = ch.pipeline();

	pipeline.addLast(new LineBasedFrameDecoder(65536));
	pipeline.addLast(new StringDecoder());
	pipeline.addLast(new StringEncoder());
	pipeline.addLast(new ClientHandler());
}

서버와 비슷하게 채널 파이프라인을 생성한다.

Client 시작

ChannelFuture channelFuture = clientBootstrap.connect().sync(); 종료시에는 channelFuture.channel().closeFuture().sync();

ClientHandler

@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
	System.out.println((String)msg);
}

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

서버로 문자열을 던지면 서버는 문자를 좀 더 붙여서 클라이언트로 던져준다. 클라이언트는 서버로부터 문자열을 받아 channelRead0() 메소드를 호출한다. 이 메소드는 결국 받은 문자열을 화면에 출력한다.

exceptionCaught() 메소드는 예외가 발생했을 때 호출된다.

이 Netty 클라이언트 코드는 위 그림과 같이 동작한다.

댓글