앞서 '[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 클라이언트 코드는 위 그림과 같이 동작한다.
댓글