15 July, 2010

Auto Update Agent on Windows CE (interprocess comm.)

I today needed to create an autoupdate solution for an application on a device (Win CE, Windows Mobile 6.5 Professionel). The application is to support auto update, to allow the device to always be running the latest bits. Now, this can seem a pretty trivial task, but again – you are quite limited when it comes to support on the CE-device.

The issue is not so much how to download the new files from a remote server. The issue is more how do I shutdown an application in a nice way to be allowed to update parts of this application and next restart the application (in the illustration: how does ProcessB kill ProcessA?). Of course you could enumerate the processlist and invoke KILL on a process, but that would be like “pulling away the carpet” under the feet of the application. Not the best solution. I want to be allowed to inform the application that it should shutdown in it’s own pace.

The options I could come up with were these:
1) WCF
2) Interprocess Communication (named pipe)
3) Interprocess (tcp-sockets)
4) ClickOnce

 image

1) WCF
Well – this was quite a short adventure. It is not supported on CE-devices to host a WCF-service that one could call to shut down the Process (A). So, so much for that.

2) Interprocess Comm. (named pipe)
Again a pretty short adventure. Named pipes are not supported in any CE-framework.

3) Interprocess Comm. (tcp-sockets)
Now we are getting somewhere. This is supported by CE and is a viable route for this purpose.

4) ClickOnce
Again – this would have been the best solution of them all. But, ClickOnce is not supported on a CE-device.

Solution:
So we are left with option (3): Socket communication. As a POC, I made a system with 2 applications (Client to shutdown a Server). The 2 applications are seen here.

ClientCE       serverce

Server (to be shutdown, called “Process A” in above illustration):
This application does nothing (in this POC), but listening for an incoming message from the Client. Once this message is received and it contains the word “KILL”; it will gracefully shutdown after having freed resources.

Note: In a real life scenario – you would most likely want the listening part to run on a background thread. In the POC, this is run on the UI-thread hence it is pretty non-responsive when it comes to the UI of the application (as this thread is now blocked by the TcpListener!)

Code:

private void btnStart_Click(object sender, EventArgs e)
{

var ip = IPAddress.Loopback;
var listener = new TcpListener(ip, 9090);

lblStatus.Text = listener.LocalEndpoint.ToString();
listener.Start();
var client = listener.AcceptTcpClient(); //block and wait...

//if here - received data
byte[] buffer = new byte[1024]; //should be enough for now.
try
{
var ns = client.GetStream();
int index = 0;
while (true)
{
int readData = ns.Read(buffer, index, 1); //read one at the time (CHANGE)
index += readData;

if (readData == 0)
break;
}
}
finally
{
client.Close();
}

//write out buffer
string rcv = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
tbxData.Text = rcv;

bool doKill = (rcv == "KILL");

lblStatus.Text = "Stopped";
buffer = null;

if (doKill)
{
CleanUpResources(); //clean up stuff before shutting down
Application.Exit();
}
}

Client (to shutdown server, called “ProcessB” in the above illustration):
This application can ask the Server to shut down. It basically just establishes a socket connection to the Server, and tells this to shutdown. The “Command” sent is only a simple string at this time; but it could be an object that was serialized by the Client and then de-serialized on the Server side. This could carry more meaningful information.


Code:

private void btnKill_Click(object sender, EventArgs e)
{
var ip = IPAddress.Loopback;
var ipEp = new IPEndPoint(ip,9090);
var cl = new TcpClient();
cl.Connect(ipEp);

byte[] data = Encoding.ASCII.GetBytes("KILL");

cl.GetStream().Write(data, 0, data.Length);
cl.GetStream().Flush();
cl.GetStream().Close();

//done
}

No comments:

InRiver: Not loading your extensions?

(You really need to in the loop to appreciate the issue this post addresses). Man, I've been fighting this problem for hours before I ...