Netty 기반 서버-클라이언트 통신 구조에서 서버 측 메시지 전송을 구현하는 방법을 설명합니다. WebSocket 프로토콜을 활용한 채팅 시스템 예제로 핵심 구성 요소를 다룹니다.
Netty 서버 기본 구조
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private ServerBootstrap bootstrap;
public NettyServer() {
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer());
}
public void start(int port) {
try {
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
shutdown();
}
}
private void shutdown() {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
채널 파이프라인 초기화
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(65536))
.addLast(new WebSocketServerProtocolHandler("/ws"))
.addLast(new ConnectionMonitor())
.addLast(new MessageHandler());
}
}
연결 상태 모니터링
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
public class ConnectionMonitor extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.ALL_IDLE) {
ctx.channel().close();
}
}
}
}
메시지 핸들링 로직
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
public class MessageHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
public static final ChannelGroup activeChannels =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
activeChannels.add(ctx.channel());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
String payload = frame.text();
String receiverId = parseReceiverId(payload);
Channel target = ClientRegistry.getChannel(receiverId);
if (target != null && activeChannels.contains(target)) {
target.writeAndFlush(new TextWebSocketFrame(payload));
}
}
private String parseReceiverId(String payload) {
// JSON 파싱 로직 구현
return extractIdFromJson(payload);
}
}
클라이언트 채널 레지스트리
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentHashMap;
public class ClientRegistry {
private static final ConcurrentHashMap<String, Channel> clients =
new ConcurrentHashMap<>();
public static void register(String clientId, Channel channel) {
clients.put(clientId, channel);
}
public static Channel getChannel(String clientId) {
return clients.get(clientId);
}
}
메시지 데이터 구조
public class MessageData {
private String senderId;
private String recipientId;
private String content;
// Getter/Setter 생략
}
public class MessageWrapper {
private int messageType;
private MessageData payload;
// Getter/Setter 생략
}