웹서버와 자바 어플리케이션 간의 소켓 연결을 위해 Netty를 사용하고 있는데 특성상 연결이 끊길경우 반드시 자동 재접속을 시도를 해야 한다. 이것을 구현해보려고 많은 예시를 봐왔지만 딱히 해결 될 기미가 보이지 않던 찰나에 좋은 코드를 찾았고 이를 적용하니 정상적으로 대응이 되었다.
public final class UptimeClient {
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
// Sleep 5 seconds before a reconnection attempt.
static final int RECONNECT_DELAY = Integer.parseInt(System.getProperty("reconnectDelay", "5"));
// Reconnect when the server sends nothing for 10 seconds.
private static final int READ_TIMEOUT = Integer.parseInt(System.getProperty("readTimeout", "10"));
private static final UptimeClientHandler handler = new UptimeClientHandler();
private static final Bootstrap bs = new Bootstrap();
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
bs.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(HOST, PORT)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(READ_TIMEOUT, 0, 0), handler);
}
});
bs.connect();
}
static void connect() {
bs.connect().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.cause() != null) {
handler.startTime = -1;
handler.println("Failed to connect: " + future.cause());
}
}
});
}
}
위의 예제의 경우 Netty Client 소스 코드를 Main에서 실행하도록 했지만 사실 Main 문을 그냥 하나의 메서드로 바꿔버린다음 다른 곳에서 호출하는 형식으로 써도 된다. 실제로 그렇게 쓰고 있고 적용이 잘 된다.
예를 들어 public static void main 을 public void NettyClient로 바꾼다음 다른 곳에서 NettyClient()로 바꾼다던지 말이다.
NettyClient의 실질적인 기능을 부여할 Handler의 경우 addLast를 통해 추가한다
package io.netty.example.uptime;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.util.concurrent.TimeUnit;
/**
* Keep reconnecting to the server while printing out the current uptime and
* connection attempt getStatus.
*/
@Sharable
public class UptimeClientHandler extends SimpleChannelInboundHandler<Object> {
long startTime = -1;
@Override
public void channelActive(ChannelHandlerContext ctx) {
if (startTime < 0) {
startTime = System.currentTimeMillis();
}
println("Connected to: " + ctx.channel().remoteAddress());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Discard received data
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (!(evt instanceof IdleStateEvent)) {
return;
}
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) {
// The connection was OK but there was no traffic for last period.
println("Disconnecting due to no inbound traffic");
ctx.close();
}
}
@Override
public void channelInactive(final ChannelHandlerContext ctx) {
println("Disconnected from: " + ctx.channel().remoteAddress());
}
@Override
public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception {
println("Sleeping for: " + UptimeClient.RECONNECT_DELAY + 's');
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
println("Reconnecting to: " + UptimeClient.HOST + ':' + UptimeClient.PORT);
UptimeClient.connect();
}
}, UptimeClient.RECONNECT_DELAY, TimeUnit.SECONDS);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
void println(String msg) {
if (startTime < 0) {
System.err.format("[SERVER IS DOWN] %s%n", msg);
} else {
System.err.format("[UPTIME: %5ds] %s%n", (System.currentTimeMillis() - startTime) / 1000, msg);
}
}
}
Netty에서 많은 기능을 제공하지만 재접속 부분만 보자면 channelUnregistered를 보면 된다.
channelUnregistered에서 EventLoop를 통해 꾸준히 Connection을 시도하는 것을 확인할 수 있다.
이렇게 적용했을때 단순 연결 끊김 외에도 유령 세션에도 대응할 수 있었다.
'Programming Languages > Java' 카테고리의 다른 글
SourceTree 설치 후 CredentialHelperSelector가 자꾸 팝업 될 경우 해결 방법 (0) | 2022.03.22 |
---|