네티 서버&클라이언트 프로그래밍 시작
By on December 22, 2019
자바 네트워크 프로그래밍에서 매우 유명한 프레임워크인 Netty 를 이용 해서 서버 & 클라이언트 프로그래밍을 하는 방법을 알아보는 첫번째 포스팅 입니다.
서버 어플리케이션 개발을 하면서 Netty는 알고 있었지만 대부분의 서버 어플리케이션이 Java.Nio API 로 개발이 되어 있어 Netty를 사용해 보지 못하였기 때문에
“네티 인 액션” 도서를 학습하면서 정리 해보겠습니다.
네티란?? 네티는 고성능의 안정적인 서버 & 클라이언트를 개발 할 수 있는 자바 기반의 프레임워크 입니다.
네티는 2009년 부터 시작이 되었고 많은 개발자들이 네티 오픈소스 프로젝트에 참여 하여 발전 하고 있고 많은 프로젝트에서 이미 검증이 되었습니다.
조금 늦었지만 고성능의 서버 & 클라이언트 프로그래밍을 할때 활용 하기 위해 학습하기로 합니다.
네티 서버&클라이언트 예제 프로그램을 작성 하여 어떤 기능들을 구현 했고 제공 하는지 학습 해보겠습니다.
네티를 이용한 서버 & 클라이언트 예제 소스 분석
-
테스트 환경
- JDK 1.8
- Maven
- Eclipse IDE
- Netty-all-4.1.44.final
- 예제소스 : https://github.com/ParkHyeokJin/NettyRepository.git
-
Maven dependency
<!-- https://mvnrepository.com/artifact/io.netty/netty-all --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.44.Final</version> </dependency>
-
서버
1) EchoServerhandler.java
@Sharable //@Sharable 어노테이션은 여러 채널에서 Handler를 공유 할 수 있음을 나타냄. public class EchoServerHandler extends ChannelInboundHandlerAdapter{ /** * 메시지가 들어올때마다 호출되는 메소드 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf) msg; System.out.println( "Server received : " + in.toString(CharsetUtil.UTF_8) ); ctx.write(in); } /** * channelRead 메소드가 처리 완료 되었다는 것을 핸들러에게 통보 하는 메소드 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } /** * 읽기 작업중 오류가 발생 했을 경우 호출 되는 메소드 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
2) EchoServer.java
public class EchoServer { private final int port; public static void main(String[] args) throws InterruptedException { int port = 25; new EchoServer(port).start(); } public EchoServer(int port) { this.port = port; } private void start() throws InterruptedException { final EchoServerHandler serverHandler = new EchoServerHandler(); EventLoopGroup group = new NioEventLoopGroup(); //NIO 처리를 다루는 이벤트 루프 인스턴스 생성 try { ServerBootstrap b = new ServerBootstrap(); //ServerBootstrap 인스턴스 생성. ServerBootstrap은 서버 Channel 을 셋팅 할 수 있는 클래스 b.group(group) .channel(NioServerSocketChannel.class) // Nio 전송 채널을 사용 하도록 셋팅 .localAddress(new InetSocketAddress(port)) // 서버 포트 주소 설정 .childHandler(new ChannelInitializer<SocketChannel>() { // child 이벤트 핸들러를 설정 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(serverHandler); //serverHandler 을 pipeline으로 설정 한다. } }); ChannelFuture f = b.bind().sync(); //서버를 비동기 식으로 바인딩 한다. sync() 는 바인딩이 완료되기를 대기한다. /** * ChannelFuture 는 작업이 완료되면 그 결과에 접근 할 수 있게 해주는 자리 표시자 역활을 하는 인터페이스이다. */ f.channel().closeFuture().sync(); //채널의 CloseFuture를 얻고 완료 될때 까지 현재 스레드를 블로킹한다. } finally { group.shutdownGracefully().sync(); //EventLoopGroup 을 종료 하고 모든 리소스를 해제 한다. } } }
Netty 를 이용한 서버 구현이 완료 되었습니다. 클라이언트 소켓에서 msg 가 수신이 되면 channelRead() 메소드가 호출 되어 메시지를 출력하고
출력된 메시지를 되돌려 주는 동작을 하게 됩니다.Java NIO Api 를 이용한 서버 코드를 보고 네티에서 어떤 기능들을 구현 하고 제공 하고 있는지 살펴 보겠습니다.
public class nioServer { public static void main(String args[]) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress("localhost", 3000)); serverSocket.configureBlocking(false); serverSocket.register(selector, SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate(256); while (true) { selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectedKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); //채널 accept 처리 //채널 read 처리 //채널 write 처리 //...... iter.remove(); } } } }
많은 코드가 생략 되었지만 Java NIO API를 사용 하면 위 처럼 channel, selector, buffer 처리를 직접 해야 합니다. 하지만 네티를 이용하여 작성을 하게 되면
이런 부분들을 네티 프레임워크에서 관리를 해주기 때문에 사용자는 EchoServerHandler 를 이용한 비즈니스 로직 부분만 작성을 하면 될 것 입니다. -
클라이언트
1) EchoClientHandler.java
@Sharable //@Sharable 어노테이션은 여러 채널에서 Handler를 공유 할 수 있음을 나타냄. public class EchoClientHandler extends ChannelInboundHandlerAdapter{ /** * 서버로 연결이 만들어지면 channelActive 메소드가 호출 된다. */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); } /** * 서버에서 메시지를 수신 하면 channelRead0 메소드가 호출 된다. */ @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client received : " + msg.toString(CharsetUtil.UTF_8)); } /** * 예외 발생시 exceptionCaught 메소드가 호출된다. */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
2) EchoClient.java
public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public static void main(String[] args) throws InterruptedException { String host = "localhost"; int port = 25; new EchoClient(host, port).start(); } private void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(); //NIO 처리를 다루는 이벤트 루프 인스턴스 생성 try { Bootstrap b = new Bootstrap(); //클라이언트 를 셋팅 할수 있는 인스턴스 bootStrap 생성 b.group(group) .channel(NioSocketChannel.class) // Nio 전송 채널을 사용 하도록 셋팅 .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); //EchoClientHandler 을 pipeline으로 설정 한다. } }); ChannelFuture f = b.bind().sync(); //서버를 비동기 식으로 바인딩 한다. sync() 는 바인딩이 완료되기를 대기한다. /** * ChannelFuture 는 작업이 완료되면 그 결과에 접근 할 수 있게 해주는 자리 표시자 역활을 하는 인터페이스 */ f.channel().closeFuture().sync(); //채널의 CloseFuture를 얻고 완료 될때 까지 현재 스레드를 블로킹한다. }finally { group.shutdownGracefully().sync(); //EventLoopGroup 을 종료 하고 모든 리소스를 해제 한다. } } }
네티를 이용한 클라이언트도 서버와 동일하게 네트워크 부분은 네티에게 맏기고 EchoClientHandler 를 이용하여 메시지를 보내고 받는 부분을 어떻게 처리를 할지만
작성을 하면 될 것입니다.
정리
네티를 이용한 강력한 서버 & 클라이언트 를 개발 하기 위해 학습을 시작 하였습니다. 네티 프레임워크를 내것으로 만들기 위해 계속 해서 학습 하기로 합니다. 다음 포스팅에서는 네티의 channel, eventLoop, channelFuture 에 대해서 정리 해보겠습니다.