站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2ME 专题在MIDP应用中如何存储和发送信息(英文)

在MIDP应用中如何存储和发送信息(英文)

摘要: 在MIDP应用中如何存储和发送信息(英文)
<tr><td>
http:///tech/article648.html
Simple Store-and-Forward Messaging for MIDP Applications
by Eric Giguere
March 25, 2002

Applications written for the Mobile Information Device Profile (MIDP) are rarely stand-alone applications. Normally, there's some part of the application that involves calling server-side code for processing. In MIDP 1.0, the only way to portably invoke server-side code is by making HTTP requests to a servlet running in an external Web server. On a wireless device, this can be quite slow, and so the communication is best done on a background thread. In fact, it might make more sense to move to a truly asynchronous communication model, where your application interacts with other devices by sending and receiving messages. This Tech Tip shows you how to implement this kind of simple store-and-forward messaging system using MIDP's persistent record stores. To run the code in this tip, you need Tomcat or another servlet-enabled Web server. You can download Tomcat from the Jakarta Project.
Let's first define the term "message". A message is simply a class that holds some data, as in this example:
[]import java.io.*;
public class SimpleMessage implements Persistent {;
private String dest;
private Object data;
[] public SimpleMessage(){;
};
[] public SimpleMessage( String dest, String data ){;
setDestination( dest );
setData( data );
};
[] public SimpleMessage( String dest, byte[] data ){;
setDestination( dest );
setData( data );
};
[] public String getDestination(){;
return dest != null ? dest : "";
};
[] public Object getData(){; return data; };
public void setDestination( String dest ){;
this.dest = dest;
};
public void setData( String data ){;
this.data = data;
};
public void setData( byte[] data ){;
this.data = data;
};
private static final int IS_NULL = 0;
private static final int IS_STRING = 1;
private static final int IS_BINARY = 2;
public byte[] persist() throws IOException {;
[] ByteArrayOutputStream bout =
new ByteArrayOutputStream();
[] DataOutputStream dout =
new DataOutputStream( bout );
dout.writeUTF( getDestination() );
Object obj = getData();
if( obj instanceof String ){;
dout.writeInt( IS_STRING );
dout.writeUTF( (String) obj );
[] }; else if( obj instanceof byte[] ){;
dout.writeInt( IS_BINARY );
byte[] arr = (byte []) obj;
dout.writeInt( arr.length );
[] dout.write( arr );
}; else {;
dout.writeInt( IS_NULL );
};
dout.flush();
return bout.toByteArray();
};
public void resurrect( byte[] indata )
throws IOException {;
ByteArrayInputStream bin =
new ByteArrayInputStream( indata );
DataInputStream din =
new DataInputStream( bin );
dest = din.readUTF();
int type = din.readInt();
if( type == IS_STRING ){;
data = din.readUTF();
}; else if( type == IS_BINARY ){;
int len = din.readInt();
byte[] arr = new byte[ len ];
if( len > 0 ){;
din.readFully( arr );
};
data = arr;
}; else {;
data = null;
};
};
public String toString(){;
[] StringBuffer buf = new StringBuffer();
buf.append( "{;destination=\"" );
if( dest != null ) buf.append( dest );
buf.append( "\",data=" );
if( data instanceof byte[] ){;
buf.append( "byte array of length " +
((byte[]) data).length );
}; else if( data instanceof String ){;
buf.append( '"' );
buf.append( (String) data );
buf.append( '"' );
}; else {;
buf.append( "null" );
};
buf.append( '};' );
return buf.toString();
};
};
The SimpleMessage class holds two things: a destination name (a string) and some data, which is either a string or a byte array. The format of the destination name is arbitrary. It could, for example, be the name of a Java Message Service (JMS) queue. Or it could be an email address. Later on in this tip you'll see that a servlet is responsible for interpreting the destination name.
Notice that the class implements the Persistent interface. This interface was defined in a previous Tech Tip, Object Serialization in CLDC-Based Profiles. You need this capability to serialize the message for storage and transport.
The first step in building a simple store-and-forward messaging system is to build the message hub. The hub is the class that the application uses to send and receive messages. The hub uses two record stores, one to track incoming messages, and the other to track outgoing messages. The messages themselves are sent and received by a background thread. Using the hub is fairly simple. The constructor takes two parameters: the URL of a servlet and a timeout value (in milliseconds) for polling:
String url =
[] "http://localhost:8080/MessagingServlet";
int timeout = 60000; // every minute
MessageHub hub = new MessageHub( url, timeout );
You then start the hub's background thread:
hub.start();
Now the application can send and receive messages. It sends messages using the send method:
SimpleMessage msg = new SimpleMessage( "eric",
"Hello!" );
hub.send( msg );
This send method does not block, it serializes the message and adds it to a record store. The hub's background thread is then responsible for delivering it.
[]Receiving a message, however, is a blocking operation:
int timeout = 5000; // wait at most 5 seconds
SimpleMessage msg = hub.receive( timeout );
[]If the hub receives a message, it returns it immediately. Otherwise the calling thread is blocked until a message arrives or the given timeout expires.
To suspend message processing, you can call the hub's stop method:
hub.stop();
Finally, when the application is about to terminate, it should destroy to hub in order to free the hub's resources:
[] hub.destroy();
Let's look at the code for the message hub:
[]import java.io.*;
import javax.microedition.io.*;
[]import javax.microedition.rms.*;
/**
* Defines a class that can send and receive
* messages asynchronously. Messages are stored
* in record stores and processed by a background
* thread, which posts them via HTTP to a servlet
* running on an external web server.
[] */
public class MessageHub implements Runnable {;
// Constructor creates two record stores: one for
// outgoing messages and one for incoming messages.
[] // Pass in the URL to the servlet and a timeout
// value in milliseconds for how often to poll the
// server if no messages are being sent by the
// client.
public MessageHub( String url, int pullTimeout )
throws RecordStoreException {;
inrs = RecordStore.openRecordStore( "mhubin",
true );
outrs = RecordStore.openRecordStore( "mhubout",
[] true );
this.url = url;
this.pullTimeout = pullTimeout;
};
// Convenience method.
private HttpConnection closeConnection(
HttpConnection conn ){;
[] try {;
[] if( conn != null ){;
conn.close();
};
};
catch( IOException e ){;
};
return null;
};
// Client calls this to receive a message, passing
// in an appropriate timeout value in milliseconds.
// If the timeout expires, null is returned.
[] // Otherwise the message is returned.
public SimpleMessage receive( int timeout )
throws IOException, RecordStoreException {;
SimpleMessage msg = null;
RecordEnumeration enum = null;
synchronized( inrs ){;
[] try {;
if( inrs.getNumRecords() == 0 ){;
inrs.wait( timeout );
};
enum = inrs.enumerateRecords( null,
null, false );
[] if( enum.hasNextElement() ){;
int id = enum.nextRecordId();
byte[] rawdata =
inrs.getRecord( id );
SimpleMessage tmp =
new SimpleMessage();
tmp.resurrect( rawdata );
msg = tmp;
inrs.deleteRecord( id );
};
};
catch( InterruptedException e ){;
};
finally {;
if( enum != null ){;
[] enum.destroy();
[] };
};
};
return msg;
[] };
// Client calls this to send a message. The
// message is queued in the outgoing queue and
// the background thread is woken up if necessary.
public int send( SimpleMessage msg )
throws IOException, RecordStoreException {;
int id = 0;
synchronized( outrs ){;
byte[] rawdata = msg.persist();
id = outrs.addRecord(
rawdata, 0, rawdata.length );
outrs.notify();
};
return id;
};
// The background thread that reads messages
// from the outgoing queue, POSTs them to the
// server and places incoming messages into
// the incoming queue.
public void run(){;
HttpConnection conn = null;
boolean checkForMore = true;
[] while( thread == Thread.currentThread() ){;
byte[] record = null;
int recordID = 0;
// First stage: read a record from the
// outgoing store. If there is no record,
// wait for a specific time.

synchronized( outrs ){;
[] try {;
if( outrs.getNumRecords() == 0
&& !checkForMore ){;
outrs.wait( pullTimeout );
};
RecordEnumeration e =
outrs.enumerateRecords( null,
null, false );
[] while( e.hasNextElement() ){;
recordID = e.nextRecordId();
record =
outrs.getRecord( recordID );
[] if( record != null ){;
[] outrs.setRecord( recordID,
[] null, 0, 0 );
break;
}; else {;
recordID = 0;
record = null;
};
};
e.destroy();
};
catch( RecordStoreException e ){;
break;
};
catch( InterruptedException e ){;
};
};
// Second stage: POST the record, if any,
// to the web server. If successful,
// delete the record, otherwise restore it.
try {;
[] conn = (HttpConnection)
Connector.open( url );
conn.setRequestMethod( conn.POST );
conn.setRequestProperty(
"Content-Type",
"application/octet-stream" );
conn.setRequestProperty( "User-Agent",
"Profile/MIDP-1.0 Configuration/CLDC-1.0" );
conn.setRequestProperty(
"Content-Length",
[] Integer.toString( record != null ?
record.length : 0 ) );
OutputStream os =
[] conn.openOutputStream();
if( record != null ){;
os.write( record );
};
os.close();
int rc = conn.getResponseCode();
[] if( rc == HttpConnection.HTTP_OK ){;
if( recordID != 0 ){;
try {;
outrs.deleteRecord(
recordID );
};
catch(
[] RecordStoreException e ){;
};
};
};
[] };
catch( IOException e ){;
if( record != null ){;
try {;
outrs.setRecord( recordID,
record, 0, record.length );
};
catch( RecordStoreException re ){;
};
};
conn = closeConnection( conn );
[] };
recordID = 0;
record = null;
checkForMore = false;
// Third stage: if the POST was successful,
// read in an incoming record, if any.
if( conn != null ){;
DataInputStream is = null;
try {;
int len = (int) conn.getLength();
if( len > 0 ){;
record = new byte[ len ];
[] is = conn.openDataInputStream();
is.readFully( record );
checkForMore = true;
};
};
catch( IOException e ){;
record = null;
};
finally {;
if( is != null ){;
[] try {;
is.close();
};
catch( IOException e ){;
};
[] };
};
[] };
conn = closeConnection( conn );
// Fourth stage: write out the new message
// and notify a waiting thread.
if( record != null ){;
synchronized( inrs ){;
try {;
inrs.addRecord( record, 0,
[] record.length );
inrs.notify();
};
catch( RecordStoreException re ){;
};
};
};
};
// Some cleanup code
synchronized( this ){;
if( thread == Thread.currentThread() ){;
thread = null;
};
};
};
// Starts the background thread.
public synchronized void start() {;
if( thread == null ){;
[] thread = new Thread( this );
[] thread.start();
};
};
// Stops the background thread.
public synchronized void stop(){;
if( thread != null ){;
Thread tmp = thread;
thread = null;
[] try {;
tmp.join();
};
catch( InterruptedException e ){;
};
};
};
[] // Cleanup by waiting for the thread to
// stop and then closing the record stores.
public synchronized void destroy(){;
stop();
synchronized( outrs ){;
try {;
outrs.closeRecordStore();
};
catch( RecordStoreException e ){;
};
[] outrs.notifyAll();
[] };
synchronized( inrs ){;
try {;
inrs.closeRecordStore();
};
catch( RecordStoreException e ){;
[] };
inrs.notifyAll();
};
[] };
[] private RecordStore inrs;
private RecordStore outrs;
private Thread thread;
private String url;
private int pullTimeout;
};
[]As mentioned earlier, two MIDP record stores are associated with the hub. One holds the outgoing messages, and the other holds incoming messages. These provide the persistent storage required to build a reliable store-and-forward messaging system. The send method in the hub serializes a message and adds it to the outgoing record store. The receive method removes a message from the incoming record store and deserializes it.
The real work is done by the hub's background thread in the run method. Whenever a message is added to the outgoing record store, or a polling timeout expires, the background thread wakes up and sends a POST request to the servlet, sending a message (if available) as part of the request body. The servlet can respond with a simple status or with a message to deliver to the client. The background thread then adds the message to the incoming record store and wakes a waiting thread.
The servlet code itself is quite simple when compared to the hub:
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* A simple servlet that receives a message
* from a client and immediately sends one back.
*/
public class MessagingServlet extends HttpServlet {;
private static int counter = 0;
public void doPost( HttpServletRequest request,
HttpServletResponse response )
throws IOException, ServletException {;
SimpleMessage msg = null;
byte[] rawdata = null;
Object received = null;
// Read the incoming message, if any...
int len = request.getContentLength();
if( len > 0 ){;
[] ServletInputStream in =
request.getInputStream();
DataInputStream din =
new DataInputStream( in );
rawdata = new byte[ len ];
din.readFully( rawdata );
try {;
msg = new SimpleMessage();
msg.resurrect( rawdata );
received = msg.getData();
};
catch( IOException e ){;
};
din.close();
};
rawdata = null;
// Send a canned response for any
// incoming message
if( received != null ){;
++counter;

msg = new SimpleMessage();
msg.setDestination( "client" );
[] msg.setData( "Counter: " + counter +
" Date: " + new Date().toString() +
" Received: " + received );

try {;
rawdata = msg.persist();
};
catch( IOException e ){;
rawdata = null;
[] };
};
// Send it out!
[] response.setContentType(
"application/octect-stream" );
response.setContentLength(
rawdata != null ? rawdata.length : 0 );
response.setStatus( response.SC_OK );
if( rawdata != null ){;
OutputStream out =
response.getOutputStream();
out.write( rawdata );
out.close();
[] };
[] };
};
This servlet is just a test servlet. All it does is generate a response for each message it receives. Normally, you'd have the servlet convert the messages to and from JMS, or something more meaningful.
Finally, here is a very simple MIDlet to test the message hub's interaction with the test servlet:
import java.io.*;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.rms.*;
[]// A very simple MIDlet to test out the message hub.
[]// Doesn't create any UI, just prints messages out
// to the console.
public class MessagingTest extends MIDlet
implements CommandListener {;
private Display display;
private MessageHub hub;
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
[] public MessagingTest(){;
};
public void commandAction( Command c,
Displayable d ){;
if( c == exitCommand ){;
[] exitMIDlet();
[] };
};
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {;
exitMIDlet();
};
public void exitMIDlet(){;
if( hub != null ){;
hub.destroy();
};
notifyDestroyed();
};
public Display getDisplay(){; return display; };
protected void initMIDlet(){;
try {;
[] testHub();
};
catch( Exception e ){;
System.err.println( "Exception " + e );
};
[] exitMIDlet();
};
protected void pauseApp(){;
};
protected void startApp()
throws MIDletStateChangeException {;
if( display == null ){;
display = Display.getDisplay( this );
initMIDlet();
};
};
private void log( String str ){;
System.out.println( str );
};
private static final String TEST_URL =
"http://localhost:8080/MessagingServlet";
private static final int PULL_TIMEOUT = 5000;
// The code to test the hub. Sends ten messages
// and then reads the replies.
private void testHub() throws IOException,
RecordStoreException {;
log( "Creating hub to " + TEST_URL );
hub = new MessageHub(
TEST_URL, PULL_TIMEOUT );
log( "Starting hub..." );
hub.start();
log( "Sending ten messages to server..." );
for( int i = 1; i <= 10; ++i ){;
SimpleMessage msg =
new SimpleMessage( "serverUserID",
"Client message " + i );
log( "Sending " + msg + "..." );
hub.send( msg );
log( "...sent" );
};
log( "Receiving messages from server..." );
while( true ){;
log( "Waiting..." );
SimpleMessage msg = hub.receive(
PULL_TIMEOUT );
if( msg == null ){;
[] log( "Receive timed out, quitting" );
break;
};
[] log( "Received " + msg );
};
hub.stop();
hub.destroy();
};
};
This MIDlet is meant to be run from an emulator, not from a real device. That's because it doesn't define a user interface. Instead, it simply prints messages to the console.
There are many improvements you can make to this example. For example, you can batch multiple messages together into a single HTTP request/response cycle. Or you can add the ability to register listeners which are asynchronously notified when there are waiting messages. There are also commercial messaging solutions available which are probably worth exploring. As you can see, though, it doesn't really take that much code to build a basic messaging framework.

[]--------------------------------------------------------------------------------
About the Author: Eric Giguere is a software developer for iAnywhere Solutions, a subsidiary of Sybase, where he works on Java technologies for handheld and wireless computing. He holds BMath and MMath degrees in Computer Science from the University of Waterloo and has written extensively on computing topics.

http:///tech/article648.html
</td></tr></table></td> </tr> <tr> <td background="/pic/split.gif" height=1></td> </tr> <tr> <td class="tdMargin1">
↑返回目录
前一篇: 如何在midlet里面实现https(英文)
后一篇: 优化J2ME应用程序