How to Transfer Files between a Windows Store App and any Silverlight Windows Phone App
First things first: how it works
If we ever want to have our apps both on the Windows Phone and the Windows 8 stores, we might want to use some kind of syncing mechanism between the two. Since neither can access the other’s isolated data directly, some sort of file transfer between the two apps will be necessary. There are, of course, several possibilities here, and the most popular one is using an intermediate web server, located “somewhere”. However, this has a few disadvantages; if the user’s wide world internet access is restricted due to various circumstances, your apps might not interact properly any more. While we like to think that the wide world web is generally available for everyone at any given moment, this is not always true. The user might not like paying for that data transfer and you have to host some web site which is unlikely to be free.
However, users usually have an internet connection of their own, called intranet or ad-hoc internet, most of the time, without even realizing it. For instance, if a user has a router or an access point with several devices connected (wirelessly or not), the devices in that network form some sort of “internet”, and the protocols associated with internet will work regardless of the wide world web accessibility.
Instead of using an intermediate server, we can use direct communication between the devices on the same network, through the TCP protocol.
The TCP protocol is the building foundation of the internet. Pretty much any application which accesses the internet uses TCP to get some data from distant servers (most of the time, the request is routed through several servers but that is a story for another day). The TCP protocol consists of a server (listener) and a client, the two entities exchanging data through the various stages of the request. The request must always be initiated by the client by opening a connection, using the server IP address and a specified port. The server cannot simply connect to the client. The main advantage of the TCP protocol is that all packages of data sent by either of the entities will always be received by the other. If a package is lost along the way, the sender will keep sending said package until the receiver acknowledges complete delivery.
This protocol basically behaves like a stream of data, hence all TCP sockets are also known as “stream sockets”. A socket is basically the low level object used in connections between devices. If the data queued for transmission is too big to fit in a single package, the LAN driver will split said data into packages, and send packages one after another, until all data is successfully sent, the entire process is abstracted away from the inquisitive eyes of the developer.
TCP sockets can be opened through a wide range of connections: RFCOMM Bluetooth, devices that share the same access point (one device can be connected wirelessly and the other through Ethernet and it would still work), even when the phone is connected through USB to the tablet/computer and it would still work. Of course, you can use this to connect to any PC on the planet (and probably outside of the planet?), if you know the port name and IP address.
For the purpose of this article, the computer will act as server and phone as client. Computer refers to anything that runs windows runtime apps, including ARM tablets, x86 tablets and the good old PC running windows 8. Until the release of windows phone 8, the phones could only be client. Now the phones can be server as well. The funny part is that the line between server and client is not really distinguishable. Either device can be server or client, but trying to do both at the same time may prove unstable.
So let’s get ourselves familiar with the classes we will work with, by consulting the MSDN documentation. Stream Socket, Stream Socket Listener.
Notice the documentation even covers a step by step guide on how to do things properly.
Ok, now that we got bored enough with the documentation, it’s time to get to work. We shall start with the listener. Create a windows 8 app, and create the UI as you see fit.
It is advisable to create a new class to store the whole thing, so it is easier to access latter on.
The file we want to send contains some random misc data about the app’s state. Imagine we want the phone app to continue whatever work the computer app was doing. So we store reconstruction data in said file.
We will hide the file in the Roaming folder
string fileName = "MyFileName.txt";
you create the file using this line of code
ApplicationData.Current.RoamingFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
And you get it using this line of code
var myfile =await ApplicationData.Current.RoamingFolder.GetFileAsync(fileName);
You can write things in the file using the methods in the FileIO class. Make sure you actually write something in the file so the size is bigger than 0. You can wrap the code you use to write things in that file in a for or while loop, so that you can artificially inflate the file size, if you want to observe the behavior of stream sockets with files that do not fit in one package.
Now that you’ve prepped the file, it is time to set up the listener. Since we can’t use async calls in the class constructor, we must create a new task for this purpose, and call it after using the class constructor.
The variables at work here…
public StreamSocketListener ServerListener = new StreamSocketListener();
StreamSocket socket;
public async Task InitStuff()
{
var f = ServerListener.Control;
f.QualityOfService = SocketQualityOfService.LowLatency;
ServerListener.ConnectionReceived += ServerListener_ConnectionReceived;
await ServerListener.BindEndpointAsync(null, "13001");
}
The variable ‘f” is used to set the QualityOfService property for the stream socket listener. We set it to low latency to make sure the transfer occurs as fast as possible. We must also make sure the packages are processed fast enough, to avoid a time out (this will actually be our job too).
After we set the event handler for received connections, we simply start listening.
await ServerListener.BindEndpointAsync(null, "13001");
The line above does just that. It defines the host name and port on which to listen for connection. Since we do not have a host name for our phone, we simply make it null to accept connections from any host.
Jumping to the connection received event handler
async void ServerListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
The field args contains the socket used in the request. We shall assign said socket to the stream socket we declared earlier.
socket = args.Socket;
The stream socket class exposes two streams. An input stream and an output stream. The input stream is the stream of incoming data sent by the remote socket. The output stream is the stream of outgoing data to the remote socket. Since this is a fairly simple request and serve communication, we don’t really care what the phone sends us. It will always demand the file in the same fashion, so we only play with the output stream.
var outputstream = socket.OutputStream;
DataWriter writer = new DataWriter(outputstream);
We will use the writer object to write data to the output stream.
Now, we open our file, read its contents, and then use the data writer to write those bytes to the output stream.
1.var myfile =await ApplicationData.Current.RoamingFolder.GetFileAsync(fileName);
2.
3.var streamdata = await myfile.OpenStreamForReadAsync();
The first stage of the transfer involves sending the length of the file in bytes
if(stage==0)
stage++;
writer.WriteBytes(UTF8Encoding.UTF8.GetBytes((await streamdata.Length.ToString()))
await writer.StoreAsync();
The second stage code is as follows. You should use a variable to count the stage index. If you want, you can also read what the client is sending you. Check out the stream socket sample for windows 8.1 which gives a pretty detailed example on how to read input data. How you figure out the stage is really not important.
if(stage==1)
byte[] bb = new byte[streamdata.Length];
streamdata.Read(bb, 0, (int)streamdata.Length);
writer.WriteBytes(bb);
await writer.StoreAsync();
stage=0;
Notice the call to StoreAsync() method. This call flushes the data writer’s buffers and pours all the data in the output stream. And this is pretty much all we have to do. The system takes care of the rest. Another thing to note is how we send the data. We first write the length of the file, then we write the file itself. Keep this in mind, we will use this latter on.
Now, this code presents itself with one big problem: the entire thing is not really type safe. We send raw bytes to the client, and the client also sends us raw bytes. For the purpose of transferring a single file from one device for another, we don’t have to worry about type safety that much. But be warned: if you want some more complex, like a message based communication, you will need to take into account the fact that what you get are only bytes, and you need to create the parsing system yourself.
Now, it’s time for the client code.
This time we will use the Silverlight runtime. This code will work on both windows phone 7 and windows phone 8.
The Socket class is contained in the System.Net.Sockets namespace.
Create a new class, add the following fields to it
MemoryStream ArrayOfDataTransfered;
string _serverName = string.Empty;
private int _port = 13001;
long FileLength = 0;
int PositionInStream = 0;
int Stage = 0;
The user will need to input the server name and port numbers. Because the connection is established in a home or private network, the actual IP address of the server can change. Using the name of the computer directly solves this problem. It would be a good idea to have some sort of settings page where the user can set up this connection. The following lines of code should be located in some initialization method, like the class constructor. The flow of the process looks like this: connect > send > receive. If you want to transfer more than 1 file, you will restart the entire process.
if (String.IsNullOrWhiteSpace(serverName))
{
throw new ArgumentNullException("serverName");
}
if (portNumber < 0 || portNumber > 65535)
{
throw new ArgumentNullException("portNumber");
}
_serverName = serverName;
_port = portNumber;
public void SendData(string data);
This method just sends data to the remote server. This is the breakdown of the method:
First, we will need this object, we will use it later on. The SocketAsyncEventArgs represents a socket operation. Operations can be either send, receive or connect.
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
Then, we will need the server end point, construct the object using the port number and server name.
DnsEndPoint hostEntry = new DnsEndPoint(_serverName, _port);
Next step is creating the socket and setting various properties then connecting to the remote server
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
socketEventArg.RemoteEndPoint = hostEntry;
socketEventArg.UserToken = sock;
and then we connect
sock.ConnectAsync(socketEventArg);
The event handler for the SocketEventArgs looks like this
void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
throw new Exception("Invalid operation completed");
}
}
When the operation is a connection request, we will simply use the SendData method defined above to send data to the server, something along the lines of
byte[] buffer = Encoding.UTF8.GetBytes(dataIn);
e.SetBuffer(buffer, 0, buffer.Length);
Socket sock = e.UserToken as Socket;
sock.SendAsync(e);
Where dataIn is a random string we want to send to the server. This is useful if you ever want to request more than a single file from the server, or if the transfer is done in more than one stage. The SendAsync method sends raw bytes of data to the remote connected socket.
The ProcessSend method looks like this
if (e.SocketError == SocketError.Success)
{
//Read data sent from the server
Socket sock = e.UserToken as Socket;
sock.ReceiveAsync(e);
}
else
{
ResponseReceivedEventArgs args = new ResponseReceivedEventArgs();
args.response = e.SocketError.ToString();
args.isError = true;
OnResponseReceived(args);
}
The only interesting thing here is the call to RecieveAsync(e). This method is basically the core of the file transfer.
We will want to get the data in two stages. The first stage sends us the size of the file. The chances of not getting the file size in one piece is slim to non-existent (just convert the biggest possible 64 bit number in megabytes and see for yourself) . The second one sends us the file itself.
var dataFromServer = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
Stage++;
FileLength = long.Parse(dataFromServer);
The second stage is where all the interesting things happen. Basically, this is the most important part of the process.
First, we will read the bytes in the buffer.
ArrayOfDataTransfered.Write(e.Buffer, 0, e.BytesTransferred);
The PositionInStream field simply counts how many bytes we got so far. If its value is lower than the FileLength, it means the file is not yet here. Otherwise it means the file transfer is done.
if (PositionInStream < FileLength)
{
Socket socks = e.UserToken as Socket;
socks.ReceiveAsync(e);
}
else
{
Stage = 0;
//save the file
}
If the file is not yet here, it means it was too big to fit in one package. Which means more packages are on their way and we simply have to read from the input buffer of the network adapter, by calling the RecieveAsync(e) method again.
Now all you have to do is copy the memory stream to an isolated storage file stream, and the transfer will be complete. Since we are using a memory stream, you should probably prevent the users from sending files bigger than 100 MB, otherwise it might trigger an Out of Memory Exception. In that case, you can write the data directly to an isolated storage file stream if you so desire. You also need to take into account the security of the socket connection, if you send sensitive data.
PS: You also need to add capabilities to your apps : ID_CAP_NETWORKING for windows phone, and Private Networks for windows store apps. The code works only on windows phone silverlight apps.
See Also Another important place to find a huge amount of Windows Phone related articles is the TechNet Wiki itself. The best entry point is
Windows Phone Resources on the TechNet Wiki.