Pages

2014년 10월 30일 목요일

[TOMCAT] Apache - Tomcat 로드밸런싱 및 클러스터링 설정하기



Apache - Tomcat 로드밸런싱 및 클러스터링 설정하기



[Synology] Asterisk IP-PBX 인터넷전화

1. Asterisk란 무엇인가?

요점만 말해서 인터넷을 통해 전화를 주고 받을 수 있는 서비스를 제공하는 프로그램입니다.
자세한 내용은 http://blog.naver.com/PostView.nhn?blogId=hsunryou&logNo=60104736007 를 참조하시고
http://cafe.naver.com/asterisker 카페에 가입하시면 많은 정보를 얻으실 수 있습니다.

2. Asterisk를 어떻게 설치하나?

DSM에 접속하셔서 패키지센터을 뒤져보시면 나옵니다. 
설치안하신 분은 사용 가능 > 서드 파티의 패키지 항목에서 찾으실 수 있습니다.

3. 어디까지 해봤나?

- 서버 설정 후 
- 사용자를 추가하여 전화번호를 부여하였고
- 아이폰4와 안드로이드폰 갤럭시S에 SIP 어플을 설치하여
- 무선랜 환경에서 서로 통화해보고
- 3G 환경에서 서로 통화해 보았습니다.

참고로 갤럭시S는 데이터쉐어링용 유심만 장착한 폰입니다.
따라서 아이팟터치나 아이패드와 같은 단말에서도 사용이 가능합니다.

4. 서버 설정하기

패키지센터에서 설치하고 나면 DSM에 바로가기 아이콘이 생성됩니다.
아이폰을 누르면 새창이 열리면서 로그인 할 수 있습니다. admin으로 로그인하여 설정할 수 있습니다.
아래 설정에서 ... Name 부분은 각자 알아서 적으시면 됩니다. 제가 설정한 내용을 적겠습니다.

1) Trunks 설정
- 왼쪽 메뉴에서 Trunks 를 누르고, VOIP Trunks > New SIP/IAX Trunk 클릭
- Provider Name : iPhoneDS(제 NAS 이름입니다)
- Hostname : NAS의 IP주소(공인IP)나 DDNS 주소
- Codec : 제일 중요한데 설명이 너무 길어지고 기술적입니다. 제가 설정한 것은 SPEEX, GSM, ILBC, u-law, a-law 입니다.
- 나머지는 모두 비워두고 Save (사실 뭘 적어야 할지 몰라서 비워뒀는데 사용하는데는 문제 없더군요)

2) Outgoing Calling Rules 설정
- New Calling Rule 클릭
- Calling Rule Name : Local
- Pattern : XXXX (다양한 패턴을 넣을 수 있는데 일단 통화되는 것만 확인하기 위해서)
- Save

3) Dial Plans 설정
- New DialPlan 클릭
- DialPlan Name : DialPlanLocal
- 나머지 모두 체크 후 Save

4) Users 설정
- Create New User 클릭
- 첫번째 전화번호가 6000번 이네요. 저는 테스트를 위해서 ID, Password, CID등 모든 것을 6000으로 통일시켰습니다.
- Extention, CallerID Name, CallerID Number, VoiceMail Access PIN code, MAC Address, SIP/IAX Password 모두 6000
- DialPlan : 3)에서 설정한 이름을 고릅니다. DialPlanLocal
- 체크 : Enable Voicemail for this User, SIP, NAT
- Codec Preference : SPEEX, GSM, ILBC, u-law, a-law
- DTFM Mode : RFC2833
- Line Number, LineKeys, Pickup Group : 1
- Update
- 마찬가지 방법으로 필요한 만큼 번호 생성

5) Apply Changes 클릭 (화면 오른쪽 상단에 있습니다)
- 나머지 설정은 안해도 통화하는데는 문제 없더군요.

5. 스마트폰용 어플리케이션 설치하기

많은 어플들이 있어 선택하는데 어려움이 많습니다.
제가 선택하여 설치한 어플은 다음과 같습니다.

1) 아이폰용 어플 : C2Phone(무료)
- Settings > SIP account
  * User name : 6000
  * Password : 6000
  * Proxy : NAS의 IP주소 또는 DDNS주소:5060
- Settings > Setting > Audio > Codecs
  * Speex 8Khz, GSM, ILBC, PCMU, PCMA : ON
- Settings > Network
  * Random Port, Push Notification : ON

2) 안드로이드용 어플 : CSipSimple(무료)
- 설정 > 네트워크
  * 수신전화, 발신전화 : 모두 체크
- 설정 > 미디어
  * 에코취소, Codec priority list per bandwidth : 체크
  * 코덱 체크(Fast/Slow): speex 8 kHz, GSM 8 kHz, ILBC 8 kHZ, PCMU 8 kHZ, PCMA 8 kHz 
- 설정 > 사용자 인터페이스
  * 전화걸기통합, 통화기록통합, 화면회전차단 : 체크

6. 마무리

- 가장 품질이 좋은 코덱은 G729라고 합니다. 다만 유료라서 별도의 지출을 하셔야 합니다.
- PCMU = u-law : G711 북미 방식
- PCMA = a-law : G711 유럽 방식
- C2Phone 지원 코덱 : Speex, SILK, G722, GSM, ILBC, PCMU, PCMA
- CSipSimple 지원 코덱 : Speex, SILK, G722, GSM, ILBC, PCMU, PCMA, AMR, ISAC, G729, CODEC2, opus, G726, G7221

이상으로 제가 지금까지 설정했던 내용을 마무리합니다.
이렇게 하면 스마트폰끼리 무료통화가 가능합니다.
추가로 3G 네트워크에서 통화를 해보면 도저히 사용할 수 없을 정도의 품질을 보여줍니다. 이건 품질도 아니죠.
그래서 NAS에서 추가로 설치해 줘야 하는게 VPN 서버입니다. 이것도 패키지 센터에서 설치 가능합니다.
스마트폰들을 NAS에 설정한 VPN에 접속한 후 3G상태에서 통화를 해보면 무지 깨끗한 품질을 확인할 수 있습니다.
(통신사의 요금제 정책에 따라 필요없을 수도 있습니다.)

여기까지는 초보자인 제가 작성을 했구요, 이제 고수들께서 나머지 부분에 대해서 강좌를 적어주셨으면 좋겠습니다.

Synology NAS를 정말 잘 선택했다는 생각이 점점 많이 들고, 주변에 계속 봄뿌질을 하여 사무실에만 벌써 4명이나 사용중입니다.
점점 더 기대가 됩니다.

7. 추가사항: 방화벽 설정 (중요)

가장 중요한 방화벽 설정을 까먹었네요.
대부분 DSM에서 방화벽 설정하셔서 사용하실겁니다. 만약 사용안하신다면 당장 사용하시기 바랍니다.
제가 이 설정 때문에 이틀동안이나 고생했는데 그 부분을 놓쳤습니다.

- DSM 접속
- 제어판 > 방화벽 > 생성 클릭
  * 포트 : 사용자 지정 > 유형: 대상포트, 프로토콜: UDP, 포트범위 시작: 10000, 종료: 20000
  * 소스 IP : 모두
  * 작업 : 허용
- 확인 눌러 저장 후 다시 생성 클릭
  * 포트: 내장된 응용 프로그램 목록에서 선택
  * 선택: 응용프로그램 Asterisk 인 모든 항목 선택 후 확인
  * 소스 IP : 모두
  * 작업 : 허용
- 확인 누른 후 저장 눌러 방화벽 정책 활성화 시키시면 됩니다.

8. 추가사항2: 스크린샷


[기타] Asterisk 을 이용한 voip 테스트

1. 무엇을 할 것인가?

먼저 위에서 언급한 내선번호 통화에 대해 구체적으로 정리하고자 한다. asterisk를 설치하고 간단한 설정을 마치면 sipdroid나 netdial 등의 소프트웨어를 스마트폰에 설치하여 한대는 내선번호 100번으로 또 다른 한대는 101번으로 할당하여 이들간의 통화가 가능하다. sipdroid나 netdial의 사용법은 인터넷 검색을 통해 쉽게 찾을 수 있을 것으로 생각한다.


[그림 1] 개념도

위 그림에서 볼 수 있는 것처럼 100, 101번의 전화번호는 asterisk에서 설정해 주는 값이며, 사용자의 전화에는 사용자 계정, 비밀번호, asterisk 서버를 지정해 주면된다. 물론 다른 설정도 많지만, asterisk에서 특별히 설정을 만지지 않는다면 voip 소프트웨어의 기본 설정으로도 잘 동작할 것이다.


2. asterisk 설치 및 설정

본인의 경우 설치에서는 큰 어려움이 없었으므로 이 글에서는 설명을 생략하겠습니다. 자세한 설명은Asterisk™: The Definitive Guide, Chapter 3. Installing Asterisk 을 참고해 주세요.
설정을 해 주어야 하는 화일은 2가지로, /etc/asterisk 아래에 sip.conf, extension.conf 입니다. (책에는 iax.conf 설정이 나와 있지만, 내선 통화에서는 필요하지 않은 화일입니다.) 아래 그림은 sip.conf와 extensions.conf가 어떻게 연결되는지를 보여주는 좋은 그림입니다.


[그림 2] sip.conf와 extensions.conf와의 관계


2.1 sip.conf 설정

------------------------------------------------------------------------------------
[general]
context=unauthenticated     ; default context for incoming calls
allowguest=no                 ; disable unauthenticated calls
srvlookup=yes                 ; enabled DNS SRV record lookup on outbound calls
udpbindaddr=0.0.0.0 ; listen for UDP requests on all interfaces
tcpenable=no ; disable TCP support

[office-phone](!) ; create a template for our devices
type=friend ; the channel driver will match on username first, IP second 
context=LocalSets ; this is where calls from the device will enter the dialplan
host=dynamic ; the device will register with asterisk
nat=yes ; assume device is behind NAT
; *** NAT stands for Network Address Translation, which allows
; multiple internal devices to share an external IP address.
secret=s3CuR#p@s5 ; a secure password for this device -- DON'T USE THIS PASSWORD!
dtmfmode=auto ; accept touch-tones from the devices, negotiated automatically
disallow=all ; reset which voice codecs this device will accept or offer
allow=ulaw ; which audio codecs to accept from, and request to, the device
allow=alaw ; in the order we prefer

; define a device name and use the office-phone template
[0000FFFF0001](office-phone)
; define another device name using the same template
[0000FFFF0002](office-phone)
------------------------------------------------------------------------------------
[]로 묶여진 섹션들이 4개 보이는 군요, general, office-phone, 0000FFFF0001, 0000FFFF0002. 
설명에 들어가기 앞서 dialplan에 대한 설명이 있어야 할 것 같습니다. 제가 지금까지 파악한 바로 dialplan은 전화를 거는 사람과 받는 사람을 어떻게 연결시켜 줄 것인지를 정의하는 문장들입니다. extensions.conf에 정의되는 문장들이 바로 dialplan 이죠.

general은 일반적인 설정값을 정의합니다. context를 unautenticated로 정의하면 전화 연결이나 통화 내용을 보호할 수 없다는 뜻 정도로 생각됩니다만.. 저도 정확히는 모르겠습니다. 원문을 참고해 주세요. allowguest가 no로 정의되어 있으므로 명시적으로 계정을 가진 사람만 통화를 할 수 있다는 뜻입니다. srvlookup 은 yes로 설정되었으므로 받는 사람이 위치한 네트워크를 dns 이름으로도 찾을 수 있도록 한 것으로 보입니다. 아~~~ 정확하게는 모르겠어요.. 처음이니까.. 그냥 따라 합니다.^^; udpbindaddr 은 asterisk 서버에 네트워크 인터페이스가 여러 개 있을 경우에 의미를 갖는 설정인데요. 특정 인터페이스가 아닌 모든 인터페이스를 사용하고자 할 때는 0.0.0.0을 씁니다. voip 전화는 udp를 이용하므로 tcpenable을 no로 설정하여 TCP는 사용하지 않습니다.

[office-phone]옆에 있는 (!) 보이시죠. 이 표시는 템플릿을 정의한다는 의미를 갖습니다. 이 예제에서 0000FFFF0001, 0000FFFF0002 총 2개의 sip 계정을 정의하고 있는데요. 각 사용자별로 공통된 사항은 템플릿으로 정의하여 계정옆에 템플릿 이름을 붙이면 템플릿과 다른 내용만 각 사용자 항목에서 정의하면 됩니다. 예를 들어 위의 sip.conf 화일에 
[0000FFFF0003](office-phone) ; template must be on same line and no space between
secret=@N0th3rP4S5
을 추가하면 [0000FFFF0003] 사용자에 대해서는 비밀번호를 다른 것으로 하겠다는 의미가 되겠습니다.

office-phone 섹션에서 type은 peer, user, friend가 가능합니다. peer는 전화를 거는 사람의 IP 주소와 udp port 번호를 참조해서 적합한 사용자 인지를 인식하고 user는 사용자 계정을 가지고 적합성을 판단합니다. friend는 peer와 user를 합쳐놓은 것입니다. 책에서는 friend를 사용하는 것을 추천하고 실제 설정도 그렇게 되어 있네요. context에는 LocalSets로 정의되어 있는데요. extensions.conf에 LocalSets이 섹션으로 정의되어 있습니다. 여기에는 dialplan을 위한 참조점으로 보시면 되겠습니다. host를 dynamic으로 등록하면 IP를 동적으로 할당받는 환경에서도 voip 전화에 할당된 IP 주소를 인식할 수 있습니다. 명시적으로 IP 주소를 할당하여 사용할 수도 있습니다. 사설IP를 쓰는 환경이라면 nat를 yes로 해 주어야 겠죠. secret는 계정에 대한 비밀번호입니다. dtmf는 touch-tone과 관련이 있다는데.. 뭔지 모르겠습니다. disallow와 allow는 음성통화시 사용되는 codec을 정의하는 부분인데요. 정의하기전에 disallow=all로 하여 일단 모든 코덱을 쓰지 않는 것으로 설정한 후 사용할 코덱을 설정합니다. allow중 위에 있는 줄이 더 높은 사용 우선순위를 갖습니다.

이 책에서는 계정이름으로 장치의 MAC 주소를 사용할 것을 권장하고 있습니다. 예를 들면, 0000FFFF0001, 0000FFFF0002 이지요. 하지만, 공백이 없는 영문과 숫자로 구성된 아이디면 아무거나 사용할 수 있습니다. 사용자 계정과 관련하여 흥미있는 내용이 있는데요. 전화를 걸 때는 내선 번호인 100, 101을 이용하지만, 수신했을 때 발신자 정보에는 전화건 사람의 계정 이름이 나옵니다. 

sip.conf 를 위의 내용으로 입력한 후 저장합니다. sip.conf의 수정된 내용을 asterisk에 반영하기 위해 서버를 죽였다가 살리거나, CLI에서 아래와 같이 입력해 줍니다.
*CLI> module reload chan_sip.so
참고로 asterisk를 실행시킬 때 
$ sudo asterisk -vvvc
위 명령어를 이용하면 asterisk가 무슨 일을 하고 있는지 자세히 볼 수 있고 CLI도 입력할 수 있습니다.

sip 모듈을 다시 실행시킨 후 
*CLI> sip show peers
*CLI> sip show users
를 실행시키면 sip.conf에 입력한 내용이 시스템에 어떻게 반영되었는지 볼 수 있습니다.

예)
*CLI> sip show peers
Name/username Host Dyn Nat ACL Port Status
0000FFFF0001/0000FFFF0001 192.168.1.100 D N 5060 Unmonitored
0000FFFF0002/0000FFFF0002 192.168.1.101 D N 5060 Unmonitored

* 혹시 sip.conf가 시스템이 이미 존재해서 이 문서에서 추가된 내용을 어떻게 넣어야 할지 고민하시는 분이 있다면 mv로 원 화일을 과감히 백업하시고 이 문서에 있는 예제로 대체해서 테스트해 주세요.


2.2 extensions.conf 설정

------------------------------------------------------------------------------------
[LocalSets]
exten => 100,1,Dial(SIP/0000FFFF0001) ; Replace 0000FFFF0001 with your device name
exten => 101,1,Dial(SIP/0000FFFF0002) ; Replace 0000FFFF0002 with your device name

exten => 200,1,Answer()
    same => n,Playback(hello-world)
    same => n,Hangup()
------------------------------------------------------------------------------------

책의 5장에 extensions.conf의 각 라인에 대한 자세한 설명은 없습니다. 하지만 간단하죠. LocalSets 섹션은 sip.conf의 context에서 나왔던 설정값이죠. 그 부분이 여기로 점프하도록 되어 있는거죠. 그리고 내선(extension) 번호 100번은 SIP 계정, 0000FFFF0001으로 101번은 0000FFFF0002으로 맵핑되는 것입니다. 중간에 1은 뭔지 모르겠어요. 그리고 200번 번호는 테스트용으로 전화를 걸면 자동으로 전화수신이 되고 1초 정도 후에 자동으로 끊어집니다. 전화를 걸고 바로 수화기를 귀에 대면 "Hello"라는 여성의 목소리를 들을 수 있습니다. asterisk 콘솔에는 전화가 걸려왔다는 표시가 되고 hello-world를 뿌리고 전화가 끊어진다는 표시가 나와요..

화일을 저장한 후 아래 명령을 이용해 변경된 사항을 적용합니다.
*CLI> dialplan reload

이제 전화할 준비가 되었군요. 100번으로 설정된 전화에서 101번으로 전화를 걸어보세요. 코덱이 나빠서 그런데 조금씩 끊기고 딜레이도 좀 있는 것 같지만, 벨도 울리고 전화도 됩니다. 감격입니다.

[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 ]