개발/자바

jNetServer Socket Framework

은우 아빠 2008. 12. 10. 17:59

jNetServer1.0 Readme

 

     Network Server Application을 구현할 때 마다 느끼는 것이지만 맨날 반복되는 코드에
    지겹다는 생각을 많이 했다. 물론 초보자에게는 Thread, Socket, IO 관련 프로그램
    이 어렵게 느껴지겠고, 그리고, Thread Pool, Concurrency Issue등의 문제도 골치가 
    아플것이다. 아마도 이런 이유 때문에 application server를 쓰는지도 모르겠다. 하
    지만 application server에서는 Socket Connector는 제공하지 않는다.
     어째든 server application을 재활용하기 위한 차원에서 Socket Server Framework
    을 만들어 보았다. 요즘 프로젝트가 다 웹이라 이런 Socket server를 얼마나 많이 개발
    할지는 모르겠지만, 지금 참여하고 있는 프로젝트(EAI)에서는 중요한 부분으로 사용
    되고 있다. (2003.11.23 일요일)
 
 written by Jeon HongSeong [hsjeon70@dreamwiz.com]      
jNetServer1.0 Java Documentation API
 

1. 개 요

 

 ◆ jNetServer1.0 개발 시 이용한 open source List
- MX4J-1.1 : JMX1.0 Reference Implementation - Jakarta Log4J-1.2.8 - Jakarta Common Digester-1.5 - Jakarta Common Pooling-1.1 - Jakarta Tomcat source 일부
 

 ◆ jNetServer Framework은 Java Network Server Programming에 대한 Basic 
    Infrastructure를 제공한다.
 ◆ Network Server Application을 구현할 때 필요한 Thread Pooling이나, Object 
    Pooling, Logger 등의 기능을 제공한다.
 ◆ API에 정의된 NetTask 내 클라이언트로부터 데이터를 read하고, 결과를 write하는
    로직만 간단히 구현하면 된다.
 ◆ Configuration 설정만으로 Network Server Connector, InputAdapter, Object 
    Pool이 생성 초기화 된다.
 ◆ 클라이언트 시스템(IP) 별로 접근 권한 및 통신 프로토콜을 다르게 정의할 수 
    있다.
 ◆ 모든 Java Object들이 JMX MBean object로 관리되고, MX4J에서 제공하는 JMX Http
    Admin 관리 콘솔을 제공한다.
 ◆ 관리 콘솔 상에서 서버 및 모든 MBean object를 제어 할 수 있다.
 ◆ JMX Monitor Bean을 이용해 Connector의 상태를 Counter, Gauge 방식으로 모니터
    링 할 수 있다.
 ◆ jNetServer Framework 내에서 configuration 설정 만으로 SSL(Secure Socket Layer)
    를 지원한다. SSL용 NetTask의 구현은 일반 Socket 일때와 동일하다.
 ◆ NetTask의 확장으로 프락시 서버, 로드 발런스 등의 서버를 쉽게 구현할 수 있다.
	
 

2. 설치 및 실행

 
 ◆ jNetServer1.0-app.jar 파일을 c:\jNetServer1.0 디렉토리 밑에 압축을 해제한다.
	
C:\jNetServer1.0>jar xvf jNetServer1.0-app.jar
C:\jNetServer1.0>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: 3D26-12D4

C:\jNetServer1.0 디렉터리

2003-11-29  12:48a      <DIR>          .
2003-11-29  12:48a      <DIR>          ..
2003-11-29  12:48a                 637 runSsl.bat
2003-11-29  12:48a                 789 startup.bat
2003-11-29  12:48a                  18 lcp.bat
2003-11-29  12:48a                 705 runMulti.bat
2003-11-29  12:48a                 720 stop.bat
2003-11-29  12:48a                 645 runEcho.bat
2003-11-29  12:48a      <DIR>          logs
2003-11-29  12:48a      <DIR>          docs
2003-11-29  12:48a      <DIR>          server
2003-11-29  12:48a      <DIR>          common
2003-11-29  12:48a      <DIR>          config
              6개 파일           3,514 바이트
              7 디렉터리   2,378,612,736 바이트 남음
 
 ◆ jNetServer1.0/config/server.xml에 Server 태그의 address 속성을 설치 시스템의 
    IP 주소로 변경한다.	

   <ServerGroup>
         <Server info="jNetServer1.0" 	name="svr1" 
                     address="192.168.0.13" 	port="8110" 
                     mode="standalone"	shutdown="SHUTDOWN">

 ◆ startup.bat 파일을 실행하여 jNetServer를 start 시킨다. 이때 keystore file의 
 비밀번호를 입력해야 하는데 "java11"을 입력한다. 

C:\jNetServer1.0>startup
CLASSPATH=.;D:\bea\weblogic81\server\lib\weblogic.jar;server\lib\jNetServer1.0.j
ar;server\lib\jakarta\commons-configuration-0.8.1.jar;server\lib\jakarta\commons
-digester.jar;server\lib\jakarta\commons-net-1.0.1-dev.jar;server\lib\jakarta\co
mmons-daemon.jar;server\lib\jakarta\commons-collections.jar;server\lib\jakarta\c
ommons-logging-api.jar;server\lib\jakarta\commons-beanutils.jar;server\lib\jakar
ta\commons-logging.jar;server\lib\jakarta\commons-pool-1.1.jar;server\lib\jakart
a\commons-dbcp.jar;server\lib\jakarta\commons-lang.jar;server\lib\log4j\log4j-1.
2.8.jar;server\lib\mx4j\mx4j-jmx.jar;server\lib\mx4j\mx4j-tools.jar;common\class
es;config
[00:51:29]  INFO - @ jNetServer1.0 @@@@@@@@@@@@@@@@@@@@@@@@@@
[00:51:29]  INFO - >> jnet.home=.
[00:51:29]  INFO - >> jnet.server=svr1
[00:51:29]  INFO - >> Logger=jnet.logger
Enter the password of the keystore file : java11

[00:54:12]  INFO - >> Including directory C:\jNetServer1.0\.\common\classes
[00:54:12]  INFO - StandardConnector{7130} listen 192.168.0.13
[00:54:12] DEBUG - EchoTask#taskCreate()- EchoTask{echo1}[0]
[00:54:12] DEBUG - EchoTask#taskPassivate()- EchoTask{echo1}[0]
[00:54:12] DEBUG - EchoTask#taskCreate()- EchoTask{echo1}[1]
[00:54:12] DEBUG - EchoTask#taskPassivate()- EchoTask{echo1}[1]
[00:54:12] DEBUG - EchoTask#taskCreate()- EchoTask{echo1}[2]
[00:54:12] DEBUG - EchoTask#taskPassivate()- EchoTask{echo1}[2]
[00:54:12]  INFO - StandardTask{echo1} started
[00:54:12]  INFO - StandardInputAdapter{everyone} started
[00:54:12]  INFO - StandardMonitor[idleHandlers] started
[00:54:12]  INFO - StandardMonitor[curHandlers] started
[00:54:12]  INFO - Handler{7130}[0] has been started
[00:54:12]  INFO - Handler{7130}[1] has been started
[00:54:12]  INFO - Handler{7130}[2] has been started
[00:54:12]  INFO - Handler{7130}[3] has been started
[00:54:12]  INFO - Handler{7130}[4] has been started
[00:54:12]  INFO - StandardConnector{7130} started
[00:54:12]  INFO - StandardConnector{7131} listen 192.168.0.13
[00:54:13] DEBUG - SSLProxyTask#taskCreate()- SSLProxyTask{proxy}[0]
[00:54:13] DEBUG - SSLProxyTask#taskPassivate()- SSLProxyTask{proxy}[0]
[00:54:13] DEBUG - SSLProxyTask#taskCreate()- SSLProxyTask{proxy}[1]
[00:54:13] DEBUG - SSLProxyTask#taskPassivate()- SSLProxyTask{proxy}[1]
[00:54:13] DEBUG - SSLProxyTask#taskCreate()- SSLProxyTask{proxy}[2]
[00:54:13] DEBUG - SSLProxyTask#taskPassivate()- SSLProxyTask{proxy}[2]
[00:54:13] DEBUG - SSLProxyTask#taskCreate()- SSLProxyTask{proxy}[3]
[00:54:13] DEBUG - SSLProxyTask#taskPassivate()- SSLProxyTask{proxy}[3]
[00:54:13] DEBUG - SSLProxyTask#taskCreate()- SSLProxyTask{proxy}[4]
[00:54:13] DEBUG - SSLProxyTask#taskPassivate()- SSLProxyTask{proxy}[4]
[00:54:13]  INFO - StandardTask{proxy} started
[00:54:13]  INFO - StandardInputAdapter{192.168.0.13} started
[00:54:13] DEBUG - SSLEchoTask#taskCreate()- SSLEchoTask{echo2}[0]
[00:54:13] DEBUG - SSLEchoTask#taskPassivate()- SSLEchoTask{echo2}[0]
[00:54:13] DEBUG - SSLEchoTask#taskCreate()- SSLEchoTask{echo2}[1]
[00:54:13] DEBUG - SSLEchoTask#taskPassivate()- SSLEchoTask{echo2}[1]
[00:54:13] DEBUG - SSLEchoTask#taskCreate()- SSLEchoTask{echo2}[2]
[00:54:13] DEBUG - SSLEchoTask#taskPassivate()- SSLEchoTask{echo2}[2]
[00:54:13] DEBUG - SSLEchoTask#taskCreate()- SSLEchoTask{echo2}[3]
[00:54:13] DEBUG - SSLEchoTask#taskPassivate()- SSLEchoTask{echo2}[3]
[00:54:13] DEBUG - SSLEchoTask#taskCreate()- SSLEchoTask{echo2}[4]
[00:54:13] DEBUG - SSLEchoTask#taskPassivate()- SSLEchoTask{echo2}[4]
[00:54:13]  INFO - StandardTask{echo2} started
[00:54:13]  INFO - StandardInputAdapter{everyone} started
[00:54:13]  INFO - Handler{7131}[0] has been started
[00:54:13]  INFO - Handler{7131}[1] has been started
[00:54:13]  INFO - Handler{7131}[2] has been started
[00:54:13]  INFO - Handler{7131}[3] has been started
[00:54:13]  INFO - Handler{7131}[4] has been started
[00:54:13]  INFO - StandardConnector{7131} started
[00:54:13]  INFO - StandardServer{svr1} started
[00:54:13]  INFO - StandardServerGroup started
[00:54:13]  INFO - ConsoleGaugeListener>> MonitorNotification [ sequence=1, time
Stamp=Sat Nov 29 00:54:13 KST 2003, type=jmx.monitor.gauge.low, userData=null, m
essage=, derivedGauge=5, observedObject=jnet.server:name=svr1/con7130, observedA
ttribute=IdleHandlers, trigger=5, source=javax.management.monitor.GaugeMonitor@c
623af ]
[00:54:13]  INFO - ConsoleCounterListener>> MonitorNotification [ sequence=1, ti
meStamp=Sat Nov 29 00:54:13 KST 2003, type=jmx.monitor.counter.threshold, userDa
ta=null, message=, derivedGauge=5, observedObject=jnet.server:name=svr1/con7130,
 observedAttribute=CurHandlers, trigger=3, source=javax.management.monitor.Count
erMonitor@50ca0c ]
	

3. Admin Console

 
 ◆ jNetServer가 실행되면, Http Admin Console에 접근해 관리할수 있다.
 
 	http://192.168.0.13:8080
 	
    브라우저로 8080 포트로 접근해 보면 아래와 같이 로그온 화면이 나타나는데, 
    jlook/jlook으로 오그온을 한다. admin 계정과 비밀번호는 은 config/server.xml
    에 설정되어 있다.
    
 	
 ◆ 로그인하면 start된 jNetServer의 JMX MBean object를 관리할수 있는 기능과 상태
    를 모니터링 할 수 있는 화면을 제공한다. 	
    
	

4. EchoTask Bean 및 테스트

 
 ◆ 설치된 jNetServer에는 예제로 EchoTask Bean이 제공된다. Task Bean의 
    개발은 jlook.jnet.task.NetTask 인터페이스를 구현하면 된다. 다음은 NetTask의 
    소스이다.       
    
     package jlook.jnet.task;
     
     import java.io.IOException;
     import java.io.BufferedInputStream;
     import java.io.BufferedOutputStream;
     
     /**
      * Socket 요청을 받아 처리할 Beans를 구정의하기 위한 NetTask interface 
      *
      * @since 	jNetServer1.0
      * @author 	HongSeong Jeon(hsjeon70@dreamwiz.com)
      */
     public interface NetTask {
     	/**
     	 * NetTask object의 id를 반환한다.
     	 *	
     	 * @return	id
     	 */
     	public String getId();	
     		
     	/**
     	 * NetTask Beans object 생성 후 callback
     	 */
     	public void taskCreate();
     	
     	/**
     	 * NetTask Beans object가 요청에 의해 pooling으로 부터 나와 할당된 
     	 * 후 callback
     	 */
     	public void taskActivate();
     	
     	/**
     	 * NetTask Beans object가 요청을 처리하고, pooling으로 반환된 후 
     	 * callback
     	 */
     	public void taskPassivate();
     	
     	/**
     	 * NetTask Beans object가 pooling으로 부터 deallocated 될때 callback
     	 */
     	public void taskDestroy();
     	
     	
     	/**
     	 * 클라이언트의 요청을 받아 NetContext object를 초기화하기 위해 
     	 * 실행된다.
     	 *
     	 * @param	context	NetContext object
     	 */
     	public void setContext(NetContext context);
     	
     	/**
     	 * 클라이언트의 요청에 대한 처리로직을 위해 실행된다.
     	 *	
     	 * @param	in	BufferedInputStream object
     	 * @param	out	BufferedOutputStream object
     	 * @exception	TaskException
     	 */
     	public void doTask(BufferedInputStream in, BufferedOutputStream out)
     	throws TaskException;
     	
     	/**
     	 * 클라이언트의 요청에 대한 처리로직을 실행한 후 후처리 작업을 
     	 * 위해 실행된다.
     	 *
     	 * @param	success	doTask() 메서드에서 Exception의 발생여부를 
     	 * 		나타내는 flag
     	 */
     	public void doEnd(boolean success);
     }
- taskCreate() : Task Bean이 생성되고, Bean의 초기화 작업을 위해 호출된다. - taskDestory() : Task Bean이 Object Pool에서 삭제될 때 초기화 작업의 undo를 위해 호출된다. - taskActivate() : Task Bean이 Object Pool에 반환될때 호출된다. - taskPassivate() : Task Bean이 Object Pool로 부터 나와 서비스 되기 직전에 호출된다. - setContext() : 클라이언트의 요청 시 클라이언트의 정보를 갖는 Context 정보를 초기화 하기 위해 호출된다.\ - doTask() : 클라이언트 요청시 setContext() 가 실행된 다음 호출된다. 파라미터의 InputStream, OutputStream을 이용해 실재 해당 요청의 처리로직을 구현한다. - doEnd() : 후처리 작업을 위해 마지막에 호출된다. ◆ NetTask의 구현은 실재 NetTaskSupport 클래스를 상속받아 정의한다. 다음은 제공 되는 EchoTask의 소스이다.
    
     package jlook.jnet.task;
     
     import java.io.*;
     
     import jlook.jnet.Keys;
     import jlook.jnet.util.Logger;
     
     public class EchoTask extends NetTaskSupport {
     	private static Logger logger = Logger.getLogger(Keys.LOGGER);
     	
     	public void taskCreate() {
     		logger.debug("EchoTask#taskCreate()- "+id);	
     	}
     	
     	public void taskActivate(){
     		logger.debug("EchoTask#taskActivate()- "+id);	
     	}
     	
     	public void taskPassivate(){
     		logger.debug("EchoTask#taskPassivate()- "+id);	
     	}
     	
     	public void taskDestroy(){
     		logger.debug("EchoTask#taskDestroy()- "+id);	
     	}
     	
     	public void setContext(NetContext context) {
     		super.setContext(context);
     		logger.debug("EchoTask#setContext()- "+id);	
     	}
     	
     	public void doTask(BufferedInputStream in, BufferedOutputStream out)
     	throws TaskException {
     		Logger logger = context.getLogger();
     		logger.debug("EchoTask#doTask()- "+id);	
     		byte[] msg = new byte[100];
     		logger.debug(id+"#doTask()- read....");
     		
     		try {
 		   int len = in.read(msg);
 		   String str = new String(msg,0,len);
 	
 		   logger.debug(id+"#doTask()- received msg>> "+str);
 		   str = "Hi... "+str;
 		   byte[] rt = str.getBytes();
 		   out.write(rt, 0, rt.length);
     		} catch(Exception e) {
     		   throw new TaskException(e.getMessage());
     		}
     	}	
     	
     	public void doEnd(boolean success) {
     		logger.debug("EchoTask#doEnd()- "+id);	
     		
     	}
     	
     	public String toString() {
     		return "SampleNetTask>>"+id;	
     	}
     }
◆ 개발된 NetTask Bean은 config/server.xml에 다음과 같이 설정해야 한다.
    
     <InputAdapter source="everyone" 		
     		 connectionTimeout="10000" 	
     		 sendBufferSize="10240"
     		 receiveBufferSize="10240"
     	  	 className="jlook.jnet.connector.StandardInputAdapter">     
     	<Task 	name="echo1"	initSize="5"	maxSize="50"
     		className="jlook.jnet.connector.StandardTask"
     		beanClass="jlook.jnet.task.EchoTask"/>
     			
    </InputAdapter>
- name : unique name - initSize : object pool의 initial size - maxSize : object pool의 maximum size - className : Tag에 대한 정보를 갖는 Bean으로 위와 같이 반드시 제공되는 StandardTask 클래스를 설정한다. - beanClass : 개발한 NetTask Bean class를 설정한다. ◆ 다음은 EchoTask에 대한 Client 프로그램이다.
    
     package jlook.jnet.task;
     
     import java.io.*;
     import java.net.*;
     
     public class EchoClient implements Runnable {                            
     	private String server;
     	private int    port;
     	private String message;
     	
     	private boolean success;
     	private Socket sck;
     		
     	public EchoClient(String server, int port, String message) 
     	throws Exception {
	   this.server = server;
	   this.port = port;
	   this.message = message;	
	   try {
	   	sck = new Socket(server, port);
	   } catch(Exception e) {
	   	System.out.println(Thread.currentThread().getName() + 
	   	" Error>> "+e.toString());
	   	sck = new Socket(server, port);
	   }
     	}
     	
     	public static void main(String[] args) throws Exception {
	   if(args.length!=3) {
	   	System.out.println("usage : java EchoClient "+
	   	"  ");
	   	return;
	   }
	   
	   int port = -1;
	   try {
	   	port = Integer.parseInt(args[1]);
	   } catch(Exception e) {
	   	System.out.println("invalid port - "+port);
	   	throw e;
	   }
	   
	   EchoClient ec = new EchoClient(args[0], port, args[2]);
	   Thread t = new Thread(ec);
	   t.start();
     	}
     	
     	public boolean isSuccess() {
     		return success;	
     	}
     	
     	public void run() {
   	   InputStream in = null;
   	   OutputStream out = null;
   	   try {
   	     in = sck.getInputStream();
   	     out = sck.getOutputStream();
   	     	
   	     byte[] b = message.getBytes();
   	     out.write(b, 0, b.length);
   	     	
   	     byte[] buff = new byte[100];
   	     int len = in.read(buff);
   	     
   	     System.out.println(Thread.currentThread().getName()+
   	     "] "+ new String(buff, 0, buff.length));
   	     success = true;
   	     } catch(Exception e) {
   	     success = false;
   	     System.out.println(Thread.currentThread().getName() + 
   	     " Error>> "+e.toString());
   	     e.printStackTrace();
   	   } finally {
   	     try { notifyAll();} catch(Exception e){}	
   	     try { if(in!=null) in.close();} catch(Exception e) {}
   	     try { if(out!=null) out.close();} catch(Exception e) {}
   	     try { if(sck!=null) sck.close();} catch(Exception e) {}
   	   }
     	}
     }    
◆ 위 EchoClient를 테스트 하기 위해 runEcho.bat 파일 내 ip를 수정하고, 다음과 같이 runEcho.bat를 실행한다. C:\jNetServer1.0>runEcho CLASSPATH=.;D:\bea\weblogic81\server\lib\weblogic.jar;lib\jNetServer1.0.jar; classes;config Thread-1] Hi... hongseong ◆ 다음은 jNetServer쪽 콘솔에 나타난 로그 내용이다. 이 로그를 통해 NetTask에 정의된 메서드가 언제 실행되는지 이해할 수 있다. [21:32:48] DEBUG - @ request client address : 192.168.0.13 [21:32:48] DEBUG - StandardInputAdapter#doExecute() is started [21:32:48] DEBUG - EchoTask#taskActivate()- EchoTask{echo1}[4] [21:32:48] DEBUG - EchoTask#setContext()- EchoTask{echo1}[4] [21:32:48] DEBUG - EchoTask#doTask()- EchoTask{echo1}[4] [21:32:48] DEBUG - EchoTask{echo1}[4]#doTask()- read.... [21:32:48] DEBUG - EchoTask{echo1}[4]#doTask()- received msg>> hongseong [21:32:48] DEBUG - EchoTask#doEnd()- EchoTask{echo1}[4] [21:32:48] DEBUG - EchoTask#taskPassivate()- EchoTask{echo1}[4] [21:32:49] DEBUG - Handler{7130}[4] process time : 0.38 sec
 

5. SSL 설정 및 테스트

 
 ◆ config/server.xml의 내용을 보면 다음과 같이 7131 포트가 SSL 로 설정되어 있다.	
	
    
<Connector name="con7131" className="jlook.jnet.connector.StandardConnector" 
	    	port="7131" 		ssl="true"
	        enableLookups="true" acceptCount="50"
	        minHandlers="5" 		maxHandlers="50">
	<InputAdapter 	source="everyone" 		
		connectionTimeout="10000" 	
		sendBufferSize="10240"
		receiveBufferSize="10240"
	  	className="jlook.jnet.connector.StandardInputAdapter">
		
		<Task 	name="echo2" initSize="5" maxSize="50"
		  className="jlook.jnet.connector.StandardTask"
		  beanClass="jlook.jnet.task.SSLEchoTask"/>
				
	</InputAdapter>		
	
	<InputAdapter 	source="192.168.0.10" 		
		connectionTimeout="10000" 	
		sendBufferSize="10240"
		receiveBufferSize="10240"
	  	className="jlook.jnet.connector.StandardInputAdapter">
		
		<Task 	name="proxy" initSize="5" maxSize="50"
		  className="jlook.jnet.connector.StandardTask"
		  beanClass="jlook.jnet.task.SSLProxyTask">
		  <Parameter name="target.server" value="192.168.0.13"/>
		  <Parameter name="target.port"	 value="7130"/>
		  <Parameter name="buffer.size"	 value="9"/>
		</Task>
						
	</InputAdapter>
◆ 7131이 SSL로 설정되어 있으므로 앞에서 실행했던 runEcho.bat 파일의 port를 7131로 수정 후 실행해 보면 다음과 같이 에러가 발생한다. ### Client console] C:\jNetServer1.0>runEcho CLASSPATH=.;D:\bea\weblogic81\server\lib\weblogic.jar;server\lib\jNetServer1.0.j ar;common\classes;config Thread-1] ### jNetServer console] [01:16:24] ERROR - Unrecognized SSL message, plaintext connection? javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection? at com.sun.net.ssl.internal.ssl.InputRecord.b(DashoA6275) at com.sun.net.ssl.internal.ssl.InputRecord.read(DashoA6275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA6275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.j(DashoA6275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(DashoA6275) ◆ runssl.bat 파일을 오픈해 ip{192.168.0.13}를 시스템에 맞게 변경하고, 실행해 보면 다음과 같은 결과를 확인해 볼수 있다. ### Client console] C:\jNetServer1.0>runssl CLASSPATH=.;D:\bea\weblogic81\server\lib\weblogic.jar;server\lib\jNetServer1.0.j ar;common\classes;config connect to the server.[192.168.0.13, 7131] received message>> Hello hongseong ### jNetServer console] [01:27:12] DEBUG - @ request client address : 192.168.0.13 [01:27:12] DEBUG - StandardInputAdapter#doExecute() is started [01:27:12] DEBUG - InputAdapter>>svr1/con7131/everyone [01:27:12] DEBUG - StandardInputAdapter#doExecute()- start handshake [01:27:12] DEBUG - SSLEchoTask#taskActivate()- SSLEchoTask{echo2}[4] [01:27:12] DEBUG - SSLEchoTask#setContext()- SSLEchoTask{echo2}[4] [01:27:12] DEBUG - SSLEchoTask#doTask()- SSLEchoTask{echo2}[4] [01:27:12] DEBUG - SSLEchoTask{echo2}[4]#doTask()- read.... [01:27:12] DEBUG - SSLEchoTask{echo2}[4]#doTask()- received msg>> hongseong [01:27:12] DEBUG - SSLEchoTask#taskPassivate()- SSLEchoTask{echo2}[4] [01:27:12] DEBUG - Handler{7131}[4] process time : 0.431 sec ◆ 클라이언트 시스템이 192.168.0.13 이므로 클라이언트 시스템에 대하 InputAdapter가 설정되어 있지 않기 때문에 default인 "everyone"의 InputAdapter가 동작한 것이다. 그리고, Task beans는 SSLEchoTask가 실행되었고, 그 소스는 아래와 같다. SSL 관련 추가 코드는 필요하지 않다.
package jlook.jnet.task;

import java.io.*;

import jlook.jnet.Keys;
import jlook.jnet.util.Logger;

public class SSLEchoTask extends NetTaskSupport {
	private static Logger logger = Logger.getLogger(Keys.LOGGER);
	
	public void taskCreate() {
		logger.debug("SSLEchoTask#taskCreate()- "+id);	
	}
	
	public void taskActivate(){
		logger.debug("SSLEchoTask#taskActivate()- "+id);	
	}
	
	public void taskPassivate(){
		logger.debug("SSLEchoTask#taskPassivate()- "+id);	
	}
	
	public void taskDestroy(){
		logger.debug("SSLEchoTask#taskDestroy()- "+id);	
	}
	
	public void setContext(NetContext context) {
		super.setContext(context);
		logger.debug("SSLEchoTask#setContext()- "+id);	
	}
	
	public void doTask(BufferedInputStream in, BufferedOutputStream out) 
	throws TaskException {
		Logger logger = context.getLogger();
		logger.debug("SSLEchoTask#doTask()- "+id);	
		byte[] msg = new byte[100];
		logger.debug(id+"#doTask()- read....");
		
		try {
			int len = in.read(msg);
			String str = new String(msg,0,len);
		
			logger.debug(id+"#doTask()- received msg>> "+str);
			str = "Hi... "+str;
			byte[] rt = str.getBytes();
			out.write(rt, 0, rt.length);
		} catch(Exception e) {
			logger.error(e.getMessage(),e);
			throw new TaskException(e.getMessage());
		}
	}	
	
	public void doEnd(boolean success) {
		logger.debug("SSLEchoTask#doEnd()- "+id);	
		
	}
	
	public String toString() {
		return "SampleNetTask>>"+id;	
	}
}
◆ 다음은 위에서 테스트한 ssl Client application 소스이다. 서버와 동일한 keystore 파일을 이용한 것을 알 수 있다.
package jlook.jnet.task;

import java.io.*;
import java.net.*;
import java.security.*;
import javax.net.*;
import javax.net.ssl.*;

public class SSLEchoClient {
	
  public static void main(String[] args) throws Exception {
	  System.setProperty("javax.net.ssl.trustStore", 
	    "config/jnet.keystore"); 
	  
	  if(args.length!=2) {
	  	System.out.println("java SSLEchoClient  ");
	  	return;	
	  }
	  
	  System.out.println("connect to the server.["+
	    args[0]+", "+args[1]+"]");
	  int port = Integer.parseInt(args[1]);
	  
	  SSLContext ctx;
	  KeyManagerFactory kmf;
	  KeyStore ks;
	  char[] passphrase = "java11".toCharArray();
      
	  ctx = SSLContext.getInstance("TLS");
	  kmf = KeyManagerFactory.getInstance("SunX509");
	  ks = KeyStore.getInstance("JKS");
      
	  ks.load(new FileInputStream("config/jnet.keystore"), passphrase);
      
	  kmf.init(ks, passphrase);
	  ctx.init(kmf.getKeyManagers(), null, null);
      
	  SSLSocketFactory factory = ctx.getSocketFactory();
      SSLSocket sck = (SSLSocket)factory.createSocket(args[0], port);
	  sck.setEnabledCipherSuites(sck.getSupportedCipherSuites());
	  sck.startHandshake();
	  
	  BufferedInputStream in = 
	  	new BufferedInputStream(sck.getInputStream());
	  BufferedOutputStream out = 
	  	new BufferedOutputStream(sck.getOutputStream());
	  
	  String msg = "hongseong";
	  byte[] tmp = msg.getBytes();
	  
	  out.write(tmp, 0, tmp.length);
	  out.flush();
	  
	  byte[] buff = new byte[1024];
	  int len = in.read(buff);
	  
	  System.out.println("received message>> "+new String(buff, 0, len));
	  out.close();
	  in.close();
	  sck.close();	

  }
}
 

6. SSLProxyTask Beans

 
 ◆ NetTask 응용으로 Proxy Server Beans를 소개한다. 서버를 7130, SSL 7131로 서비스 할때
    7131로 들어온 요청을 7130쪽으로 forwarding하는 SSLProxyTask의 소스와 설정을 살펴보자.
    다음은 server.xml의 7131쪽 Connector 설정 부분인데, ssl="true"로 설정된 것을 
    확인할 수 있다. 그리고, 앞 예제와는 다르게 InputAdapter의 source가 현 시스템의 
    ip{192.168.0.13}로 변경된것을 확인할 수 있다. 즉, 192.168.0.13 시스템의 클라이
    언트의 요청을 SSLProxyTask 가 처리한다는 것을 설정한 것이다.
	
    
<Connector name="con7131" className="jlook.jnet.connector.StandardConnector" 
	    	port="7131" 		ssl="true"
	        enableLookups="true" acceptCount="50"
	        minHandlers="5" 		maxHandlers="50">
	<InputAdapter 	source="everyone" 		
		connectionTimeout="10000" 	
		sendBufferSize="10240"
		receiveBufferSize="10240"
	  	className="jlook.jnet.connector.StandardInputAdapter">
		
		<Task 	name="echo2" initSize="5" maxSize="50"
		  className="jlook.jnet.connector.StandardTask"
		  beanClass="jlook.jnet.task.SSLEchoTask"/>
				
	</InputAdapter>		
	
	<InputAdapter 	source="192.168.0.13" 		
	   connectionTimeout="10000" 	
	   sendBufferSize="10240"
	   receiveBufferSize="10240"
  	   className="jlook.jnet.connector.StandardInputAdapter">  
		
	   <Task 	name="proxy" initSize="5" maxSize="50"
	     className="jlook.jnet.connector.StandardTask"
	     beanClass="jlook.jnet.task.SSLProxyTask">
	     <Parameter name="target.server" value="192.168.0.13"/>  
	     <Parameter name="target.port"	 value="7130"/>
	     <Parameter name="buffer.size"	 value="9"/>
	   </Task>
						
	</InputAdapter>
◆ 이제 SSLProxyTask Beans의 소스를 살펴 볼것인데, 위에서 Task의 Parameter 태그는 Task Beans에 넘길 key/value 데이터를 설정한 태그이다. 포워딩할 서버 및 포트 정보를 설정했다. 이것을 어떻게 얻어 내었는지 살펴보기 바란다.
 
package jlook.jnet.task;

import java.io.*;
import java.net.*;

import jlook.jnet.Keys;
import jlook.jnet.util.Logger;

public class SSLProxyTask extends NetTaskSupport {
	private static Logger logger = Logger.getLogger(Keys.LOGGER);
	
	private String 	server;
	private int 	port;
	private int 	bufferSize;
	
	public static final String TARGET_SERVER 	= "target.server";
	public static final String TARGET_PORT 	= "target.port";
	public static final String BUFFER_SIZE 	= "buffer.size";
	
	public void taskCreate() {
		logger.debug("SSLProxyTask#taskCreate()- "+id);	
	}
	
	public void taskActivate(){
		logger.debug("SSLProxyTask#taskActivate()- "+id);	
	}
	
	public void taskPassivate(){
		logger.debug("SSLProxyTask#taskPassivate()- "+id);	
	}
	
	public void taskDestroy(){
		logger.debug("SSLProxyTask#taskDestroy()- "+id);	
	}
	
	public void setContext(NetContext context) {
		super.setContext(context);
		logger.debug("SSLProxyTask#setContext()- "+id);	
		server = context.getParameter(TARGET_SERVER);
		if(server == null) server = "127.0.0.1";
		try {
		  port = Integer.parseInt(context.getParameter(TARGET_PORT));
		} catch(Exception e) {
			port=80;
		}
		try {
		  bufferSize = Integer.parseInt(
		  		context.getParameter(BUFFER_SIZE));
		} catch(Exception e) {
			bufferSize=10240;
		}
	}
	
	public void doTask(BufferedInputStream in, BufferedOutputStream out) 
	throws TaskException {
		Logger logger = context.getLogger();
		logger.debug("SSLProxyTask#doTask()- "+id);	
		
		Socket proxy = null;
		BufferedInputStream 	pin  = null;
		BufferedOutputStream 	pout = null;
		try {
		  proxy= new Socket(server,port);
		  pin  = new BufferedInputStream(proxy.getInputStream());
		  pout = new BufferedOutputStream(proxy.getOutputStream());
		} catch(Exception e) {
		  String msg = "Cannot connect to target system>> "+
			e.getMessage();
		  logger.error(msg, e);
		  byte[] tmp = msg.getBytes();
		  try {
			out.write(tmp, 0, tmp.length);
			out.flush();
	  	  } catch(IOException ex){}
		  return;
		}
		
		try {
			byte[] buff = new byte[bufferSize];
			while(true) {
				int len = in.read(buff);
				if(len>"+id;	
	}
}
◆ 다음은 runssl.bat 파일의 실행 결과이다. 서버 콘솔을 보면 클라이언트 요청이 7131{SSLProxyTask}, 7130{EchoTask} 두번 처리된것을 확인할 수 있다. ### Client console] C:\jNetServer1.0>runssl CLASSPATH=.;D:\bea\weblogic81\server\lib\weblogic.jar;server\lib\jNetServer1.0.j ar;common\classes;config connect to the server.[192.168.0.13, 7131] received message>> Hello hongseong ### Server console] [01:54:29] DEBUG - @ request client address : 192.168.0.13 [01:54:29] DEBUG - StandardInputAdapter#doExecute() is started [01:54:29] DEBUG - InputAdapter>>svr1/con7131/192.168.0.13 [01:54:29] DEBUG - StandardInputAdapter#doExecute()- start handshake [01:54:29] DEBUG - SSLProxyTask#taskActivate()- SSLProxyTask{proxy}[4] [01:54:29] DEBUG - SSLProxyTask#setContext()- SSLProxyTask{proxy}[4] [01:54:29] DEBUG - SSLProxyTask#doTask()- SSLProxyTask{proxy}[4] [01:54:29] DEBUG - @ request client address : 192.168.0.13 [01:54:29] DEBUG - StandardInputAdapter#doExecute() is started [01:54:29] DEBUG - InputAdapter>>svr1/con7130/everyone [01:54:29] DEBUG - EchoTask#taskActivate()- EchoTask{echo1}[2] [01:54:29] DEBUG - EchoTask#setContext()- EchoTask{echo1}[2] [01:54:29] DEBUG - EchoTask#doTask()- EchoTask{echo1}[2] [01:54:29] DEBUG - EchoTask{echo1}[2]#doTask()- read.... [01:54:29] DEBUG - EchoTask{echo1}[2]#doTask()- received msg>> hongseong [01:54:29] DEBUG - EchoTask#doEnd()- EchoTask{echo1}[2] [01:54:29] DEBUG - EchoTask#taskPassivate()- EchoTask{echo1}[2] [01:54:29] DEBUG - SSLProxyTask#doEnd()- SSLProxyTask{proxy}[4] [01:54:29] DEBUG - SSLProxyTask#taskPassivate()- SSLProxyTask{proxy}[4] [01:54:29] DEBUG - Handler{7130}[4] process time : 0.01 sec [01:54:29] DEBUG - Handler{7131}[4] process time : 0.37 sec

 
 written by Jeon HongSeong