Pages

2014년 10월 30일 목요일

[JAVA] Netty 사용 TIP

ClientBootStrap 설정 샘플

bootstrap = new ClientBootstrap( new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));

//Naggle 알고리즘 disable
bootstrap.setOption("tcpNoDelay", true);
//Connection Timeout 설정
bootstrap.setOption("connectTimeoutMillis", 5000);

Netty를 Little Endian으로 사용하는 방법

Netty는 자바 기반이라서 기본이 Big Endian이다.
C로 구성된 서버의 경우 기본이 Little Endian이다.
ascii 문자를 주고 받을 경우에는 Endian이 무관해 보이지만, integer 등을 보내려면 Endian을 반드시 고려해야 한다.


//기본 ChannelBuffer를 Little Endian으로 설정
bootstrap.setOption("bufferFactory", HeapChannelBufferFactory.getInstance(ChannelBuffers.LITTLE_ENDIAN));

char 배열로 보내진 문자열 변환 방법

예를 들어 char[20] 배열에 char들이 담겨져 온다고 가정하자.
근데 문제는 20바이트가 꽉 차서 오는 게 아니고 몇 바이트만 채워져서 들어오고 나머지는 0×00이 들어온다는 점이다.
보통 0×00으로 초기화 후 필요한 부분만 copy하니 그렇게 되는 것이다.
char[20]에 "hi"이라고만 들어가게 되면 char[0]='h', char[1]='i', char[2]=0×00…char[19]=0×00이 될 것이다.
이렇게 들어온 것을 "hi"로만 변환하고자 한다면? 다음을 참고하면 된다.
ChannelBuffer buffer = …
int nullIndex = buffer.indexOf(40, 60, ChannelBufferIndexFinder.NUL);
String result = buffer.toString(40, nullIndex – 40, "US-ASCII");
즉 ChannelBufferIndexFinder를 가지고 해당 null값의 위치를 파악 후 지정된 범위만 변환하는 것이다.
ChannelBufferIndexFinder에는 NUL, NOT_NUL, CR, LF 등 몇몇 구분 문자들이 미리 지정되어 있다.


Handler의 messageReceived 작성시 나타나는 흔한 Exception

바로 그것은 ChannelBuffer에서 하나도 안 읽었다면서 나오는 Exception이다.
Netty 내부에서는…
(1) 읽혀진 byte내용이 ChannelBuffer에 담겨진 채로 Decoder를 통해 Handler에게 넘겨졌다.
(2) 근데 Handler에서 exit하려고 보니 ChannelBuffer의 readerIndex가 Handler에게 넘겨질 때의 값과 같다.
(3) 이 상황은 "하나도 안 읽은 상황"으로 보여진다.
(4) 따라서 Exception을 발생시킨다.
이를 위해서는 messageReceived가 return되기 전에 buffer.readerIndex(buffer.writerIndex())등을 호출해서 읽었다고 "표시"해주면 된다.

ChannelBuffer에 대해

ByteBuffer와 다른 것 중에 제일 중요한 사항은 읽기용 Index와 쓰기용 Index가 따로 있다는 것이다.
readerIndex, writerIndex로써 각각 readXXX, writeXXX할 때마다 증가한다.
읽기와 쓰기에 대해 각각 Index를 관리해줌으로써 "아주 편리하다"
writeZero 메쏘드를 가지고 한방에 0×00으로 채울 수 있다 ^^*

ChannelBuffers는 뭘까?

ChannelBuffer 생성, 복사 등의 편리한 기능을 제공해주는 유틸리티성격의 클래스
Little Endian으로 ChannelBuffer 생성 예는 다음과 같다
ChannelBuffer buf = ChannelBuffers.buffer(ChannelBuffers.LITTLE_ENDIAN, 40);
hexDump 메쏘드는 쉽게 16진수 방식으로 logging해볼 수 있게 해준다. 다만 아쉽게도 무조건 Big Endian형식으로만 보여주는듯 하다.


ChannelFuture에 대해

ChannelFuture는 Channel의 미래다 -.-a 너무 직역해서 어렵다.
쉽게 말하면 좀 있다 어떻게 될 예정인 것이라고 할 수 있다.
Netty의 모든 I/O는 기본적으로 비동기라서 요청한 후에 결과를 바로 얻을 수 있지 않다.
지금 close했다고 해서 해당 write가 성공했는지 실패했는지를 알 수 없다는 것이다.
이 경우 close한 후 얻어지는 ChannelFure에 좀 있다 날라올 이벤트에 대한 리스너를 등록해두면 이벤트가 생기면 Listener를 호출해주게 된다.
예제를 보면 이해가 쉽다.

//채널 종료를 요청하고 Listener를 등록한다
e.getChannel().close().addListener(new ChannelFutureListener() {
    //진짜 종료될 경우 호출될 콜백 함수를 작성
    public void operationComplete(ChannelFuture future) {
        System.out.println("이제야 종료되었어요 ^^");
    }
});

필요없다면 리스너 없이 그냥 호출해도 된다. 즉 비동기 호출이니까 호출 즉시 return된다.
e.getChannel().close()

어? 바로 결과를 얻을 수 없을까? 하는 경우는 "동기"처리를 원하는 경우라고 할 수 있다.
e.getChannel().close().awaitUninterruptibly();
e.getChannel().close().await…

이렇게 호출하면 close() 요청 후 해당 close동작이 operationCompleted될 때까지 현재 쓰레드에서 await하는 것이다.
awaitUninterruptibly라고 할 경우는 "방해받지 않고 대기"하게 된다.
그냥 await라고 할 경우는 대기하는 동안 방해받을 수 있다 ^^*
채널 연결, write 등 주요 I/O 모두에 대한 사항이니 적절하게 사용하면 된다.

ServerBootstrap 중 Pipeline 설정 방법


Case #1 ==> 처음에는 이 방법으로 했고 별 문제없었다. 하지만 테스트하다보니 데이타가 뒤섞이는 경우 발생
//Http 서버
objServerBootstrap = new ServerBootstrap( new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));

objServerBootstrap.getPipeline().addLast("http_decoder", new HttpRequestDecoder());
objServerBootstrap.getPipeline().addLast("http_encoder", new HttpResponseEncoder());
objServerBootstrap.getPipeline().addLast("handler", new HandlerForHTTPServer());

objServerBootstrap.setOption("tcpNoDelay", true);
objServerBootstrap.setOption("connectTimeoutMillis", 30000);
objServerBootstrap.setOption("reuseAddress", true);
objServerBootstrap.setOption("keepAlive", false);            

objServerBootstrap.bind(new InetSocketAddress(9090);


Case #2 ==> 이 방법으로 해야 한다. 안 그러면 동시 전송시 데이타가 뒤섞여 들어오는 경우가 발생한다
//Http 서버
objServerBootstrap = new ServerBootstrap( new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
objServerBootstrap.setPipelineFactory(new PipelineFactoryForHTTPServer());

objServerBootstrap.setOption("tcpNoDelay", true);
objServerBootstrap.setOption("connectTimeoutMillis", 30000);
objServerBootstrap.setOption("reuseAddress", true);
objServerBootstrap.setOption("keepAlive", false);            

objServerBootstrap.bind(new InetSocketAddress(Integer.parseInt(UbiMon.objXMLConfiguration.getString("network-config.http-server.port"))));

PipelineFactoryForHTTPServer.java            
public class PipelineFactoryForHTTPServer implements ChannelPipelineFactory {
    public ChannelPipeline getPipeline() throws Exception {
        // Create a default pipeline implementation.
        ChannelPipeline objPipeline = Channels.pipeline();
        objPipeline.addLast("http_decoder", new HttpRequestDecoder());
        objPipeline.addLast("http_encoder", new HttpResponseEncoder());
        objPipeline.addLast("handler", new HandlerForHTTPServer());
        return objPipeline;
    }
}

연결된 상태에서 한쪽이 Down된 상태에서 강제로 접속 해제하기

Client – Server가 연결된 후 Server의 랜선을 뽑아보면 연결이 재빨리 끊기지도, 바로 끊긴걸 인식하지도 못한다. 이것은 TCP/IP의 특성상 그런 것이다.
이것은 그렇다고 해도 연결이 끊어진 것을 확인한 후 다시 재접속을 하려고 하면 Connection Refused 되어버려 난감했다.
수없는 시도끝에 다음과 같이 해결하였다.
1.ReadTimeoutHandler or WriteTimeoutHandler로 데이타 흐름이 없을 시 강제 종료
2.강제종료하는 함수

private void closeClearly(ChannelHandlerContext objChannelHandlerContext, ExceptionEvent objExceptionEvent, Bootstrap objBootstrap) {
objExceptionEvent.getChannel().close().awaitUninterruptibly();
ChannelFuture objFuture = objExceptionEvent.getChannel().getCloseFuture();
if(objFuture.isDone()) {
UbiMon.objLogger.error("Closing channel…done");
if(objFuture.isSuccess()) {
UbiMon.objLogger.error("Closing channel…success");
}
}    

objBootstrap.releaseExternalResources();==> i/o thread에서 실행시 deadlock 발생됨, 별도 thread에서 연결모니터링하다가 release해줘야함
Channels.fireChannelClosed(objChannelHandlerContext);
Channels.fireChannelDisconnected(objChannelHandlerContext);
}
3.연결상태를 주기적으로 모니터링하고 있다가 연결해제된 걸 발견하면 Bootstrap 부터 다시 생성하여 재연결

[ 출처 : http://blog.pointbre.com ]

댓글 없음:

댓글 쓰기