톰캣의 컴포넌트 포함관계는 위의 그림과 같다.

  Bootstrap > Server > Service > Connector , Engine > Host > Context  > Loader, Wrapper


  1. 서버 

     -  카탈리나 서블릿 컨테이너 전체를 나타냄 + 다른 모든 컴포넌트를 포함.

     -  전체 시스템을 시작하고 종료할수 있다.

     -  server.xml 파일과 연관되있다.

     -  start() 메소드에서는 , 모든 서비스들을 시작한다. (http://dbdb.tistory.com/3)

     -  await() 함수안에서 8085 포트로 생성한 서버소켓에 시스템 종료 메세지기 올때까지 기다린다.


 2. 서비스

     - 1개의 컨테이너나 1개 이상의 커넥터등의 컴포넌트를 포함한다.

     - 여러개의 커넥터를 가짐으로서 톰캣은 여러 프로토콜(http, https)에 대해 서비스하는것이 가능해진다.

     - 서비스와 연결된 컨테이너는 각 커넥터의 setContainer 메소드에 전달되어 서로 연결된다.

 3. 엔진 

     - 컨테이너 인터페이스를 상속받는다. (최상위 컨테이너 역할을 한다)

     - 카탈리나 서블릿 엔진 전체를 나타낸다.

     - 여러개의 가상 호스트를 지원하고 싶다면 엔진을 사용해야 할것이다.(http://dbdb.tistory.com/4)

     - 엔진이 사용되면 엔진은 항상 최상위의 컨테이너가 된다. 

     - 엔진에 추가되는 하위 컨테이너는 보통 Host 나 Context 를 구현한 컨테이너이다. 

     - 기본적으로 톰캣 배포본에서는 엔진이 사용되며, 엔진은 하나의 기본 호스트를 갖는다.


 4. 호스트

     - 컨테이너 인터페이스를 상속받는다.

     - 최상위 컨테이너로 호스트를 사용할수도 있다. (엔진을 사용하면 호스트가 차일드)

     - 각 컨텍스트가 ContextConfig 를 사용하도록 설정 되 있다면 반드시 호스트가 존재한다.

     - 컨텍스트의 상위 컨테이너는 일반적으로 호스트이지만, 다른 컨테이너의 구현을 사용하거나 

       필요없다면 생략될 수도 있다. (실제 상황에서는 톰캣은 항상 호스트를 필요로한다) 

 

5. 컨텍스트 

    - Context 인터페이스를 상속받는다. (StandardContext 가 구현체) 

    - 하나의 웹 어플리케이션을 나타내며 1개 이상의 래퍼를 포함한다.

    - 로더와 매니저를 필요로한다.

    - start() 메소드가 호출되야한다.

    - %CATALINA_HOME%/conf 디렉토리에 있는 기본 web.xml 내용을 파싱해, 배치돼있는 모든 

      애플리케이션에 적용할수 있도록 준비한다.

    - 각 애플리케이션수준의 web.xml 을 처리한다. 

    - 각종 밸브 (인증자, 증명등)를 처리한다.

      * start() 메소드에서 하는일 

          가. BEFORE_START_EVENT 이벤트 발생 

          나. available 특성의 값을 false 로 지정

          다. configured 특성의 값을 false 로 지정

          라. 자원준비

          마. 로더 준비

          바. 매니저 준비

          사. 문자셋 맵퍼 초기화

          아. 이 컨텍스트와 연관된 다른 컴포넌트 구동

          자. 하위 컨테이너(래퍼) 구동

          차. 파이프라인 구동

          카. 매니저 구동

          파. START_EVENT 이벤트 발생 (리스너 (ContextConfig) 는 몇가지 설정 작업을 수행)

          타. configured 값 확인

          하. AFTER_START_EVENT 이벤트 발생 



6. 로더

   - 컨텍스트마다 하나의 클래스 로더가 존재한다. 따라서 컨텍스트끼리 클래스 공유는 안된다.


7. 래퍼 

   - 하나의 서블릿을 래핑한다.   




시작 소스 예제 

public final class Bootstrap {

  public static void main(String[] args) {


    System.setProperty("catalina.base", System.getProperty("user.dir"));

    Connector connector = new HttpConnector();


    Wrapper wrapper1 = new StandardWrapper();

    wrapper1.setName("Primitive");

    wrapper1.setServletClass("PrimitiveServlet");

    Wrapper wrapper2 = new StandardWrapper();

    wrapper2.setName("Modern");

    wrapper2.setServletClass("ModernServlet");


    Context context = new StandardContext();

    // StandardContext's start method adds a default mapper

    context.setPath("/app1");

    context.setDocBase("app1");


    context.addChild(wrapper1);

    context.addChild(wrapper2);


    LifecycleListener listener = new SimpleContextConfig();

    ((Lifecycle) context).addLifecycleListener(listener);


    Host host = new StandardHost();

    host.addChild(context);

    host.setName("localhost");

    host.setAppBase("webapps");


    Loader loader = new WebappLoader();

    context.setLoader(loader);

    // context.addServletMapping(pattern, name);

    context.addServletMapping("/Primitive", "Primitive");

    context.addServletMapping("/Modern", "Modern");


    Engine engine = new StandardEngine();

    engine.addChild(host);

    engine.setDefaultHost("localhost");


    Service service = new StandardService();

    service.setName("Stand-alone Service");

    Server server = new StandardServer();

    server.addService(service);

    service.addConnector(connector);


    //StandardService class's setContainer will call all its connector's setContainer method

    service.setContainer(engine);


    // Start the new server

    if (server instanceof Lifecycle) {

      try {

        server.initialize();

        ((Lifecycle) server).start();

        server.await();

        // the program waits until the await method returns,

        // i.e. until a shutdown command is received.

      }

      catch (LifecycleException e) {

        e.printStackTrace(System.out);

      }

    }


    // Shut down the server

    if (server instanceof Lifecycle) {

      try {

        ((Lifecycle) server).stop();

      }

      catch (LifecycleException e) {

        e.printStackTrace(System.out);

      }

    }

  }

}

리눅스에서 애플리케이션 시작하기

java -classpath ./lib/servelt.jar:./lib/commons-collections.jar:./commons-digester.jar:./libnaming-factory.jar:./lib/naming-common.jar:./lib/tomcat-util.jar:./  ex14.pyrmont.startup.Bootstrap


중지하기

java ex14.pyrmont.startup.Stopper


서블릿 호출하기

http://localhost:8080/app1/Primitive


'WAS & 웹서버' 카테고리의 다른 글

톰캣 최종분석 ex05 소스 분석  (0) 2015.08.15
톰캣 클래스패스의 이해 (번역)  (0) 2015.07.31

1. 투명 PNG 파일 만들기  를 먼저해야한다.

https://pixlr.com/editor/   이 싸이트를 이용한다. 
사용방법은 http://belitino.tistory.com/96 참고~

2. Button을 상속받은 비트맵 버튼을 만든다. (이미지 버튼은 좀 이상한듯) 

package com.company.mybitmapbutton;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

/**
* Created by brad on 2015-09-14.
*/
public class MyBitmapButton extends Button {

private int normalButton = 0;
private int clickedButton = 0;


public MyBitmapButton(Context context) {
super(context);
}

public MyBitmapButton(Context context, AttributeSet attrs) {
super(context, attrs);
}


public void setImage(int normalImg, int clickedImg) {
this.normalButton = normalImg;
this.clickedButton = clickedImg;
super.setBackgroundResource(normalButton);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

int action = event.getAction();

if(action == MotionEvent.ACTION_DOWN){
setBackgroundResource(clickedButton);
}else if(action == MotionEvent.ACTION_UP){
setBackgroundResource(normalButton);
}

return true;

}
}


3. 아래처럼 가져다가 사용한다.


plusButton = (MyBitmapButton) findViewById(R.id.button);
minusButton = (MyBitmapButton) findViewById(R.id.button2);
onOff = (MyBitmapButton) findViewById(R.id.button3);


plusButton.setImage(R.drawable.plus, R.drawable.plus);
minusButton.setImage(R.drawable.minus, R.drawable.minus);
onOff.setImage(R.drawable.on, R.drawable.off);





레퍼런스:

https://www.youtube.com/watch?v=P-A2m2KuTNU&index=55&list=PLG7te9eYUi7vXZf7O6Fd2YCnJlx5YG9qq







첫째. 웹과 상호통신을 하기위해 WebViewInterface 클래스 만들기 

public class WebViewInterface {

private WebView mWebView;
private Activity mContext;
private final Handler handler = new Handler();


public WebViewInterface(Activity activity, WebView view) {
mWebView = view;
mContext = activity;

}

@JavascriptInterface
public void callSettingsActivity(final String message) {
Toast.makeText(mContext, "settings in ...", Toast.LENGTH_LONG).show();

Intent intent = new Intent(mContext, SettingsActivity.class);
mContext.startActivity(intent);

}
}

- 웹에서 callSettingsActivity 함수를 호출하면 인텐트를 통해  SettingsActivity 를 시작한다.



둘째. 웹뷰와 인터페이스클래스와 연결해주기


mWebViewInterface = new WebViewInterface(MainActivity.this, webView);
webView.addJavascriptInterface(mWebViewInterface, "android");

 생성자로 this 넘기고 webView 를 넘겨준다. 

 - webView 에 인터페이스 객체의 alias 로  "android" 를 설정한다. 



셋째. 웹페이지 상에 자바스크립트 함수 만들기 

- TEST.HTML -

function  callActivity1(){

if(window.android){

    console.info("android");

    window.android.callSettingsActivity(number.value);

}

else{

console.info("web");

document.form.submit();

}

}

}

 - callActivity 함수가 호출되면 "android" 가 정의되있으면 네이티브앱쪽으로 호출하고, 아니면 웹서버로 호

   출하는 코드 


이 글의 요약 

-  로컬에 있는  웹 파일들을 불러와서 작업할때


웹뷰를 사용하는데 있어서 두드러지는 장점은 , 앱안에 필요한 웹 리소스들을 저장할수있다는것이다.

그것은 오프라인일때도 작업을 가능케 해주며, 로딩시간을 증진시킬것이다. 

HTML, JavaScript, CSS  를 assets 디렉토리 (src/main/assets 등에 만듬)로부터 가져와보자.

주의:   CSS 나 Javascript  를 참조할때 절대경로는  WebView 에서 작동하지 않는다.  다음과 같이 상대경로로  설정해야한다. ("/pages/somelink.html"  -> "./pages/index.html" )

아래와 같이 로딩하자.  ( 리모트에 있는 URL 을 읽어와서 작업하기 전에 전처리할것들을 이렇게 처리해도될듯)

mWebView.loadUrl("file:///android_asset/www/index.html");

( assets 안에 www 만들었어도, 링크는 저렇게'android_asset'  해야한다.)


shouldOverrideUrlLoading  를 요렇게 하면 로컬페이지가 아닐경우 브라우저를 오픈한다. 

public class MyAppWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if(Uri.parse(url).getHost().length() == 0) {
            return false;
        }

        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        view.getContext().startActivity(intent);
        return true;
    }

}




레퍼런스 :

https://developer.chrome.com/multidevice/webview/gettingstarted  



이 글의 요약 

-  디바이스의 돌아가기버튼 (back 버튼) 을 클릭했을때, 이전 웹 페이지로 돌아가고싶을때 


WebView 는  canGoBack  함수를 가지고있는데, 이것을 가지고 요리하면 됩니다. 코드의 굵은 부분을 보시면됨.

public class MainActivity extends Activity {
   
 private WebView mWebView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     ...
 }
   
    @Override
    public void onBackPressed() {
        if(mWebView.canGoBack()) {
            mWebView.goBack();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
       ...
    }
    
}




레퍼런스 :

https://developer.chrome.com/multidevice/webview/gettingstarted  

이 글의 요약 

-  원하는 URL 만 웹뷰로 보여주고, 나머지는 모바일 브라우저를  새창으로 띄우고 싶을때 



webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("http://www.html5rocks.com/");

안드로이드에 webview 를 추가한후에 이것만 코딩한후에  실행시켜보면 , 의도와는 다르게 

앱 내부의 webview 에서 페이지가 뜨는것이아니라, 외부 브라우저를 통해서 띄우려고 할것인데..

이벤트가 일어나는 순서는 :

  1. WebView 는 리모트서버로부터 원래  URL 로딩을 시도하고, 새로운  URL 로 리다이렉트를 갖는다.
  2. WebView 는 시스템이 URL 에 대해 뷰 인텐트를 핸들링할수있는지 체크하고 만약 그렇다면 시스템은 URL 네비게이션을 핸들링한다. 그렇지 않으면  웹뷰는 내부적으로 네비게이트할것이다. (즉, 디바이스에 브라우저가 없다면)
  3. 시스템은 사용자의 http:// URL 을 핸들링할수있는 더 나은 어플리케이션을 선택한후 (즉, 사용자 디폴트 브라우저) 만약, 브라우저가 많으면 아래처럼 선택창을 띄운다.

browser selection dialog

만약 , 어플리케이션의 내부의 웹뷰를 통해서 보고싶으면 WebView 로 부터의 다양한 이벤트를 핸들링할수있 WebViewClient 를 오버라이딩해야한다. 

아래 같은 기본 WebViewClient 구현은 웹뷰에서 어떤 URL 이나 열수있게 한다.

// 브라우저 대신해 WEBVIEW 로 열기위해 강제로 링크하고 리다이렉트한다.

webView.setWebViewClient(new MyAppWebViewClient());

public class MyAppWebViewClient extends WebViewClient {

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
         return false;
}
}


return true 로 하면 처리하지 않는다. (웹뷰에 아무것도 나타나지 않음) 


그럼 다른 싸이트 말고, 원하는 싸이트만 열수있게 하려면 어떻게 해야하는가?

shouldOverrideUrlLoading  메소드를 오버라이딩하면 된다. 

아래와 같이 코드를 추가해보자.


  1. public class MyAppWebViewClient extends WebViewClient {
            
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if(Uri.parse(url).getHost().endsWith("html5rocks.com")) {
                    return false;
                }
                 
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                view.getContext().startActivity(intent);
                return true;
            }
        }

    URL 이 html5rocks.com 으로 끝나는것은 내부 웹뷰에서 처리한다는 뜻이다.  리턴 false 는 그 자신이 처리한다는 뜻이고, 만약 다른 URL 이라면 새로운 안드로이드 Intent 를 사용해서 새로운 액티비티 (설치된 모바일 브라우저) 를 호출하게된다.



레퍼런스 :

https://developer.chrome.com/multidevice/webview/gettingstarted  

(http://tutorials.jenkov.com/java-concurrency/starvation-and-fairness.html 요약 )

자바 Starvation 요인 

  1. 은 우선순위의 쓰레드가 모든 CPU Time 을 소모한다.
  2. 쓰레드들은 synchronzed 블럭안에 들어가기위해 무한정 기다리며 블럭된다.
  3. 무작정 기다리며  wait() 가 불리기를 기다리는 쓰레드.  

포인트는 위의 요인들이 발생하는 이유는  무작위로 깨어난다는 점이다.


자바 쓰레드 점유율을  공정하게 만들기

다음 코드 블럭을 보자 

public class Synchronizer{

  public synchronized void doSynchronized(){
    //do a lot of work which takes a long time
  }

}

만약 doSynchronized 메소드에 쓰레드들이 경쟁할때 , 하나만 들어갈수있고, 나머지는 들어간놈이 나

올때까지 대기다. 근데 여러놈이 기다리는데 순서대로 나오질 못한다. 따라서 주구장창 대기하는놈이 

생길수도있다.


Synchronized 블럭대신 Locks 사용

public class Synchronizer{
  Lock lock = new Lock();

  public void doSynchronized() throws InterruptedException{
    this.lock.lock();
      //critical section, do a lot of work which takes a long time
    this.lock.unlock();
  }

}

synchronized 는 더이상 사용되지 않고 대신해 크리티컬 섹션이 사용된다.

public class Lock{
  private boolean isLocked      = false;
  private Thread  lockingThread = null;

  public synchronized void lock() throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked      = true;
    lockingThread = Thread.currentThread();
  }

  public synchronized void unlock(){
    if(this.lockingThread != Thread.currentThread()){
      throw new IllegalMonitorStateException(
        "Calling thread has not locked this lock");
    }
    isLocked      = false;
    lockingThread = null;
    notify();
  }
}

주저리 주저리 했는데, 결국 이 버전도 그닥 메리트가 없어보인다. 아래것이 핵심.


페어 락(Fair Lock)


public class FairLock {
    private boolean           isLocked       = false;
    private Thread            lockingThread  = null;
    private List<QueueObject> waitingThreads =
            new ArrayList<QueueObject>();

  public void lock() throws InterruptedException{
    QueueObject queueObject           = new QueueObject();
    boolean     isLockedForThisThread = true;
    synchronized(this){
        waitingThreads.add(queueObject);
    }

    while(isLockedForThisThread){
      synchronized(this){
        isLockedForThisThread =
            isLocked || waitingThreads.get(0) != queueObject;
        if(!isLockedForThisThread){
          isLocked = true;
           waitingThreads.remove(queueObject);
           lockingThread = Thread.currentThread();
           return;
         }
      }
      try{
        queueObject.doWait();
      }catch(InterruptedException e){
        synchronized(this) { waitingThreads.remove(queueObject); }
        throw e;
      }
    }
  }

  public synchronized void unlock(){
    if(this.lockingThread != Thread.currentThread()){
      throw new IllegalMonitorStateException(
        "Calling thread has not locked this lock");
    }
    isLocked      = false;
    lockingThread = null;
    if(waitingThreads.size() > 0){
      waitingThreads.get(0).doNotify();
    }
  }
}
public class QueueObject {

  private boolean isNotified = false;

  public synchronized void doWait() throws InterruptedException {
    while(!isNotified){
        this.wait();
    }
    this.isNotified = false;
  }

  public synchronized void doNotify() {
    this.isNotified = true;
    this.notify();
  }

  public boolean equals(Object o) {
    return this == o;
  }
}


포인트는 QueueObject 객체를  세마포어처럼 사용한다는 점이다.  각각의 대기하는 쓰레드가 

하나의 wait() 에 걸리는것이아니라, 각각의 wait()에 걸리게하고, 그것들을 큐로 관리하며, 

가장 먼저 큐에 들어간 쓰레드에 대해 notify 를  해주게되면 , 공평하게 쓰레드들이 점유율을 갖게된다

는 점. 


성능 문제

일반 lock 이나 synchronized 에 비해 느릴수 밖에 없는데, 자주 호출되거나, 크리티컬섹션사이에

작동되는 로직이 초간단하면 더더욱 성능에는 불리하다.





 1 번쓰레드가  synchronized  안으로 들어가면  2 번쓰레드는 synchronized 안으로 진입 불가.

 1 번쓰레드가 함수자체를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 

1 번쓰레드가  synchronized  안으로 들어가면  2 번쓰레드는 synchronized 안으로 진입 불가.

1 번쓰레드가 synchronized 를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 

1 번쓰레드가  synchronized  안으로 들어가면  2 번쓰레드는 synchronized 안으로 진입 불가.

1 번쓰레드가 5초후에 synchronized 를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 

          1 번쓰레드가 synchronized  안으로 들어가면 , 2번 쓰레드는 synchronized 에 대기하고있다가 

          1 번 쓰레드가 wait 에 진입하는 순간 , 2 번쓰레드는 synchronized 안으로 진입

          2 번 쓰레드도 wait 에 진입하고 ,

          1 번 쓰레드가 wait 에서 3초 대기후 빠져나오면서 synchronized 의 락을 다시 되찾고, 

          synchronized 의 락을 되돌려 주면서 종료

          2 번 쓰레드도 wait 에서 3초 대기후 빠져나옴. 



 1 번쓰레드가  1번째  synchronized  안으로 들어가고, 2번째 synchronized 안의 wait 에 진입해도

  2 번쓰레드는 synchronized 안으로 진입 불가.

  1 번쓰레드가 3초후 함수자체를 빠져나오면, 그때서야 2번 쓰레드는 함수 진입가능 



 1 번쓰레드가 synchronized  안으로 들어가면 , 2번 쓰레드는 synchronized 에 대기하고있다가 

 1 번 쓰레드가 wait 에 진입하는 순간 , 2 번쓰레드는 synchronized 안으로 진입

 2 번 쓰레드도 wait 에 진입하고 ,  두개의 쓰레드가 wait 하고 있을때 

 notify 가 호출되면 ??

 테스트에 의해서는 먼저 wait 된것이 먼저 빠져나왔는데, 문서에는 보장못한다고 한다. 

 주의할것은 notify() 하면 하나만 빠져나오지만, notifyAll() 하면 모두 빠져나온다.

 물론, 1번 , 2 번 쓰레드가 동시에 빠져나오는것은 아니며, 1번이 빠져나오면 synchronized 에 대한 

 락을 소유하기때문에, 2 번은 대기하다가, 1번이 락을 해제하면, 그때 2번이 락을 잡고 빠져나온다.

 

 따라서, 아래처럼  wait() 문 주위로 while 로 감싸주는 코드를 사용한다.(notifyAll 한번 호출에 하나씩 활동

 하기위해~notify 써도되긴 한다.근데 notify 를 쓰면 좀비쓰레드가 생길 가능성 up!!)


public synchronized void Func throws InterruptedException {       
while (!active) {
wait();
}
active = false;
}

하지만 위 처럼 하면, wait 에 2개의 쓰레드가 걸려있고, notifyAll() 가 2번 불려졌을때에도 
쓰레드중 하나만 빠져나올가능성이 있다.  (notifyAll 이 먼저 2번 호출되었을 경우) 

따라서 아래처럼 while 문의 조건을 list 로 둘수도있게된다.

public synchronized void Func() throws InterruptedException {
while (list.isEmpty()) {
wait(1000);
}
Integer i = list.pop();
}

public synchronized void Func2(int i ) {
notifyAll();
list.add(i);
}


+ Recent posts