>
> {소켓 통신시 패킷의 제한이 있나요}
> {양웅직(twist), twist@orgio.net}
>
> InputStream fromWapClient;
> OutputStream toWapClient;
>
>
> fromWapClient = new BufferedInputStream(sock.getInputStream());
> toWapClient = new BufferedOutputStream(sock.getOutputStream());
>
>
> byte[] buf1 = new byte[10000];
> 위처럼 버퍼를 잡아주고
> 파일의 끝까지 오는 것을
> int count;
> while((count = fromWapClient.read(buf1)) != -1)
> {
>                                         
>    num = new String(buf1,0,4);
>    sourdt = new String(buf1,4,12);
>    total_size = new String(buf1,28,8);
>    ....
>   뭐 이런식을 받아서  
>
>    System.out.println("num="+num);
>    System.out.println("sourdt="+sourdt);
>    System.out.println("total_size="+total_size);
>    System.out.println(count+"byte 복사되었습니다");
>
> }
>
> 이렇게 찍어보면 2번 찍힐 때가 있습니다
> 내용의 앞부분이 찍히고
> 그 다음에 나머지 부분이 찍히고
>
> 그래서 잘 살펴보니 2048byte가 넘을때는 꼭 이렇게 2번에 거쳐서 날아옵니다
> 예를 들어서 3048byte을 보낼때는 2048에 해당하는 byte가 먼저찍히고
> 다음에 1000byte에 해당하는 byte가 찍히고 말입니다
> 그래서 버퍼를 크게 잡아주어도
> 물론 한글의 경우는 운좋으면 잘 나오고 아님 이상한 글자로 나올때도 있구요
> 보낼때는 잘 한번에 가는 것 같은데요
> 그래서 버퍼의 크기도 크게 해보고 또 BufferedInputStream을 사용도 해봤는데
> 똑 같은 결과가 나옵니다
>
> packet에 크기의 한계가 있어서 그런것인지
> 아님 프로그램에 문제가 있는 것인지
>
> 답변 꼭 부탁드립니다

네, 나누어서 날아 옵니다. TCP/IP가 그렇게 동작하는 듯 합니다. 따라서, 여러번 받으셔야
합니다. 한번에 날아오는 byte 의 량은 N/W 환경에 따라 다르더군요.
때론, 2048 byte  만큼, 때론 1460 byte 씩, 때론 384 byte씩 날아오고 있네요.

아래의 함수가 도움이 될런지요...

  /* InputStream으로 부터 EOF를 만날 때까지 모든 데이타를 읽어들임 */
  private static byte[] read_data(InputStream in) throws Exception {
    java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
    int bcount = 0;
    byte[] buf = new byte[2048];
    while( true ) {
      int n = in.read(buf);
      if (n == -1) break;
      //System.out.println(n);
      bcount += n;
      bout.write(buf,0,n);
    }
    bout.flush();
    //return bout.toString();
    return bout.toByteArray();
  }
  /* 주어진 길이만큼만 읽어들임 */
  private static byte[] read_data(InputStream in, int len) throws Exception {
    java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
    int bcount = 0;
    byte[] buf = new byte[2048];
    while( bcount < len ) {
      int n = in.read(buf,0, len-bcount < 2048 ? len-bcount : 2048 );
      if (n == -1) break;
      //System.out.println(n);
      bcount += n;
      bout.write(buf,0,n);
    }
    bout.flush();
    //return bout.toString();
    return bout.toByteArray();
  }

NOTE: 위 메소드는 넘어오는 모든 데이타를 ByteArrayInputStream에 임시로 담아둔 후
이를 다시 응용어플리케이션에 넘겨주는 구조이기 때문에, 대량데이타를 전송하고자
할 때는 사용하시면 안됩니다. 위 예제에서 언급하고자 했던 것은 단지 여러번에 나뉘어
날아올 수 있으므로, 필요한 데이타가 모두 올 때까지 끝까지 받으라는 것을 강조하고자
했으며, 하나의 샘플을 제공한 것에 지나지 않습니다.
예를 들어 1MB의 데이타를 위와 같은 메소드를 이용해 동시에 같이 여러 Thread가
받는다고 가정하면, 메모리가 남아 나지 않겠지요.

NOTE: 또한, 위 메소드는 방화벽을 사이에 두고 통신할 경우, 경우에 따라 영원히
blocking 될 가능성이 있습니다. while loop 내에서 retry 할 횟수의 제한을 두는 것도
한가지 방법일 듯 합니다.

-------------------------------------------------------  
  본 문서는 자유롭게 배포/복사 할 수 있으나 반드시
  이 문서의 저자에 대한 언급을 삭제하시면 안됩니다
================================================
  자바서비스넷 이원영
  E-mail: javaservice@hanmail.net
  PCS:019-310-7324
================================================


제목 : Java Socket Utilities API : SocketUtil.java
글쓴이: 이원영(javaservice)   2002/06/06 08:33:45  조회수:792  줄수:126
  
다시 정리했습니다. 유용하게 사용하시길 바랍니다.


-------------8><------------------------------------------------------------------
package org.jsn.jdf.util;

/**
* @(#) SocketUtil.java
* Copyright 1999-2002 by  Java Service Network Community, KOREA.
* All rights reserved.  http://www.javaservice.net
*
* NOTICE !      You can copy or redistribute this code freely,
* but you should not remove the information about the copyright notice
* and the author.
*
* @author  WonYoung Lee, javaservice@hanmail.net
*/
import java.io.IOException;
import java.io.ByteArrayOutputStream;
public class SocketUtil {

    public static final int INTPUTSTREAM_READ_RETRY_COUNT = 10;

    private SocketUtil() {}


    /**
     * The <code>read_data</code> method of <code>SocketUtil</code> reads the
     * specified length of bytes from the given input stream.
     *
     * @param      in   an inputstream
     * @param      len  the number of bytes read.
     * @return     The specified number of bytes read until the end of
     *             the stream is reached.
     * @exception  IOException  if an I/O error or unexpected EOF occurs
     */
    private static final byte[] read_data(InputStream in, int len) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        int bcount = 0;
        byte[] buf = new byte[2048];
        int read_retry_count = 0;
        while( bcount < len ) {
            int n = in.read(buf,0, len-bcount < 2048 ? len-bcount : 2048 );
            if ( n > 0 ) { bcount += n; bout.write(buf,0,n); }
            // What would like to do if you've got an unexpected EOF before
            // reading all data ?
            //else if (n == -1) break;
            else if ( n == -1 ) throw
                 new IOException("inputstream has returned an unexpected EOF");
            else  { // n == 0
                if (++read_retry_count >= INTPUTSTREAM_READ_RETRY_COUNT)
                    throw new IOException("inputstream-read-retry-count( " +
                        INTPUTSTREAM_READ_RETRY_COUNT + ") exceed !");
            }
        }
        bout.flush();
        return bout.toByteArray();
    }

    /**
     * The <code>read_data</code> method of <code>SocketUtil</code> reads all
     * the bytes from the given inputstream until the given input stream
     * has not returned an EOF(end-of-stream) indicator.
     *
     * @param      in   an inputstream
     * @return     all bytes read if the end of the stream is reached.
     * @exception  IOException  if an I/O error occurs
     */
    private static final byte[] read_data(InputStream in) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        int bcount = 0;
        byte[] buf = new byte[2048];
        int read_retry_count = 0;
        while( true ) {
            int n = in.read(buf);
            if ( n > 0 ) { bcount += n; bout.write(buf,0,n); }
            else if (n == -1) break;
            else  { // n == 0
                if (++read_retry_count >= INTPUTSTREAM_READ_RETRY_COUNT)
                    throw new IOException("inputstream-read-retry-count( " +
                        INTPUTSTREAM_READ_RETRY_COUNT + ") exceed !");
            }
        }
        bout.flush();
        return bout.toByteArray();
    }
  
    /**
     * Read a line of text.  A line is considered to be terminated by a line
     * feed ('\n') or a carriage return followed immediately by a linefeed.
     *
     * @return     A String containing the contents of the line, not including
     *             any line-termination characters, or null if the end of the
     *             stream has been reached
     *
     * @exception  IOException  If an I/O error occurs
     */
    private static final String read_line(InputStream in) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        boolean eof = false;
        while( true ) {
            int b = in.read();
            if (b == -1) { eof = true; break;}
            if ( b != '\r' && b != '\n' ) bout.write((byte)b);
            if (b == '\n') break;
        }
        bout.flush();
        if ( eof && bout.size() == 0 ) return null;
        //Or return ""; ? what's fit for you?
        return bout.toString();
    }        
}
-------------8><------------------------------------------------------------------

PS: 아래의 ByteUtil.java도 함께 참조 하세요.
18   parse int and long type from byte[]
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=953537896

-------------------------------------------------------  
  본 문서는 자유롭게 배포/복사 할 수 있으나 반드시
  이 문서의 저자에 대한 언급을 삭제하시면 안됩니다
================================================
  자바서비스넷 이원영
  E-mail: javaservice@hanmail.net
  PCS:011-898-7904
================================================
>
> {쿼리문 실행이 오래걸릴 경우}
> {손님(guest), lumb2000@hanmail.net}
>
> jdbc프로그램에서 쿼리문의 실행시간이 오래걸리면 어떻게 처리를 하는지 궁금합니다.
> 제가 쓰는 쿼리문의 경우 여러 테이블을 조인해야만 원하는 데이타를 가져올 수
> 있습니다. 여러개의 테이블을 조인하기도 하고, 각 테이블에 데이타가 많아서 쿼리문의
> 결과를 얻는데 시간이 오래걸리더군요.
>
> 그런데 문제는 이렇게 오래 걸리는 동안 계속 pool을 잡고 있어서, 반환하기 전에 요청
> 이 들어오면 다시 pool이 잡혀서 Current in use의 개수가 계속 늘어만 갑니다.
> 물론 어느정도 시간이 지나면 다시 반환되기는 하지만 많은 유저가 붙었을 때는
> 시스템이 제대로 동작하지 않습니다.
> 이렇게 처리시간이 오래 걸리는 경우 보통 어떻게 처리하는지 궁금합니다.
>
> 그리고 쿼리문이 실행되는 동안 다른 링크를 클릭하거나 중지를 눌렀을 때, 링크를
> 클릭한 경우는 이동한 화면으로, 중지를 누른 경우는 멈추는 것으로 브라우저의 상태가
> 바뀌어 버리지만 이미 실행된 쿼리문은 계속 자기 일을 수행하더군요.
> 이미 다른 액션이 취해진 상태에서 쿼리문이 수행되고 있는것은 의미가 없는데
> 이것이 pool을 계속 잡고 있습니다.
> 이런 경우 즉, 현재 페이지에서 쿼리문을 날리다가 다른곳을 클릭하거나,
> 중지시켰을 때 바로 쿼리문수행도 중지하고 connection을 닫고 나오게 하는 방법이
> 있는지요?
>
> 더운신데 수고많으십니다.
> 감사합니다.


질문하신 분이 기대하는 바와 같이, 일단 실행된 Thread 를 중지할 방법은 없습니다.
사용자 브라우져의 forward/backword/stop 혹은 브라우져를 close 하는 것과 무관하게
일단 그 요청을 server side에서 받아 처리하고 있는 Thread 는 끝까지 자기 일을
수행하게 됩니다.

결국, 해당 어플리케이션의 단위시간당 수행할 수 있는 능력(개수) 대비, 단위시간당
요청빈도가 넘어설 경우, 요청을 하여도 응답이 급격하게 느려지는 "hang현상" 이
유독 웹기반의 사이트에서 빈번하게 발생하는 주 원인이 여기에 있습니다.
사용자는 응답이 안올 경우, 묵묵하게 기다려주는 인내심은 커녕, 또다시 버튼을 마구
누르는 경향이 있잖습니까.

1. 1차적으로는 해당  쿼리를 튜닝하셔서, 단위시간당 요청빈도 보다 단위시간당
  처리능력을 높이셔야 합니다.

2. 쿼리가 느린 서비스일 경우, 대부분 어플리케이션서버 Tier의 CPU는 25-50% 를
  넘어서지 못하고 결과를 기다리는 I/O waiting 상황에 빠지는 것이 일반적입니다.
  이 경우, DB서버의 CPU/Memory가 허용하는 한도까지 Connection Pool의 개수를 적당히
  늘려 줌으로써, 가용한 DB연결에 대한 Bufferring 효과를 보실 수도 있습니다.

3. 쿼리수행속도 저하로 인해 단위시간당 요청빈도가 단위시간당 처리능력보다 크고,
  bottle-neck의 원인이 DB서버에 분명히 있다면, 어플리케이션서버 Tier를 여러대로
  클러스트링하는 것은 전혀 효과가 없으며, (더이상의 쿼리튜닝이 불가능할 경우)
  DB서버의 H/W적 Capacity 를 늘리셔야 합니다.

  Performance Tuning 에 대한 이론적인 부분은 아래 세 글을 참조하세요.

  [강좌]웹기반시스템하에서의 성능에 대한 이론적 고찰
  http://www.javaservice.net/~java/bbs/read.cgi?m=resource&b=consult&c=r_p&n=1008701211

  Performance Tuning QuickGuide for BenchmarkTest
  http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=985764595

  WebSphere BMT 최적 파라메터 셋팅 방법론
  http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=989760940


4. 만약, DB서버의 CPU나 Memory가 bottle-neck의 원인이 아니라 DB Lock 혹은 한순간엔
  반드시 하나씩만 수행되어야 하는 것과 같은 어플리케이션 레벨의 문제로 인한
  bottle-neck 이라면 해당 어플리케이션을 튜닝하는 것만이 유일한 해결방법이 될
  것입니다.

5.이도저도 불가능할 경우, PPC(Peak Point Control) 기법을 적용하셔야 합니다.
성능저하로 인한 장애의 원인으로 발견된 어플리케이션이 있다면, 그것을 원천적으로
개선하여야 겠지만, 그것이 도저히 불가능할 경우, 특정 블록에 동시에 접근하는
Thread의 수를 제어 함으로써, 제한적으로 서비싱 하여야 할 것입니다.
특정 개수 이상이 동시에 진입할 경우, 더이상 진입케 하지 말고, "서비스 폭주중입니다.
잠시뒤에 다시 이용해 주십시요" 와 같은 메세지를 사용자에게 내보내고, 해당 서비스의
진입 자체를 막아 줌으로서, 단위시간당 처리능력(처리개수)보다 단위시간당 요청빈도
를 근본적으로 줄여 주어야 하는 것이지요...

이러한 PPC 기법은 최근 웹기반의 Performance Tuning 을 하다보면 항상 직면하게
되는 문제중의 하나입니다만, 아직 이것의 필요성을 인식하고 있는 분이 드문듯 합니다.

자바에서 PPC를 적용하는 것은 아주 간단합니다. 만약, 특정 instance method 에 PPC
기법을 적용하는 방법은 다음과 같습니다.

-------------------------------------------------------------------------------
public class BadPerfClass
{
     public void performTask(…) throws … {
         <original business logic>
     }
}
-------------------------------------------------------------------------------

위 소스를 아래와 같이 변형합니다.

-------------------------------------------------------------------------------
public class BadPerfClass
{
     private static int MAX_THREADS = 30;
     private static int count = 0;
     private static Object lock = new Object();

     public void performTask(…) throws … {
         boolean executable = true;
         try {
             synchronized ( lock ) {
                 if ( count >= MAX_THREADS ) executable = false;
                 else count++;
             }
             if ( executable == false ) {
                 < "maximum threads reached !!!">
                 throw or return;
             }
            
             performTask2(….);
         }
         finally {
             synchronized( lock ) {
                 if ( executable ) count--;
             }
         }
     }
     public void performTask2(…) throws … {
         <original business logic>
     }
}
-------------------------------------------------------------------------------


만약, 특정 Servlet의 성능이 너무나 저하되어, 시스템 장애의 핵심원인이라면, 해당
특정 서블렛에 한해서, 동시에 실행될 개수제한을 두고 싶다면, 다음과 같이 하시면
될 것입니다.

-------------------------------------------------------------------------------
public class BadPerfServlet extends HttpServlet
{
     public void doGet(...) .... {
        performTask(...);
     }
     public void doPost(...) .... {
        performTask(...);
     }
     public void performTask(…) throws … {
         <original business logic>
     }
}
-------------------------------------------------------------------------------

위 소스를 아래와 같이 변형합니다.

-------------------------------------------------------------------------------
public class BadPerfServlet extends HttpServlet
{
     public void doGet(...) .... {
        performTask(...);
     }
     public void doPost(...) .... {
        performTask(...);
     }

     private int MAX_THREADS = 30;
     private int count = 0;

     public void performTask(...) throws ... {
         boolean executable = true;
         try {
             synchronized ( this ) {
                 if ( count >= MAX_THREADS ) executable = false;
                 else count++;
             }
             if ( executable == false ) {
                 < "maximum threads reached !!!">
                 throw or return;
             }
            
             performTask2(...);
         }
         finally {
             synchronized( this ) {
                 if ( executable ) count--;
             }
         }
     }
     public void performTask2(...) throws ... {
         <original business logic>
     }
}
-------------------------------------------------------------------------------



만약, 특정 Base Servlet 을 상속받아 모든 Servlet 들이 한 곳을 경유하여 실행되도록
JDF(Java Development Framework)와 같은 기법으로 적용되어 있는 Servlet 의 경우라면
다음과 같이 BaseServlet에만 변경 적용함으로써, BaseServlet을 상속받는 모든 Servlet
들이 동시에 실행될 수 있는 개수제어을 할 수도 있을 것입니다.

-------------------------------------------------------------------------------
public abstract class BaseServlet extends HttpServlet
{
     public void doGet(...) .... {
        preformPreTask(...);
     }
     public void doPost(...) .... {
        preformPreTask(...);
     }
     public void performPreTask(...) throws ... {
         ...
         preformTask(...);
     }
     protected abstract void performTask(...);
}

public class BadPerfServlet extends BaseServlet
{
     public void performTask(...) throws ... {
         <original business logic>
     }
}
-------------------------------------------------------------------------------

위 소스를 아래와 같이 변형합니다.

-------------------------------------------------------------------------------
public abstract class BaseServlet
{
     public void doGet(...) .... {
        preformPreTask(...);
     }

     public void doPost(...) .... {
        preformPreTask(...);
     }

     private static int __MAX_THREADS = 50;
     private static int __count = 0;
     private static Object __lock = new Object();

     public void performPreTask(...) throws ... {
         boolean executable = true;
         try {
             synchronized ( __lock ) {
                 if ( __count >= MAX_THREADS ) executable = false;
                 else __count++;
             }
             if ( executable == false ) {
                 < "maximum threads reached !!!">
                 throw or return;
             }
            
             performTask(...);
         }
         finally {
             synchronized( __lock ) {
                 if ( executable ) __count--;
             }
         }
     }
     protected abstract void performTask(...);
}
-------------------------------------------------------------------------------

위의 예는 단지 예제일 뿐이며, 여하한의 곳에서 동시에 진입할 Thread 의 개수를
제한 시켜야 하는 곳에서 다양하게 응용해서 사용할 수 있을 것입니다.
synchronized ( lock ) 에서 Lock 객체를 뭘로하느냐에 따라, JVM 시스템 전체를 제어
하거나, 혹은 특정 영역의 lock 객체를 사용함으로써, 각 응용어플리케이션 개별적으로
각각 적용할 수도 있을 것입니다.


웹서버 -- 어플리케이션서버 -- DB서버 와 같은 전형적인 구조 외에도, Backend 서비스가
CICS CORBOL이나, Socket 통신으로 C Socket Gateway를 통해 IBM HOST CICS 혹은 IMS DB
CORBOL 프로그램과 연동하는 경우도 생각할 수 있으며, 여타의 Backend 서비스와 연동되어
운영되는 n-tier 시스템의 경우, 과거 HOST단말기 혹은 C/S 시스템의 제한된 사용자에게
서비스하던 처리 능력을 과신하다보면, 인터넷으로 오픈된 웹시스템으로의 전환 시점에,
대부분 bottle-neck point 는  backend 서비스라는 것에 주목하셔야 합니다.
이 경우 Backend 시스템의 성능을 근본적으로 높일 수 있는 방법은 대부분 제한적일 수
밖에 없을 것이고, 이 때 이슈로 떠 오르는 기법이 PPC가 될 것입니다.

PPC기법은 사실 돌이켜 생각해 보면, 새로운 그 무엇도 아니며, 이미 광범위하게 적용되어
있는 일반적인 기법입니다. Apache 웹서버의 MaxClients 파라메터가 그것이며, DB
Connection Pooling 의 Maximum 개수 제한을 두는 것이 그 단적인 예가 될 것입니다.
이 기법을 제품이나, 특정 영역에 국한시키지 말고, 응용어플리케이션에서도 적용시켜야
한다는 것을 고급개발자라면 각인하고 있어야 할 것입니다.

특정환경에서 특정 어플리케이션은 단위시간당 최대로 처리할 수 있는 개수는 반드시
제한되어 있습니다. 1초당 200개 혹은 1분당 2만개 등과 같이 TPS(Transaction Per
Second) , TPM(Transaction Per Minute), 혹은 PPS(Page Per Second), RPS(Request Per
Second)  등과 같이 스트레스 툴을 통해 다양한 이름으로 그 성능이 측정되곤 합니다.
PPC기법은 단위시간당 처리할 수 있는 성능의 한계를 넘어서서 특정시점에 몰려드는
Request 들 중, 처리할 수 있는 개수만 처리하고, 나머지를 한가한 시간대에 다시
요청하도록 유도함으로써, 결과적으로 부하를 분산시키고 있는 형태가 됩니다.

MAXIMUM 수치에 도달했을 때, 곧바로 service fail을 발생시켜 return 시키지 말고,
일정 시간동안 가용한 자원이 생길 때까지 약간의 시간을 기다려 주는 기법을 연상하실
수 있을 것입니다. 그리고, 그 "약간의 시간"을 제어함으로써, PPC 적용의 깊이(?)를
또 다시 제어하고 싶으실 겁니다.
Hans Vergstain 이 만든 DBConnectionManager.java 라는 DB Connection Pool 소스에
getConnection( long timeout ) 메소드가 있음에도 불구하고, 이것을 왜 사용해야
하는지에 대해 정확히 인식하고 있는 개발자가 또한 드문 듯 합니다. 변동이 심하게
요청빈도가 달라지는 경우, 순간적으로 동시에 수행할 최대치에 도달했다고 하여
곧바로 request fail 을 발생시키는 것은 때론 적절치 않을 수도 있습니다. 약 1-3초
정도 기다려 주었다가 가용한 자원이 그 시간내에 생길 경우, 그대로 정상적인
서비스를 하게 하는 것이 더욱 바람직 할 수도 있다는 것이지요.

위의 PPC기법에 Timeout 기법을 적용하시려면, 다음 글에 포함된 소스를 응용하면
될 겁니다.

Object Pool Contorl 기법
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=974750350

그렇다면 MAXIMUM 수치 혹은 Timeout 수치를 얼마로 하는 것이 가장 적절하냐라는 부분이
바로 Performance Tuning 의 핵심적인 사항이며, 해당 시스템의 특성에 맞게 적절히
적용하여야 하는 부분입니다. 이 수치는 결코 결정된 수치가 존재할 수는 없으며,
해당 시스템의 H/W용량과 아키텍쳐, 그리고, 응용어플리케이션의 특성에 따라 항상
달라지기 때문입니다. 어떻게 달라지느냐는 스트레스 테스팅 툴을 통해 측정함으로써
그 변화의 정도의 판단할 수 있을 것입니다.

주지하셔야 할 것은, 이같은 PPC 기법은 가장 마지막 수단인 궁여지책의 방법이라는
것입니다. 요청을 날렸는데, "폭주중..."이라는 메세지가 나오는 것은 해당 비즈니스
요건에는 존재치 않았을 것이며, 해당 서비스는 이유야 어떠하든 "실패(!)"할 수 있다는
것을 허용하겠다는 의미가 됩니다. "hang현상"과 같은 장애를 극복하기위한 몸부림(?)인
것이지요.

PS: PPC(Peak Point Control)이라는 이름을 최초로 명명한 분은 한국IBM의 이형기님
  입니다.


-------------------------------------------------------  
  본 문서는 자유롭게 배포/복사 할 수 있으나 반드시
  이 문서의 저자에 대한 언급을 삭제하시면 안됩니다
================================================
  자바서비스넷 이원영
  E-mail: javaservice@hanmail.net
  PCS:011-898-7904
================================================
제목 : Re: chatting프로그램 가이드
글쓴이: 신형주(shin7688)   2002/10/11 02:58:47  조회수:141  줄수:88
  
자바 처음짜는 사람을 위한 chatting프로그램 가이드

네트웍은 모르더라도 OOP는 필요한것 같구요.
Chat이란 사용자가 connection을 시작해서 이 세션이 끊어질때까지 문자를 주고 받는것을
의미 합니당. 서버 프로그램에서는 기초적으로 프로그램을 볼께요.
최고의 서버는 fault-tolerance, load-balancing, sessioning을 제공하는 솔루션이어야
하지만 처음에는 간단한 데이타 교환이 좋지요.

기초:
1.클라이언트가 구동하기전에 서버는 구동해야 하죠.
2.커넥션을 얻기 위해서 기다려야 되고 물론 port(당연 한대의 컴퓨터를 기준으로요)가
  열려져 있는 상태로요.
3.client, server간 통신을 위해서 Socket object를 통하여 통신을 하죠
4.Socket은 InputStream과 OutputStream을 가지고 있어서 데이타를 읽고 쓸수 있죠.

서버 프로그램 계획:
1.서버는 커넥션이 초기화 되기 전까지는 기다리는 거죠.
2.ServerSocket은 port에서 새로운 커넥션이 들어 올때 Socket을 생성하죠.
3.이때 주의 할점은 클라이언트는 예고 없는 시점에 마구 들어오고 이때 멀티쓰레드를
사용하죠.

Server Side code:
private void listen( int port ) throws IOException {
    ss = new ServerSocket( port );
    while (true) {
        Socket s = ss.accept();
        DataOutputStream dout = new DataOutputStream( s.getOutputStream() );
        // HashTable인 outputStreams에 데이타를 저장 하저.
        outputStreams.put( s, dout );
        // 새로운 thread 생성을 위해서 있는 ServerThread
        new ServerThread( this, s );
    }
}

다룰것:
1.Server Class
2.While-Accept Loop
3.Per-Thread Class(쓰레드 풀 필요 없어요)
4.While-Read/Write Loop (Server)
5.Removing Dead Connections
6.Client Class
7.While-Read/Write Loop (Client)

쓰레드 뭔지 알죠:
1.일반적인 쓰레드라는 것은 코드를 한라인씩 읽어가는 프로세스 이져.
2.근데 멀티 쓰레드는 동시간에 활동하고 있는 쓰레드를 의미하져.
3.데이타에 대한 접근뿐만 아니라 효율적으로 사용 할수 있죠. 비동기의 장점이라고 할까요.
4.많은 사용자 처리에 쉽게 처리 할수도 있고요.


통신 프로토콜:
-DataInputStream 과 DataOutputStream    를 쓰느 이유는 Low-level(Integer, String 등)에
대한 포멧의 변화없어서요
-DataOutputStream으로 쓰면 Integer을 쓰면  DataInputStream에서는 변화 없이 받죠.
1.한 유저가 창에 메시지를 보내면 DataInputStream를 통하여 message를 보내죠.
2.서버가 DataInputStream로 메시지 받아서 DataOutputStream으로 모든 유저에게
  message를 보내죠.
3.사용자들은 DataInputStream로 데이타를 받고요.


public void run() {
    try {
        DataInputStream din =
            new DataInputStream( socket.getInputStream() );
        while (true) {
            String message = din.readUTF();
            // 모든 사용자에게 메시지 보내여
            server.sendToAll( message );
        }
    } catch( IOException ie ) {
        ie.printStackTrace();
    } finally {
        // connection을 클로즈 해요
        server.removeConnection( socket );
    }
}


Thread각각이 cpu자원과 메모리(0.5MB)의 스택을 가지고 있죠.
Stack은 heap속의 인스턴스로의 reference가 들어 있으므로, GC는 Stack영역도쭉 검사하죠.
1000개의 쓰레드 = 수천MB어치의 stack을 GC마다 검색을 하게 되죠
쓰레드가 늘어난다고 고성능이 되는 것은 아니죠.
JVM 리소스의 과다 사용은 처리를 늦추는 결과를 가져 오구요.
처리용 쓰레드를 적당수로 낮추고[chatting은 ThreadPool사용 말아야],
복수의 JVM을 사용하도록 검토하는 것이 좋죠.

내일 워크삽 가네요. 밤이 늦어서 2부는 일요일에 쓰죠



제목 : Re: 채팅에서 쓰레드풀 이용하지 말아야??
글쓴이: 나승민(rulu)   2002/10/11 10:12:51  조회수:89  줄수:19
  
채팅은 Thread pool을 사용하지 말아야하는 이유가 어떤건지 알고 싶습니다
단지 쓰레드개수가 많다고 성능이 좋아지는건 아니라는데 공감합니다.

짧은 생각인지 모르겠으나 저의 생각으로는 자바경우 ServerSocket.accept() 직후에
쓰레드 객체를 생성하는 구조는 오히려 객체생성시간을 많이 차지하므로, 사용자의
접속시간이 길어지는 단점이 있는걸로 알고 있습니다.
그래서 쓰레드풀을 사용한다고 알고 있는데요. 제가 잘못알고 있는지 궁금하네요.

그레고 쓰레드풀을 이용하므로서  GC(Garbage collection)로 인한 부하를 더 감소할 수
있는걸로 알고 있습니다만.. 왜냐면.. 사용자 접속이 끊겨도 해당 쓰레드는 버리지
않으므로 즉, GC의 대상이 안된다는겁니다.
그러나 쓰레드풀을 이용하지않는구조는 사용자 접속이 끊어질 때마다. GC의 대상이
되므로, 매번 불필요한  GC를 수행하여 서버에 부담을 주는걸로 알고 있는데요,
이 또한 제가 잘못 알고 있는건지  정확히 지적해주시면 좋겠습니다.

사실 쓰레드의 동작 메카니즘을 제대로 이해하지 않은 상태에서 채팅을 만들다 보니
어려운점이 한두가지가 아닙니다.

바쁘실텐데.. 리플달아주신 신형주님께 감사드립니다.


제목 : 쓰레드갯수 & 소켓풀링(Socket Pooling) [질문]
글쓴이: 나승민(rulu)   2002/10/11 17:13:09  조회수:171  줄수:26
  
이원영님의   'TCP/IP Socket 프로그램 구현시 고려사항' 글을 잘 읽어 보았습니다
TCP/IP Socket 프로그램 구현시 고려사항
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=1009171849

저 또한 이글을 왜 이제야 읽게되었는지 안타까운 마음입니다.. 그나마 다행이라면 다행,,

TCP/IP Socket 프로그램 작성시 이원영님이 제시한 4가지 방식중 Thread Pooling을 이용한
방식에서 한가지 궁금한 점이 있습니다.
제가 구현한 방식중에 실수한 점이라고도 생각합니다만, Pool에 담아둘 쓰레드의 개수라는게
클라이언트에게 서비스할 기능을 분류하여 쓰레드로 처리한 것이 맞는지요?
저의 경우 클라이언트 1명당 쓰레드 1개로 생성을하여 풀에 담아두고 있습니다..
예를들어 서버 1대에 동시접속자수를 1000명으로 잡을 때.. 서버에서 풀에 담아둘 쓰레드
개수는 1000개가 되는 것입니다.. 현재는 이런 구조로 되어있습니다..
구조상 문제가 있는지 알고 싶습니다..

두번째로 Socket Pool을 이용하는 구조에서 한가지 궁금한게 있습니다하나의 클라이언트가
TCP/IP Socket 연결을 Pool에서 하나 할당 받아서 통신하는 도중에 클라이언트가 로그아웃
하기전까지는 해당 Socket과 계속 연결된 상태인지 아니면.. 메시지를 주고 받을 때만
Pool에서 하나의 Socket을 할당해서 그때 그때 통신을 하는지 궁금합니다.
만일 후자의 경우라면,, 실시간 쪽지같은건 수신이 불가능할거 같은 문제가 있겠구요.
DB Connection Pool의 경우는 후자의 경우라고 생각합니다만..

아무튼 Socket Pooling 이 어떤식을 동작하는지 Client 와 Server Node로 연관지어
설명해주시면 이해하는데 큰 도움이 될거 같습니다

명확한 설명을 주시면 감사하겠습니다



제목 : Re: 비동기 통신은 서버간에
글쓴이: 김홍구(Icoo)   2002/10/11 17:22:40  조회수:62  줄수:17
  
이원영님께서 말씀하신 방법중 Thread Pooling 을 이용하는 방식은 비동기 통신을 말씀하시는
걸로 알고 있습니다..(읽은지 좀 오래되서 기억이 가물가물.. ^^;;)
비동기는 송신하는넘 수신하는 넘을 따로 분리 해서.. 송신은 계속 송신 수신은 계속 수신..
이렇게 하는 방식으로 클라이언트 가 직접 접속하는 방식으로는 적합하지 않을것 같습니다.

보통 비동기 통신은 서버간에 많이 사용하지요.
비지니스로직 서버 <---> 웹서버. 이런 사이에서..

비동기 통신이다 보니 클라이언트 수만큼 스레드가 늘어 나는 방식이 아니라, 보통 10개
안쪽의 수를 사용하구요..  송신 10개 수신 10개... 이런식으로...

그리고 서버에 개인 클라이언트가 직접 접속하는 방식이면.(가령 갬이나,채팅같이)
Socket Pooling이 전혀 의미가 없지요... 헐헐헐... 어짜피 혼자 쓰니까...
그리고 서버 입장에서는 당근히 전혀.. 의미가.. 없지요..

잘못된 내용있으면.. 올려 주십시오.. 그럼. (--) (__) (--)



제목 : Re: Socket Pooling이 의미가 없다구요
글쓴이: 나승민(rulu)   2002/10/11 17:35:38  조회수:60  줄수:11
  
저의 경우 채팅서버를 구현중에 있습니다
구조 및 설계를 제대로 안잡고(몰라서ㅡ.ㅡ) 구현하다보니.. 다시 원점으로 돌아오고
말았습니다.

겜이나 채팅같은 프로그램에서는 Socket Pooling이 전혀 의미가 없다고 하셨는데 정말
그런가요? Pooling이란것은 제한된 자원을 다수 사용자가 공유하는 개념인데, 채팅같은
클라이언트가 많은 구조에서 더 필요하지 않을까요?

제가 아직은 Socket Pooling 에 대해서 제대로 이해하지 못하고 있는 상태라서
이해해주세요.



제목 : Re: 채팅서버에서 Connection Pool이 무의미한 이유
글쓴이: 김홍구(Icoo)   2002/10/11 19:25:15  조회수:76  줄수:25
  
가령 Client와 Server 입장에서 Pooling을 생각 해보지요..

우선 Client... 흠... 어짜피 1나의 Connection만 있으면 됩니다.(필요 없지요..--;;)

다음 Server Connection Pool을 어떻게 구성 하실것인가요?

Client가 다 틀린넘들입니다. Pool을 만들수가 없지요..

Pool은

+-------------+  Tcp/IP                 +----------+
|                    |                             |               |  ------------ 다수의 사용자
| 업무Server    | ------------------ |  웹서버    |  ------------
|                    |                             |               |  ------------
+-------------+                             +----------+

위와 같이 구성된 그림에서 업무 서버와 웹서버 사이에서 사용할때 유용합니다.
업무 서버입장에서는 클라이언트에 해당하는 서버가 제한적이고 어짜피계속 요청이
오기때문에 한번 맺은 컨넥션으로 계속 통신 해도 상관없습니다.

그러나 채팅서버에 해당하는 넘은 웝서버와 일반 사용자에 해당하는 구간인데, 이구간에서는
접속하는 사용자를 알수도 없고 너무 많고...
A라는 사용자와 맺은 접속을 B라는 사용자가 사용하는것도 불가능하기 때문에 소용 없다고
했던것입니다.



제목 : Re: chatting프로그램 가이드(두번째)
글쓴이: 신형주(shin7688)   2002/10/13 14:54:33  조회수:139  줄수:158
  
Connection의 제거:
Server object를 사용하고 있는 Per-Thread는 connection이 client에 의해서 끊어질때
사용하고 있던 Socket을 채팅방의 인원에서 제거하고 Socket을 닫아야 하죠.

server.removeConnection( socket );
는 connection이 정상 상태의 종료(사용자가 종료를 선택)또는 다른 이유 Client와 끊어질때
서버가 connection을 제거 할수 있게 만든 거죠. 이 부분에서 중요한것은 이런 종료 상태를
서버에게 알리는 것이 중요하구요. 서버는 채팅방 리스트에서 제거하고 서버는 더 이상
더 이상 Client와 connection상태가 아닌 Socket에 메시지는 보내는데 자원을 낭비하지
않게되죠.

Connection의 제거의 중요성:
죽은 connection들을 제거 하는것은 처음 서버를 만드는 사람이 자주 잊어서 구현하지 않기
때문에 중요한 요소인데도 구현되지 않은 경우가 많죠.
만약에 현재 Connection의 제거를 구현 하지 않았다면 어떻게 되는지 한번 생각 해보는 것도
좋죠.그러면 서버는 속도가 점점더 느려질거에요. 그리고 메모리는 점점더 많이 차지 하게
되겠죠.서버 프로세스는 메시지를 보낼때마다 프로세스는 Exception으로 폭주 하겠죠.
죽어있는 connection들 에게 메시지를 보내면 메시지를 보낼수 없게되죠.
일반적인 채팅 서버는 5초마다 한번씩 메시지를 보내고 사용자는 평균 15분 동안 채팅을
하죠. 이것이 의미하는 것을 생각해보면 200명의 사용자가 같은 채팅방에서 채팅을 한다고
예상을 하고 많은 사용자들이 connection도 하고 disconnection도 하면서 200명정도의
사용자들을 유지한다고 치면 24시간 후에는 약 2만명의 사용자들이 서버의 케넥션에 접속해
있는 결과를 예측하죠. 첫날 하루가 지나고 나서도 실제 채팅방에 있는 사용자는
200명이고요. 약 1만8천명이 사용 했던 죽은 connection을 서버는 보유하고 있겠죠.
만약 당신이 만든 서버에 죽은 connection을 처리 하지 않는다면 메시지를 보내고 실패하고
시간을 소비하게 되죠. 서버는 통신시간보다 System.out으로 Exception을 처리 하는 시간이
더 많아진다는 거죠.


The Client:
서버는 이정도에서 끝내고요. Client쪽을 보죠.
보통 서버는 새로운 connection을 위해서 wait한 상태에 있고 connection의 요청이 오면
Server의 Per_Thread는 ServerSocket에 의해서 connection을 하죠.
그러면 클라이언트는 어떻죠?
클라이언트는 applet로 구성되어 있을테죠.
자바 application고 웹페이지에서 사용하길 원하기 때문이죠.

GUI:
Applet은 간단히 사용자간 통신을 할수 있는 GUI인터페이스를 가지고 있죠.
클라이언트는 TextField에 입력하고 그 순간에 서버에게 데이타를 보내죠.
처음 GUI초기 구성은 아래처럼 초기화를 하죠.

public class Client extends Panel implements Runnable {
        private TextField tf = new TextField();
        private TextArea ta = new TextArea();  
        public Client( String host, int port ) {
                // 화면 setup
                setLayout( new BorderLayout() );
                add( "North", tf );
                add( "Center", ta );
                //문자 입력하고 enter치면 메시지 가죠.
                tf.addActionListener( new ActionListener() {
                        public void actionPerformed( ActionEvent e ) {
                                processMessage( e.getActionCommand() );
                        }
                } );
        //중간생략
}        

TextField에 문자 입력하고서 어떤액션이 일어나면 모든 유저의 TextArea에 문자열이
보여지죠. 문자열이 입력되면 DataOutputStream을 이용하여 메시지를 보내는
processMessage()사용하죠.


서버에 연결:
클라이언트는 서버에 연결을 하고 메시지를 받는 Thread를 만들죠.

//서버 연결
try {
        // connection 초기화
        socket = new Socket( host, port );
        // 서버 연결 확인
        System.out.println( "connected to "+socket );
        // socket으로부터 DataInput/Output streams 취득
        din = new DataInputStream( socket.getInputStream() );
        dout = new DataOutputStream( socket.getOutputStream() );
        // 메시지를 받기 위해서 background thread를 만들어야죠
        new Thread( this ).start();
} catch( IOException ie ) {
        System.out.println( ie );
}

사용자 입력:
Applet이란 FrameWork은 윈도우즈에서 message를 입력하면 입력event를 통하여 필요한
작업을 하죠. event를 받기전까지는 wait상태에 있는거죠.
아래의 경우와 같이 사용자가 입력받으면 inner class에서 processMessage()를 호출하는
형태죠. 이메소드는 사용자가 입력한 문자열을 받는 역할을 하고, 서버에 메시지를 보내고
TextField를 지우죠.


// 사용자 입력 데이타를 가지고 호출
private void processMessage( String message ) {
        try {
                // 서버에게 보낸다.
                dout.writeUTF( message );
                // TextField를 지우기
                tf.setText( "" );
        } catch( IOException ie ) { System.out.println( ie ); }
}


사용자 출력:
클라이언트 프로그램을 GUI이벤트 기반으로만 움직이지는 않죠.
또 하나 움직이는 이벤트가 있는데 네트웍 이벤트라는 것이 있죠.
서버로부터 오는 데이타를 기다리는 것이 바로 그것이죠.
사용자 입력은 이벤트 처리를 GUI framework을 이용했지만 사용자 출력을 위한 network
event처리는 직접 구현을 해야하죠. 그래서 이것을 Background thread를 이용하여
while-read/write loop를 run 하죠


while-read/write loop:
서버에서 read/write loop를 가진것처럼 클라이언트도 가지고 있죠.
서버와 같이 메시지를 받아서 TextArea에 뿌려 주는 역할을 하죠.
그리고 loop로 돌아와서 다음 메시지를 기다리죠.

// Background thread: 메시지를 TextArea에 뿌려준다.
public void run() {
        try {
                // Receive messages one-by-one, forever
                // 메시지를 계속해서 받아 들이는 역할을 한다.
                while (true) {
                        // 다음 메시지를 받는다.
                        String message = din.readUTF();
                        // TextArea에 메시지를 뿌린다.
                        ta.append( message+"\n" );
                }
        } catch( IOException ie ) { System.out.println( ie ); }
}
L

제한적 사항(단순한 Socket모델):
멀티 Thread 채팅 프로그램은 이대로 완전히 끝난것인가의 의문이 남을 것에요
채팅방이 유일하나 하나만 있다는 가정하에 모든 메시지 처리를 하게 두었죠.
이런 채팅 시스템은 상용에서 사용할때는 문제점을 가질수 밨에 없어요.
왜냐하면 사용자는 채팅방을 여러개로 나누는 것을 원할테니까요.
또한 채팅방에 들어 갈수 있는 권한의 선택도 없고 채팅방을 매개체로 통신하지도 않지요.
서버에서는 sendToAll()을 사용해서 모든 사용자에게 메시지를 보내지만 상업적인
채팅서버는 구분을 해서 메시지를 포워딩 할 시스템을 가지고 있죠.


제한적 사항(너무 많은 Thread):
클라이언트마다 Thread를 만든다는 것은 좋은 아이디어가 아니죠.
많은 Thread들이 있고 sleep상태에 있다 하더라도 시스템이 다운 될수 있는 가능성은 있죠.
무엇보다 중요한것은 한번 프로그램을 만들어 돌려 보는거죠.
왜냐면 시도없이 Thread의 성능을 측정하기는 어렵잖아요.
테스트를 하다 보면 많은 Thread이 접속을 하다보면 문제점이 발생하고 채팅 솔루션은 더욱더
복잡해지죠. JVM은 이런 많은 쓰레드를 처리를 하지 못할테고 고객은 당신이 만든 솔루션에
불평을 할거에요.


제한적 사항(ThreadPool):
매번 생겨나는 새 Thread는 시스템 자원을 소비하죠. Thread를 만드는데는 CPU사이클이
걸리고 각 스레드는 시스템 자원을 소비하는 스택구조를 사용하고 있고요. Thread가
증가할때마다 Thread overhead 에 의해 더 많은 시간을 소비하게 됩니다. 결국 채팅 시스템은
연결 서비스보다 Thread관리에 더 많은 시간을 소비하게 되죠. 스레드의 증가는 클라이언트
서비스 시간을 증가 시키는 원인이 되죠. Thread는 되독록이면 최소화 시키거나 재사용을
해야 하는데 채팅 시스템은 사용자당 사용시간이 15분 가량  사용 되어 지므로 ThreadPool을
사용 해봐야 의미가 없기 때문에 고정된 쓰레드를 만들어 봐야 소용이 없죠.


제목 : Re: 초기 소스에 문제가 있는듯 한데요..
글쓴이: 명랑폐인™(merong)   2002/10/13 22:16:49  조회수:43  줄수:19
    
comunicate() 메소드인가요?
거기보면 socket 객체를 넘겨주었을때, 계속해서 stream 객체를 여는군요...
제 생각이 맞다면, 메모리 아웃이나, 기타 다른 resource 에러가 발생할것 같은데요..
메세지가 있을때마다 스트림을 생성시킨다는게 좀 이상하군요..(제가 코드를 잘 못
본걸까요?)

그리고, 채팅 프로그램에는 쓰레드 풀링을 사용하는게 좋습니다. 소켓은 풀링할 대상이
아닙니다. TCP/IP로 사용자와 서버간에 1:1 로 연결되어 있는 상태에서 풀링을 한다는거
자체가 말이 안되는듯합니다.

그러나 nio를 사용하지 않는 일반 blocking 메소드를 사용하는 채팅 프로그램이라면,
사용자가 입력이 없을경우는 thread 는 sleep 되거나 read input stream 에서 blocking
되어 있거나 둘중에 하나일테니 말이죠..

client에서 데이타가 오면 blocking이 해제 되면서 다음 작업을 수행하게 될테니, 그
부분에서 Pool에서 thread 를 가져다가 작업을 시키고, 작업이 없으면 다시 thread를
pool에 집어 넣는것이죠..

그럼..


제목 : Re: 메시지 수신부에서 non-blocking I/O  사용(nio패키지) ?
글쓴이: 나승민(rulu)   2002/10/14 01:48:23  조회수:90  줄수:49
  

**^.^**
채팅에서 소켓풀에 대한 부분은 의미가 없다는데 공감하구요..

쓰레드풀을 사용할지 말지는 한번 더 고려를 해볼 문제라고 생각합니다(테스트가
필요하겠네요^^) Allen Holub이란 사람이 쓴 책('쓰레드 능숙하게 다루기)에 서 저자는
쓰레드 객체 생성시간이 많이 걸리므로 쓰레드 풀을 사용하기를 권장하고 있습니다
그러나 Scott Oaks & Henry Wong 라는 분이 쓴 책 자바쓰레드(번역서) -- O'Reilly
한빛미디어출판에서 보면은 쓰레드 객체 생성이간이 풀을 사용할 때와 사용하지 않을 때
별 차이가 나지 않는다고 얘기하고 있습니다..  
제 생각으로는 쓰레드 객체생성을 하는데 걸리는 시간은 클라이언트가 느끼는 상대적
시간이라고 생각이됩니다. 즉 클라이언트가 느끼기에 오래걸린다 싶으면 쓰레드풀을
사용하면 좋겠죠..

신형주님이 올린 chatting프로그램 가이드(두번째)에서 제한적 사항(너무 많은 Thread):
글에 대한 부분입니다..
사용자 마다 쓰레드를 만든다는것은 좋은 아이디어가 아니다.. 저도 그리생각합니다
하지만, 일단 소켓이 연결된(서버-클라이언트간) 후에는 클라이언트가 언제 어느때 ,
메시지를 보낼지 알수 없기 때문에,  사용자별로 읽기-쓰기 부분에 대한 쓰레드를 생성해야
되지 않을까 하는데요.. 쓰레드를 몇개로 구성할지, 그리고 어떻게 분류해서 기능적으로
쓰레드를 구성할지도 중요하지만,,  더 중요한건.

제가 구현하면서 고생하고 있는 부분입니다만 C/S간에 소켓연결이 일단 이루어진다음에
서버쪽에서 메시지를 수신하는 부분에서 blocking I/O를 사용하느냐 non-blocking I/O 를
사용하는가가 상당히 중요한 부분이라고 생각합니다..
당연히 성능상 non-blocking I/O를 고려하고 있습니다
제가 첨에 첨부한 소스코드에서는 --------------->
// Client(사용자)로부터 데이터 수신
while(true){
        lm_oIs = socket.getInputStream();                                        
        lm_lSize = lm_oIs.available();
        if(lm_lSize>0){
                lm_oOis = new ObjectInputStream(lm_oIs);
                lm_oRMesg = (Hashtable)lm_oOis.readObject();
                
                // 이 Terminal Server에 연결된 클라이언트에게 메시지 전송
                sendPacket(lm_oRMesg);
        }else{        
                Thread.sleep(20);
        }
}
나름대로 non-blocking 읽기를 구현하고 있습니다  '자바 쓰레드(한빛미디어)' 책에서는
블록킹되지않는 I/O 중 폴링(Polling) 형태에 해당하고 있습니다.
그런데 위 코드의 문제점은 20 mili seconds 가 지나고 난후 매번 CPU자원을 소비하게된다는
것입니다.  수신한 메시지가 있든 없든지 간에 말입니다..
명랑폐인님이 말씀하신 nio 패키지에 저도 관심이 많습니다(보고있는 중...)
혹시 위 코드를 대체할만한 좋은 예제라도 있는지 궁금합니다.
그리고 JDK1.4에서 발표한 nio패키지를 이용하여 서버 코드를 구성한다고 할 때..
클라이언트는 애플릿을(JDK1.1.8) 쓸수 밖에 없는 구조인데.. 문제가 없는지 궁금합니다



제목 : Re: available구문이 잘못된것 아닌가요
글쓴이: 신형주(shin7688)   2002/10/10 17:18:27  조회수:62  줄수:7
  
lm_lSize는 메시지 교환 하지 않으면 리턴 값이 0(추출할수 있는 데이타수) 이잖아요.
근데 while(lm_lSize==0) 이렇게 구현된 부분이 메모리 소모가 심각 할것 같네요.

입력스트림을 조사하는 InputStream의 available을 조사하고 입력이 있는 경우에만
처리를 해야 하지 않을까요.
이 화일문안의 구조는 잘 모르겠구요. 데이타를 얻었을때만 while(lm_lSize>0)
이었을때만 잘동을 해야 하지 않을까 하네요.


제목 : Re: 입력스트림 조사에 대한 더 좋은방안?
글쓴이: 나승민(rulu)   2002/10/10 18:06:15  조회수:38  줄수:4
  
그럼 while(true){ ~ } 블럭안에서 입력스트림을 조사하는(입력이 있는지 여부)하는
더 좋은 방법이 있을까요? Thread.sleep()을 사용하지 않는 구조로 말입니다.

어렵게 잡은기회인데 꼭 성공하고 싶네요


제목 : Re: 참고소스
글쓴이: 신형주(shin7688)   2002/10/10 18:37:08  조회수:52  줄수:16
  
while (true) {
    try {
        if(lm_oIs.available() > 0) {
            Thread thread = new Thread() {
                public void run() {
                     // 클라이언트들에게 데이타를 보낸다.
                    sendMessage2Client(lm_oIs, client);
                }
            };
            thread.start();
        }          
    } catch(Exception e){}
}

혹시 소스를 보내 주실 생각은 없으신지요?
여기 있는 분들중에 TCP/IP 서버 만든 분들 꽤 일을거에요.


제목 : Re: 대안이 될만한 코드가 아닌거 같습니다
글쓴이: 나승민(rulu)   2002/10/10 19:09:22  조회수:46  줄수:14
  
신형주님이 올려주신 리플 코드는 문제의 소지가 있습니다.
Thread.sleep(miliseconds) 을 주지 않으면 while 구조상 무한루프를 돌기 때문에
CPU 점유가 순식간에 100%까지 올라갈것입니다.


그리고 소스의 완전공개는 개인적으로 어렵습니다.  개인적으로 만든게 아니고 회사의
제품 으로 만들고 있는거라서...(지송^.^~)


그리고 제가 첨에 제시한 코드에서 communicate()메소드 부분은 각각의 쓰레드의
run()메소드에서 호출되는 메소드입니다.
저의 경우 쓰레드는 쓰레드풀링기법을 이용하여 1000개의 쓰레드를 만들어놓고
클라이언트접속을 기다리는 구조로 되어있습니다(참고로 쓰레드풀링은 제가 직접
구현하지않고.. Allen Holub 이라는 분이 만든 걸 이용하고 있습니다.




제목 : Re: 안녕하세요 초보자인데요.. ^^;;
글쓴이: 손님(guest)   2002/10/14 19:27:07  조회수:57  줄수:16
  
토론 재미있게 보고 있습니다. 전 네트워크게임을 짜고 있는데요.
채팅도 들어가고 서버구성도 해야하기때문에.. ^^*

==================================================================================
JDK1.4에서 발표한 nio패키지를 이용하여 서버 코드를 구성한다고 할 때..
클라이언트는 애플릿을(JDK1.1.8) 쓸수 밖에 없는 구조인데.. 문제가 없는지 궁금합니다
==================================================================================

라고 하셨는데 상관 없지 않을까요?
왜냐하면 어차피 서버는 nio로 local 에서 처리하고 client 는 1.1.8 이더라고 어차피
처리는 Stream 으로 이루어 지기때문에 상관 없을거 같습니다.
해보고 말해야되는데 짐작으로 말해서 죄송합니다.

단지 토론에 살짝 참가 하고 싶어서.. ^^;;
계속 좋은 정보 주시면 감사하겠습니다. 수고 하세요



제목 : Re: socket pool 이 아니라 connection managerment pool 이겠죠.
글쓴이: 명랑폐인™(merong)   2002/10/18 21:30:51  조회수:114  줄수:26
    
보통 서버 소켓의 쓰레드에서 메소드에서 connection 요청이 올경우에
socket = sever.accept();

new ChatProcessor(sokcet).start() 형태로 많이 쓰리라 봅니다.
대부분의 책에서도 이렇게 예제를 만들어 놓구요...
그러나 채팅 프로그램을 만들어 보면, ChatProcessor에 여러가지 기능이 들어가서
class 크기가 상당히 커집니다. User class와 Room Class 를 따로 분리한더 하더라도

클라이언트에서 온 각종  메세지들을 parse 하고, 필요한 처리를 하고 db와 연결하여
작업하는 코드를 넣게 되면, 기능만큼 코드가 길어집니다. 코드가 길다는것은 class의
사이즈가 크다는 의미이고, 이것은 객체 생성에 상당한 비용이 든다는 겁니다.

socket pool이라는 의미는 socket을 pooling 하는 의미가 아닌 이 ChatPorcessor를
(runnable class가 아닐수도 있습니다만, 대부분 이렇게 처리하니까..) pool에 미리
생성해놓고 쓰자는 거겠죠..
채팅이다 보니 accept(); 가 주기적으로 생기는것이 아니라 어느때에 몰릴때도 있는데
몰리는 시간에는 접속이 상당히 느린것처럼 보이겠죠..(객체가 늦게 생성되니까.)

채팅 프로그램을 보면, 거의 모든 사용자에 대해서 처리하는 코드가 같고, 사용자에 따라
달라지는 부분은 id, name, 성별, age, email, intro, ip 정도가 되겠죠.

소켓풀이 아닌 client socket를 포함한 ChatProcessor class는 Pool 을 사용하면 성능이
많이 향상될뿐 아니라, 가용갯수를 한정함으로써 OutofMemory를 예방할수 있는 효과도
있겠죠..

그럼.
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 - 커넥션풀링을 할수 있게 소켓객체들을 담아놓은 큐

도움이 되었음 좋겠네요.
자바 스크립트가 실행할 메소드는 public로 잡아야 함.

http://mjava.net

188:         //sleep 할 시간을 초기화,, 쪽지가 도착했을때, 쪽지를 보냈을때..실행..
189:         //javascript에서 호출함으로 반드시 public이어야 함..
190:         public void initTime() {
191:                 this.startTime = System.currentTimeMillis(); //시작시간을 현재시간으로 설정
192:                 this.dtime = this.checkNewMemoTime; //기본 checkNewMemoTime을 thread의 dtime(sleep)으로 설정
193:                
194:                 thread.interrupt();
195:         }
스레드를 실행 시키는 간격이 0.2초 정도라면
거의 cpu를 소모 하지 않는다고 함.

대신 쪽지 클라이언트일때만 가능할 것임.

( 서버라면 순식간에 들어 오는 사람을 모두 처리해야 함으로 )
( 채팅 클라이언트라도 빠른 속도를 위해서라면 ?  잘 모르겠음 )

http://mjava.net

114:         public void run() {
115:                 System.out.println("webclient run");
116:                 int ttime = 200; //thread sleep time;
117:                 int mtime = 0; //memo time;
118:                 int ctime = 0; //check time;
119:                 while(!isStop) {
120:                         try {
121:                        
122:                                 // 0.2초단위로 쓰레드가 쓰레드가 실행되도록 함..
123:                                 // 0.2초라하지만 로컬 시스템의 cpu를 거의 소모하지 않는다.
124:                                 if(this.isLoginCheck) {
125:                                         ctime += ttime;
126:                                         if(ctime > checkLoginTime) {
127:                                                 ctime = 0;
128:                                                 checkLoginUser();
129:                                         }
130:                                 }
131:                                
132:                                 if(this.isNewMemoCheck) {
133:                                         mtime += ttime;
134:                                         if(mtime > dtime) {
135:                                                 mtime = 0;                                        
136:                                                 checkNewMemo();
137:                                         }        
138:                                 }
139:                                
140:                                 thread.sleep(ttime);
141:                                
142:                        
143:                                
144:                         } catch(InterruptedException e) {
145:                                
146:                         }
147:                                
148:                 }
=======================================================
서버에서 클라이언트로 메세지를 보내기 전에 먼저
접속이 현재 제대로 이루어져 있는지 확인해 보아야 한다.


/**
   * 메세지를 모든 다른 클라이언트에게 보낸다.
   */

private void broadcast(Client fom, String message) {
    // 연결이 끊겨진 클라이언트들을 잠시 저장하기 위해 사용
    Vector zombies = new Vector(5);

    Enumeration enum = clients.elements();
    while ( enum.hasMoreElements() ) {
        // 클라이언트 객체가 발송자인 경우 무시
        if (client == from) {
            continue;
        }

        if (client.socket == null) {
            zombies.addElement(client);
            continue;
        }
       client.sendMessage(message);
    }

    enum = zombies.elements();
    while ( enum.hasMoreElements() ) {
        Client client  = (Client) enum.nextElement();
        clients.removeElement(client);
    }
}


메세지를 보낼때 문제가 생기면 try catch 부문에서 잡을 수도 있을것 같다.
하지만 어느게 성능이 더 좋은지 잘 모르겠다.

현재 있는 것은 다음과 같이 움직인다.
1. 살아 있나?
2. 살아 있으면 메세지 보냄
3. 죽어 있으면 Enumeration 에 저장
4. 메세지 전부 보낸후 각 Client 삭제 처리

하지만 메세지를 보낼때 문제가 생긴다면 다음과 같을것이다.
1. 메세지 보냄
2. 죽어 있으면
3. 죽어 있는 클라이언트 삭제
4. 살아 있으면 메세지 보냄.

첫번째것은 각 메세지를 보낼때마다 Enumeration 를 생성하는 로드.
두번째것은 죽은 클라이언트들에게 보내는 메세지 로드와 예외 처리 부분의 로드.


아직은 실력이 없어서 확인을 못 해 본다.
채팅 서버의 경우 처럼 쓰레드별로 클아이언트의 소켓 연결을 전담해서 처리하면
프로그램은 아주 간단해 진다. 하지만 쓰레드를 생성하는 것이 프로세스의 생성보다는
훨씬 가벼운 일이지만, 많은 클라이언트의 동시 접속을 처리하기 위해 네트웍 연결마다
쓰레드를 생성하게 되면 메모리 등 리소스 오버헤드가 커지게 된다. 이런 경우에는
쓰레드 하나에 몇 개의 네트웍 연결을 할당하는 방식으로 개선할 수 있다.

쓰레드 하나가 여러 개의 네트웍 연결을 처리하려면 이 채팅 서버처럼 readLine()과 같은
블로킹 메소드를 사용하면 메시지가 들어올 때까지 이 메소드에서 블록되어 버리기 때문에
그동안 다른 네트웍 연결을 처리할 수가 없다. 따라서 이런 경우에는 넌블로킹 메소드인
InputStream의 available() 메소드나 Reader의 ready()를 사용하여 입력이 있는지 여부를
조사해보고, 입력이 있을 경우 현재 가능한 만큼만 읽어들이는 메소드인 read(byte[]) 혹은
read(char[]) 메소드를 사용해야 한다.

단일 쓰레드로 위의 채팅 서버를 고친다면 단일 쓰레드 부분은 다음과 같은 코드로 실행
될 수 있따. 책의 부록에 포함됨 NonBlockingChatServer.java 소스 코드를 참고하기 바란다.


/**
   * 각 클라이언트의 소켓 입력 상태를 검사한다.
   */

public void run() {
    wile (true) {
        Enumeration enum = clients.elements();
        while ( enum.hasMoreElements() ) {
            Client client = (Client) enum.nextElement();
            try {
                if ( client.in.ready() ) {
                    client.getMessage();
                }
            } catch (IOException e) {
                System.err.println(client.getname()+ " 출력 에러 : " + e.getMessage() ) ;
                client.closeSocket();
            }
        }
        try {
            Thread.sleep(10); // 잠깐씩 쉰다.
        } catch ( InterrupteExecption e) { }
    }
}


서버에서 유령사용자 처리는 클라이언트의 일방적인 PING (Not ICMP but send dummy byte)을 사용하도록 함.
irc를 이용해서 서버를 만들고,
클라이언트는 java applet 과 javascript로 만들어 낸다.

irc에 대한 자료는 나중에 추가하도록 한다.

세이클럽, cafe24 등이 irc 기반 이라고 함
페이지 이동
pageList (target, start, scale, view, total, URL)
target = 표시할 객체
start = 글위치 (페이지 단위가 아니고 순차적인 글의 위치)
scale = 페이지의 글리스트 갯수
view = 표시될 페이지 갯수
total = 전체 글 갯수
URL = 링크URL (마지막에 '&'은 빼고... 안빼도 상관없지만^^)


<TABLE bgColor=#999999 height=30><TR><TD><DIV id=pageListDIV></DIV></TD></TR></TABLE>
<SCRIPT>
  function pageList (target, start, scale, view, total, URL) {
    var html = "";
    var reLoading = " <a href=\"javascript:pageList(" +target.id+ ",{page}," +scale+ "," +view+ "," +total+ ",'" +URL+ "');\">{PAGE}</a>";

    if (total % scale) add = 1; else add = 0;
    maxPage = Math.floor(total / scale) + add;

    begin = Math.floor(Math.floor(start/(scale * view)) * view + 1);
    end = Math.floor(begin + view - 1);
    if(end > maxPage) end = maxPage;

    if (begin > 1) {
      html += " <a href='" +URL+ "&start=0'>[1]</a>";
      html += reLoading.replace ("{page}", (begin-2)*scale).replace ("{PAGE}", "◀:");
    }
    for(var i=begin; i<=end; i++) {
      page = (i - 1) * scale;
      if(start != page) {
        html += " <a href='" +URL+ "&start=" +page+ "'>[" +i+ "]</a>";
      } else {
        html += " <b>" +i+ "</b>";
      }
    }
    if (end < maxPage) {
      if (end < maxPage-1) {
        html += reLoading.replace ("{page}", end*scale).replace ("{PAGE}", ":▶");
      }
      page = maxPage * scale;
      html += " <a href='" +URL+ "&start=" +page+ "'>[" +maxPage+ "]</a>";
    }

    target.innerHTML = html;
  }
  pageList (self.pageListDIV, 0, 30, 10, 2000, 'http://phpschool.com/bbs2/inc_board.html?mode=&field=&period=&s_que=&code=tnt2&operator=&category_id=');
</SCRIPT>
<style>
img { margin:0; border-width:0; }
</style>
<table style="table-layout:fixed; overflow:hidden;">
<tr>
<td width="50px">글글글</td>
<td width="22px" height="22px" onmouseover="over.style.visibility='visible';" onmouseout="over.style.visibility='hidden'; push.style.visibility='hidden';" onmousedown="push.style.visibility='visible';" onmouseup="over.style.visibility='visible'; push.style.visibility='hidden';"
><nobr
><img name="normal" src="http://my.netian.com/~crosser/images/button_go_normal.png"
><img name="over" src="http://my.netian.com/~crosser/images/button_go_over.png" style="position:relative; left:-22; visibility:hidden;"
><img name="push" src="http://my.netian.com/~crosser/images/button_go_push.png" style="position:relative; left:-44; visibility:hidden;"
></nobr></td>
<td width="50px">글글글</td>
</tr>
</table>
역시 플래쉬는 노가다다 ;;;
( 자기가 못한다는 이야기는 안한다 ;;; )


그냥 끄적여 본건데 ...
때려치아야 겠다. ;;;


디자인은 감이다. ;;;;

난 사람이라서 못하는 것 뿐이다. ;;;;


그래도 한건 아쉬워서 올려 놓음 ;;;



"꼭 가고 싶습니다" 라고 하지 않아도 "꼭 보내 준다"

'재미' 카테고리의 다른 글

온라인에서의 말 실수  (0) 2003.07.16
[추천] 매트릭스 핑퐁  (0) 2003.07.15
박카스 CF의 문제점.  (0) 2003.07.12
[펌] 문자삐삐 메신저 아르바이트에서 생긴일  (0) 2003.07.09
가공할 협박  (0) 2003.07.09
일본인이 보는 한국  (0) 2003.07.06
Java를 이용해서 클라이언트 쪽 채팅을 만들어 보자.

우선 웹에서 채팅이 가능해야 하기 때문에 Applet를 사용하여야 한다.

그리고 HTML 페이지를 이용하여 화면을 보여 줄것임으로. DHTML을 사용하면 된다.

javascript를 사용하여 DHTML 객체(?)에 write를 해 주는 방법을 쓰면
리프레쉬가 없이 화면을 보여 줄 수 있다.
( 여기서 JSObject 라는 클래스를 사용하여야 한다. )

채팅에서 보내지고 받아 오는 데이타는 모두 숨겨진 프레임에 있는 애플릿을 이용한다.
물론 채팅에 쓰여질 스크립트 역시 숨겨진 프레임에 있으면 될 것이다.

보내는 데이타일 경우에는 자바 스크립트를 이용해서  숨겨진 애플릿으로 데이타를 전송한다.

그리고 받는 데이타의 경우에는 애플릿으로 데이타를 받고,
받은 데이터에 따른 자바 스크립트를 실행 시켜 주어서 화면에 출력 또는
또 다른 작업 ( 로그아웃(이것은 서버에서 처리) 메세지라면
화면을 닫아주는것(이것은 클라이언트의 자바스크립트가 실행)) 을 할것이다.

서버의 사용량을 최소한으로 줄이기 위해 모든 자료들은
최대한 자바 스크립트를 이용해서 처리를 해야 하며
서버는 단지 최소한의 자료만을 처리하고 전송해야 한다.
( 많은 사용자가 채팅을 할 수도 있으므로... )


간단한 자바 소스들을 보면서 이야기를 진행해 보자.
( 여기서 사용된 소스는 http://netb.co.kr 에 있는 채팅 소스를 이용한 것임을 밝힌다. )


애플릿에서는 소켓을 이용해 데이터를 전송한다.
간단한 예제:
chatSocket = new Socket(host, port);
out = new PrintWriter(chatSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(chatSocket.getInputStream()));        

보낼때는 그냥 이미 만들어진 out 스트림을 이용해서 데이터를 전송해 준다.
간단한 예제 :
public void send(String pStr)
{
        out.println(pStr);
}


애플릿이 데이터를 수신할때에는, 데이터가 언제 날아 올지 알 수 없으므로
스레드를 이용해서 항상 데이터를 받을 준비를 하고 있어야 한다.
간단한 예제 :
public void connect(String host, int port)
{
           .......
        Thread thd;
        thd = new Thread(this);
        thd.start();        
           ........
}

public void run()
{
        try
        {
                while(true)
                {
                        //RecvProcess(in.readLine());                
                        RecvProcess(new StringTokenizer(in.readLine(), sep));        
                }                                        
        }catch (IOException e){
                disconnect();
        }
}


데이터 수신에 문제가 생겼다면 disconnect() 해 준다.
(위의 예제에서 try ~ catch 부분임 )
물론 다른 사용자들에게 이 사람이 접속을 끊었다 또는 자신이 접속을 끊는다는것을
서버에게 알려 준뒤 in 과 out 두개의 스트림을 닫아 주고 socket의 연결도 닫아야 할 것이다.
간단한 예제 :
public void disconnect()
{
        if (bConnected == true)
        {
                send("exit" + sep + client.strID + sep + client.strRoomname + "\n");
                try
                {
                        out.close();
                        in.close();
                        chatSocket.close();
                }catch (IOException e){}

                bConnected = false;
        }
}        


제대로 된 데이터가 들어 왔다면 거기에 맞게 각 항목을 실행 시켜 주면 된다.
public void RecvProcess(StringTokenizer token)
{
              ......
        else if (mode.equals("introduce"))
        {
                client.RecvIntroduce(user, info1);
        }
              ......
}


여기에서 client는 역시 클래스이며 실제적인 채팅 클라이언트가 들어 있다.
간단한 예제 ( Client.java ) :
public class Client extends Applet ;

위에서와 같이 Applet로 이루어져 있다. ( 그러므로 당연히 HTML에서도 이것을 불러 올 것이다. )

client.RecvIntroduce(user,info1)을 실행 하면 아래와 같은 코드가 실행이 된다.
간단한 예제 ( Client.java ) :
public void RecvIntroduce(String pUser, String pStr)
{                                        
        win.eval("WriteIntroduce(\"" + pStr + "\")");
}

이 코드는 win의 객체에 eval를 이용해서 WriteIntroduce("소개");를 실행 시키게 된다.
여기서 win객체는
public JSObject win;
win = (JSObject)JSObject.getWindow(this);  
로 되어 해당 프레임을 선택하도록 했다.

WriteIntroduce("소개"); 부분은 (html 프레임이기 때문에)당연히
자바 스크립트로 이루어져 있을 것이다.
간단한 예제 ( applet.php ) :
function WriteIntroduce(pMsg)
{
          ......
        top.main.document.write("</td>");
          ......
}
그러므로 접속한 모든 채팅 클라이언트들의 화면에 소개가 나갈 것이다.

화면을 제일 아래로 자동으로 스크롤 시키는 것도 빼 먹으면 안 될 것이다.
간단한 예제 :
function SetScroll()
{
        //top.main.document.write("<script>scroll(0,1000000);<\/script>\n");
        top.main.scrollBy(0,top.main.document.body.scrollHeight);
}



만약 내가 소개를 보내다고 해 보자.

내 소개를 보내기 위해 특정 버튼 또는 명령을 클릭했을때 다음과 같은 구문이 실행 될것이다.
다음을 실행 시키면 될것이다.
간단한 예제 :
<script>
    var msg;
    msg = "<table border='0' cellspacing='0' cellpadding='0'>";
    msg += "<tr>";
    top.ChatApplet.chat.SendIntroduce(escape(msg));
</script>

여기 ChatApplet.chat.SendIntroduce 에서
chat는 애플릿의 이름이며 top.ChatApplet는 프레임 명이다.
즉 top.ChatApplet에 있는 chat라는 애플릿에서 ( Client.class )
SendIntroduce(..)라는 메소드를 실행 시키게 되는 것이다.

이제 SendIntroduce() 메소드의 동작을 보도록 하자.
간단한 예제 (Client.java) :
public void SendIntroduce(String pStr)
{                
        client.send("introduce" + sep + strID + sep + pStr + "\n");                
}

여기서의 client는 ChatClient client; 라고 선언 되어 있다.
간단한 예제 ( ChatClient.java ) :
public void send(String pStr)
{
        out.println(pStr);
}

다시 out 스트림(소켓으로 연결 되어있음)으로 데이터를 전송한다.
( 여기서 알 수 있듯이 서버와의 모든 데이터 송 수신은 ChatClient 가 한다. )
( 단지 Client는 어떻게 동작하고 어떻게 자바 스크립트를 실행 시켜 줄것인가를 결정한다. )

서버쪽에서는 현재 같은 방에 접속 되어 있는 모든 사람에게 해당 메세지를 뿌려준다.
물론 저기 위에 설명한 방법대로 데이터가 넘어 오고 화면에 출력 될것이다.


그리고 다음은 애플릿을 화면에 보여 주기 위한 예제이다.

간단한 예제:
<applet code="Client.class" MAYSCRIPT codebase="applet" width=100 height=100 name='chat'>

위와 같이 불러 온다.
여기서 MAYSCRIPT라는 것은 자바 스크립트와 연동을 위해서 필요한 부분이라고 한다.



많은 도움이 되기를.
즐거운 하루~~!

다시 한번 말하지만. http://netb.co.kr 에 있는 채팅 소스를 이용한 설명 입니다.
문자삐삐 메신저 아르바이트에서 생긴일


안녕하세여~~

징징이에여~~

원래 진진인뎅 li 희망선택~~~

예전에 잠깐 문자삐삐 메신저 알바를 했었는데.

참 재미있던일이 많았어여!!

문자삐삐 메신저는 문자삐삐를 치는 사람의 목소리를 듣고

그것을 글로 처서 삐삐에 전송해주는 역할입니다.

사용자들이 20초 정도 녹음하고 그것을 그대로

녹취해서 글로써 보내는 역할을 하는거죠..

그런데 회사 방침에 의해서 절대 욕을 써서 보내선 안되죠..

삐삐치는 사람들이

"야! 이 X만한 새끼야! 죽을래? 어디 처박혀있어 씨X (X발)! 빨리 연락해!"

라고 녹음하면.

"어디있냐? 빨리 연락해" 등으로 축소 시켜서 문자를 전송해주어야 되죠.

또 욕만 하다가 끊는 경우가 있는데. 그것은(너무 심한경우는)

(음성메시지 확인바람)이란 말을 전송해주죠..

넘 황당&당황&어처구니없음을 느낀 메시지 녹음을 보여드리겠어여~~ 공개~~


"야! 너 이X새끼야! 너 걔랑 X구리(빠X리) 떴지! 죽여버리겠어?"

문자메시지 전송->
혹시 너 바람 피웠니? 혼날래? (음성메시지 확인바람)

"아따 쓰블름 마빡의 심줄을 뽑아서 기타줄로 튕겨뿐다! 빨리 연락하그래이~!"

문자메시지 전송->
이마에 힘줄을 뽑아주기 전애 연락해죠

"(남자목소리로) 나 미영인데. 난 너랑 이제 더이상 만나기 싫어! 연락도
하지마!"

문자메시지 전송->
나 미영인데. 난 너랑이제 더이상 만나기 싫어! 연락하지마!
(남자목소리임)

점점 일하다 보니깐

장난끼가 발동해서 약간 장난좀 쳤습니다..

이히히 아래 또 보면.

"병풍뒤에서 향냄새 맡기 싫으면 올때 CD 꼭 가져와"

문자메시지 전송->
올때 꼭 CD가져와 (상당히 벼루고 계심)

"형님! 당했습니다! 사시미가 빠구리한테 칼빵을 맞았뎁니다!"

문자메시지 전송->
형! 당했어요! 칼이 성관계(?)한테 칼에 찔렸데요

"너 오늘도 안사오면 죽여버릴꺼야! 이개새끼야! 벌써 몇일째야!"

문자메시지 전송->
너 오늘도 안사오면 혼나! 이 강아지야! 벌써 몇일째야!

이 강아지야.. 그거 한번했더니 우리 상무님이 뭐라고 막 혼내시더라고요..

제일 황당한 음성은!!

갑자기 어느 여자가 "으아!!!!!" 하더니 비명을 지르고 끊는거에요.

어떻게 하겠어요,... 위급한 상황같으니 알려줘야 겠더라구여..

문자메시지 전송->
긴급<<어느 여성이 큰 신음소리를 지르며.. 위급한 상황같아요>>
빨리 어떻게든 해보새여.. (음성메시지 확인바람)

걱정되서 한사람한테 꼐속 보냈죠..

황당하더라구요.어떻게 됐을까...아하핫..

여러분 행복하세여





-하이텔 / 보낸이:TgThKim (김태균 )

출처 : http://www.humoruniv.com/hwiparambbs/read.php?pri=0&table=new&best=&page=1&sort=&number=424&tableid=humor01&ref=411&divfire=

'재미' 카테고리의 다른 글

[추천] 매트릭스 핑퐁  (0) 2003.07.15
박카스 CF의 문제점.  (0) 2003.07.12
[펌] 문자삐삐 메신저 아르바이트에서 생긴일  (0) 2003.07.09
가공할 협박  (0) 2003.07.09
일본인이 보는 한국  (0) 2003.07.06
미운 당근 자식  (0) 2003.06.25
테러리스트들이 여야 정치인으로 가득 찬 비행기를 공중납치했다. 그들은 관제탑에 여러 가지 요구사항을 말하면서 이렇게 덧붙였다.
 
"만약 요구조건을 들어주지 않으면 1시간에 1명씩 풀어 놓겠다."

'재미' 카테고리의 다른 글

박카스 CF의 문제점.  (0) 2003.07.12
[펌] 문자삐삐 메신저 아르바이트에서 생긴일  (0) 2003.07.09
가공할 협박  (0) 2003.07.09
일본인이 보는 한국  (0) 2003.07.06
미운 당근 자식  (0) 2003.06.25
희한한 공식 (different equation)  (0) 2003.06.24

하하.
* Chan님에 의해서 게시물 이동되었습니다 (2003-07-06 15:57)

'재미' 카테고리의 다른 글

박카스 CF의 문제점.  (0) 2003.07.12
[펌] 문자삐삐 메신저 아르바이트에서 생긴일  (0) 2003.07.09
가공할 협박  (0) 2003.07.09
일본인이 보는 한국  (0) 2003.07.06
미운 당근 자식  (0) 2003.06.25
희한한 공식 (different equation)  (0) 2003.06.24


^^
* Chan님에 의해서 게시물 이동되었습니다 (2003-07-06 15:57)

'재미' 카테고리의 다른 글

박카스 CF의 문제점.  (0) 2003.07.12
[펌] 문자삐삐 메신저 아르바이트에서 생긴일  (0) 2003.07.09
가공할 협박  (0) 2003.07.09
일본인이 보는 한국  (0) 2003.07.06
미운 당근 자식  (0) 2003.06.25
희한한 공식 (different equation)  (0) 2003.06.24
romance mathematics
(로맨스 수학)
smart man + smart woman = romance
(똑똑한 남 + 똑똑한 녀 = 로맨스)
smart man + dumb woman = affair
(똑똑한 남 + 멍청한 녀 = 불륜)
dumb man + smart woman = marriage
(멍청한 남 + 똑똑한 녀 = 결혼)
dumb man + dumb woman = pregnancy
(멍청한 남 + 멍청한 녀 = 임신)

office arithmetic
(사무 공식)
smart boss + smart employee = profit
(똑똑한 사장 + 똑똑한 직원 = 수익)
smart boss + dumb employee = production
(똑똑한 사장 + 멍청한 직원 = 생산)
dumb boss + smart employee = promotion
(멍청한 사장 + 똑똑한 직원 = 승진)
dumb boss + dumb employee = overtime
(멍청한 사장 + 멍청한 직원 = 초과 근무)

shopping math
(쇼핑 산수)
a man will pay $2 for a $1 item he needs.
(남자는 필요한 $1짜리 물건을 $2에 산다.)
a woman will pay $1 for a $2 item that she doesn
(여자는 필요없는 $2짜리 물건을 $1에 산다.)

general equations & statistics
(전반적인 공식과 통계들)
a woman worries about the future until she gets a husband.
(여자는 미래에 대한 걱정을 한다. 남편을 얻을 때까진.)
a man never worries about the future until he gets a wife.
(남자는 미래에 대한 걱정을 전혀 하지 않는다. 부인을 얻을 때까진.)
a successful man is one who makes more money than his wife can spend.
(자기의 부인이 쓰는 것보다 많이 버는 남자를 우리는 성공한 남자라 칭한다.)
a successful woman is one who can find such a man.
(그런 남자를 찾은 여자를 우리는 성공한 여자라 칭한다.)

happiness
(행복)
to be happy with a man, you must understand him a lot and love him little.
(남자와 행복하기 위해서 당신은 그를 많이 이해하고 사랑은 조금 해야 한다.)
to be happy with a woman, you must love her a lot and not try to understand her at all.
(여자와 행복하기 위해서 당신은 그녀를 많이 사랑하고 절대 이해하려 해서는 안된다.)

appearance
(외모)
men wake up as good-looking as they went to bed.
(남자는 잘려고 누웠을때와 같은 모습으로 일어난다.)
women somehow deteriorate during the night.
(여자는 왠지 자는 동안 좀 썩은 듯 하다.)

propensity to change
(변화의 경향)
a woman marries a man expecting he will change, but he doesn
(여자는 남자가 변할거라 예상하고 결혼한다. 하지만 그는 변하지 않는다.)
a man marries a woman expecting that she won change, and she does.
(남자는 여자가 변하지 않을거라 예상하고 결혼한다. 하지만 그녀는 변한다.)

discussion technique
(토론의 미학)
a woman has the last word in any argument.
(어떠한 말싸움에서도 마지막 말을 하는 사람은 여자다.)
anything a man says after that is the beginning of a new argument.
(그 이후에 남자가 어떤 말을 한다면, 그것은 새로운 말싸움의 시작이다.)
* Chan님에 의해서 게시물 이동되었습니다 (2003-07-06 15:57)

'재미' 카테고리의 다른 글

박카스 CF의 문제점.  (0) 2003.07.12
[펌] 문자삐삐 메신저 아르바이트에서 생긴일  (0) 2003.07.09
가공할 협박  (0) 2003.07.09
일본인이 보는 한국  (0) 2003.07.06
미운 당근 자식  (0) 2003.06.25
희한한 공식 (different equation)  (0) 2003.06.24

+ Recent posts