개발/자바 | Posted by 은우 아빠 2008. 12. 10. 17:59

jNetServer Socket Framework


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
개발/자바 | Posted by 은우 아빠 2008. 12. 9. 11:42

eclipse TCP/IP 모니터 사용하기


파이어폭스의 파이어버그나 ie의 피들러 또는 상용인 httpwatch 등을 통해서 웹브라우저에서 일어나는 통신의 안 보이는 부분을 볼 수 있습니다. 헤더 영역의 정보 같은 것이죠.

이클립스 WTP에서도 같은 기능을 지원합니다. TCP/IP 모니터뷰를 이용하면 지정된 포트에서 발생하는 교신 정보를 눈으로 확인할 수 있습니다.

설정은 다음과 같이 Preferences 에서 tcp 필터 단어를 입력하면 메뉴가 보입니다.
Add 버튼을 클릭하고 상단의 Local Monitoring port 를 8090 등 사용하지 않는 포트를 입력합니다. 모니터링할 서비스 정보를 입력합니다. 로컬의 톰캣을 모니터링하기 위해서 localhost 포트는 8080 이라고 입력했습니다.

사용자 삽입 이미지


등록을 마치면 해당 모니터링 항목을 선택하고 우측의 Start 버튼을 클릭해서 모니터링을 시작합니다.
사용자 삽입 이미지


모니터링하는 포트로 호출을 합니다. 8090이라고 정했기 때문에 http://localhost:8090/index.jsp 주소로 접근하면 TCP/IP Monitor 뷰가 자동으로 뜨게 됩니다.
사용자 삽입 이미지


TCP/IP Monitor 탭을 더블클릭해서 크게 확대해서 보면 서버로 요청한 주소 목록과 선택한 주소의 헤더 정보와 주고받은 내용들이 하단에 나옵니다.
사용자 삽입 이미지

이미지를 선택하면 해당 이미지도 볼 수 있습니다.
사용자 삽입 이미지


유용하게 쓰시기 바랍니다.
카테고리 없음 | Posted by 은우 아빠 2008. 12. 8. 10:38

[JAVA] NIO 를 이용한 Echo Server / Client


모든 네트워크 프로그래밍의 시작은 Echo 인듯 ...

어디서 긁어서 테스트 해본 소스라 올리기 힘듬

크크 누가 들어올라만은...

[ EchoClient.java ]

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

class EchoClient
{ public static void main( String[] args )
throws IOException
{
Socket sock = null;
try
{
sock = new Socket(args[0], Integer.parseInt(args[1]));
System.out.println(sock + ": 연결됨");
OutputStream toServer = sock.getOutputStream();
InputStream fromServer = sock.getInputStream();

byte[] buf = new byte[1024];
int count;
while( (count = System.in.read(buf)) != -1 )
{
toServer.write( buf, 0, count );
count = fromServer.read( buf );
System.out.write( buf, 0, count );
}
toServer.close();
while((count = fromServer.read(buf)) != -1 )
System.out.write( buf, 0, count );
System.out.close();
System.out.println(sock + ": 연결 종료");
} catch( IOException ex )

{
System.out.println("연결 종료 (" + ex + ")");
} finally
{
try
{
if ( sock != null )
sock.close();
} catch( IOException ex ) {}
}
}
}


///////////////////////////////////////////////////////////////////////////////////////////////////

[ EchoServer.java ]

import java.nio.channels.*;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.util.Set;
import java.util.Iterator;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.CharBuffer;

public class NIOServer implements Runnable {

//어떤 채널이 어떤 IO를 할 수 있는지 알려주는 클래스(Seelctor)
Selector selector;
int port = 9999;

//한글 전송용
Charset charset = Charset.forName("EUC-KR");
CharsetEncoder encoder = charset.newEncoder();

public NIOServer() throws IOException {

//Selector를 생성 합니다.
selector = Selector.open();

//ServerSocket에 대응하는 ServerSocketChannel을 생성, 아직 바인딩은 안됨
ServerSocketChannel channel = ServerSocketChannel.open();
//서버 소켓 생성
ServerSocket socket = channel.socket();

SocketAddress addr = new InetSocketAddress(port);
//소켓을 해당 포트로 바인딩
socket.bind(addr);

//Non-Blocking 상태로 만듬
channel.configureBlocking(false);

//바인딩된 ServerSocketChannel을 Selector에 등록 합니다.
channel.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("---- Client의 접속을 기다립니다... ----");
}

public void run() {
//SocketChannel용 변수를 미리 만들어 둡니다.
int socketOps = SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE;

ByteBuffer buff = null;

try {

//생성된 서버소켓채널에 대해 accept 상태 일때 알려달라고 selector에 등록 시킨 후
//이벤트가 일어날때 까지 기다립니다. 새로운 클라이언트가 접속하면 seletor는
//미리 등록 했던 SeerverSocketChannel에 이벤트가 발생했으므로 select 메소드에서
//1을 돌려줍니다. 즉 Selector에 감지된 이벤트가 있다면
while(selector.select() > 0) {

//현재 selector에 등록된 채널에 동작이 라나라도 실행 되는 경우 그 채널들을 SelectionKey의
//Set에 추가 합니다. 아래에서는 선택된 채널들의 키를 얻습니다. 즉 해당 IO에 대해 등록해
//놓은 채널의 키를 얻는 겁니다.
Set keys = selector.selectedKeys();
Iterator iter = keys.iterator();

while(iter.hasNext()) {
SelectionKey selected = (SelectionKey)iter.next();
//현재 처리하는 SelectionKey는 Set에서 제거 합니다.
iter.remove();

//channel()의 현재 하고 있는 동작(읽기, 쓰기)에 대한 파악을 하기 위한 겁니다.
SelectableChannel channel = selected.channel();
if(channel instanceof ServerSocketChannel) {

//ServerSocketChannel이라면 accept()를 호출해서
//접속 요청을 해온 상대방 소켓과 연결 될 수 있는 SocketChannel을 얻습니다.
ServerSocketChannel serverChannel = (ServerSocketChannel) channel;
SocketChannel socketChannel = serverChannel.accept();

//현시점의 ServerSocketChannel은 Non-Blocking IO로 설정 되어 있습니다.
//이것은 당장 접속이 없어도 블로킹 되지 않고 바로 null을 던지므로
//체트 해야 합니다.
if (socketChannel == null ){
System.out.println("## null server socket");
continue;
}

System.out.println("## socket accepted : " + socketChannel);

//얻어진 소켓은 블로킹 소켓이므로 Non-Blocking IO 상태로 설정 합니다.
socketChannel.configureBlocking(false);

//소켓 채널을 Selector에 등록
socketChannel.register(selector, socketOps);
}
else {
//일반 소켓 채널인 경우 해당 채널을 얻어낸다.
SocketChannel socketChannel = (SocketChannel) channel;
buff = ByteBuffer.allocate(100);

//소켓 채널의 행동을 검사해서 그에 대응하는 작업을 함
if (selected.isConnectable()){
System.out.println("Client와의 연결 설정 OK~");
if (socketChannel.isConnectionPending()){
System.out.println("Client와의 연결 설정을 마무리 중입니다~");
socketChannel.finishConnect();
}
}
//읽기 요청 이라면
else if(selected.isReadable()) {
//소켓 채널로 데이터를 읽어 들입니다.
try {
socketChannel.read(buff);

//데이터가 있다면
if (buff.position() != 0){
buff.clear();
System.out.print("클라이언트로 전달된 내용 : ");

//Non-Blocking Mode이므로 데이터가 모두 전달될때 까지 기다림
while(buff.hasRemaining()) {
System.out.print((char) buff.get());
}

buff.clear();
System.out.println();
//쓰기가 가능 하다면
if (selected.isWritable()){
String str = "이건 서버에서 보낸 데이터...";

//한글 인코딩
socketChannel.write(encoder.encode(CharBuffer.wrap(str + "
" )));
System.out.println("서버가 전달한 내용 : " + str);
}
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
socketChannel.finishConnect();
socketChannel.close();
}
}
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)throws IOException {
NIOServer s = new NIOServer();
new Thread(s).start();
}
}