본문 바로가기

공부/컴퓨터

TCP/IP Socket 프로그램 구현시 고려사항

http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=1009171849


제목 : TCP/IP Socket 프로그램 구현시 고려사항
글쓴이: 이원영(javaservice)   2001/12/24 14:30:49  조회수:6479  줄수:96
  
TCP/IP Socket 프로그램 구현시 고려사항

CPU나 메모리와 같은 자원은 항상 한계점이 있기 마련입니다. 그 한계점에 도달했을 때,
어떻게 동작케 하도록 조정하겠느냐의 문제가 매우 중요합니다. 이러한 고민의 여부가
때론 프로그래머의 수준이 실무적인 경험이 있느냐 그렇지 않느냐의 차이로 나타납니다.

TCP/IP Socket 프로그램을 짤때, 크게 수준에 따라 네가지 방법이 있습니다.

첫째, ServerSocket에서 accept()상태에서 대기하다가 accept()에서 요청이 떨어지면
그제서야 new YourThread(client) 를 생성하여 해당 Socket 요청을 처리하는 Thread를
만들고, ServerSocket은 다시 accept()의 while loop로 돌아가는 것이지요.
이것의 문제는 두가지인데, 하나는 매 요청마다 Thread가 생성되고 처리가 끝나면
GC에 의해 사라지므로 부하를 매우 많이 받는 구조라는 것입니다. 두번째 문제는, 이렇게
짰다는 것은 한계상황에 대한 처리구조가 없다는 것입니다. 몇개의 Thread가 Job을
동시에 처리하면 해당 H/W의 한계점에 이르를 것인지를 확인하고, 그 개수제한을 두어야
하는데, 전혀 이러한 고려가 없으니, 일정한 부하 이상의 상황에 직면하게 되면 요청이
지속적으로 큐잉된다거나 혹은 생각지 못했던 Exception들을 만나게 될 수도 있습니다.
어떤 자원이 고갈되어 한계상황에 직면했을 땐, 추가적인 요청들에 대해서는 fail을
발생하여 곧바로 return케 하여야만이 장애가 일어 나지 않습니다.
PS: 이를 Peek Point Contron 기법이라고 명명하였었습니다
PPC(Peek Point Control) 기법
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=consult&c=r_p&n=996642473

두번째 방법은 Thread Pooling을 사용하는 방식입니다. 대부분의 웹어플리케이션서버의
엔진소스에서 서블렛/JSP를 처리하는 내부적인 방식이 이렇게 되어 있습니다.
요청을 처리하는 Thread는 해당 프로그램이 처음 뜰 때 이미 50개면 50개, 100개면
100개가 모두 기동됩니다. 그 Thread들은 run()메소드에서 while loop를 돌며 무한루프에
빠져 있는데, run() 메소드 첫부분에서 모든 Thread들이 어떤 lock 객체에 synchronized
되어 wait()상태에 빠져 있습니다.
만약, ServerSocket의 accept()에서 요청이 들어오면, 기본적인 정보만 추출하여 이를
적당한 queue에 해당 client socket을 큐잉하고, Thread들이 wait()되어 있는 lock을
notify()/notifyAll() 시킵니다. 그러면 수 많은 Thread들 중 한 Thread가 깨어나
queue의 값을 꺼내어 SocketClient 요청을 처리하고 자신은 다시 무한루프에 의해
다시 wait() 됩니다.
이 같은 구조는 앞서의 문제를 모두 해결합니다. 즉, 매번 Thread를 생성하지 않으니
부하가 없고, 또, 초기에 몇개의 Thread들을 기동시켜두느냐는 결국 동시에 수행할
개수제한의 효과가 되는 것이지요. 그 개수는 Performance Tuning의 방법론에 따라
해당 H/W와 S/W환경에 맞게 적정값으로 조정되는 것이지요.

세번째 방법은 Socket Server측에서 Thread Pooling을 사용하는 것은 두번째 방법과
동일한데, Socket Client와 Server 사이의 TCP/IP Socket연결자체를 어떻게 Pooling
할 것인가의 문제가 추가됩니다. 지금까진 매번 Socket을 open하고 close하는 구조로
가져갔을 겁니다. 그러나, socket open은 부하가 걸리는 작업이므로 미리 socket을
열어두고 이를 pool에 넣어 둔 후, 필요시 pool에서 꺼내와 사용하는 것이 보다 효율적일
것입니다. 이는 Socket client와 server 측 모두에서 고려되어야 합니다. 5개면 5개,
10개면 10개 미리 Socket들이 맺어져 있고, 필요시 가져다 사용하고 모두 사용하고 난
뒤 곧바로 close하는 것이 아니라 pool로 돌려보내는 방법이지요.
이 방법은 socket을 매번 open/close하지 않으니 성능향상을 볼 수 있습니다. 그러나
단점은 Pool에 연결되어 있는 Socket들이 어떤 N/W 장애로 인해 물리적인 단절이 됐을
경우, 이를 어떻게 Recovery 할 것인가의 이슈가 추가로 고려되어야 합니다.

네번째 방법은 앞서 세번째가지의 기법이 모두 적용된 상태에서 다시 추가 됩니다.
즉, 앞서의 방법은 socket pool을 사용하므로, send를 날린 후 아직 receive를 받지 않는
동안 이 TCP/IP socket은 멍청하게 놀리고 있는 결과가 되고 맙니다. 즉, 실질적인
데이타는 이용되고 있지 않으면서도 매우 많은 수의 TCP/IP Socket이 필요하게 됩니다.
그래서 send 전용 socket과 receive전용 Socket을 별도로 구분하게 됩니다. 결국, send용
queue와 receive용 queue가 만들어져야 하고, client는 send용 queue에 넘길 데이타를
채운 후, send용 Thread들을 notify시킵니다. 곧바로 recevice queue에서 자신의 데이타가
오기를 기다려야 겠지요. Recieve용 Socket에 달려 있는 receive 용 Thread는 데이타가
오는 즉시 receive용 queue에 값을 채워넣고, 자신의 데이타가 오기를 기다리는 Thread
의 lock을 notify 시킵니다. (잘 깨워야겠지요, 모두 깨울것이냐, 혹은 해당 데이타에
관련된 것만 깨울 것인가가 고민되겠지요)
이 경우는 또한, send후 receive하기까지의 Timeout옵션을 지정할 수 있는 잇점도 가질
수 있습니다.
단, 이 경우의 단점은 성능은 가장 빠르지만, 서로 다른 데이타들이 영향을 미칠 수
있습니다. 앞서 보낸 데이타에 이용된 send socket이 장애를 일으키면 뒤따라 보낸 데이타
역시 깨어질 수 있는 것이지요.
또, 자칫 구현을 잘못하면, 동일한 Socket으로만 계속 데이타를 보내려는 시도를 하게
됩니다. OutputStream의 write() 는 순식간에 끝나지만, 실질적인 데이타는 OS나 네트웍
카드의 Send Queue에 대기중에 있을 수 있으며 아직 상대방에 받지 않았을 수 있습니다.
이 때, 같은 Socket에다 또 다시 데이타를 날리면 계속 큐잉만 일어나고, send용과
receive용을 구분했던 장점들을 제대로 살리지 못하게 되는 것이지요.


주고 받는 데이타는 반드시 header와 body로 구분된 약정된 프로토콜을 정의해서
사용해야 합니다. 예를 들면 10byte을 먼저 받아서 기본적인 정보와 body에 따라올
실제 데이타의 길이을 확인하여 해당 길이만큼의 body 데이타를 다시 읽는 것과 같은
동작이지요. 만약, header가 부정확한 정보들로 채워져 있거나 header에서 명시된 것보다
body데이타가 짧다면 적절한 에러처리를 해야 겠지요. OutputStream으로 단 한번에
write()를 한 byte[] 데이타들이, 받는 측에서 InputStream의 read(buf)를 통해 곧바로
받을 것이라고 여기는 경향이 있습니다.
N/W의 상황에 따라 몇 byte씩 나눠서 날아가지요. 에러가 나기전까지, timeout이 되기
전까지, 그리고 모든 데이타가 올 때 까지 loop를 돌면서 끝까지 받아내야 한다는 것을
아셔야 합니다. 아래글을 참고하세요.

Java Socket Client  read 시 data 한계
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=976117970


PS: 시간이 나면 참한 샘플들을 제공하겠지만, .....

================================================
  자바서비스넷 이원영
  E-mail: javaservice@hanmail.net
  PCS:011-898-7904
================================================


제목 : Re: TCP/IP Socket 프로그램 구현시 고려사항
글쓴이: 최용하(smashing)   2001/12/28 14:59:04  조회수:3121  줄수:23
  
안녕하세요. 이원영 과장님의 글 읽어보니 참 좋은 글이네요.
이 글을 애초에 볼 수 있었다면 그 수많은 시행착오를 거치지 않을 수 있었을텐데 ^^

밑에 첨부한 소스는 실제 모은행에서 인터넷뱅킹과 eCRM 시스템에서 사용되는 TCP/IP
Socket 통신 프로그램입니다.
제가 직접 구현하였으며 그동안 수많은 시행착오를 거치면서 정제되었습니다.
스레드풀링 방식으로 세번째에 해당하는 모델이고요 미약하나마 N/W 장애나
프로세스 상태로 인해 커넥션이 단절되었을 경우에 대한 Recovery 기능도 있습니다.

Peek Point Control 은 다음 네가지 부분에서 적절히 처리됩니다.
1. Established 되어있는 Socket Connection 을 항상 일정갯수 유지 (30개)
2. notify 를 받으려고 wait 하고 있는 스레드 갯수를 한정 (20개)
3. wait 하고 있는 스레드 시간 제약 (20초)
4. 호스트와의 타임아웃은 30초

- 소스역할
Connection.java - 호스트와 통신부분(send & receive 및 에러처리시 자원반환부분 중요)
BankObject.java - 생성된 소켓객체
BankQueue.java - 커넥션풀링을 할수 있게 소켓객체들을 담아놓은 큐

도움이 되었음 좋겠네요.