1. 다국어 패키지를 복사..

 1- 1. cmd >cd $FlexHome$\sdks\3.2.0\bin

 1 - 2. cmd > copylocale.exe en_US 적용할 언어[ex)ko_KR]


2. 제대로 생성됐는지 확인.

  2 - 1. cmd > cd $FlexHome$\3.2.0\frameworks\locale

  2 - 2. cmd > dir --> swc파일 생성 확인.

3. Flex Builder에서 설정.

  3 - 1. Project >>마우스 오른쪽 클릭 >> Properties >> Flex Compiler

  3 - 2. Additional compiler arguments  >> -locale en_US en_KR -source-path=locale/{locale}

     ( 경로 확인. - src 디렉토리 기준임.)

얼마전 프로젝트를 하면서 Tour de Flex 를 다운받은적이 있다..

이것저것 보는데
Localization 이라는게 있는것이다


플젝 하면서 다국어 버전을 만들라고 요청 온적이 있었는데..

우리는 그냥 Localizer.as 파일을 만들어서 일일히 변환 해주는 노가다를 했었다(물론 xml로 언어는 가져오지만 그래도 좀...)

Localization을 보니 플랙스에서 가지고 있는 locale을 바꿔 주는것이다..
오~! 이런 획기적인?

그래서 한번 찾아봤다..

Flex 3:Feature Introductions: Runtime Localization


오 이런게? 흠....

보면 알겠지만 (사실 나도 영어는 딸려서...)
fr_FR, en_US 버전 두개를 만들어 언어 변환을 해주는것이다

그럼 TEST 시작..

일단 en_US의 카피 본이 필요 하다
자신이 설치한 Flex Builder 에 가보면
난 디폴트로 설치를 해서 경로가 C:\Program Files\Adobe 이곳이다
그럼 기본으로 Flex 가 가지고 있는 locale 을 알아보자
C:\Program Files\Adobe\Flex Builder 3 Plug-in\sdks\3.2.0\frameworks\locale
으로 들어가보자
경로는 Bulider 설치 폴더\sdks\3.2.0\frameworks\locale 이다
그럼 en_US,ja_JP 폴더 두개가 있을것이다..

이게 플렉스에서 가지고 있는 두가지 버전의 locale 이다...하난 영어,하는 일어..(췟)

난 한국사람이기 때문에 영어,한국어 두가지의 locale 버전을 만들것이다..

그럼 기본으로 가지고 있는 locale 을 카피 한다

도스창을 연다 시작프로그램 >> 실행 >> cmd
C:\Program Files\Adobe\Flex Builder 3 Plug-in\sdks\3.2.0\bin
요기로 가서
copylocale.exe 원본locale 복사locale 이렇게 적는다
나는 copylocale.exe en_US en_KR 을 했다


이렇게 실행이 된다
다시
C:\Program Files\Adobe\Flex Builder 3 Plug-in\sdks\3.2.0\frameworks\locale
로 가보면 en_US, en_KR, ja_JP 가 있다
en_KR  폴더에 가면
automation_agent_rb.swc, datavisualization_rb.swc
파일만 없고 나머지 파일은 있는것을 확인할 수 있다 (저 위에 있는것은 bulider 정식 버전에서 쓰이나 차트나, AdvancedDataGrid등 이런것들이다..)

이제 빌더로 가보자~!

flex 에서 application.mxml 이 있는 경로에 locale 이란 폴더를 만들어 준다
그리고 언어 변환을 할 en_US,en_KR 두개의 폴더를 locale 폴더 안에 만들어준다
그리고 properties 파일을 만들어 준다
properties 파일은 java에서 HashMap,HashTable 과 같이 key = value 로 들어가는 프로퍼티들을 모은 파일이다 flex에서 따지자면 Object같은? Object도 Object.key = value; 로 할수 있으니까..

나는 jang.properties 파일을 만들것이다 (각각 en_US, en_KR 폴더에 하나씩 만들어 준다)
이제 properties 파일을 열고 KEY = VALUE를 넣어준다

이런구조?

여기서 주의해야 할점... properties 파일은 이클립스에서 기본적으로 만들때 파일 encoding이
ISO-8859-1 이다 저장을 할려면 안될것이다..그러므로 UTF-8로 변환을 해주어야 한다
(각 파일마다 전부 UTF-8로 변환을 해주었다면 알아서 UTF-8로 변환 해주니까 상관없음...

encoding 변환방법

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Metadata>
        [ResourceBundle("jang")]
    </mx:Metadata>
   
    <mx:Script>
        <![CDATA[
           
        [Bindable]
        private var locales:Array = [ "en_US" , "en_KR" ];

        private function localeComboBox_initializeHandler(event:Event):void
        {
            localeComboBox.selectedIndex = locales.indexOf(resourceManager.localeChain[0]);
        }
  
        private function localeComboBox_changeHandler(event:Event):void
        {
            resourceManager.localeChain = [ localeComboBox.selectedItem ];
        }
        ]]>
    </mx:Script>
   
    <mx:Label text="{resourceManager.getString('jang', 'NAME')}"/>
   
     <mx:ComboBox id="localeComboBox" dataProvider="{locales}"
                 change="localeComboBox_changeHandler(event)"/>
</mx:Application>

Localization.mxml 파일을 이렇게 고친후

Flex 컴파일에서 설정 하자~!


Project >>마우스 오른쪽 클릭 >> Properties
를 클릭후
Flex Compiler >> Additional compiler arguments 로 간다
그리고 -locale en_US en_KR -source-path=locale/{locale}이렇게 넣어 준다


OK 누른후 Localization.mxml 실행


After a question on our flex forum about MD5 encryption, I decided to write an example about all the encryption types available in Flex.

Encryption is not available by default, you have to download the latest version of the (open source) project AS3corelib.

If you would like to encrypt a String with an MD5, you have the MD5 class in as3corelib-.92.1srccomadobecryptoMD5.as.

Import this class and than just call

1
MD5.hash("some string");

and there you go…

To show you every encryption method I’ve written this example.


Flex in HMAC with Your HMAC key as security key: 28df358053e410a65bae3d52a73ef921  
Actionscript code: HMAC.hash("Flex","Your HMAC key");


Flex in MD5:   09d2bd168181f1e64c2b1651a80a8aa8          

Actionscript code: MD5.hash("Flex");


Flex in SHA1:   6b1f893805e29582969095a741d60a5f43734af8        

Actionscript code: SHA1.hash("Flex");


Flex in SHA224:  c5e91507b9a0e573c6e93e5adec2de26c27545704e2f954db0c9fbd5   

Actionscript code: SHA224.hash("Flex");


Flex in SHA256:  c2a520b84ceea67ca4f3f74f908a5a310d104b42226714809b4e39de7eae2d24  Actionscript code: SHA256.hash("Flex");

'Flex' 카테고리의 다른 글

Flash Builder 4 Beta2에서 SVN 설치하기  (0) 2010.10.18
flex 다국어  (0) 2010.10.01
HTTPService Parameters  (0) 2010.09.29
FLEX로 화면캡쳐 및 이미지파일로 저장하기  (0) 2010.08.13
flex  (0) 2010.06.21

HTTPService 콤포넌트를 이용하여 웹서버에 요청할 때 파라미터를 전달하는 다양한 방법

MXML

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" height="359" width="480">
 <mx:Script>
  <![CDATA[
   import mx.rpc.events.ResultEvent;
   import mx.rpc.events.FaultEvent;
   import mx.controls.Alert;
         [Bindable]
         private var employeeInfo:XMLList;
         public function handleXML(event:ResultEvent):void
         {
            employeeInfo = event.result.emp as XMLList;
         }
         public function handleFault(event:FaultEvent):void
         {
            Alert.show(event.fault.faultString, "Error");
         }
        
         public function getDetailedInfo1():void {
          var http:HTTPService = new HTTPService();
          http.method="POST";
          http.url = "http://localhost/flex/xmlEmpService4.jsp";
          http.resultFormat = "e4x";
          http.request.empno = txtEmpno.text;
          http.addEventListener("result", handleXML);
          http.addEventListener("fault", handleFault);
          http.send();
         }
        
         public function getDetailedInfo2():void {
           var params:Object = new Object();
           params.empno = txtEmpno.text;
           xmlRPC.send(params);
         }
        
         public function getDetailedInfo3():void {
           xmlRPC.url="http://localhost/flex/xmlEmpService4.jsp?empno="+txtEmpno.text;
           xmlRPC.send();
         }
        
         public function getDetailedInfo4():void {
           var params:Object = new Object();
           params["empno"] = txtEmpno.text;
           xmlRPC.send(params);
         }

         public function getDetailedInfo5():void {
           xmlRPC.request.empno = txtEmpno.text;
           xmlRPC.send();
         }
        public function clearDataGrid():void {
          employeeInfo = null;
        }
  ]]>
 </mx:Script>
    <mx:HTTPService result="handleXML(event);" fault="handleFault(event);" id="xmlRPC" resultFormat="e4x"
         method="POST" url="http://localhost/flex/xmlEmpService4.jsp" useProxy="false">
     <mx:request xmlns="">
      <empno>{ txtEmpno.text }</empno>
     </mx:request>
 </mx:HTTPService>
 
  <mx:Label x="49.5" y="39" text="HTTPService를 이용하여 파라미터를 전달하는 방법 테스트" fontSize="13" fontWeight="bold" textDecoration="underline"/>
  <mx:Label x="43" y="85" text="사번(empno)입력" fontSize="12"/>
  <mx:TextInput x="157" y="83" width="88" id="txtEmpno" text="7839"/>
 
  <mx:DataGrid x="43" y="113" height="142" id="EmpList" dataProvider="{employeeInfo}" fontSize="12" textAlign="center">
   <mx:columns>
    <mx:DataGridColumn headerText="사 번" dataField="empno"/>
    <mx:DataGridColumn headerText="이 름" dataField="ename"/>
   </mx:columns>
  </mx:DataGrid>
 
  <mx:Button x="253" y="83" label="&lt;empno&gt;7839&lt;/empno&gt;" click="xmlRPC.send();" width="187" height="22"/>
  <mx:Button x="253" y="113" label="http.request.empno='7839'" width="187" id="btnDetail"
  click="getDetailedInfo1();"/>  
  <mx:Button x="253" y="143" label="params.empno='7839'" width="187" id="btnDetail0"
   click="getDetailedInfo2();"/>
  <mx:Button x="253" y="173" label="emp.jsp?empno=7839" width="187" id="btnDetail1"
   click="getDetailedInfo3();"/>
  <mx:Button x="253" y="203" label="params['empno']='7839'" width="187" id="btnDetail2"
   click="getDetailedInfo4();"/>
  <mx:Button x="253" y="208" label="http.request.empno='7839'" width="187" id="btnDetail5"
   click="getDetailedInfo5();"/>
  <mx:Button x="253" y="233" label="Clear DataGrid" width="187" id="btnDetail3"
   click="clearDataGrid();" fillAlphas="[1.0, 1.0]" fillColors="[#F8BA35, #F8BA35]"/>
</mx:Application>



테스트용 서버측 프로그램 (xmlEmpService04.jsp)

<%@ page contentType="text/xml;charset=utf-8" import="java.sql.*" %>
<%
 Connection conn = null;
 Statement stmt = null;

 String jdbc_driver = "oracle.jdbc.OracleDriver";
 String db_url = "jdbc:oracle:thin:@localhost:1521:ora9i";
 String empno = request.getParameter("empno");

 try{
  Class.forName(jdbc_driver);

  conn = DriverManager.getConnection(db_url,"scott","tiger");

  stmt = conn.createStatement();

  ResultSet rs = stmt.executeQuery("select * from emp where empno="+empno);
 // EMPNO  ENAME  JOB  MGR  HIREDATE  SAL  COMM  DEPTNO        
  %>
  <?xml version="1.0" encoding="utf-8"?>
  <employee>

  <%
  while(rs.next()) { %>
  <emp>
    <empno><%=rs.getInt(1)%></empno>
    <ename><%=rs.getString(2)%></ename>
    <job><%=rs.getString(3)%></job>
    <mgr><%=rs.getString(4)%></mgr>
    <hiredate><%=rs.getString(5)%></hiredate>
    <sal><%=rs.getInt(6)%></sal>
    <comm><%=rs.getString(7)%></comm>
    <deptno><%=rs.getInt(8)%></deptno>
</emp>
<%  } %>
</employee>
<%
  rs.close();
  stmt.close();
  conn.close();
 }
 catch(Exception e) {
  out.println(e);
 }
%>

'Flex' 카테고리의 다른 글

Flash Builder 4 Beta2에서 SVN 설치하기  (0) 2010.10.18
flex 다국어  (0) 2010.10.01
Flex Encryption (MD5, SHA1, SHA224, SHA256, HMAC)  (0) 2010.10.01
FLEX로 화면캡쳐 및 이미지파일로 저장하기  (0) 2010.08.13
flex  (0) 2010.06.21
예전부터 StringTokenizer 와 String.split()의 차이가 궁금했었습니다. 그냥 보기에는 둘 다 똑같은 기능처럼 보이거든요.

  특히나 .split()은 알아서 배열까지 만들어 주기에 굉장히 편리해서, 학교에서 배울 때 말고는 직접 일하면서 StringTokenizer를 써본 적이 없었습니다.

  StringTokenizer 와 String.split() (내용을 보시려면 클릭)

  그랬는데 이 글을 보니 몇 가지 차이점이 있군요. 개인적으로 주목한 건, .split()은 구분자 사이에 문자열이 없어도 공백인 배열을 만들지만 StringTokenizer는 무시한다는 점이군요.

  일전에 만들었던 프로그램 중에 .split()을 썼더니 구분자가 연속으로 들어오면 그걸 다 빈 공백값으로 만드는 바람에 일일이 번거롭게 걸러내게 프로그래밍했었는데 StringTokenizer를 쓰면 간단하게 해결될 일이었습니다..._no

  역시 Java는 API를 얼마나 잘 알고 있느냐에 따라 괜한 삽질을 안 하게 되는 것 같습니다.(물론 그 많은 API를 다 알기에는 한계가 있긴 하지만요.)
Java Map(HashMap, TreeMap, Hashtable)

1. Collection
  • Map은 key와 value를 가진 집합이며, 중복을 허용하지 않는다.
  • 즉, 한개의 key에 한개의 value가 매칭된다.
  • java.util 패키지에 여러 집합들을 사용하기 위한 여러 interface와 class 들이 정의되어 있다.
2. HashMap
  • HashMap은 Map interface를 implements 한 클래스로서 중복을 허용하지 않는다.
  • Map의 특징인 key와 value의 쌍으로 이루어지며, key 또는 value 값으로써 null을 허용한다.
  • 아래의 예는 HashMap을 사용한 간단한 예제이다.

    import java.util.*;


    public class HashMapTest
    {
        public static void main(String argv[])
        {
            HashMap hm = new HashMap();
            System.out.println(hm.put("aaa", "111"));
            System.out.println(hm.put("bbb", "222"));
            System.out.println(hm.put("aaa", "444"));
            System.out.println(hm.put("ccc", "333"));   
            System.out.println(hm.put("ccc", null));      
           
            System.out.println("HashMap size : " + hm.size());
           
            Set set = hm.keySet();
            Object []hmKeys = set.toArray();
            for(int i = 0; i < hmKeys.length; i++)
            {
                String key = (String)hmKeys[i];  
                System.out.print(key);
                System.out.print(" - ");
                System.out.println((String)hm.get(key));
            }
        }
    }


    /**
    실행:java HashMapTest
    결과:
    null
    null
    111
    null
    333
    HashMap size : 3
    ccc - null
    bbb - 222
    aaa - 444
    */

3. TreeMap
  • TreeMap역시 중복을 허용하지 않으며, key와 value의 쌍으로 이루어져 있다.
  • HashMap과 다른 점은 SortedMap을 implements 하였으므로, key 값들에 대한 정렬이 이루어진다는 점이다.
  • 아래의 예는 TreeMap을 사용하여 각 요소가 몇몇 이나 나왔는지 알아보는 간단한 예제이다.

    import java.util.*;

    public class Freq

    {
        private static final Integer ONE = new Integer(1);

        public static void main(String args[])

        {
            Map m = new TreeMap();

            // Initialize frequency table from command line
            for (int i=0; i < args.length; i++)

            {
                Integer freq = (Integer) m.get(args[i]);
                m.put(args[i], (freq==null ? ONE :
                                new Integer(freq.intValue() + 1)));
            }

            System.out.println(m.size()+" distinct words detected:");
            System.out.println(m);
        }
    }


    /**
    실행:java Freq if it is to be it is up to me to delegate
    결과:
    8 distinct words detected:
    {be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
    */

4. Hashtable
  • Hashtable Map interface를 implements 한 클래스로서 중복을 허용하지 않는다.
  • Map의 특징인 key와 value의 쌍으로 이루어지며, key 또는 value 값으로써 null을 허용하지 않는다.(HashMap과의 차이점)
  • 아래의 예는 HashTable을 사용한 간단한 예제이다.

    import java.util.*;

    public class HashtableTest

    {

        public static void main(String argv[])
        {
            Hashtable ht = new Hashtable();
            System.out.println(ht.put("aaa", "111"));
            System.out.println(ht.put("bbb", "222"));
            System.out.println(ht.put("aaa", "444"));
            System.out.println(ht.put("ccc", "333"));   
           
            System.out.println("Hashtable size : " + ht.size());
           
            System.out.println("aaa value : " + (String)ht.get("aaa");
           
        }
    }


    /**
    실행:java HashMapTest
    결과:
    null
    null
    111
    null
    Hashtable size : 3
    aaa value : 444
    */



 

간단한사용 예제

<%@page import="java.util.*" contentType="text/html;charset=euc-kr"%>
<%

     HashMap hm = new HashMap();

     List al = new ArrayList();


     hm.put("name","진우");
     hm.put("nick","쪼");

     al.add( hm );


     hm = new HashMap();
     hm.put("name","애희");
     hm.put("nick","줴인");
     al.add( hm );


     // for문 내에 사용되는 변수들

     HashMap getHm = new HashMap();

     String sName = null;

     String sNick = null;

     for ( int i=0; i<al.size(); i++ ){
          getHm = (HashMap)al.get(i);
          sName = (String)getHm.get("name");
          sNick = (String)getHm.get("nick");
          out.println( (i+1)+"번째 이름 : "+sName+", 애칭 : "+sNick+"<br>" );
     }

     // 어려운거 없다
%>


출력:

1번째 이름 : 진우, 애칭 : 쪼
2번째 이름 : 애희, 애칭 : 줴인
*개발 하실때 참고 하세요..

package LogManager;

import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class LogTrace {
 
 private String _filename;
 private Logger _logger;
 private FileHandler _fHandler;
 
 public LogTrace(String filename){
  this._filename  = filename;
  this._logger = java.util.logging.Logger.getLogger("net.anfamily.logging");
 }
 
 public LogTrace(String filename, java.util.logging.Logger logger){
  this._filename  = filename;
  this._logger = logger;
 }
 
 /**
  * 로그를 저장한다.
  */
 public void writeLog(String log){
  try{
   this._fHandler = new java.util.logging.FileHandler(this._filename,true);
   
   _logger.addHandler(this._fHandler);
   _logger.setLevel(java.util.logging.Level.ALL);
   SimpleFormatter formatter = new SimpleFormatter();
   
   //로그 메시지를 남긴다.
   _logger.log(Level.FINER,log);
   _logger.log(Level.FINE,log);
   _logger.log(Level.FINER,log);
   _logger.log(Level.CONFIG,log);
   _logger.log(Level.INFO,log);
   _logger.log(Level.WARNING,log);
   _logger.log(Level.SEVERE,log);
  }catch(Exception e){
   e.printStackTrace();
   System.out.println("");
  }
 }
 
 
 /**
  *로그를 저장한다.
  * @param log
  * @param filename
  */
 public void writeLog(String log, String filename){
  try{
   this._fHandler = new java.util.logging.FileHandler(this._filename,true);
   
   _logger.addHandler(this._fHandler);
   _logger.setLevel(java.util.logging.Level.ALL);
   SimpleFormatter formatter = new SimpleFormatter();
   this._fHandler.setFormatter(formatter);
   
   _logger.log(Level.FINER,log);
   _logger.log(Level.FINER,log);
   _logger.log(Level.FINE,log);
   _logger.log(Level.CONFIG,log);
   _logger.log(Level.INFO,log);
   _logger.log(Level.WARNING,log);
   _logger.log(Level.SEVERE,log);
   
  }catch(Exception e){
   e.printStackTrace();
  }
 }
 
 /**
  * 로그를 저장한다.
  * @param log
  * @param logger
  * @param handler
  */
 public static void writeLog(String log,Logger logger, ConsoleHandler handler){
 
  try{
   logger.addHandler(handler);
   logger.setLevel(java.util.logging.Level.ALL);
   SimpleFormatter formatter = new SimpleFormatter();
   handler.setFormatter(formatter);
   
   logger.log(Level.FINEST,log);
   logger.log(Level.FINER,log);
   logger.log(Level.FINE,log);
   logger.log(Level.CONFIG,log);
   logger.log(Level.INFO,log);
   logger.log(Level.WARNING,log);
   logger.log(Level.SEVERE,log);
  }catch(Exception e){
   e.printStackTrace();
   System.out.println("");
  }
 }
 
 /**
  * 로그를 파일로 저장한다.
  * @param filename
  * @param log
  * @param logger
  */
 public static void writeLog(String log,Logger logger,String filename){
  try{
   FileHandler handler = new java.util.logging.FileHandler(filename,true);
   logger.addHandler(handler);
   logger.setLevel(java.util.logging.Level.ALL);
   SimpleFormatter formatter = new SimpleFormatter();
   handler.setFormatter(formatter);
   
   logger.log(Level.FINEST,log);
   logger.log(Level.FINER,log);
   logger.log(Level.FINE,log);
   logger.log(Level.CONFIG,log);
   logger.log(Level.INFO,log);
   logger.log(Level.SEVERE,log);
  }catch(Exception e){
   e.printStackTrace();
   System.out.println("");
  }
 
 
 }
 
}
==========================================================================================

package LogManager;

import java.util.logging.ConsoleHandler;
import java.util.logging.Logger;

public class LogingTest {

 public LogingTest(){
 }
 
 public static void main(String [] args){
  LogTrace logs = new LogTrace("c:\\log_Test100917.txt");
  logs.writeLog("Test입니다.");
 
  LogTrace.writeLog("Test입니다._1", Logger.getLogger("new.anfamily.net"), new ConsoleHandler());
  LogTrace.writeLog("Test입니다._2", Logger.getLogger("new.anfamily.net"), "c:\\log_Test100917_1.txt");
 }
}
=======================================================================================

실행 결과

2010. 9. 17 오전 11:15:31 LogManager.LogTrace writeLog
아주 자세히: Test입니다._2
2010. 9. 17 오전 11:15:31 LogManager.LogTrace writeLog
더 자세히: Test입니다._2
2010. 9. 17 오전 11:15:31 LogManager.LogTrace writeLog
자세히: Test입니다._2
2010. 9. 17 오전 11:15:31 LogManager.LogTrace writeLog
구성: Test입니다._2
2010. 9. 17 오전 11:15:31 LogManager.LogTrace writeLog
정보: Test입니다._2
2010. 9. 17 오전 11:15:31 LogManager.LogTrace writeLog
심각: Test입니다._2

 
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지

'Java' 카테고리의 다른 글

Java Map(HashMap, TreeMap, Hashtable)  (0) 2010.09.17
로그 남기기.. -java  (0) 2010.09.17
TCP/IP Socket 프로그램 구현시 고려사항공부/Java  (0) 2010.08.19
httpsession 을 이용한 로그인 관리  (0) 2010.08.13
Web Server 와 WAS  (0) 2010.07.28
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 - 커넥션풀링을 할수 있게 소켓객체들을 담아놓은 큐

도움이 되었음 좋겠네요

+ Recent posts