여기서는 데이터를 수신 받아서 어떻게 핸들링 하는지 보면 될것 같다.
1. Visual Studio Code로 java project 하나 만들고
2. Reference Library 에 3개 추가 하자
3. EchoServer.java
특별한건 없다. 이름만 바뀌었을 뿐..
그거외에 logger를 사용하기위한 설정외엔 특별한게 없다. logger를 각 단계별로 넣어서 어떤 이벤트가 발생하는지 볼수있게 했다.
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class EchServer {
private static final Logger logger = LogManager.getLogger(EchServer.class.getName());
public static void main(String[] args) throws Exception {
Configurator.setLevel(EchServer.class.getName(), Level.ALL);
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
logger.info("고객 들어옴 : initChannel..");
ChannelPipeline p = ch.pipeline();
p.addLast(new EchoServerHandler());
}
});
logger.info("편의점 여는 중 ..");
ChannelFuture f = b.bind(8009).sync();
logger.info("편의점 열었음 ..");
f.channel().closeFuture().sync();
logger.info("편의점 닫는 중 ..");
} catch (Exception e) {
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
logger.info("편의점 닫음 ..");
}
}
}
4. EchoServerHandler.java
여기도 특별한건 없고, channelActive, channelInactive, channelRead 만 남기고 나머진 삭제했다
그거외에 생성자에 log4j 2 로그관련 초기화 코드가 들어가 있다
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LogManager.getLogger(EchoServerHandler.class.getName());
public EchoServerHandler() {
Configurator.setLevel(EchoServerHandler.class.getName(), Level.ALL);
logger.info("DiscardServerHandler init..");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("channelActive..");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("channelInactive..");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("channelRead..");
}
}
3. 서버를 돌려보자
main 함수 위에 보면 Run | Debug 중 아무거나 하나 누르면 된다
돌려보면 로그는 아래 처럼 나온다.
14:36:55.989 [main] INFO EchServer - 편의점 여는 중 ..
14:36:56.042 [main] INFO EchServer - 편의점 열었음 ..
4. 텔넷으로 접속해보자
텔넷 접속했을때
[flowx@dev:~/doc/study/netty/EchoServer] telnet localhost 8009
Trying ::1...
Connected to localhost.
Escape character is '^]'.
서버 로그가 나온다. 아래서 정의한 이벤트 중 3개만 사용했다.
2021.12.27 - [프로그래밍/Netty 기초] - 05. channeInboundAdapter 이벤트 순서
14:40:33.574 [nioEventLoopGroup-3-1] INFO EchServer - 고객 들어옴 : initChannel..
14:40:33.583 [nioEventLoopGroup-3-1] INFO EchoServerHandler - DiscardServerHandler init..
14:40:33.583 [nioEventLoopGroup-3-1] INFO EchoServerHandler - channelActive..
5. Echo를 구현해 보자
echo라는게 손님이 말하면 그대로 돌려주는 내용이라, channelRead에서 구현하면 될듯하다
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.channel().writeAndFlush(msg); // msg 받아서 다시 전달한다
logger.info("channelRead..{}", msg); // 메시지를 찍어보자
}
Telnet 창에서 입력하고 Enter치면 음... 메시지가 안나오고 PooledUnsafeDirectByteBuf(freed) 이렇게만 나오넹
[main] INFO EchServer - 편의점 여는 중 ..
[main] INFO EchServer - 편의점 열었음 ..
[nioEventLoopGroup-3-1] INFO EchServer - 고객 들어옴 : initChannel..
[nioEventLoopGroup-3-1] INFO EchoServerHandler - DiscardServerHandler init..
[nioEventLoopGroup-3-1] INFO EchoServerHandler - channelActive..
[nioEventLoopGroup-3-1] INFO EchoServerHandler - channelRead..PooledUnsafeDirectByteBuf(freed)
코드를 수정해보자. logger 찍을때 (String)으로 형변환
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.channel().writeAndFlush(msg);
logger.info("channelRead..{}", (String) msg); // String으로 형변환
}
다서 돌려보면 아무 결과도 나오지 않는다.
EchoServerHandler.java에다가 ChannelInboundAdapter 이벤트 중 exceptionCaught 이벤트 추가하고 다시 돌려보자
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.info("exceptionCaught..{}", cause.getMessage());
}
음. 형변환을 할수가 없다고 하는군..
[nioEventLoopGroup-3-1] INFO EchoServerHandler - exceptionCaught..io.netty.buffer.PooledUnsafeDirectByteBuf cannot be cast to java.lang.String
msg를 ByteBuf로 변환해서 toString() 해서 볼수 있다
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String str = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
logger.info("channelRead..{}", str);
}
VSCode 개발하는 모습을 잠시..
왼쪽에는 EchoServer.java를 뛰우고, 오른쪽에는 EchoServerHandler.java를 띄워놨다.
그리고 왼쪽하단에는 EchoServer.java를 실행한 TERMINAL, 오른쪽 하단에는 서버접속을 위한 TERMINAL 이다
순서는 아래처럼 하면된다. 난 이게 편하다 ㅎㅎ
1) EchoServer.java 실행하면 TERMINAL 창이 열림
2) VS Code 우측 하단에 책모양 클릭하면 오른쪽으로 TERMINAL 이 열림
아래 부분은 그냥 Skip 해도 된다. 개 삽질의 결과
바꿔보자.
여기서 ByteBuf라는 데이터 타입이 나오는데, 이것은 Byte 배열이지만 추가적인 정보를 갖고 있다.
이 변수의 사이즈는 얼마이고, 얼만큼 읽었고(index), 어디부터 쓸수 있는지에 대한 위치정보(index)와 누가 얼마나 참조하는지(Refcnt) 가 들어가 있다. ( 이건 차근차근.. 경험 하고프면 여기 )
그러고 다시 돌려보자 .
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg; // ByteBuf로 형변환
ctx.channel().writeAndFlush(msg); // 클라이언트로 내려보내고
logger.info("channelRead..{}", buf.toString(CharsetUtil.UTF_8)); // ByteBuf to String변환
}
다시 코드로 돌아가서
오류가 다시 발생했다.
ByteBuf 에서는 누가 얼마나 참조하고 있는지를 카운팅하게된다.
[nioEventLoopGroup-3-1] INFO EchoServerHandler - exceptionCaught..refCnt: 0
로그 찍어보자
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("첫번째 msg refcnt = {}", ReferenceCountUtil.refCnt(msg)); //msg refcnt = 1
ByteBuf buf = (ByteBuf) msg;
logger.info("두번째 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf)); // msg refcnt =1, buf refcnt = 1
ctx.channel().writeAndFlush(msg);
logger.info("세번째 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf)); // msg refcnt = 0, buf refcnt = 0
logger.info("channelRead..{}", buf.toString(CharsetUtil.UTF_8)); // 오류
logger.info("네번째 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf));
}
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 첫번째 msg refcnt = 1
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 두번째 msg refcnt = 1 buf refcnt=1
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 세번째 msg refcnt = 0 buf refcnt=0
[nioEventLoopGroup-3-1] INFO EchoServerHandler - exceptionCaught..refCnt: 0
위 코드에서 보듯이 Reference Count가 0인 상태에서 참조하게 되면 위처럼 refCnt: 0 오류가 발생하게 된다
retain(), release()를 사용하자 .
아래처럼 간단하게 보자. 이 정도만 알고 있어도 코딩하는데 지장이 없을 껄(?)
ReferenceCountUtil.retain(msg : Any) : 한번 더 쓸거니깐 참조 한번 올려줘
ctx.channel().writeAndFlush(msg) : 아싸.. 이미 한번 내가 써버렸어...
ReferenceCountUtil.release(msg : Any) : 다 썼어~~
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("첫번째 msg refcnt = {}", ReferenceCountUtil.refCnt(msg));
ByteBuf buf = ((ByteBuf) msg);
logger.info("두번째 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf));
ReferenceCountUtil.retain(msg);
logger.info("retain msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf));
ctx.channel().writeAndFlush(msg);
logger.info("세번째 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf));
logger.info("channelRead..{}", buf.toString(CharsetUtil.UTF_8));
logger.info("네번째 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf));
ReferenceCountUtil.release(msg);
logger.info("마지막 msg refcnt = {} buf refcnt={}", ReferenceCountUtil.refCnt(msg),
ReferenceCountUtil.refCnt(buf));
}
자 쓰고 반환도 잘되고 메시지도 잘 찍힌다.
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 첫번째 msg refcnt = 1
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 두번째 msg refcnt = 1 buf refcnt=1
[nioEventLoopGroup-3-1] INFO EchoServerHandler - retain msg refcnt = 2 buf refcnt=2
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 세번째 msg refcnt = 1 buf refcnt=1
[nioEventLoopGroup-3-1] INFO EchoServerHandler - channelRead..abc
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 네번째 msg refcnt = 1 buf refcnt=1
[nioEventLoopGroup-3-1] INFO EchoServerHandler - 마지막 msg refcnt = 0 buf refcnt=0
'프로그래밍 > Netty EchoServer 만들기' 카테고리의 다른 글
02. EchoServer 응답값 바꿔보기 (0) | 2022.01.07 |
---|