편의점을 예로 들어 설명했었는데, 이번엔 카페로 한번 해보자. ( 명퇴나 은퇴하면 내 꿈이다.. )
손님이 문을 열고 들어온뒤 손님을 대응하는 메뉴얼에 대한 절차라고 언급했던 내용을 다시 보자
CodecSample.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 CodecSample {
final static Logger logger = LoggerFactory.getLogger(CodecSample.class);
final static int PORT = 8009;
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 카페 대문
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 편의점 카운터
try {
ServerBootstrap b = new ServerBootstrap(); // 편의점
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class); // 카페에서 대화에 사용할 언어? 정도
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 손님 들어왔을때 대응메뉴얼
logger.info("손님 문열고 들어옵니다..");
ch.pipeline().addLast(new CodecSampleHandler());
}
});
logger.info("PORT : {} 카페 여는 중..", PORT);
ChannelFuture f = b.bind(PORT).sync(); // 편의점 문열음
logger.info("카페 오픈!!..");
f.channel().closeFuture().sync();
logger.info("카페 닫기 시작!!..");
} finally {
logger.info("가계 닫는 중..", PORT);
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
logger.info("가계 닫음...");
}
}
}
1) 카페 대문과 카페 카운터 라고 보면 될까?
bossGroup은 접속을 담당하고, workGroup 은 이벤트와 I/O를 담당한다고 한다.
bossGroup은 도어멘, workGroup은 내부에서 손님을 처리를 담당하는 사람이라고 보면 되지 않을까?
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 카페 대문
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 편의점 카운터
ServerBootstrap b = new ServerBootstrap(); // 편의점
EventLoopGroup 의 다양한 종류는 여기에서 확인 가능하다.
1) 카페를 열기 전 카페내에서 영어를 사용할지, 한글을 사용할지 결정한다?
ServerBootstrap.channel 은 우리가 대화는 어떻게 할꺼야라고 알려준다는걸로 이해하면 될듯?
종류는 여기서 확인 해보면 될듯..
그다음은 pipeline 이다. 손님이 들어와서 어떤 순서로 처리할지 메뉴얼이라고 보면 될듯하다
카페가면 아래처럼 고객이 일반적으로 행동하는 모습을 pipeline에 담는다
QR코드 찍고 ▶ 메뉴보고 ▶ 주문하고 ▶ 결제하고 ▶ 자리 앉는다. ▶ 마시고 ▶ 나간다 ▶ 컵 치운다
b.channel(NioServerSocketChannel.class); // 카페에서 대화에 사용할 언어? 정도
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 손님 들어왔을때 대응메뉴얼
logger.info("손님 문열고 들어옵니다..");
ch.pipeline().addLast(new CodecSampleHandler());
}
});
2) 가게문 열고 기다리기
bind는 소켓 통신 시 이제 접속가능해~ 드루와 라는걸로 보면 된다.
bind(PORT).sync() 보면 sync()가 있으니깐 끝나야만 다음코드로 진행될것 같다.
그 다음이 채널을 닫는 내용이다.
수행해보면 f.channel().closeFuture().sync() 에서 코드가 멈춰져 있다.
이게 netty 국룰이라 이것까진 나중에 카피해서 쓰자.
logger.info("PORT : {} 카페 여는 중..", PORT);
ChannelFuture f = b.bind(PORT).sync(); // 편의점 문열음
logger.info("카페 오픈!!..");
f.channel().closeFuture().sync();
logger.info("카페 닫기 시작!!..");
3) 깔끔하게 처리
이것도 Netty 국룰인가보다. CodecSampleHandler에서 강제로 끊어서 호출되게 해보자
finally {
logger.info("가계 닫는 중..", PORT);
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
logger.info("가계 닫음...");
}
4) CodecSampleHandler.java에서 강제 종료
channelRead 함수에서 ctx.channel().parent().close() 함수를 써서 돌려보자
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class CodecSampleHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(CodecSampleHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("channelActive..");
String strWelcome = "환영합니다. 고객님!!\n";
ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(strWelcome.length()); // 필요한 길이만큼 할당
buf.writeBytes(strWelcome.getBytes(CharsetUtil.UTF_8)); // 얻어온 ByteBuf에 쓰기
ctx.channel().writeAndFlush(buf); // 보내기
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("channelInactive..");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String strRecv = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
logger.info("channelRead.. READ : {}", strRecv);
String strReturn = ">> " + strRecv;
ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(strReturn.length()); // 필요한 길이만큼 할당
buf.writeBytes(strReturn.getBytes(CharsetUtil.UTF_8)); // 얻어온 ByteBuf에 쓰기
ctx.channel().writeAndFlush(buf); // 보내기
ctx.channel().parent().close(); //강제 종료 해보기
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.info("exceptionCaught..{}", cause.getMessage());
}
}
결과가 아래와 같다
Hi를 보내고, 받아서 >> Hi를 리턴하고 다음에 ctx.channel().parent.close()를 호출하니 다음 코드들이 주루룩 호출되었다.
자 드디어 pipeline에 뭔가를 추가해보자
여기에 보면 엄청나게 많다..
그중에 LoggingHandler를 추가한 뒤 상황을 보자.
LogLevel.INFO는 현재 나의 로그 레벨이 INFO라 로그에 찍히기 위해선 INFO 레벨로 LoggingHandler를 추가하란 뜻이다.
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 손님 들어왔을때 대응메뉴얼
logger.info("손님 문열고 들어옵니다..");
ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); // 추가
ch.pipeline().addLast(new CodecSampleHandler());
}
});
결과는 아래와 같다
뭔가 무서운 코드들이 나왔다.
REGISTER ▶ ACTIVE ▶ WRITE ▶ FLUSH 로 흘러간다
WRITE라는게 서버입장에선 보냈다는 뜻이고 보내는 자료를 한땀한땀 Byte로 보여준다.
이거 개발하다 보면 진짜 많이 쓰게 된다
다음엔 다양한 핸들러를 추가해보자
'프로그래밍 > Netty Codec 이해하기' 카테고리의 다른 글
Netty Pipeline 및 Codec 활용(4) (0) | 2022.01.10 |
---|---|
Netty Pipeline 및 Codec 활용(3) (0) | 2022.01.10 |
Netty Pipeline 및 Codec 활용(2) (0) | 2022.01.07 |