SIP(Session Initiation Protocol)는 두 엔드포인트 사이에 세션을 설정, 수정하고 종료할 때 쓰이는 시그널링 프로토콜입니다. SIP는 양자간 호출, 다자간 호출 심지어 인터넷 호출/멀티미디어 호출/멀티미디어 배포를 위한 멀티캐스트 세션을 설정할 때도 사용됩니다.

JSR 116: SIP Servlet API는 SIP 구성요소 또는 서비스의 컨테이너를 설명하는 서버측 인터페이스입니다. SIP 서블릿, 즉 SIP 컨테이너에서 실행되는 서블릿은 HTTP 서블릿과 비슷하지만, SIP 프로토콜도 지원합니다. SIP와 SIP 서블릿 모두 VoIP(Voice-over-IP), 인스턴트 메시징, 상태 알림 및 버디 목록 관리 그리고 웹 컨퍼런싱과 같은 서비스를 제공하는, 널리 이용되는 여러 통신 기반 애플리케이션의 배후에 구현되어 있습니다.

SIP와 SIP 서블릿은 엔터프라이즈 환경에서도 중요합니다. SIP 서블릿을 자바 EE 기술과 함께 사용하면 엔터프라이즈 애플리케이션에 리치 미디어 상호작용을 추가할 수 있습니다. JSR 289: SIP Servlet v1.1은 SIP 서블릿 API를 업데이트하며, SIP 서블릿과 자바 EE 구성요소를 혼합하기 위한 표준 애플리케이션 프로그래밍 모델을 정의합니다. SIP 서블릿은 차세대 통신 서비스 구축에서 더 큰 역할을 할 것으로 예상됩니다.

이번 테크팁에서는 SIP와 SIP 서블릿의 바탕이 되는 기본적인 개념 몇 가지를 다루겠습니다. 또한 SIP 서블릿과 HTTP 서블릿을 사용하여 VoIP 전화 서비스를 제공하는 샘플 애플리케이션도 소개합니다.


SIP란 무엇입니까?

SIP를 쉽게 설명하는 방법 중 하나는 사용 시나리오를 살펴보는 것입니다. 예를 들어, A라는 사용자가 B라는 사용자와의 호출을 설정하려고 합니다. 통신 설정에서는 사용자 에이전트라는 것을 통해 사용자 A와 B가 통신하게 됩니다. 사용자 에이전트의 한 예가 소프트 폰, 즉 인터넷으로 전화 통화를 할 수 있는 소프트웨어 프로그램입니다. 또 다른 예로 VoIP 전화기, VoIP를 사용하는 전화기를 들 수 있습니다. 다음은 호출을 설정하는 데 필요한 단계입니다.

  1. 대화를 시작하기 위해 A가 B를 초대합니다. 초대하면서 A는 어떤 미디어가 지원 가능한지 알립니다.
  2. 초대를 받은 B는 즉시 A에게 응답한 다음 초대를 평가합니다.
  3. B가 초대를 받아들일 준비가 되었으면 A에게 확인 응답(acknowledgement)을 보냅니다. 확인 응답 과정에서 B는 자신이 어떤 미디어를 지원하는지 알립니다.
  4. A는 B로부터 받은 확인 응답을 검사하고 B와 A가 지원하는 미디어가 동일한지 확인합니다. A와 B가 동일한 미디어를 지원할 경우 A와 B 사이에 호출이 설정됩니다. 초대에서 지정된 미디어가 호출을 지원합니다.

그림 1에서는 호출 설정 단계를 나타냅니다.

사용자 삽입 이미지
그림 1. 호출 설정 단계

SIP는 이 단계를 수행하는 표준화된 방식을 제공합니다. 그러기 위해 구체적인 요청 메소드, 응답, 응답 코드 그리고 시그널링 및 호출 제어를 위한 헤더를 정의합니다. 이 프로토콜은 IETF(Internet Engineering Task Force)의 RFC3261에서 표준화되었으며, 현재 3GPP(3rd Generation Partnership Project)의 표준 시그널링 프로토콜 및 IMS(IP Multimedia Subsystem) 아키텍처의 영구적인 요소로 채택되었습니다.


SIP는 HTTP와 어떤 관련이 있습니까?

SIP가 기반 프로토콜로 HTTP를 사용하는지 묻는 사람이 많습니다. 답은 '아니요'입니다. SIP는 HTTP와 동일한 계층, 즉 애플리케이션 계층에서 작동하는 프로토콜이며 TCP, UDP 또는 SCTP를 기반 프로토콜로 사용합니다. 그러나 SIP는 HTTP와 유사한 점이 많습니다.

예를 들어, HTTP처럼 SIP는 텍스트 기반이며 사용자가 읽을 수 있습니다. 또한 HTTP와 마찬가지로 SIP는 특정 메소드, 응답, 코드 및 헤더가 있는 요청-응답 메커니즘을 사용합니다. HTTP와 SIP의 뚜렷한 차이점 중 하나는 요청-응답 메커니즘이 SIP에서는 비동기식이라는 것입니다. 요청 다음에 그에 대한 하나의 응답이 올 필요는 없습니다. 실제로 SIP 요청의 결과로 하나 이상의 요청이 생성되기도 합니다.

SIP는 피어 투 피어 프로토콜입니다. 즉 사용자 에이전트는 클라이언트뿐 아니라 서버 역할을 할 수도 있습니다. 이는 SIP와 HTTP의 또 다른 차이점입니다. HTTP의 경우 클라이언트는 항상 클라이언트이며 서버는 항상 서버입니다.

SIP는 다음 요청 메소드와 응답 코드를 지원합니다.

요청 메소드:

  • REGISTER. 클라이언트가 SIP 서버에 주소를 등록할 때 사용합니다. .
  • INVITE. 사용자 또는 서비스가 세션에 참가하도록 초대받고 있음을 나타냅니다. 이 메시지의 본문에는 사용자 또는 서비스가 초대된 세션에 대한 설명이 포함되어 있습니다.
  • ACK. 클라이언트가 INVITE 요청에 대한 최종 응답을 받았음을 확인합니다. 이 메소드는 오로지 INVITE 요청과 함께 사용합니다.
  • CANCEL. 보류 중인 요청을 취소할 때 사용합니다.
  • BYE. 사용자 에이전트 클라이언트가 호출 종료를 원한다는 것을 서버에 전달할 때 보냅니다.
  • OPTIONS. 서버에 그 기능에 대해 쿼리할 때 사용합니다.

응답 코드:

  • 1xx: 임시. 액션이 성공적으로 수신, 이해되고 수락되었음을 나타내는 ACK입니다.
  • 3xx: 리디렉션. 이 요청을 처리하려면 추가 액션이 필요합니다.
  • 4xx: 클라이언트 오류. 요청이 잘못된 구문을 포함하며, 이 서버에서 실행할 수 없습니다.
  • 5xx: 서버 오류. 서버는 유효해 보이는 요청을 처리하지 못했습니다.
  • 6xx: 전역 실패. 어떤 서버에서든 요청을 처리할 수 없습니다.


SDP

SDP(Session Description Protocol)는 멀티미디어 세션에 사용할 미디어 형식과 유형을 설명하는 형식입니다. SIP에서는 SDP를 메시지의 페이로드로 사용하면서 여러 사용자 에이전트 간의 기능 교환을 지원합니다. 예를 들어, SDP의 내용에서는 사용자 에이전트가 지원할 코덱 그리고 RTP(Real-time Transport Protocol)와 같이 사용할 프로토콜을 지정하기도 합니다.


SIP 메시지

그림 2에서는 SIP 메시지의 구성을 보여 줍니다. 크게 세 부분으로 구성됩니다.

  • 요청 라인. 요청 메소드, 주소 및 SIP 버전을 지정합니다.
  • 헤더. 설정하거나 종료할 세션이나 호출에 대한 데이터를 지정합니다.
  • 메시지 본문. 세션을 위한 미디어를 설명하는 페이로드, 즉 SDP를 제공합니다.
사용자 삽입 이미지
그림 2. SIP 메시지의 구성
 


SIP 서블릿 모델

SIP 서블릿 프로그래밍 모델은 서블릿 프로그래밍 모델을 기반으로 합니다. SIP에서의 프로그래밍을 자바 EE와 더 가깝게 만듭니다. 서블릿은 서버측 개체로서 들어오는 요청을 처리하고 적합한 응답을 클라이언트에게 보냅니다. 일반적으로 서블릿은 서블릿 컨테이너에 배포되며, 수명 주기가 잘 정의되어 있습니다.

서블릿 컨테이너는 컨테이너에 있는 서블릿의 수명 주기를 관리하고 JNDI, JDBC와 같이 서블릿이 사용하는 기술과 관련된 리소스를 관리하는 일을 담당합니다. 또한 서블릿 컨테이너에서는 서블릿을 위한 네트워크 연결을 관리합니다.

앞서 언급한 대로, SIP 서블릿은 SIP 요청을 처리한다는 점을 제외하고 HTTP 서블릿과 비슷합니다. 그러기 위해 SIP 요청 메소드 각각을 처리할 구체적인 메소드를 정의합니다. 예를 들어, HTTP 서블릿은 doPost() 메소드를 정의하는데, 이 메소드는 service() 메소드를 대체하면서 POST 요청을 처리합니다. 그에 비해 SIP 서블릿은 doInvite() 메소드를 정의하는데, 이 역시 service() 메소드를 대체하면서 INVITE 요청을 처리합니다.

JSR116에서는 SIP 서블릿 API 1.0을 정의하면서 다음을 지정했습니다.

  • SIP 서블릿 프로그래밍 모델을 위한 API
  • SIP 서블릿 컨테이너의 책임
  • SIP 서블릿이 HTTP 서블릿 및 자바 EE 구성요소와 인터페이스하는 방법

초기 SIP 서블릿 API 스펙은 JSR 289: SIP Servlet v1.1에 의해 개정되는 중입니다.


SIP 서블릿 API - 주요 개념

SIP 서블릿의 기본이 되는 주요 개념은 HTTP 서블릿의 기본 개념과 비슷합니다. 다음 섹션에서는 그 개념 중 몇 가지를 간략하게 설명합니다.

SipServletRequestSipServletResponse

SIP의 요청-응답 방법론은 HTTP 서블릿의 경우와 비슷합니다. 요청은 SipServletRequest 개체에서 정의되며, 응답은 SipServletResponse 개체에서 정의됩니다. 그러나 단 하나의 ServletRequest 또는 ServletResponse 개체만 null이 아닙니다. SIP 요청이 대칭형 응답을 내놓지 않기 때문입니다. 또한 SipServletRequestSipServletResponse 개체 모두의 공통적인 수퍼 인터페이스인 SipServletMessage가 있습니다. SipServletMessage 인터페이스는 SipServletRequestSipServletResponse 개체의 공통된 메소드를 정의합니다.

그림 3에서는 SipServletRequestSipServletResponse 개체의 계층 구조를 보여 줍니다.

사용자 삽입 이미지
그림 3. SipServletRequestSipServletResponse 개체의 계층 구조
 

서블릿 컨텍스트

서블릿 스펙에 정의된 서블릿 컨텍스트는 SIP 서블릿에도 적용됩니다. 서블릿 스펙에서는 SIP 서블릿에 관한 정보를 저장, 검색하는 데 쓰이는 구체적인 컨텍스트 속성 그리고 그 컨텍스트로부터의 인터페이스를 정의합니다. 서블릿 컨텍스트는 동일한 애플리케이션 내의 HTTP 서블릿과 공유할 수 있습니다. 자세한 내용은 통합 애플리케이션 섹션을 참조하십시오.


배포 설명자

XML 기반의 배포 설명자는 SIP 서블릿과 이를 호출하기 위한 규칙 그리고 애플리케이션에 쓰이는 리소스 및 환경 등록 정보를 설명할 때 사용합니다. 이 설명자는 sip.xml 파일에 있으며, HTTP 서블릿에 쓰이는 파일과 비슷합니다. sip.xml 파일은 XML 스키마에 의해 정의됩니다.


SIP 애플리케이션 패키징

SIP 애플리케이션은 웹 애플리케이션과 동일한 패키징 구조를 갖습니다. .sar(Sip 아카이브) 또는 .war(웹 아카이브) 파일 확장자를 갖는 JAR 파일 형식으로 패키징됩니다.


통합 컨텍스트 및 통합 애플리케이션

애플리케이션에서는 SIP 및 HTTP 서블릿을 모두 사용하여 서비스를 생성할 수 있습니다. HTTP 및 SIP 서블릿이 동일한 애플리케이션 패키지에 있을 수 있도록 SIP 서블릿 스펙에서 ConvergedContext 개체를 정의합니다. 이 개체는 HTTP 및 SIP 서블릿 모두가 공유하는 서블릿 컨텍스트를 보유하며 서블릿 컨텍스트 속성, 리소스 및 JNDI 네임스페이스 측면에서 동일한 애플리케이션 뷰를 SIP 및 HTTP 서블릿에게 제공합니다.

애플리케이션이 SIP 및 HTTP 서블릿을 모두 포함할 경우 통합(converged) 애플리케이션이라고 합니다. 이는 SIP 전용 애플리케이션, 즉 SIP 애플리케이션과 대비되는 용어입니다. 통합 애플리케이션은 sip.xml 파일 외에도 web.xml 파일을 배포 설명자로 갖는다는 점을 제외하고, SIP 애플리케이션과 비슷한 구조입니다.

SIP Servlet API 1.1(JSR289)에서 통합 애플리케이션의 개념은 엔터프라이즈 애플리케이션도 다루도록 확장되었습니다. 이제 엔터프라이즈 애플리케이션은 SIP 애플리케이션 또는 통합 애플리케이션을 모듈로 포함할 수 있습니다. 이런 유형의 엔터프라이즈 애플리케이션을 통합 엔터프라이즈 애플리케이션(converged enterprise application)이라고 합니다.


SIP 세션

SIP 서블릿 스펙에서는 HttpSession 개체가 HTTP를 통한 세션을 나타내는 것과 동일한 방법으로 SIP를 통한 세션을 나타내는 SipSession 개체를 정의합니다. 통합 애플리케이션과 같은 하나의 애플리케이션이 HTTP를 통한 세션과 SIP를 통한 세션을 포함할 수 있으므로, 이 스펙에서는 애플리케이션 수준의 세션 개체인 SipApplicationSession도 정의합니다. SipApplicationSession 개체는 애플리케이션에서 HTTP 및 SIP 세션(즉 프로토콜 세션)의 상위 역할을 합니다.


주석

SIP Servlet API 1.1의 목적은 SIP 서블릿을 자바 EE 5에 맞도록 조율하는 것입니다. 따라서 이 스펙에서는 자바 EE 5에서 정의한 주석의 사용을 SIP 서블릿 및 수신기 내부에 도입합니다. 또한 SIP 서블릿 스펙에서 정의한 인터페이스를 나타내도록 커스텀 주석도 정의합니다. 이 스펙에서는 다음 주석을 도입합니다.

  • @SipServlet. 특정 클래스가 SipServlet임을 나타낼 때 사용합니다.
  • @SipApplication. SIP 애플리케이션을 정의할 때 사용합니다. 이 주석에는 속성의 집합이 있는데, 그 중 하나인 "name" 속성은 애플리케이션의 이름을 정의할 때 사용합니다. SipApplication 주석은 애플리케이션을 구성하는 서블릿의 논리적 컬렉션을 배포 설명자를 사용하지 않고 생성할 때 사용합니다..
  • @SipListener. 어떤 클래스가 특정 SIP 애플리케이션의 SipListener로 등록될 수 있게 합니다. SIP 애플리케이션의 이름은 이 주석의 속성으로 정의됩니다.
  • @SipApplicationKey. SIP 애플리케이션에 대해 SipApplicationKey를 정의하는 데 도움이 되는 메소드 수준입니다. SipApplicationKey는 기존의 SipApplicationSession과 요청을 연결할 때 사용합니다.


프로젝트 Sailfin - 오픈 소스 SIP 애플리케이션 서버

SIP 서블릿 컨테이너는 SIP 서블릿만 지원하는 독립형이거나 HTTP와 SIP 서블릿을 모두 지원하는 통합 컨테이너일 수 있습니다. 그러나 대부분의 엔터프라이즈 용도에서는 SIP 서블릿이 애플리케이션 서버 내부의 통합 컨테이너가 되어야 합니다. 프로젝트 Sailfin에서는 GlassFish 애플리케이션 서버를 사용하여 SIP 서블릿 컨테이너의 오픈 소스 구현을 생성합니다.

이 프로젝트는 java.net 아래 개발 중이며, 썬과 에릭슨이 주 기여자로 참여하고 있습니다. SailFin 프로젝트에서 개발 중인, GlassFish의 SIP 서블릿 컨테이너 구현인 Sailfin은 SIP 서블릿 API 1.0을 지원하며, 스펙이 완료될 무렵에는 SIP Servlet API 1.1을 지원할 예정입니다.


CallSetup 샘플 애플리케이션

이 팁의 샘플 애플리케이션인 CallSetup은 SailFin 다운로드의 일부로 제공됩니다. Download SailFin Builds 페이지에서 SailFin을 다운로드할 수 있습니다. SailFin Project - Instructions를 참조하여 SailFin을 설치하고 구성합니다. CallSetup 애플리케이션의 코드는 <sailfin-install-home>/samples/sipservlet/CallSetup 디렉토리에 있으며, 여기서 <sailfin-install-home>은 SailFin을 설치한 디렉토리입니다.

CallSetup 애플리케이션에서는 VoIP 전화 서비스를 제공하기 위해 SIP 서블릿과 HTTP 서블릿을 사용합니다. 이 애플리케이션에서는 B2BUA(Back-to-Back User Agent) SIP 서블릿을 이용하여 VoIP 호출을 실행합니다. B2BUA는 두 사용자 에이전트 사이에 호출을 설정하는데, 각 사용자 에이전트를 개별적으로 호출한 다음 둘을 연결하는 방법입니다.


CallSetup 구성요소

CallSetup은 다음 구성요소로 이루어져 있습니다.

  • Registration.java. 등록된 사용자를 나타내는 POJO(plain old Java object)
  • RegistrarServlet. 사용자가 등록할 수 있게 해주는 SIP 서블릿. 또한 이 서블릿은 SailFin과 함께 제공된 자바 DB 데이터베이스에서 사용자 데이터를 유지하는 데 쓰입니다.
  • RegistrationBrowserServlet.java. 호출을 위해 등록된 사용자를 선택하기 위한 인터페이스를 제공하는 HTTP 서블릿
  • SipCallSetupServlet.java. 첫 번째 사용자(UserB)에게 INVITE 메시지를 보내는 HTTP 서블릿
  • B2BCallServlet.java. 첫 번째 사용자로부터의 응답을 처리하고 두 번째 사용자(User A)와의 호출을 설정하는 SIP 서블릿
  • web.xml. HTTP 서블릿의 배포 설명자
  • sip.xml. SIP 서블릿의 배포 설명자
  • sun-web.xml. 제품별 배포 설명자
  • persistence.xml. 지속성 단위를 정의합니다.

그림 4에서는 애플리케이션의 실행 순서를 보여 줍니다.

사용자 삽입 이미지
그림 4. CallSetup 실행 순서
 

CallSetup을 구성하는 요소의 코드 중 일부를 살펴 보겠습니다. 애플리케이션의 구성요소 중 일부는 여기에 소개되지 않았으며, 각 구성요소의 모든 코드가 제시된 것은 아닙니다. SailFin 다운로드에서 애플리케이션의 전체 코드를 검토하는 것이 좋습니다.

RegistrarServlet.java

사용자 에이전트가 REGISTER 요청을 보내면 doRegister() 메소드가 호출되며 등록 데이터가 저장됩니다. 그런 다음 상태 코드가 있는 응답이 사용자 에이전트로 보내집니다.

import com.ericsson.sip.Registration;

@PersistenceContext(name = "persistence/LogicalName", unitName = "EricssonSipPU")

public class RegistrarServlet extends SipServlet{

    PersistenceUnit 주석은 EntityManagerFactory에 PU라는
    이름이 사용될 것이라는 주석을 표시하기 위해 사용됩니다.

    @PersistenceUnit(unitName = "EricssonSipPU")
    private EntityManagerFactory emf;

    Resource 주석은 UserTransaction을 삽입하기 위해 사용됩
    니다.

    @Resource
    private UserTransaction utx;

    protected void doRegister(SipServletRequest  
    request) throws ServletException, IOException {


        SipServletResponse response = request.createResponse
                                                      (200);
        try {
           
       
SipServletRequest 개체를 구문 분석하여 주소와 요청 헤더
       를 얻습니다. Contact 헤더를 얻어 데이터베이스에 저장합
       니다.

           
SipURI to = cleanURI((SipURI) request.getTo
                          ().getURI());

            ListIterator<Address> li =
                    request.getAddressHeaders("Contact");

            while (li.hasNext()){
                Address na = li.next();
                SipURI contact = (SipURI) na.getURI();
                logger.log(Level.FINE, "Contact = " + contact);
                 
               
사용자 데이터를 저장하기 위해 EntityManager 개체
                가 생성됩니다.

               
EntityManager em = emf.createEntityManager
                                        ();

                try {
                        utx.begin();
                        Registration registration = new Registration();
                        registration.setId(to.toString());

                        registration = em.merge(registration);
                        em.remove(registration);
                        utx.commit();
                        logger.log(Level.FINE, "Registration was
                        successfully created.");
                 } catch (Exception ex) {
                        try {
                            utx.rollback();
                        } catch (Exception e) {

                        }
                }
                em.close();
                등록이 성공하면 응답 코드 200 OK가 보내집니다.
                response.send();
        } catch(Exception e) {
         
등록이 성공하지 않으면 응답 코드 500이 보내집니다.
           response.setStatus(500);
            response.send();
        }
    }

RegistrationBrowserServlet.java

등록된 사용자를 나열하고 둘 사이에 호출을 설정하기 위해 인터페이스를 제공하는 HTTP 서블릿입니다.

@PersistenceContext(name = "persistence/LogicalName", unitName = "EricssonSipPU")
public class RegistrationBrowserServlet extends HttpServlet {
@PersistenceUnit(unitName = "EricssonSipPU") private EntityManagerFactory emf;


   public Collection getRegistrations() {
       EntityManager em = emf.createEntityManager();
       Query q = em.createQuery("select object(o) from
       Registration as o");
       return q.getResultList();
   }

   protected void processRequest(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
       response.setContentType("text/html;charset=UTF-8");
       PrintWriter out = response.getWriter();
       
       그러면 등록의 목록이 얻어집니다.

      Collection registrations = getRegistrations();
       Iterator iter = registrations.iterator();

       out.println("<html><body>");
       HTTP 서블릿 SipCallSetupServlet을 호출합니다.
      out.println("<FORM ACTION = \"/CallSetup/SipCallsetupServlet\" METHOD = POST>");

       out.println("<INPUT TYPE=SUBMIT NAME=Submit
       VALUE=\"Submit\">");
       out.println("</FORM>");
       out.println("SipFactoryInstance = "+sf.toString());
       out.println("</body></html>");
       out.close();
   }

SipCallSetupServlet.java

이 HTTP 서블릿은 RegistrationBrowserServlet에서 호출되며, 두 사용자 간에 호출을 설정하면서 B2BUA와 같은 역할을 합니다.

public class SipCallSetupServlet extends HttpServlet {

        SipFactory sf = null;
        TimerService ts = null;
        ServletContext ctx = null;

public void init(ServletConfig config) throws ServletException {
        ctx = config.getServletContext();

        ServletContext에서 SIpFactory 개체를 얻습니다.
       sf = (SipFactory) ctx.getAttribute
             (SipServlet.SIP_FACTORY);

    }


protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

        String callA = null;
        String callB = null;
         요청 매개 변수에서 contact을 얻습니다.
       
String[] contacts = request.getParameterValues
                                 ("CONTACT");


        if ( contacts.length < 2 )  {
            return;
        }

        callA = contacts[0];
        callB = contacts[1];

        SipFactory 개체를 사용하여 SipApplicationSession을
        생성합니다.

       
SipApplicationSession as =  
                               sf.createApplicationSession();


       
SipFactory 개체를 사용하여 "To" 및 "From" 주소 개체를 생성합니다.
        Address to = sf.createAddress(callB);
       Address from = sf.createAddress(callA);
       
         SipFactory 및 생성된 SipApplicationSessionObjects를
         사용하여 SipServletRequest를 생성합니다.
         UserA가 UserB를 초대하는 것처럼 INVITE가 보내지는 중
         입니다.
       
SipServletRequest sipReq = sf.createRequest(as,
       "INVITE", from, to);


          logger.log(Level.FINE, "SipCallSetupServlet sipRequest  =
          "+ sipReq.toString());
       
        
SipServletRequest에서 속성을 설정하여 최초 INVITE임  
         을 나타냅니다.

        sipReq.setAttribute("CALL","INITIAL");

         // set servlet to invoke by response
         생성된 Request에서 SipSession을 얻습니다.
        
SipSession s = sipReq.getSession();
         여기가 핵심 부분입니다. 보내지고 있는 Request를 처리
         한 서블릿의
이름을 설정합니다. 여기서 b2b는 이 요청
         에 대한 응답을 처리할 SIP 서블릿의 이름입니다.
         s.setHandler("b2b");

         // lets send invite to B ...
         
요청을 보냅니다.
         sipReq.send();

    }

B2BCallServlet.java

이 SIP 서블릿은 SipCallSetupServlet에서 보낸 INVITE 요청에 대한 응답을 수신하고 처리합니다. 응답 헤더와 본문을 처리하고 SDP를 얻은 다음, SDP 메타데이터와 함께 또 다른 INVITE 요청을 상대편 사용자 에이전트에 보냅니다. 다른 사용자로부터 성공 응답 코드와 함께 응답을 수신한 이 서블릿은 두 사용자 간에 호출을 설정합니다.

public class B2BCallServlet extends SipServlet {

    SipFactory                              sf               = null;
    ServletContext                          ctx              = null;


 public void init(ServletConfig config) throws ServletException {
        super.init(config);
        ctx = config.getServletContext();
            ServletContext에서 SipFactory를 얻습니다.
            sf = (SipFactory) ctx.getAttribute
                (SipServlet.SIP_FACTORY);

            ts = (TimerService) ctx.getAttribute
                   (SipServlet.TIMER_SERVICE);
    }


protected void doResponse(SipServletResponse resp) throws ServletException, IOException {
        응답에서 SipApplicationSession 및 SipServletRequest를
        얻습니다.

        SipApplicationSession sas = resp.getApplicationSession
                                                 (true);
        SipServletRequest origReq = resp.getRequest();

        String alreadySent = (String) origReq.getAttribute
                                     ("SENT_INVITE");
       

        if( alreadySent == null && resp.getContentLength() > 0 && 
         resp.getContentType().equalsIgnoreCase 
         "application/sdp")) {
            String responseFrom = (String) origReq.getAttribute
                                             ("CALL");
         
HTTP 서블릿에서 보낸 INITIAL INVITE에 대한 응답인지  
        그리고 응답에서 보내진 SDP가 있는지 확인하고 상대방
        사용자에게 INVITE를 생성합니다.
            if("INITIAL".equals(responseFrom)) {
                //Take the SDP and send to A
               원래 요청에서의 To 주소가 여기서 From 주소이며,
                From 주소가 To 주소입니다. 그 때문에 이 서블릿이
                B2BUA와 같은 역할을 합니다.
                SipServletRequest newReq = sf.createRequest(sas,"INVITE",origReq.getTo(),origReq.getFrom());
                newReq.setContent(resp.getContent(),resp.getContentType());
                SipSession ssA = newReq.getSession(true);
                SipSession ssB = resp.getSession(true);

                SipSession 개체를 각 호출 레그에 대한 세션 속성  
                으로 설정합니다.

                ssA.setAttribute("OTHER_SESSION",ssB);
               ssB.setAttribute("OTHER_SESSION",ssA);
                //Test              
           
  b2b 서블릿을 보내지는 새 요청에 대한 응답의 처리기
             로 설정합니다.

                ssA.setHandler("b2b");
                ssB.setHandler("b2b");
                origReq.setAttribute("SENT_INVITE","SENT_INVITE");
                상대방 사용자에게 요청을 보냅니다.
                      newReq.send(); //Send to A
                 } else {
             
User A로부터의 응답인 경우 User A로부터 SDP를
                 가져오고 설정합니다.

                SipSession ssB = (SipSession) resp.getSession
                                          ().getAttribute("OTHER_SESSION");
                ssB.setAttribute("SDP",resp.getContent());
            }
        } else {
            return;
        }
        // Count so that both sides sent 200.
          응답의 상태 코드가 200OK인 경우

        if( resp.getStatus() == 200 ) {
           
UserB(첫 번째 사용자)로부터의 응답인지 확인합니다.
            SipServletResponse first = (SipServletResponse)
                                        sas.getAttribute("GOT_FIRST_200");
            if( first == null ) { // This is the first 200
                sas.setAttribute("GOT_FIRST_200",resp);
            }
            else { //This is the second 200 sen both ACK
               
두 번째 응답이며 이제 UserA와 UserB 모두에게
                ACK를 보냅니다. 그러면 SDP를 교환하고 호출을
                설정합니다.
              sendAck(resp);
           sendAck(first);
            }
        }
    }
    
     
이 메소드는 SDP와 함께 ACK를 보냅니다.
    private void sendAck( SipServletResponse resp ) throws IOException {
        SipServletRequest ack = resp.createAck();
        //Check if pending SDP to include in ACK
        Object content = resp.getSession().getAttribute("SDP");
        if( content != null ) {
            ack.setContent(content,"application/sdp");
        }
        ack.send();
    }

}

sip.xml

sip.xml 파일에서는 SIP 서블릿을 정의하고 그 매핑을 지정합니다. SIP 서블릿의 매핑에서는 equal, and, ornot 연산자를 사용하여 서블릿이 호출되는 조건을 정의합니다. 여기서는 REGISTER, INVITE, OPTIONS 또는 MESSAGE 중 하나인 요청 메소드에 일치가 있습니다.

<sip-app>
    <display-name>SIP Registrar</display-name>
    <description>SIP Registrar application</description>
    <servlet>
        <servlet-name>registrar</servlet-name>
        <description>Registrar SIP servlet</description>
        <servlet-class>com.ericsson.sip.RegistrarServlet</servlet-class>
        <init-param>
            <param-name>Registrar_Domain</param-name>
            <param-value>ericsson.com</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet>
        <servlet-name>b2b</servlet-name>
        <servlet-class>com.ericsson.sip.B2BCallServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>registrar</servlet-name>
        <pattern>
            <and>
                <equal>
                    <var ignore-case="false">request.uri.host</var>
                    <value>test.com</value>
                </equal>
                <or>
                    <equal>
                        <var ignore-case="false">request.method</var>
                        <value>REGISTER</value>
                    </equal>
                    <equal>
                        <var ignore-case="false">request.method</var>
                        <value>INVITE</value>
                    </equal>
                    <equal>
                        <var ignore-case="false">request.method</var>
                        <value>OPTIONS</value>
                    </equal>
                    <equal>
                        <var ignore-case="false">request.method</var>
                        <value>MESSAGE</value>
                    </equal>
                </or>
            </and>
        </pattern>
    </servlet-mapping>
</sip-app>

persistence.xml

persistence.xml에서는 지속성 단위인 EricssonSipPU를 정의하는데, 이는 데이터베이스에서 등록 데이터를 지속시킬 때 사용합니다. 이 애플리케이션에서는 Sailfin과 함께 제공되는 기본 JDBC 리소스인 jdbc/__default를 사용합니다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="EricssonSipPU" transaction-type="JTA">
    <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
    <jta-data-source>jdbc/__default</jta-data-source>
    <properties>
      <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>


샘플 애플리케이션 실행

CallSetup 애플리케이션을 실행하려면 SailFin Project - Instructions 페이지의 지침을 따릅니다. 지침을 제대로 따랐다면 두 소프트폰 클라이언트 간에 호출을 설정할 수 있어야 합니다. 선택된 두 엔드포인트에서 순차적으로 전화기가 울립니다.


요약

이번 팁에서는 SIP와 SIP 서블릿의 바탕이 되는 기본적인 개념 몇 가지를 살펴 봤습니다. 또한 SIP 서블릿과 HTTP 서블릿을 사용하여 VoIP 전화 서비스를 제공하는 샘플 애플리케이션도 소개했습니다. 그리고 GlassFish 애플리케이션 서버를 사용하여 SIP 서블릿 컨테이너의 오픈 소스 구현을 만들고 있는 SailFin 프로젝트도 소개했습니다.

SIP, SIP 서블릿 및 SailFin 프로젝트에 대한 자세한 내용은 다음 자료를 참조하십시오.

SIP 서블릿을 위해 NetBeans 플러그인이 제공되며, 이를 통해 NetBeans IDE를 사용하여 SIP 애플리케이션을 생성할 수 있습니다. 이 플러그인은 SailFin에 포함되어 제공됩니다. NetBeans 플러그인에 대한 자세한 내용은 블로그 항목 NetBeans 6.1 and Project Sailfin을 참조하십시오.


저자 정보

썬 마이크로시스템즈 소속 엔지니어인 Prasad Subramanian은 프로젝트 Sailfin의 엔지니어링 책임자입니다.


이 글의 영문 원본은
http://blogs.sun.com/enterprisetechtips ··· _java_ee
에서 보실 수 있습니다.

--------------------------------------------------------------------------------------------------------------------

GlassFish에 참여

GlassFish에 연결하여 참여
GlassFish에 참여하여 iPhone 경품의 주인공이 되어 보십시오. 본 행사는 2008년 3월 23일까지 진행됩니다. 지금 바로 참여하십시오.


"Java EE" 카테고리의 다른 글

2008/03/13 13:32 2008/03/13 13:32

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/528

댓글을 달아 주세요

[로그인][오픈아이디란?]

◀ Prev 1  ... 123 124 125 126 127 128 129 130 131  ... 624  Next ▶