|
|
WMA - 필수 옵션 패키지 JTWI는 모든 준법 핸드세트가 반드시 제공해야 하는 최소/공통의 특성을 정의하며, WMA 1.1 지원을 요구하는 반면 WMA 2.0 지원은 요구하지 않습니다. 모바일 아키텍처의 진화를 계속 이어가는 MSA의 경우, WMA 2.0을 필수 패키지로 명시하고 있습니다. |
WMA 그 자체는 Java ME(Java Platform, Mobile Edition)의 옵션 패키지로 제공되지만, JTWI(Java Technology for the Wireless Industry) 스펙인 JSR 185 그리고 새로운 CLDC용 MSA(Mobile Service Architecture)인 JSR 248을 준수해야 하는 핸드세트(휴대전화 단말기)에는 의무적으로 탑재해야 합니다.
WMA 특유의 인터페이스와 클래스는 모두 무선 텍스트/바이너리/멀티파트 메시지를 주고받는 데 필요한 모든 API를 정의하는 하나의 패키지 javax.wireless.messaging
에 포함되어 있습니다. 표 1은 이 패키지를 요약한 것입니다.
Interface |
Description |
Methods |
1.1 |
2.0 |
---|---|---|---|---|
Message |
BinaryMessage, TextMessage, MultipartMessage 등의 서브인터페이스가 파생되는 기본 메시지 인터페이스 |
getAddress() getTimestamp() setAddress() |
X |
X |
BinaryMessage |
바이너리 메시지를 표현하고 바이너리 페이로드를 set/get하는 메소드를 제공하는 Message의 서브인터페이스 |
getPayloadData() setPayloadData() |
X |
X |
TextMessage |
텍스트 메시지를 표현하고 텍스트 페이로드를 set/get하는 메소드를 제공하는 Message의 서브인터페이스 |
getPayloadText() setPayloadText() |
X |
X |
MessageConnection |
Messages의 팩토리와 Messages를 send/receive하는 메소드를 제공하는 GCF Connection의 서브인터페이스 |
newMessage() receive() send() setMessageListener() numberOfSegments() |
X |
X |
MessageListener |
Message 오브젝트의 비동기 통지를 구현하는 리스너 인터페이스를 정의 |
notifyIncomingMessage() |
X |
X |
MessagePart |
MultipartMessage에 추가될 수 있는 메시지 부분을 정의 |
getContent() getContentAsStream() getContentID() getContentLocation() getEncoding() getLength() getMIMEType() |
X | |
MultipartMessage |
멀티파트 메시지를 표현하고 복수의 페이로드를 set/get하는 메소드를 제공하는 Message의 서브인터페이스 |
addAddress() addMessagePart() getAddress() getAddresses() getHeader() getMessagePart() getMessageParts() getStartContentId() getSubject() removeAddress() removeAddresses() removeMessagePart() removeMessagePartId() removeMessagePartLocation() setAddress() setHeader() setStartContentId() setSubject() |
X | |
SizeExceededException |
멀티파트 메시지 내용이 가용한 메모리 또는 지원되는 메시지 부분의 크기보다 클 경우에 throw되는 예외 |
-- |
X |
WMA 버전 1.1과 2.0의 차이는 주로 MMS 메시징에 사용되는 멀티파트 메시지의 지원 여부와 관련이 있습니다. 이어지는 섹션에서는 WMA의 다양한 컴포넌트에 관해 알아보도록 하겠으며, 각 WMA 메소드에 대한 자세한 설명은 WMA 스펙을 참고하시기 바랍니다.
WMA 2.0은 네 가지의 메시지 표현 또는 유형을 정의합니다. 이와 더불어 멀티미디어 메시지를 전송하는 데 사용되는 멀티파트 메시지를 지원하는 메시지 부분을 정의합니다. 다음 5개 섹션에서는 메시지를 표현하는 인터페이스와 메시지 부분을 표현하는 클래스에 관해 설명합니다.
Message
인터페이스javax.wireless.messaging.Message
인터페이스는 WMA를 이용하여 전달되는 모든 메시지를 위한 기본 타입이며, Message
는 송신/수신, 생성/소멸(소비)되는 내용입니다. 이 Message
는 발신지/수신지 주소와 페이로드를 가지고 있으므로 어떤 면에서는 데이터그램과 유사해 보입니다.
한편, 차이점도 적지 않은데, WMA Message
에는 타임스탬프가 포함되며, 서브인터페이스에서 정의되는 복수의 페이로드 타입이 지원됩니다. 더욱이 무선 메시징은 통상적으로 SMS나 MMS 같은 store-and-forward 시스템 상에서 구현되기 때문에 메시지 전달이 신뢰성이 있고 추적도 가능합니다.
인터페이스는 메시지의 발신지/수신지 주소를 get/set하고 타임스탬프를 get하는 메소드를 지정합니다.
String getAddress();
void setAddress(String address);
Date getTimestamp();
WMA 2.0은 그림 2에서 보는 것처럼 Message
의 서브인터페이스를 정의합니다.
|
이 때,
BinaryMessage
는 일반적으로 SMS를 이용하는 단문 바이너리 메시지용입니다.
TextMessage
는 일반적으로 SMS를 이용하는 단문 텍스트 메시지용입니다.
MultipartMessage
는 일반적으로 MMS를 이용하는 멀티태스킹 메시지용입니다. BinaryMessage
인터페이스BinaryMessage
서브인터페이스는 바이너리 페이로드를 가지는 메시지(일반적으로 SMS 기반의 단문 메시지)를 표현하는데, 바이너리 페이로드는 8비트 데이터를 이용하여 인코딩되므로 세그먼트 당 140바이트 포트 번호를 사용할 경우 133바이트를 허용합니다. 메시지 분할에 관한 자세한 내용은 본 기사 후반부의 "분할과 재조립(About Segmentation and Reassembly)" 섹션을 참조하십시오. 이 인터페이스는 바이너리 페이로드를 바이트의 배열로 set/get하는 메소드를 선언합니다.
byte[] getPayloadData();
void setPayloadData(byte[] bytes);
메시지의 주소를 set/get하고 타임스탬프를 get하는 메소드는 모두 Message
로부터 상속됩니다.
TextMessage
인터페이스TextMessage
서브인터페이스는 텍스트 페이로드를 가지는 메시지(일반적으로 SMS 기반의 단문 텍스트 메시지)를 표현합니다. 이 인터페이스는 텍스트를 String
의 인스턴스로 set/get하는 메소드를 제공합니다.
String getPayloadText();
void setPayloadText(String data);
기본 구현은 텍스트 메시지가 송신/수신되기 전에 String
을 적절한 포맷으로 인코딩/디코딩하는 일을 담당하며, 사용되는 문자 인코딩은 메시지의 크기에 영향을 줍니다. 예를 들어, GSM 7비트는 세그먼트 당 160 문자, 또는 포트 번호를 사용할 경우 152 문자를 허용하는 반면, UCS-2는 세그먼트 당 70 더블 바이트 문자, 또는 포트 번호를 사용할 경우 66 문자를 허용합니다. 자세한 내용은 "분할과 재조립(About Segmentation and Reassembly)" 섹션을 참조하십시오. BinaryMessage
의 경우, 메시지의 주소를 set/get하고 타임스탬프를 get하는 메소드는 Message
로부터 상속됩니다.
MultipartMessage
인터페이스MultipartMessage
서브인터페이스는 복수 부분으로 구성되는 메시지(일반적으로 MMS 기반의 멀티미디어 메시지)를 표현합니다. 이 인터페이스는 하나 이상의 MessageParts
컨테이너를 정의하고 발신자/수신자 주소, 메시지의 헤더, ‘start message’ 컨텐트 ID, 메시지의 부분 등을 관리하는 메소드를 제공합니다.
boolean addAddress(String type, String address);
void addMessagePart(MessagePart messagePart) throws SizeExceededException;
String getAddress();
String[] getAddresses(String type);
String getHeader(String headerField);
MessagePart getMessagePart(String contentID);
MessagePart[] getMessageParts();
String getStartContentId();
String getSubject();
boolean removeAddress(String type, String address);
void removeAddresses();
void removeAddresses(String type);
boolean removeMessagePart(MessagePart messagePart);
boolean removeMessagePartId(String contentID);
boolean removeMessagePartLocation(String contentLocation);
void setAddress(String address);
void setHeader(String headerField, String headerValue);
void setStartContentId(String contentID);
void setSubject(String subject);
MultipartMessage
는 Message
로부터 상속받는 메시지의 주소를 set/get하는 메소드를 오버라이드합니다.
멀티파트 메시지는 그림 3에서 보는 것처럼 W3C(World Wide Web Consortium)가 RFC2045 및 RFC2046에서 정의한 MIME(Multipurpose Internet Mail Extensions) 표준에 기초한 RFC822 기반 헤더 및 복수 부분으로 구성되는 표준 이메일의 포맷을 따릅니다.
|
MultipartMessage
인터페이스는 멀티미디어 메시지와 헤더를 표현하는 반면 MessagePart
클래스 인스턴스는 각각의 개별 MIME 부분을 표현합니다.
MessagePart
클래스이름에서 알 수 있듯이, MessagePart
는 메시지의 한 부분을 표현합니다. 이 클래스는 다양한 생성자(constructor) 외에도 컨텐트를 검색하는 메소드와 컨텐트에 관한 정보를 제공합니다.
MessagePart(byte[] contents, int offset, int length, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(byte[] contents, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(java.io.InputStream is, String mimeType, String contentId, String contentLocation, String encoding) throws IOException, SizeExceededException;
public byte[] getContent();
public InputStream getContentAsStream();
public String getContentID();
public String getContentLocation();
public String getEncoding();
public int getLength();
public String getMIMEType();
메시지 부분은 RFC2046에서 정의된 MIME 타입, 컨텐트 ID, 컨텐트 로케이션, 그리고 컨텐트 자체로 구성되며, 바이트 배열 또는 java.io.InputStream
으로부터 MessagePart
를 생성할 수 있습니다.
Wireless Messaging API에서 인풋, 아웃풋 및 네트워크 연결은 GCF(Generic Connection Framework)를 기반으로 하고, WMA 커넥션은 다음 그림에서 보는 것처럼 GCF의 javax.microedition.io.Connection
의 서브인터페이스인 MessageConnection
인터페이스에 기반을 둡니다.
|
MessageConnection
인터페이스는 TextMessages
, BinaryMessages
, MultipartMessages
작성을 위한 팩토리 메소드, 메시지 전송에 필요한 프로토콜 세그먼트의 수를 계산하는 메소드, 메시지를 수신/송신하는 메소드, 이 커넥션을 위한 메시지 리스너를 set하는 메소드 등을 정의합니다.
Message newMessage(String type);
Message newMessage(String type, String address);
int numberOfSegments(Message message);
Message receive() throws IOException, InterruptedIOException;
void send(Message message) throws IOException, InterruptedIOException;
void setMessageListener(MessageListener messageListener) throws IOException;
아울러, 이 인터페이스는 String
상수를 정의하고, 그 중 하나를 newMessage()
팩토리 메소드에 패스하여 작성할 Message
타입을 식별합니다.
String TEXT_MESSAGE = "text";
String BINARY_MESSAGE = "binary";
String MULTIPART_MESSAGE = "multipart";
메시지 커넥션을 생성할 때는 항상 이 상수 중 하나를 사용합니다. WMA 구현은 String.equals()
를 이용하여 문자열 값을 비교하고 이 메시지 타입이 대소문자를 구분하도록 만듭니다.
다른 GCF Connection과 마찬가지로 MessageConnection
을 열고 닫습니다. 커넥션을 생성하려면 커넥션 팩토리의 javax.microedition.io.Connector.open()
메소드를 호출하고, 커넥션을 닫으려면 기본 커넥션 인터페이스 javax.microedition.io.Connection.close()
의 해당 메소드를 호출합니다. 또한, 복수의 MessageConnections
가 동시에 열리도록 할 수도 있습니다.
MessageConnection
은 클라이언트 커넥션과 서버 커넥션의 두 가지 모드 중 하나로 생성할 수 있습니다. 클라이언트 커넥션은 메시지를 송신할 수만 있고, 서버 커넥션은 송수신이 모두 가능합니다. 다른 GCF 커넥션의 경우에는 URL을 이용하여 커넥션을 클라이언트나 서버 중 하나로 지정합니다. 이 경우,
scheme://address-part [params]
이 때,
scheme
은 사용할 프로토콜입니다.
address-part
클라이언트 또는 서버 커넥션을 위한 프로토콜 전용 주소입니다.
params
는 스킴에 따른 ;x=y
형태의 옵션 매개변수입니다.
WMA 2.0 지원 프로토콜 어댑터 및 표준 WMA 프로토콜 어댑터에 대한 지원은 다음의 표준을 따릅니다.
|
MessageConnection
생성을 위한 URL scheme
은 하나의 프로토콜에 한정되지 않으며, 대신 다수의 무선 메시징 프로토콜을 지원하도록 되어 있습니다. WMA 스펙은 다음과 같은 메시징 프로토콜 어댑터를 정의합니다.
sms
. SMS는 양방향입니다. 메시지를 생성, 송신, 수신할 수 있고, 따라서 클라이언트/서버 커넥션을 모두 지원합니다. mms
. MMS도 SMS와 마찬가지로 양방향이므로 클라이언트/서버 커넥션 모두 지원이 가능합니다. Message
를 위한 cbs
. CBS 메시지는 베이스 스테이션을 통해 브로드캐스트되며 오직 수신만 가능하며, cbs
를 통해 송신하려고 하면 IOException
이 발생합니다. CBS 메시지에는 타임스탬프가 없으므로 메시지의 타임스탬프를 get하려고 하면 null
이 반환됩니다. URL의 address-part
은 핸드세트와 애플리케이션을 지정하며, 이는 또한 커넥션이 클라이언트인지 서버인지도 지정합니다. 클라이언트 커넥션의 URL에는 정식 수신지 주소가 포함되는 반면 서버 커넥션의 URL은 로컬 주소 호스트가 없는 프로토콜 전용 로컬 주소(일반적으로 포트 번호 또는 애플리케이션 ID)를 지정합니다. sms
의 경우 address-part
는 핸드세트를 식별하는 MSISDN이나 애플리케이션을 식별하는 포트 번호, 또는 두 가지 모두로 이루어지고, mms
의 경우 주소 부분은 이메일 주소, 전화 번호, IPv4, IPv6, 숏코드 주소나 애플리케이션 ID 또는 이 두 가지 모두로 구성될 수 있습니다. 수신 메시지만을 지원하는(서버 모드) cbs
의 경우 address-part
은 단지 애플리케이션을 식별하는 포트 번호입니다.
클라이언트 커넥션 URL의 예제:
(MessageConnection)Connector.open ("sms://+15121234567:5000");
(MessageConnection)Connector.open ("mms://+15121234567: com.j2medeveloper.MyMmsApp");
cbs
는 서버 커넥션만을 지원하므로 해당 예제가 없습니다.
서버 커넥션 URL의 예제:
(MessageConnection)Connector.open("sms://:5000");
(MessageConnection)Connector.open("mms://:com.j2medeveloper.MyMmsApp");
(MessageConnection)Connector.open("cbs://:6000");
애플리케이션 ID와 포트 번호에 관하여 애플리케이션 ID는 핸드세트의 수신 애플리케이션을 식별합니다. 예를 들어, 2개의 URL sms://+5121234567:5000 및 mms://+5121234567:com.j2medeveloper.MyMmsApp 에서 숫자 5000 과 문자열 com.j2medeveloper.MyMmsApp 는 모두 애플리케이션 식별자입니다. sms 와 cbs 에서는 TCP 및 UDP 커넥션에서 포트 번호를 사용하는 방식과 유사하게 포트 번호로 애플리케이션을 식별합니다. mms 에서는 포트 번호가 아니라 일반적으로 com.j2medeveloper.MyMmsApp 와 같은 정식 명칭인 문자열로 애플리케이션을 식별합니다. 포트 번호는 단문 메시지의 실제 내용과 분리된 공간(일반적으로 16비트)을 차지한다는 점에 유의하십시오. 길이가 32 문자를 초과할 수 없는 MMS 애플리케이션 ID는 옵션에 따라 멀티파트 Content-Type 메시지 헤더의 일부로 포함됩니다. 애플리케이션 ID com.j2medeveloper.MyMmsApp를 지정하는 MMS 서버 커넥션을 생성하면 Content-Type 메시지 헤더의 값에 다음 2개의 매개변수가 추가됩니다. Application-ID=com.j2medeveloper.MyMmsApp; 서버 커넥션이 열리면 포트 번호 또는 애플리케이션 ID가 지정됩니다(사용되는 프로토콜에 따름). 해당 서버 애플리케이션에 메시지를 송신하고자 하는 클라이언트는 반드시 동일한 포트 번호 또는 애플리케이션 ID를 지시해야 합니다. 주어진 포트 번호 또는 애플리케이션 ID에 바인딩되는 최초의 애플리케이션이 그것을 get하고, 이미 확보된 로컬 포트 번호 또는 애플리케이션 ID에 대한 바인딩을 시도하면 IOException 이 throw되는데, 여러분의 코드는 이를 적절히 처리해야만 합니다.개인/동적 포트 범위 49152-65635에서 사용할 포트 번호를 임의로 선택할 수 있습니다. WAP와 같은 특권 애플리케이션 또는 사용자 애플리케이션은 범위 0-49151을 사용합니다. 어떤 것들을 피해야 하는지에 관한 자세한 내용은 WMA 2.0 스펙을 참조하십시오. IANA(Internet Assigned Numbers Authority)에 문의하면 각자의 애플리케이션을 위한 자체 포트 번호를 확보할 수 있습니다. 애플리케이션 식별을 위한 포트 번호 또는 애플리케이션 ID를 지정하지 않고 핸드세트에 메시지를 송신하면 대부분의 경우 메시지가 핸드세트의 기본값 뷰어 애플리케이션으로 전달된다는 점에 유의하십시오. |
URL에 관한 자세한 내용은 기사 "The Generic Connection Framework"와 "A Generic Connection Framework cheat sheet"를 참조하십시오.
표 1에서 보는 것처럼, MessageConnection
은 Message
오브젝트를 각기 생성, 송신, 수신하는 newMessage()
, send()
, receive()
메소드를 제공합니다. numberOfSegments()
는 특히 흥미를 끌만 한데, 이 메소드는 주어진 Message
를 송신하기 전에 해당 세그먼트 정보를 결정하는 데 사용됩니다. 자세한 내용은 “분할과 재조립(About Segmentation and Reassembly)(영문)”을 참조하십시오. setMessageListener()
는 새 메시지 도착 시 WMA 구현이 호출하는 메시지 리스너를 set하는데 사용됩니다.
WMA는 매우 직관적이고 사용하기 쉬운 API로서, 그 활용 방법을 보여주는 몇 가지 코드를 살펴보기로 합시다.
클라이언트 MessageConnection
을 생성하려면 커넥션 팩토리 메소드 Connector.open()
를 호출하여 유효한 WMA 메시징 프로토콜을 지정하고 서버 커넥션을 위한 로컬 주소 또는 클라이언트 커넥션을 위한 정식 수신지 주소를 포함하는 URL을 패스합니다.
... MessageConnection mc = null; String connUrl = "mms://:com.j2medeveloper.MyMmsApp"; // My MMS server app ... try { mc = (MessageConnection)Connector.open(connUrl); } catch (IOException ioe) { // Port number or application identifier is already reserved } catch (SecurityException se) { // Permission to open the MessageConnection was not granted } ... |
애플리케이션의 포트 번호와 애플리케이션 식별자를 JAD(Java Application Descriptor)파일에서 정의하고 이를 런타임 시에(일반적으로 애플리케이션 시동 중에) 검색하는 것도 좋은 방법입니다.
예를 들어, 다음 속성을 JAD 파일에 배치할 수 있습니다.
... SmsApplicationPort: 50000 CbsMessageID: 50001 MmsApplicationID: com.j2medeveloper.MyMmsApp ... |
이들 속성 각각을 검색하려면 MIDlet.getAppProperty(String attributeName)
을 호출하고 get할 속성의 이름을 패스합니다. 이해를 돕기 위해, JAD로부터 WMA 포트 번호와 애플리케이션 ID를 검색하는 helper 메소드인 loadWmaAttributes()
를 정의해봅니다.
... // Define the SMS and MMS Port and Application Identifiers String smsPort; String cbsMsgId; String mmsAppId; ... /** * Loads the application WMA attributes */ private void loadWmaAttributes() { mmsAppId = getAppProperty("MmsApplicationID"); smsAppId = getAppProperty("SmsApplicationPort"); cbsAppId = getAppProperty("CbsMessageID"); } ... |
커넥션 생성 방법을 살펴보기 위해 MessageConnection
을 생성/반환하는 helper 메소드 newMessageConnection()
를 정의해 봅니다.
/** * Create a new MessageConnection * * @param connUrl is the server or client URL for the connection * @param messageListener is the message listener for this * connection * @return a MessageConnection object * @throws a ConnectionNotFoundException if * the Connection target is not found, * or the protocol not supported * @throws an IOException if * the Port number or application identifier is already reserved, * or the connection has been closed, * or if an attempt is made to register a listener on a client * connection * @throws an IllegalArgumentException if * a Message type parameter is invalid * or attempting to create a *Stream * @throws SecurityException if * permission to open the MessageConnection was not granted, * or, when setting the message listener, it was determined * that the application does not have permission to receive * on the given port number */ final public MessageConnection newMessageConnection( String connUrl, MessageListener messageListener) throws ConnectionNotFoundException, IOException, IllegalArgumentException, SecurityException { MessageConnection mc = null; mc = (MessageConnection)Connector.open(connUrl); mc.setMessageListener(messageListener); return(mc); } |
커넥션 팩토리는 적절히 캐스트되어야 하는 Connection
서브타입을 반환합니다. 우리의 예제에서는 Connector.open()
가 반환한 값을 MessageConnection
에 캐스트한 다음 호출자에게 반환합니다. helper 메소드는 또한 커넥션을 위한 메시지 리스너(지정된 경우)를 set하는데, 커넥션의 메시지 리스너를 null
로 set하면 현재의 모든 메시지 리스너가 등록 해제되어 어떤 통지도 수신하지 않게 된다는 점에 유의하십시오. 메시지 리스너에 관해서는 잠시 뒤에 다시 설명하도록 하겠습니다.
GCF는 StreamConnections
를 위한 InputStream
및 OutputStream
을 생성하는 편리한 방법을 제공하지만, MessageConnection
은 스트림 기반 오퍼레이션을 지원하지 않는다는 것을 알아야 합니다. *Stream
을 생성하려 하면 IllegalArgumentException
이 throw됩니다.
커넥션을 종료하려면, 다음 코드 단편에서 보는 것처럼 커넥션의 close()
메소드를 호출합니다.
/** * Closes the specified message connection * * @param connection is the connection to close */ static final public void closeConnection(MessageConnection connection) { try { if (connection != null) { connection.setMessageListener(null); // deregister the msg listener connection.close(); } } catch (IOException ioe) { // Handle or ignore the exception... } } |
이 메소드는 먼저 모든 메시지 리스너를 등록 해제한 다음 커넥션을 닫습니다. 커넥션을 닫으려면 상기 메소드를 호출하고, 삭제되는 동안 destroyApp()
에서 메시지 리스너를 unset하는 것을 잊지 마십시오.
MessageConnection
은 Message
를 작성/송신하는 메소드를 제공한다는 점을 상기하십시오. 메시지를 작성하려면 메시지 팩토리 메소드 newMessage()
를 호출하고, 메시지를 송신하려면 send()
메소드를 이용합니다. newMessage()
는 커넥션이 종료된 후에도 메시지를 반환한다는 점을 잊지 마시기 바랍니다.
앞에서 배운 것처럼, 커넥션 URL에서 선택을 지정하여 클라이언트 커넥션이나 서버 커넥션 중 하나를 생성할 수 있습니다. 클라이언트 커넥션에서 작성된 메시지는 클라이언트 커넥션이 생성될 때 패스된 커넥션 URL로부터 가져온 수신지 주소가 이미 설정되어 있는데, 이와 달리 서버 커넥션의 수신지 주소는 아직 설정되어 있지 않으므로, 메시지 송신 전에 이를 명시적으로 설정해야 합니다.
Message
를 송신하는 helper 메소드sendMessage()
를 정의해 봅시다. 이 메소드는 사용할 MessageConnection, 송신할 실제 MessageBinaryMessage, MultipartMessage, TextMessage, 그리고 옵션인 수신지 URL 등을 매개변수로 취합니다.
비(非) null
URL은 서버 커넥션의 경우에서처럼 호출자가 메시지를 송신하기 전에 수신지 주소를 설정하기를 원한다는 것을 의미하며, 메소드는 또한 numberOfSegments()
메소드를 호출하여 메시지를 송신할 수 있는지 여부를 점검합니다.
우리의 예제는 메인 디스플레이 쓰레드 같은 시f스템 쓰레드와의 경쟁을 피하기 위해 자체 실행 쓰레드(thread of execution) 상에 메시지를 송신하는 모범 사례를 따르고 있습니다. alertUser()
helper 메소드는 표시되지 않으며, 여기서는 메시지 송신이 불가능한 경우 사용자에게 경고를 전달하기 위해 호출됩니다. 메시지 송신 도중에 발생할 수 있는 다양한 예외들에 유의하십시오.
/** * Sends a Message on the specified connection * * @param mc the MessageConnection * @param msg the message to send * @param url the destination address, typically used in server mode */ final public void sendMessage( final MessageConnection mc, final Message msg, final String url) { Thread th = new Thread() { public void run() { try { if (url!= null) msg.setAddress(url); int segcount = mc.numberOfSegments(msg); if (segcount == 0) { alertUser(UiConstants.TXT_SEGCOUNT); } else { mc.send(msg); } } catch(Exception e) { // Handle the Exception: // IOException if the message could not be sent // because there was a network failure or // if the connection was closed // IllegalArgumentException if the message was // incomplete or contained invalid information // This exception is also thrown if the payload // of the message exceeds the maximum length // for the given messaging protocol // InterruptedIOException if a timeout occurs // while trying to send the message or this // Connection object was closed during this // send operation // NullPointerException if the parameter is null // SecurityException if the application does not // have permission to send the message } } }; th.start(); } |
범용(generic) sendMessage()
helper 메소드에 대한 설명을 마쳤으니 이제는 TextMessage
를 송신하는 helper 메소드를 정의해 보기로 합니다.
/** * Sends a TextMessage on the specified connection * * @param mc the MessageConnection * @param msg the TextMessage to send * @param url the destination address, typically used in server mode */ final public void sendTextMessage( MessageConnection mc, TextMessage msg, String url) { sendMessage(mc, msg, url); } |
이 메소드는 TextMessage
가 이미 작성된 것으로 간주합니다. 이제, 통상적인 String
을 인풋으로 취하고 우리를 대신해서 TextMessage
를 작성/송신하는 좀더 유용한 버전의 sendTextMessage()
를 정의해 보기로 합니다. 이 메소드는 송신 전에 setPayloadText()
메소드를 호출하여 나가는 TextMessage
를 파퓰레이트합니다.
/** * Sends a TextMessage on the specified connection * * @param mc the MessageConnection * @param msg the message to send * @param url the destination address, typically used in server mode */ final public void sendTextMessage( MessageConnection mc, String msg, String url) { TextMessage tmsg = null; tmsg = (TextMessage) mc.newMessage(MessageConnection.TEXT_MESSAGE); tmsg.setPayloadText(msg); sendTextMessage(mc, tmsg, url); } |
바이너리 및 멀티파트 메시지를 송신하는 경우에도 동일한 패턴을 따릅니다.
이제 BinaryMessage
를 송신하는 또 다른 helper 메소드를 정의해 보도록 하겠습니다.
/** * Sends a BinaryMessage on the specified connection * * @param mc the MessageConnection * @param msg the Binary Message to send * @param url the destination address, typically used in server mode */ final public void sendBinaryMessage( MessageConnection mc, BinaryMessage msg, String url) { sendMessage(mc, msg, url); } |
이 메소드는 BinaryMessage
가 이미 작성된 것으로 간주합니다. 이제, 바이트 배열을 인풋으로 취하고 우리를 대신해서 BinaryMessage
를 작성/송신하는 좀더 유용한 버전의 sendBinaryMessage()
를 정의해 보기로 합시다. 이 메소드는 송신 전에 setPayloadData()
메소드를 호출하여 나가는 BinaryMessage
를 파퓰레이트합니다.
/** * Sends a BinaryMessage on the specified connection * * @param mc the MessageConnection * @param msg the byte[] message to send * @param url the destination address, typically used in server mode */ final public void sendBinaryMessage( MessageConnection mc, byte[] msg, String url) { BinaryMessage bmsg; bmsg = (BinaryMessage) mc.newMessage(MessageConnection.BINARY_MESSAGE); bmsg.setPayloadData(msg); sendMessage(mc, bmsg, url); } |
MultipartMessage
를 송신하는 helper 메소드를 하나 더 정의해 보기로 합시다.
/** * Sends a multi-part message on the specified connection * * @param mc the MessageConnection * @param msg is the multiplart message to send * @param url the destination address, typically used in server mode */ final public void sendMultipartMessage( MessageConnection mc, MultipartMessage msg, String subject, String url) { sendMessage(mc, msg, url); } |
이 메소드는 MultipartMessage
가 이미 작성된 것으로 간주합니다. 텍스트 및 바이너리 메시지의 경우와 마찬가지로, MessageParts
의 배열을 인풋으로 취하고 우리를 대신해서 MultipartMessage
를 작성/송신하는 좀더 유용한 버전의 sendMultipartMessage()
를 정의할 것입니다. 이 메소드는 송신하기 전에 각각의 개별 MessagePart를 추가하여 나가는 MultipartMessage를 파퓰레이트합니다. 이 메소드는 MessageConnection
, MessageParts
의 배열, start-content ID, TO/CC/BCC 식별자 리스트, 메시지 제목, 메시지의 우선순위, 수신지 URL 등의 여러 가지 인자를 취합니다.
/** * Sends a multi-part message on the specified connection * * @param mc the MessageConnection * @param msgParts the array of message parts to send * @param startContentID is the ID of the start multimedia * content part (SMIL) * @param to is the message's TO list * @param cc is the message's CC list * @param bcc is the message's BCC list * @param subject is the message's subject * @param priority is the message's priority * @param url is the destination URL, typically used in server mode */ final public void sendMultipartMessage( MessageConnection mc, MessagePart[] msgParts, String startContentID, String[] to, String[] cc, String[] bcc, String subject, String priority, String url) { try { int i=0; MultipartMessage multipartMessage; multipartMessage = (MultipartMessage) mc.newMessage(MessageConnection.MULTIPART_MESSAGE); if (to != null) { for (i=0; i<to.length; i++) { multipartMessage.addAddress("to", to[i]); } } if (cc != null) { for (i=0; i<cc.length; i++) { multipartMessage.addAddress("cc", cc[i]); } } if (bcc != null) { for (i=0; i<bcc.length; i++) { multipartMessage.addAddress("bcc", bcc[i]); } } multipartMessage.setSubject(subject); if ((priority.equals("high")) || (priority.equals("normal")) || (priority.equals("low"))) { multipartMessage.setHeader("X-Mms-Priority", priority); } for (i=0; i<msgParts.length; i++) { multipartMessage.addMessagePart(msgParts[i]); } multipartMessage.setStartContentId(startContentID); sendMessage(mc, multipartMessage, url); } catch(Exception e) { // Handle the exception... } } |
프레젠테이션 정보가 담긴 멀티미디어 메시지의 경우 (W3C(World Wide Web Consortium)이 정의한 SMIL(Synchronized Media Integration Language)로 작성된 것과 같은), 멀티파트 메시지의 start-content ID는 항상 프레젠테이션 정보가 포함된 메시지 부분을 가리키도록 설정되어야 하고, 이를 위해서는 setStartContentId()
메소드를 호출해야 합니다. 또한, start-content ID를 null
로 설정할 수도 있는데, null
이 아닌 경우에는 반드시 유효한 메시지 부분이 참조되어야 하며, 그렇지 않으면 IllegalArgumentException
이 throw됩니다. 이 호출의 결과는 MMS 컨텐트 타입이 application/vnd.wap.multipart.related
가 되고 아래에서 보는 것처럼 start 매개변수가 이어집니다. 이로써, 이 메시지 타입의 수신자는 프레젠테이션 정보를 검색하고 메시지를 적절히 사용자에게 제시하는 방법을 알게 될 것입니다.
[THE FOLLOWING ARE THE MMS HEADERS] X-Mms-Message-Type: m-send-req X-Mms-Transaction-ID: 123456 X-Mms-Version 1.0 X-Mms-Message-Class: Personal X-Mms-Expiry: 36000 X-Mms-Priority: Normal From: +15121234567 To: +15121234544 Date: Fri, 17 May 2005 21:30:30 -0600 Subject: A multimedia message sample Content-Type: application/vnd.wap.multipart.related;start=<start>; type=application/smil;Application-ID=com.j2medeveloper.MyMmsApp; Reply-To-Application-ID = com.j2medeveloper.MyMmsApp nEntries: 2 [THE FOLLOWING IS THE MMS BODY THAT CONSIST OF MESSAGE PARTS] [EACH MESSAGE PART CONSISTS OF TYPE, CONTENT ID, AND CONTENT] Content-Type: image/png; name="image1.png" content-id: <001> [...IMAGE DATA...] Content-Type: application/smil; name="first.sml" content-id: <start> <smil> <body> <seq repeatCount="indefinite"> <img src="image1.png" dur="3s" /> </seq> </body> </smil> |
다음으로 MessageParts
를 살펴보기로 합시다.
이미 살펴본 것처럼 MessageParts
는 MultipartMessages
의 중심이라 할 수 있으며, 그 각각은 MIME 타입, 고유 컨텐트 ID, 그리고 컨텐트 자체로 이루어집니다.
바이트 배열이나 java.io.InputStream
로부터 MessagePart
를 구성할 수 있으며, 다음 세 가지 생성자를 이용할 수 있습니다.
MessagePart(byte[] contents, int offset, int length, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(byte[] contents, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(java.io.InputStream contents, String mimeType, String contentId, String contentLocation, String encoding) throws IOException, SizeExceededException;
이 때,
contents
실제 메시지 컨텐트를 가리킵니다.
mimeType
은 RFC2046에 정의된 MIME 타입입니다.
contentId
는 RFC2045에 정의된, 메시지 부분을 고유하게 식별하는 필수 ID입니다.
contentLocation
은 컨텐트가 나타내는 첨부 메시지에 대한 파일명을 지정합니다. null
값은 이 메시지 부분에 대해 어떤 컨텐트 위치 값도 설정되지 않았음을 의미합니다.
encoding
identifies the content's encoding scheme. A null
value means that no encoding is specified for this message part. 처음 두 생성자는 바이트 배열로부터 메시지 파트를 작성할 수 있게 해주고, 마지막 생성자는 InputStream
으로부터 메시지 파트를 작성할 수 있게 해줍니다. 일반적인 경험에 의하면 컨텐트 크기가 10KB에 가까울 경우에는 byte[]
대신 InputStream
으로부터 메시지 부분을 구성해야 합니다.
다음의 코드 단편은 text/plain
과 image/png
의 두 가지 메시지 부분을 작성하는 방법을 보여줍니다.
public final static String MIMETYPE_TEXT_PLAIN = "text/plain"; public final static String MIMETYPE_IMAGE_PNG = "image/png"; ... MessagePart textMessagePart, imageMessagePart; try { // Create a text/plain part String textContent = "Hello world"; String textContentId = "text_hello"; String textContentLocation = "/helloworld.txt"; textMessagePart = new MessagePart( textContent.getBytes(), 0, textContent.length(), MIMETYPE_TEXT_PLAIN, textContentId, textContentLocation, null); // Create an image/png part InputStream imageContent; String imageContentId = "img_hello"; String imageContentLocation = "/helloworld.png"; imageContent = getClass().getResourceAsStream (imageContentLocation); imageMessagePart = new MessagePart( imageContent, MIMETYPE_IMAGE_PNG, imageContentId, imageContentLocation, null); } catch (SizeExceededException see) { // Handle exception } catch (IOException ioe) { // Handle exception } |
사용자의 코드는 반드시 다음의 예외를 처리해야 합니다.
IOException
: InputStream
을 읽는 동안EOFException
이외의 예외가 발생할 경우.
IllegalArgumentException
: 지정된 MIME 타입이 null
인 경우.
SizeExceededException
: 컨텐트 크기가 가용한 메모리보다 크거나 크기가 해당 메시지 부분에 대해 지원되지 않는 경우. 일반 개별 메시지 부분이 작성되면, 우리를 대신해서 자동으로 MultipartMessage
를 작성/송신하는, 즉 앞서 살펴본 sendMultipartMessage()
helper 메소드를 이용하여 송신할 준비가 된 것입니다.
... String connUrl = ...; MessageConnection mc; MessagePart textMessagePart, imageMessagePart; MessagePart[] msgParts; ... mc = newMessageConnection(connUrl, null); ... // Create a message parts array textMessagePart = new MessagePart(...); imageMessagePart = new MessagePart(...); msgParts = new MessagePart[2]; msgParts[0] = textMessagePart; msgParts[1] = imageMessagePart; // Send the multiple parts sendMultipartMessage(mc, msgParts, connUrl); ... |
서버 모드의 MessageConnection
만 메시지를 수신할 수 있다는 점을 상기하시기 바랍니다. 메시지를 수신하는 방법으로는 다음의 두 가지가 있습니다.
어떤 방법을 선택하느냐는 순전히 개인적 취향의 문제입니다. 필자는 개인적으로, 처리할 메시지가 없는데도 쓰레드가 실행되는 것을 방지하기 위해 비동기 방식을 선호하는 편입니다. 이 방식은 들어오는 각 메시지에 대해 새 쓰레드를 생성/디스패치 해야 하기 때문에 리소스 비용이 약간 더 들 수는 있습니다. 그러나 WMA는 그다지 빈번하지 않은 메시지에 이용되는 것이 보통이므로 메시지 당 하나의 쓰레드를 디스패치 하더라도 별 무리는 없습니다.
메시지를 애플리케이션에 전달하기 전에 WMA 구현이 사용자의 확인을 요구할 수 있다는 점에 유의하시기 바랍니다.
동기 메시징의 경우에는 통상적으로 MIDlet 초기화 과정에서 쓰레드를 디스패치합니다. 이 쓰레드는 메시지에 대해 반복 수행(loop)되고, Message.receive()
를 인보크하여 들어오는 메시지를 대기하는 동안 차단하고, 그런 다음 메시지가 가용한 상태가 되면 이를 소비하게 됩니다. 하나의 예제로, 들어오는 메시지를 대기하고 처리하는 실행 쓰레드를 구현하는 Runnable인 MySynchronousMsgReader
클래스를 정의해 보기로 합시다.
public class MySynchronousMsgReader implements Runnable { MessageConnection messageConnection; boolean done; ... /** * Constructor * * @param messageConnection is the MessageConnection to use */ public MySynchronousMsgReader( MessageConnection messageConnection) { this.messageConnection = messageConnection; Thread th = new Thread(this); th.start(); } ... /** * Sets the done flag to true */ public void setDone() { done = true; } /** * Message receive thread of execution */ public void run() { while (!done) { try { Message message; message = messageConnection.receive(); if (message != null) { processMessage(message); } } catch (IOException ioe) { // Handle or ignore the exception } } } /** * Process the message * * @param message is the Message to process */ public void processMessage(message) { // Here process the message } ... } |
메시지 처리에 관해서는 곧 다시 논하도록 하겠습니다.
비동기 메시징의 경우 WMA는 종래의 Observer 또는 Listener 디자인 패턴을 구현하여 비동기적으로, 즉 메시지를 대기하는 동안 차단하지 않고 메시지를 수신합니다. WMA는 플랫폼이 Message
를 수신할 때마다 인보크하는 단일 메소드 notifyIncomingMessage()
를 통해 MessageListener
인터페이스를 정의합니다.
메시지 리스너를 이용하려면 애플리케이션은 반드시 MessageListener
인터페이스와 해당 notifyIncomingMessage()
메소드를 구현해야 합니다. 그러므로,
public class MyClass implements MessageListener { ... /** * Asynchronous callback for inbound message * Called by the WMA implementation when a new message is ready. * * @param connection is the message connection with incoming * messages. */ public void notifyIncomingMessage(MessageConnection connection) { readMessageThreaded(connection); } ... } |
메시지 리스너의 notifyIncomingMessage()
메소드는 인바운드 메시지를 인풋으로 하여 MessageConnection
을 수신하는데, 일단 이 메소드가 인보크되면, 애플리케이션은 반드시 messageConnection.receive()
를 호출하여 새 메시지를 검색해야 합니다. 다음의 메소드는 몇 가지 helper 메소드를 구현하여 메시지를 수신합니다.
///////////////////////////////////////////////////////////////////// // Receive Helper Methods ///////////////////////////////////////////////////////////////////// /** * Thread of execution to read messages from MessageConnection * * @param messageConnection is the messageConnection with inbound * messages */ public void readMessageThreaded(final MessageConnection messageConnection) { Thread th = new Thread() { public void run() { readMessage(messageConnection); } }; th.start(); } /** * Reads a message from MessageConnection, processes the message * @param messageConnection is the messageConnection with inbound * messages */ public void readMessage(final MessageConnection messageConnection) { try { Message message = null; message = messageConnection.receive(); if (message != null) { processMessage(message); } } catch (IOException ioe) { System.out.println("readMessage exception: " + ioe); } } /** * Process the message * @param message is the Message to process */ public void processMessage(message) { // Here process the message } |
비동기 메시징이 가능하게 되려면, 앞서 살펴본 helper 메소드 newMessageConnection()
의 경우처럼 애플리케이션이 반드시 MessageConnection.setListener()
를 호출하여 메시지 리스너를 커넥션에 등록해야 한다는 사실을 기억하고 계십시오. 아울러, 커넥션의 메시지 리스너를 null
로 설정하면 현재의 모든 리스너가 등록 해제되어 어떤 통지도 수신하지 않게 된다는 점 또한 명심해야 합니다.
... mc.setMessageListener(messageListener); ... |
우리가 방금 정의한 readMessageThreaded()
메소드는 메시지를 읽기 위한 자체 실행 쓰레드를 생성한다는 점에 유의하십시오. 리스너 메소드는 플랫폼에 의해 호출되기 때문에, 항상 시스템 쓰레드에 대해 수행하는 처리를 최소화해야 합니다. 항상 메시지를 소비하고 처리하기 위한 별도의 실행 쓰레드를 디스패치하여 플랫폼이 notifyIncomingMessage()
에 가능한 한 적은 시간을 소비하도록 해야 합니다.
일단 messageConnection.receive()
를 호출하여 메시지를 읽으면 처리할 준비가 된 것입니다. receive()
의 리턴 타입은 포괄적인 슈퍼타입 Message
이므로, 메시지를 제대로 처리하려면 먼저 해당 서브타입 - BinaryMessage
, MultipartMessage
, TextMessage
- 을 알아내야만 합니다.
LCDUI(Liquid Crystal Display User Interface)를 통해 WMA 메시지를 뷰하는 데 사용될 수 있는 helper 메소드를 정의해 보기로 합시다. 이 메소드는 Message
를 인풋으로 취하고 애플리케이션이 LCDUI Form에 디스플레이할 수 있는 UI 엘리먼트 또는 아이템을 반환합니다. processMessage()
메소드가 instanceof
연산자를 사용하여 Message
의 서브타입을 알아낸 다음 해당 process-message 메소드를 호출하면 javax.microedition.lcdui.Items
배열이 반환됩니다.
/** * ProcessMessage processes a Message */ public Item[] processMessage(Message message) { Item[] items = null; // Process the received message appropriately to its type if (message instanceof TextMessage) { TextMessage tmsg = (TextMessage)message; items = processTextMessage(tmsg); } else if (message instanceof BinaryMessage) { BinaryMessage bmsg = (BinaryMessage)message; items = processBinaryMessage(bmsg); } else if (message instanceof MultipartMessage) { MultipartMessage mpmsg = (MultipartMessage)message; items = processMultipartMessage(mpmsg); } else { // Ignore System.out.println("Unrecognized message type..."); return null; } return items; } |
이 메소드는 메시지를 처리하여 세 가지 helper 중 하나에 javax.microedition.lcdui.Items
배열을 반환하는 실제 작업을 위임하게 되는데, 이 중 하나인 processTextMessage()
는 TextMessage.getPayloadText()
를 호출하여 텍스트 메시지의 페이로드를 검색한 다음 메시지의 텍스트를 포함하는 단일 엘리먼트 StringItem
배열을 반환합니다.
/** * Process TextMessage * * @param tmsg is the TextMessage to process */ public Item[] processTextMessage(TextMessage tmsg) { Item[] items; String text = tmsg.getPayloadText(); items = new Item[1]; Item item = new StringItem(null, text); items[0] = item; return items; } |
processBinaryMessage()
helper 메소드도 이와 유사하지만, 텍스트 메시지를 처리하는 대신 BinaryMessage.getPayloadData()
를 호출하여 바이너리 메시지의 페이로드를 검색하고, 바이트 배열을 16진수 문자열로 변환한 다음 hex를 포함하는 단일 엘리먼트 StringItem
배열을 반환합니다.
/** * Process BinaryMessage * * @param bmsg is the BinaryMessage to process */ public Item[] processBinaryMessage(BinaryMessage bmsg) { Item[] items; byte[] data = bmsg.getPayloadData(); String hex = toHex(data); items = new Item[1]; Item item = new StringItem(null, hex); items[0] = item; return items; } /** * toHex converts a byte array to human-readable hexadecimal * This method was borrowed from examples in the Sun Wireless * Toolkit :-) * @param data is the array of bytes to convert * @return a String containing the byte[] in hexadecimal format */ final private String toHex(byte[] data) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < data.length; i++) { int intData = (int)data[i] & 0xFF; if (intData < 0x10) buf.append("0"); // display 2 digits per byte i.e. 09 buf.append(Integer.toHexString(intData)); buf.append(' '); } return (buf.toString()); } |
다음의 helper 메소드 processMultipartMessage()
는 약간 더 복잡합니다.
MultipartMessage
내의 MMS 헤더는 메시지가 송신자로부터 수신자에게로 확실히 전달되도록 하는 데 이용되며 TO, CC, BCC, FROM 주소 등의 정보를 포함합니다. MMS 본문 자체가 하나 이상의 메시지 부분으로 이루어지며, 그 각각은 다시 헤더, 타입, 본문으로 구성됩니다.
각각의 개별 메시지 부분에는 아무 MIME 타입이나 사용될 수 있지만 메시지의 전체 MMS 컨텐트 타입의 경우에는 다음 세 가지 중 하나가 사용됩니다.
application/vnd.wap.mms-message
전형적인(true) 멀티미디어 메시지가 아닌 경우
application/vnd.wap.multipart.related
관련 프레젠테이션 정보를 가진 멀티미디어 메시지인 경우
application/vnd.wap.multipart.mixed
무관한 부분들로 이루어진 멀티미디어 메시지인 경우 다음 helper 메소드 processMultipartMessage()
는 MultipartMessage
헤더 정보를 검색한 다음 각 부분의 MIME 타입에 일치하는 Items
의 배열을 최종적으로 반환합니다.
... // MIME Types public final static String MIMETYPE_TEXT_PLAIN = "text/plain"; public final static String MIMETYPE_TEXT_XML = "text/xml"; public final static String MIMETYPE_TEXT_HTML = "text/html"; public final static String MIMETYPE_IMAGE_PNG = "image/png"; public final static String MIMETYPE_IMAGE_JPEG = "image/jpg"; public final static String MIMETYPE_IMAGE_GIF = "image/gif"; public final static String MIMETYPE_APPLICATION_OCTET_STREAM |
RFC822 헤더로 저장된 일부 MultipartMessage
정보는 액세스 메소드를 통해 액세스가 가능해지는 반면 다른 헤더들은 오직 getHeader()
를 호출하여 직접 검색해야만 사용 가능해진다는 점에 유의하십시오. 기타 속성은 보안상의 이유로 액세스가 불가능합니다.
헤더 중 하나인 컨텐트 시작(start-content) ID는 SMIL 기반 컨텐트 같은 전형적인(true) 멀티미디어 메시지를 위한 프레젠테이션 정보가 포함된 메시지 부분을 참조합니다. 메시지 컨텐트 시작(start message content) ID를 검색하려면 아래와 같이 멀티파트 메시지 메소드 getStartContentId()
를 호출합니다.
... // Get the start-content ID, which will be set for multimedia // content, and will contain the presentation information String startContentID = mpmsg.getStartContentId(); ... |
X-Mms-Delivery-Time
헤더의 포맷은 UTC(협정 세계시) 1970년 1월 1일 자정부터 경과된 밀리초를 문자열 값으로 표시한 것입니다. X-Mms-Priority
의 컨텐트의 포맷은 "high
" 또는 "normal
", "low
"의 값을 가지는 문자열입니다.
... // Get the Multipart message headers via the access methods: // X-Mms-From, X-Mms-To, X-Mms-CC, X-Mms-BCC, X-Mms-Subject String from = mpmsg.getAddress(); String tos[] = mpmsg.getAddresses("to"); String ccs[] = mpmsg.getAddresses("cc"); String bccs[] = mpmsg.getAddresses("bcc"); String froms[] = mpmsg.getAddresses("from"); String subject = mpmsg.getSubject(); // Get the rest of the headers by calling the getHeader() method String mmsPriorityHeader = mpmsg.getHeader("X-Mms-Priority"); String mmsDeliveryTimeHeader = mpmsg.getHeader("X-Mms-Delivery-Time"); // Get the message timestamp Date timestamp = mpmsg.getTimestamp(); ... |
getAddresses()
메소드는 주소 타입 "to
" 또는 "cc
", "bcc
", "from
"을 인풋으로 취하며 String
배열 또는 지정된 주소 타입을 찾을 수 없는 경우 null
을 반환합니다. 이 때, 하나 이상의 TO, CC, BCC 주소가 어떤 방식으로든 결합될 수 있습니다. getHeader()
메소드는 헤더 이름을 인풋으로 취하는데, 요구된 헤더가 제한된 경우에는 SecurityException
을 throw하고 헤더가 알려지지 않은 경우에는 IllegalArgumentException
을 throw합니다. MMS 헤더에 관한 자세한 내용은 WMA 2.0 스펙의 부록 D를 참조하십시오.
들어오는 WMA 메시지는 처음에 장치의 SIM(Subscriber Identity Module) 카드에 저장되었다가 애플리케이션에 전달될 때 SIM에서 삭제되는데, 애플리케이션 레벨에서의 Message
지속성 여부는 개발자에 의해 결정됩니다. 여러분의 애플리케이션에 메시지 지속성을 추가하려면 반드시 메시지 페이로드를 serialize/deserialize하고 RMS(Record Management System)를 이용하여 로컬 스토어를 관리해야 합니다.
일부 트랜스포트는 단일 메시지의 크기나 메시지를 구성하는 세그먼트의 수에 제한을 두고 있습니다. 한편, SAR(Segmentation and Reassembly)는 큰 메시지를 여러 개의 작은 순차적 세그먼트 또는 전송 단위로 분할하는 일종의 low-level 트랜스포트의 기능인데, 예를 들어 SMS 네트워크를 통해 송신된 메시지는 일반적으로 160개의 7비트 인코딩 문자 또는 140개의 8비트 바이트로 제한되므로 더 긴 메시지를 송신하려면 반드시 분할을 해야 합니다. 재조립은 분할과 반대로 더 작은 관련 세그먼트들을 다시 하나의 메시지로 합치는 작업이라고 생각하시면 됩니다.
WMA 2.0 스펙은 WMA 기반 SMS 구현이 반드시 단일 메시지에 대해 세 개 이상의 SMS 프로토콜 세그먼트를 지원할 것을 요구하고 있습니다. 구현에 따라서는 더 많이 지원할 수도 있지만, 애플리케이션이 3 세그먼트 제한을 고수하고 456개의 GSM 7비트 인코딩 문자, 198개의 더블바이트 문자, 또는 399개의 바이너리 바이트보다 큰 메시지를 송신하지 않는다면 그만큼 이식성은 더 커집니다. 자세한 내용은 WMA 2.0 부록 A.1.2 "Message Payload Concatenation"을 참조하십시오. 더 큰 메세지를 보내게되면 IOException
이 발생할 수 있습니다. MMS 메시지의 경우에는 SAR가 다르게 처리되고 크기 제한이 덜 엄격합니다. 개별 메시지 부분은 크기가 킬로바이트 수준이 될 수 있습니다. MMS 메시지 부분이 가용한 메모리나 지원되는 크기보다 클 경우에는 SizeExceededException
이 발생하게 됩니다.
SMS 메시지 분할을 용이하게 하기 위해, WMA는 MessageConnection.numberOfSegments()
라는 메소드를 제공하는데, 이 메소드는 MessageConnection.send()
를 이용하여 Message
를 송신하기 전에 분할 정보를 제공합니다. numberOfSegments()
메소드는 메시지 송신에 필요한 세그먼트의 수 또는 메시지를 송신할 수 없는 경우 0
을 반환합니다. 그럼, numberOfSegments()
를 이용하는 수정된 버전의 sendTextMessage()
를 살펴보기로 합시다.
... Message msg = ...; int segcount = mc.numberOfSegments(msg); if (segcount == 0) { // can't send, alert the user alertUser(SEGMENTATI[안내]태그제한으로등록되지않습니다-xxONERROR); } ... |
또한, numberOfSegments()
로부터 얻는 정보를 이용하여 사용자에게 메시지가 길수록 송신 비용도 증가한다는 점을 경고할 수도 있습니다. 예를 들어, 단문 메시지를 송신하는 데 드는 비용이 5센트라면 3 세그먼트로 된 큰 메시지를 송신하는 비용은 15센트가 될 수 있습니다. 비용에 신경을 쓰는 사용자의 경우 긴 메시지를 취소할 기회가 주어진다면 상당히 반가워할 것입니다.
WMA 2.0은 SMSC(Short Message Service Center)와 MMSC(Multimedia Message Service Center)의 주소를 검색하기 위한 두 가지의 시스템 속성을 정의합니다.
wireless.messaging.sms.smsc
- the SMSC attribute name
wireless.messaging.mms.mmsc
- the MMSC attribute name 다음과 같이 System.getProperty(String attributeName)
메소드를 이용하여 이 값들을 검색합니다.
... String smsc = System.getProperty("wireless.messaging.sms.smsc"); String mmsc = System.getProperty("wireless.messaging.mms.mmsc"); ... |
SMSC 주소는 +18472549270
과 같은 MSISDN 번호이고, MMSC 주소는 +18472549270
과 같은 MSISDN 번호 값 또는 http://mmsc.cingular.com
과 같은 URL입니다.
MIDP 2.0의 push registry는 MIDlets가 사용자의 개시(initiation) 없이 자동으로 론치되도록 스스로를 설정할 수 있게 해줍니다. 즉, push registry가 네트워크/타이머에 의한 MIDlet 활성화를 관리하는데, 다시 말해 인바운드 네트워크 커넥션 또는 타이머 기반 경보가 잠자고 있던 MIDlet을 깨워주는 것이라고 생각하시면 됩니다.
기본 구현이 필요한 지원을 제공할 경우, MIDlets는 들어오는 WMA 커넥션에 의해 활성화될 수 있습니다. MIDlets가 활성화되려면 반드시 먼저 정적 또는 동적 등록 방식을 이용하여 push registry에 등록해야 합니다. 정적 등록은 JAD 또는 MANIFEST 파일 내에 MIDlet-Push-n
엔트리를 배치함으로써 이루어지며, 동적 등록은 PushRegistry
오브젝트의 registerConnection()
API를 이용하여 런타임 시에 이루어집니다.
다음은 정적 등록을 이용하는 예제입니다.
... MIDlet-Push-1: sms://:50000, MyWmaMIDlet, +123456789 MIDlet-Push-2: cbs://:50001, MyWmaMIDlet, * MIDlet-Push-3: mms://:com.j2medeveloper.MyMmsApp, MyWmaMIDlet, * ... |
첫 번째 속성 값은 로컬(서버) 커넥션 URL, 즉 서버 프로토콜과 포트 번호 또는 애플리케이션 ID입니다. 두 번째 값은 지정된 커넥션 URL에 대해 들어오는 메시지를 처리하는 MIDlet 클래스의 이름이며, 세 번째는 MIDlet을 활성화할 수 있는 송신자를 제한하는 데 사용되는 필터입니다. 다음 코드 단편은 동적 등록을 이용하는 방법을 보여줍니다.
... // Identify the MIDlet class String midletClassName = this.getClass().getName(); // Register a static connection String url = "sms://:50000"; // Use a filter for SMS String filter = "+123456789"; // only allow messages from +123456789 ... try { PushRegistry.registerConnection(url, midletClassName, filter); } catch (IOException ioe) { // Handle the exception } catch (ClassNotFoundException ioe) { // Handle the exception } ... |
MIDlet 인보케이션 전반에 걸쳐 제한이 유지되기 때문에, push 커넥션이 더 이상 필요치 않을 경우에는 unregisterConnection()
메소드를 호출하여 등록 해제하는 것이 중요합니다.
... String url = "sms://:50000"; ... try { // unregisterConnection returns true if successful, false if not status = PushRegistry.unregisterConnection(url); } catch(SecurityException e) { // Handle the exception } ... |
listConnections()
메소드를 호출하여 MIDlet이 활성화되었는지 여부를 알아낼 수 있습니다. pushed 커넥션을 모두 찾아내어 처리하는 helper 메소드 processPushConnections()
를 정의해 보기로 합시다. 보통 이 메소드는 애플리케이션 시동 과정에서 인보크하면 됩니다.
/** * Discover whether there are pending push inbound connections * and, if so, process */ public void processPushConnectionsThreaded() { Thread th = new Thread() { public void run() { String[] connections = PushRegistry.listConnections(true); if (connections != null && connections.length > 0) { for (int i=0; i<connections.length; i++) { String connUrl = connections[i]; if ((connUrl.startsWith("sms")) || (connUrl.startsWith("cbs")) || (connUrl.startsWith("mms"))) { try { MessageConnection messageConnection; messageConnection = (MessageConnection) Connector.open(connUrl); readMessage(messageConnection); } catch (IOException ioe) { // Ignore } } } } } }; th.start(); } |
시스템 쓰레드와의 경쟁을 피하고 메시지 처리를 serialize하여 메시지가 수신될 때와 동일한 순서로 처리될 수 있도록 하기 위해 processPushConnectionsThreaded()
메소드는 자체 실행 쓰레드 상에서 실행된다는 점에 유의하십시오. push registry의 API를 이용하는 구체적인 방법에 관해서는 "The MIDP 2.0 Push Registry(영문)"를 참조하시기 바랍니다.
WMA는 보안 메커니즘을 정의하지 않는 대신 기본 플랫폼의 보안 프레임워크를 이용합니다. MIDP 2.0을 포함한 일부 플랫폼에서는 네트워킹 오퍼레이션이 특권 오퍼레이션으로 간주되는데, 이는 커넥션을 열거나 메시지를 송수신하려면 반드시 애플리케이션이 허가를 요구하고 플랫폼이 이를 수락해야 함을 의미합니다(구체적인 내용은 구현 상태에 의해 좌우됩니다). MIDP 2.0의 경우, 허가는 JAD 또는 manifest를 통해 요구되고 오퍼레이션(우리의 경우에는 open, send, receive)이 인보크될 때 사용자에 의해 수락(또는 거부)됩니다. 서명된 MIDlet의 경우에는 허가가 반드시 manifest에서 정의되어야 한다는 점에 유의하십시오.
다음은 JAD 또는 manifest를 통해 WMA 허가를 요구하는 예제입니다.
... MIDlet-Permissions: javax.microedition.io.Connector.sms, javax.wireless.messaging.sms.receive, javax.wireless.messaging.sms.send, javax.microedition.io.Connector.cbs, javax.wireless.messaging.cbs.receive, javax.microedition.io.Connector.mms, javax.wireless.messaging.mms.receive, javax.wireless.messaging.mms.send, javax.microedition.io.PushRegistry ... |
WMA는 애플리케이션에 대해 플랫폼 서비스를 인보크할 때 마주치는 허가 예외를 통지하기 위해 SecurityException
클래스를 정의합니다. 이 예외는 다음과 같이 throw될 수 있습니다.
javax.microedition.io.Connector
: 애플리케이션이 플랫폼 보안 서비스에 의해 정의된 바에 따라 주어진 메시지 프로토콜을 위한 커넥션 생성 허가를 받지 않은 경우
MessageConnection.send()
: 애플리케이션이 지정된 포트로 메시지를 송신하는 허가를 받지 않은 경우
MessageConnection.receive()
: 애플리케이션이 지정된 포트로 메시지를 수신하는 허가를 받지 않은 경우 MIDP 2.0 허가에 관한 자세한 내용은 WMA 기술 페이지(영문)에 게시된 MIDP 2.0 스펙 및 WMA 권고 사항(Recommended Practices)을 참조하십시오.
J2ME Wireless Toolkit 기술 페이지(영문)에는 여러분의 프로그램을 테스트하는 데 사용할 수 있는 Wireless Messaging API 2.0의 참조 구현이 게시되어 있습니다.
이번 기사에서는 핸드세트로부터 ‘모바일 발신(mobile-originated)’ 메시지를 송신하고 핸드세트 또는 서버로부터 송신된 ‘모바일 착신(mobile-terminated)’ WMA 메시지를 수신하는 방법에 관해 알아보았습니다. 서버로부터 핸드세트로 메시지를 송신하려면 네트워크 제공자의 SMSC와 MMSC 서버에 액세스해야 합니다. 이 서버들에 대한 액세스는 일종의 특권으로서, 일반적으로 제3자 메시징 벤더를 이용해야 합니다. 이 벤더들은 이미 AT&T (Cingular Blue), Boost Mobile, Cingular (Orange), Nextel, T-Mobile, Sprint, Verizon 등을 비롯한 메이저 통신 사업자 및 Alltel, Western Wireless, Leap, Cincinnati Bell, Dobson 등을 비롯한 2군(second-tier) 통신 사업자와 관계를 유지하고 있습니다. 한편, 이 벤더들은 HTTP 등의 프로토콜을 이용하는 exposed API를 통해 통신 사업자의 SMSC 및 MMSC에 대한 액세스를 제공함으로써 여러분의 서버 애플리케이션이 주어진 핸드세트에 메시지를 송신, 그리고 핸드세트로부터 메시지를 수신하고, 숏코드(short-code) 같은 프리미엄 서비스를 이용할 수 있도록 해줍니다.
아울러, 핸드세트의 특정 애플리케이션에 메시지를 송신해야 하는 경우에는 반드시 메시지에 대한 해당 포트 또는 애플리케이션 ID를 설정해야 한다는 사실을 잊지 마십시오. 그렇지 않으면 메시지가 핸드세트의 기본값 뷰어로 전달됩니다. 나가는 메시지에서 포트 번호 또는 애플리케이션 ID를 설정하는 방법은 사용하는 메시징 서비스에 따라 다를 수 있습니다. 벤더가 API를 제공할 수도 있고 여러분이 바이너리 형태로 raw message를 만들어야 할 수도 있습니다. GSM SMS의 경우에는 TP-User-Data/User-Data-Header
필드에 포트 번호가 있고, MMS의 경우에는 MMS Content-type
헤더에 애플리케이션 ID가 있습니다.
Wireless Messaging API는 “프로토콜/장치 독립적인” 매우 유연한 메시징 API를 제공합니다. 참조 구현은 당장이라도 WMA 기반 애플리케이션을 작성하고 테스트할 수 있게 해주며, WMA의 유연한 디자인은 향후 용이한 확장을 보장해줄 뿐 아니라 이미 가장 흔히 사용되는 메시지 타입 - 텍스트, 바이너리, 멀티미디어 - 을 지원하고 있습니다. WMA는 개발자들이 단문 메시지의 push 동작 및 사용을 비롯하여 SMS, 이메일, 리치 멀티미디어 등의 메시징과 통합 가능한 깔끔한 애플리케이션을 작성할 수 있게 해줍니다.