티스토리 뷰

JAVA/Netty

Echo Server

Waterbottle 2017. 9. 26. 16:53


에코서버란?


간단하게 클라이언트가 요청을 보내면 적당한 응답을 보내주는 서버이다. 즉, 요청 메세지를 메아리로 보내거나 미리 정의된 메세지로 응답을 하게 된다.



목표 : 에코서버와 클라이언트의 양방향 통신 테스트.



EchoServer.java

import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; public class EchoServer { // 서버 소켓 포트 번호를 지정합니다. private static final int PORT = 6678; public static void main(String[] args) { //EvenotLoopGroup에서 새로운 객체를 생성할 떄 NioEventLoopGroup 객체를 생성함. EventLoopGroup parentGroup = new NioEventLoopGroup(1); //parentGroup 클라이언트의 연결을 수락하는 부모 스레드 그룹 //NioEventLoopGroup(인수) 스레드 그룹 내에서 생성할 최대 스레드 수가 1이므로 단일 스레드. EventLoopGroup childGroup = new NioEventLoopGroup(); //연결된 클라이언트 소켓으로부터 데이터 I/O및 이벤트처리를 담당하는 자식 스레드 그룹. //생성자에 인수가 없으므로 CPU 코어 수에 따른 스레드의 수가 설정됨. try {//서버 부트스르탭 생성. //부트스트랩 객체 생성. ServerBootstrap sb = new ServerBootstrap(); //해당 클래스를 사용하면 서버에서 channel을 직접 세팅할 수 있다. sb.group(parentGroup, childGroup); //해당 스레드 그룹 초기화. sb.channel(NioServerSocketChannel.class); //상세한 channel 구현을 위해 옵션을 지정. // sb.option(ChannelOption.SO_BACKLOG, 100); // sb.handler(new LoggingHandler(LogLevel.INFO)); sb.childHandler(new ChannelInitializer

() { @Override protected void initChannel(SocketChannel arg0) throws Exception { ChannelPipeline cp = arg0.pipeline(); cp.addLast(new EchoServerHandler()); } }); ChannelFuture cf = sb.bind(PORT).sync(); cf.channel().closeFuture().sync(); } catch ( Exception e) { e.printStackTrace(); } finally { parentGroup.shutdownGracefully(); childGroup.shutdownGracefully(); } } }


1. Window기준,  Non 블로킹 소켓 모드와 블로킹 소켓 모드의 공통 코드.



EventLoopGroup parentGroup = new NioEventLoopGroup(1); --> 최대 생성 스레드 수가 1.

EventLoopGroup childGroup = new NioEventLoopGroup(); 

 


NioEventLoop : I/O 동작을 다루는 멀티스레드 이벤트 루프.(Netty는 다양한 루프를 제공)


변수 parentGroup : incomming connection을 액세스한다.

변수 childGroup : 액세스한 connection의 traffic을 처리한다.



EchoServerHandler



import io.netty.channel.ChannelHandlerContext; import java.nio.charset.Charset; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelInboundHandlerAdapter; public class EchoServerHandler extends ChannelInboundHandlerAdapter{ //1. 입력된 데이터를 처리하는 이벤트 핸들러 상속 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { //2. 데이터 수신 이벤트 처리 메서드.

//클라이언트로부터 데이터의 수신이 이루어졌을때 Netty가 자동으로 호출하는 이벤트 메소드 String readMessage = ((ByteBuf) msg).toString(Charset.defaultCharset()); //3. 수신된 데이터를 가지고 있는 네티의 바이트 버퍼 객체로 부터 문자열 객체를 읽어온다. System.out.println("수신한 문자열 ["+readMessage +"]"); ctx.write(msg); //4.ctx는 채널 파이프라인에 대한 이벤트를 처리한다. 여기서는 서버에 연결된 클라이언트 채널로 입력받은 데이터를 그대로 전송한다. } @Override public void channelReadComplete(ChannelHandlerContext ctx){ //6. channelRead 이벤트의 처리가 완료된 후 자동으로 수행되는 이벤트 메서드 ctx.flush(); //7. 채널 파이프 라인에 저장된 버퍼를 전송 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }


telnet 접속

cmd 창에 : telnet localhost 6678 접속.








EchoServer에서 소켓을 닫아두며 대기를 하다가 Client에서 접속을 하고 "Hello,Netty" 라는 문자열을 전송하게 되면 소켓 통신을 하게 되고. 클라이언트에서 보낸 데이터를 서버 콘솔창으로 출력을 한 후 소켓을 닫고 연결된 소켓을 종료한다.


EchoClient.java



import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class EchoClient { // 접속할 포트를 정의합니다. private static final int PORT = 6678; public static void main(String[] args) { // EventLoopGroup에서 새로운 객체를 생성할 때 NioEventLoopGroup 객체를 생성한다. EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group); //1. EventLoopGroup 설정(서버랑 다르게 1개만 사용 - 연결된 채널이 하나만 존재) b.channel(NioSocketChannel.class);//2. 채널의 종류 설정(NIO 소켓 채널로 설정) b.handler(new ChannelInitializer

() {//3.채널 파이프라인 설정에 일반 소캣 채널 클래스인 SkcoetChannel 설정. @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new EchoClientHandler()); } }); ChannelFuture f = b.connect("localhost", PORT).sync(); f.channel().closeFuture().sync(); } catch ( Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } }



EchoClientHandler.java



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;
 
public class EchoClientHandler extends ChannelInboundHandlerAdapter  {
  @Override
  public void channelActive(ChannelHandlerContext ctx){
    // 소켓 채널이 최초 활성화 되었을때 실행
     
    String sendMessage = "Hello, Netty!";
     
    ByteBuf messageBuffer = Unpooled.buffer();
    messageBuffer.writeBytes(sendMessage.getBytes());
     
    StringBuilder builder = new StringBuilder();
    builder.append("전송한 문자열 [");
    builder.append(sendMessage);
    builder.append("]");
     
    System.out.println(builder.toString());
    ctx.writeAndFlush(messageBuffer);
    //2.writeAndFlush()은 내부적으로 기록과 전송의 두가지 메서드 호출( write(). flush()
    //2.1 write() : 채널에 데이터를 기록
    //2.2 flush() : 채널에 기록된 데이터를 서버로 전송
     
  }
 
  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg){
    //3.서버로부터 수신된 데이터가 있을 때 호출되는 메서드
     
    String readMessage = ((ByteBuf)msg).toString(Charset.defaultCharset());
    //4.서버로 부터 수신된 데이터가 저장된 msg 객체에서 문자열 데이터 추출
     
    StringBuilder builder = new StringBuilder();
    builder.append("수신한 문자열 [");
    builder.append(readMessage);
    builder.append("]");
     
    System.out.println(builder.toString());
     
  }
   
  @Override
  public void channelReadComplete(ChannelHandlerContext ctx){
    //5.수신된 데이터를 모두 읽었을때 호출되는 이벤트 메서드
    ctx.close();//6.서버와 연결된 채널을 닫음
    //6.1 이후 데이터 송수신 채널은 닫히게 되고 클라이언트 프로그램은 종료됨
  }
   
  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
    cause.printStackTrace();
    ctx.close();
  }
 
   
 
}





Comments