miércoles, 29 de septiembre de 2010

TC65 NATted pseudoserver using plain sockets (Push Server)

Most GPRS providers in Mexico doesn't provide a public ip and they opt to give a NATted network topology and assing a private address to each GPRS SIM. This makes impossible to create a server in a TC65 terminal using for example this example provided by blogdeelectronica.

www.blogelectronica.com/dyndns-cinterion-siemens-modems-gpr/

To endure this ugly behavior a logical solution could be write a polling client application in the TC65 terminal that starts queryng for data a remote server every X seconds. This solution can be functional for some purposes but we found mainly three big problems that prevent us of using this.

1. You will be sending data even if there's not work to process. (In GPRS this means $)
2. If there's some data to process you will not discover it until the threshold is consumed. If this threshold is to big the application will not look real time and in the opposite case you will be consuming and consuming bandwith every second processing no bussiness logic. (Our app must be close to real time)
3. The complexity of the aplication that will be queueing the data to process will increase. (We wanted to mantain the main structure of the desktop application that already exist)

Our solution was creating a "pseudo-server" in the TC65 following a push communication. What does it means? This means YES!, the host that must start the comunication is the TC65 (we can't avoid this) but after the connection is stablished the remote host (desktop app in our case) will register this "pseudo-server" and will start "pushing" data into it. The secret here is DONT CLOSE THE SOCKET and if the socket is closed by any reason quickly restablish the communication channel and register again the pseudo server.

Before giving some code about how we implemented this there are some consideration that we must exhibit:

1. You can achieve this behavior using one socket but to have a better control we decided to create two objects, one to receive data (the pseudo-server) and one to send. This means that we have a sender TCP port and a receiver TCP port.
2. The TC65 dont allow to modify some socket parameters as linger, keepalive, etc., this causes some headaches because after some time if a socket haven't received or sended some data this will be closed (We discover that this magic time is 30 seconds). To avoid this we send from the remote server (the desktop app) every 27 seconds a keepalive signal. This keepalive is achived writing a single byte into de "pseudo-server".
3. The TC65 can't handle too many sockets at a time and don't allow to close and open quickly the sockets, if you start opening and closing sockets to quickly you will get some nasty exceptions, to avoid this we reuse the sockets and try to stablish the connection again after an unexpected failure without closing it.
4. To register the new pseudo server in the remote host we use the IMEI. This is a unique number that represents a single SIM that can be obtained using ATCommands.

First the Sender, nothing new, just as many examples that can be found all over internet:


public final class Sender{

private OutputStream outputStream = null;
private SocketConnection clientSender;

public void send(WorkRequest workRequest) {
connect();
outputStream.write(sicomCommand);
}
}


private void connect() throws CommunicationChannelException {
try {
clientSender = (SocketConnection) Connector.open(createConnectionString(), Connector.READ_WRITE, false)
outputStream = clientSender.openDataOutputStream();
resourcesClosed = false;
} catch (IOException iex) {
closeResources();
}
}
}

private void closeResources() {
try {
if (outputStream != null) {
outputStream.close();
}
if (clientSender != null) {
clientSender.close();
}
resourcesClosed = true;
} catch (IOException ex) {
//Do nothing
}
}
}


Now the Receiver or "pseudo-sever". Notice that this is a Thread and can be observed by other objects!


public abstract class Receiver extends Observable implements Runnable {

private static final int SLEEP_UNTIL_RECEIVE_TIME = 100;
protected volatile boolean stopReceiver = false;

/**
* Runs the Receiver Thread.
*/
public void run() {
startConnection();
while (!stopReceiver) {
receive();
try {
Thread.sleep(SLEEP_UNTIL_RECEIVE_TIME);
} catch (InterruptedException ex) {
}
}
postReceive();
}

private void startConnection() throws CommunicationChannelException {
try {
serverListener = (SocketConnection) Connector.open(createConnectionString(), Connector.READ_WRITE, false);
outputStream = serverListener.openDataOutputStream();
//Send the id of the GRPS to the server
outputStream.writeUTF(getIMEI());
} catch (IOException ex) {}
}

private void reconnect(boolean timeOutFlag) {
try {
startConnection();
} catch (CommunicationChannelException ex) {
try {
Thread.sleep(RECONNECT_SLEEP_TIME);
} catch (InterruptedException exp) {
//DO nothing
}
}
}

protected void receive() {
try {
inputSocketStream = serverListener.openDataInputStream();
int bytesReaded;
byte[] tcpBuffer = new byte[TCP_BUFFER_SIZE];
//The next instruction read() blocks eternally until it finds data to read in the common java API
//sadly the Siemens module that we are using send an idle exception wvery 30 seconds if it could not
//read anything. So if the pseudo-server does not receive anything in 30 seconds it will need to register
//again in the real server.
while ((bytesReaded = inputSocketStream.read(tcpBuffer)) != -1) {
notifyReceivedData(tcpBuffer);
}
} catch (Exception ex) {
} finally {
closeResources();
}
if (!stopReceiver) {
//Try to reconnect after an inaccesible server or a time out
reconnect(timeOutFlag);
timeOutFlag = false;
}

}

private void closeResources() {
try {
if (null != inputSocketStream) {
inputSocketStream.close();
}
if (null != outputStream) {
outputStream.close();
}
if (null != serverListener) {
serverListener.close();
}
} catch (IOException ex) {
//Do nothing
}
}

public void sendStopSignal() {
this.stopReceiver = true;
}

}


In next weeks we will comunicating our TC65 terminals to a webserver using webservices. In this case we dont need the "real time" behavior so we are considering using polling, anyway we dont discard the posibility of implementing a fancier solution, in that case we will be writing our results.

No hay comentarios:

Publicar un comentario