Friday, 8 May 2015

Implementing Socket APIs in Store Application

Socket is an interface between application and network
Ø  The application creates a socket
Ø  The socket type dictates the style of communication
Ø  Once configured the application can pass data to the socket for network transmission receive data from the socket (transmitted through the network by some other host)

Purpose

Send and receive data with TCP sockets in your Windows Runtime app using features in the Windows.Networking.Sockets  namespace. Store application is a sandbox environment, So that we can implement restricted functionality only. Incase if you want to call your desktop application or access any file from custom location from store application, it’s not possible. So we can use windows service and implement your custom functionality and access those functionality through socket 

Sockets provide a low-level interface for sending and receiving network data. The primary classes for use with sockets include the following:
Ø  DatagramSocket - Used to support network communication using a UDP datagram socket.
Ø  StreamSocket - Used to support network communication using a TCP stream socket.
Ø  StreamSocketListener - Used to support listening for an incoming network connection using a TCP stream socket.
Implementing Socket Listener in Widows Service
            Define port number _portToListen = 39529;
This function initializes the listener socket. It binds the listener socket  to the port specified by PORT_TO_LISTEN constant defined above to enable the Windows Store apps to

Connect to. AcceptCallBack function will be called when any client issues connect request to the Service.

        private void InitializeListenerSocket()
        {
            // Creates one SocketPermission object for access restrictions
            _permission = new SocketPermission(NetworkAccess.Accept, TransportType.ConnectionOriented,
                                               "127.0.0.1", SocketPermission.AllPorts);
            // Ensures the code to have permission to access a Socket
            _permission.Demand();

            string localHost = Dns.GetHostName();
            //IPAddress[] ipAddrList = Dns.GetHostEntry(localHost).AddressList;
            IPAddress[] ipAddrList = Dns.GetHostEntry("localhost").AddressList;


            //Get the IPv4 address. For some reasons, IPv6 address doesn't work.
            //IPAddress localIp = ipAddrList.FirstOrDefault(t => t.IsIPv6LinkLocal == false);
            IPAddress localIp = ipAddrList.FirstOrDefault(t => t.AddressFamily == AddressFamily.InterNetwork);
            try
            {
                // Listening Socket object
                Socket socketListener = null;
                if (localIp != null)
                {
                    _ipEndPoint = new IPEndPoint(localIp, _portToListen);

                    // Create one Socket object to listen the incoming connection
                    if (_ipEndPoint != null)
                    {
                        socketListener = new Socket(localIp.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                        try
                        {
                            // Associates a Socket with a local endpoint
                            socketListener.Bind(_ipEndPoint);

                            socketListener.Listen(100);
                            // Begins an asynchronous operation to accept an attempt
                            if (AcceptRequestCallbackFuncPtr == null)
                                AcceptRequestCallbackFuncPtr = new AsyncCallback(AcceptClientRequestCallback);

                            socketListener.BeginAccept(AcceptRequestCallbackFuncPtr, socketListener);

                            UploadLog.Debug("~: DSP Feedback Service Started - Listening at: " +
                                                socketListener.LocalEndPoint);
                        }
                        catch (Exception exc)
                        {
                            UploadLog.Debug("~: StartAcceptingClientRequest: Exception" + exc.Message);
                        }

                    }
                }
                else
                {
                    UploadLog.Debug("~: IP Address is Invalid. Connect to the network and restart the machine");
                    Stop();
                }
            }
            catch (Exception ex)
            {
                UploadLog.Debug("~: Exception while IntializingListenerSocket" + ex.Message);
            }

        }

This function is the Accept event handler for Connection requests coming into the service from the Windows Store client.

        private void AcceptClientRequestCallback(IAsyncResult ar)
        {
            // A new Socket to handle the incoming client request.
            try
            {

                // Get Listening Socket object passed as a parameter in the BeginAccept method above.
                if (ar.AsyncState != null)
                {
                    Socket listener = (Socket)ar.AsyncState;
                    UploadLog.Debug("~: AcceptClientRequestCallback: Received a client request.");

                    // Create a new socket
                    Socket clientSocket = listener.EndAccept(ar);
                    UploadLog.Debug("~: EndAccept: Received a client request.");

                    WaitForData(clientSocket);

                    // Begins an asynchronous operation to accept an attempt. If the below code is not present,
                    //then the close and re-establishing the connection by the Windows store client is not possible.
                    listener.BeginAccept(AcceptRequestCallbackFuncPtr, listener);

                }
            }

            catch (Exception exc)
            {
                UploadLog.Debug("~: AcceptClientRequestCallback Exception: " + exc.Message);
            }
        }

        public void WaitForData(Socket clientSocket)
        {
            try
            {
                //log.Debug("~: Calling InsureRegISCurrent() to check for the latest AWS Cache file for Registration");
                //_awsWorker.InsureCachedRegistrationIsCurrent();

                if (ReceiveCallbackFuncPtr == null)
                    ReceiveCallbackFuncPtr = new AsyncCallback(ReceiveCallback);

                // Creates one object array for passing data
                byte[] buffer = new byte[70000];

                object[] obj = new object[2];
                obj[0] = buffer;
                obj[1] = clientSocket;

                UploadLog.Debug("~: Wait For Data Read");
                clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None,
                                ReceiveCallbackFuncPtr, obj);

            }
            catch (SocketException ex)
            {
                UploadLog.Debug("~: WaitForData Exception: " + ex.Message);

            }
            catch (Exception ex)
            {
                UploadLog.Debug("~: WaitForData Exception: " + ex.Message);
            }

        }

ReceiveCallback: This is a callback function that gets called when any incoming message is received.
This function receives the incoming file path, updates the Concurrent queue and sets an event.
 There is a separate thread that is waiting on this event. On triggering this event, the file is picked up

        public void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                // Fetch a user-defined object that contains information
                object[] obj;
                obj = (object[])ar.AsyncState;
                UploadLog.Debug("~: ReceiveCallback: calling Receiving callback");

                if (ar.AsyncState != null)
                {
                    UploadLog.Debug("~: ReceiveCallback: AsyncState != null");
                    // Received byte array
                    byte[] buffer = (byte[])obj[0];

                    // A Socket to handle communication with the client.
                    Socket clientSocket = (Socket)obj[1];

                    if (clientSocket != null)
                    {
                        UploadLog.Debug("~: ReceiveCallback: clientSocket != null");
                        // Received message
                        string content = string.Empty;
                        int bytesRead = 0;
                        //bytesRead = clientSocket.EndReceive(ar);
                        if (clientSocket.Connected)
                        {
                            // The number of bytes received.
                            bytesRead = clientSocket.EndReceive(ar);
                            UploadLog.Debug("~: ReceiveCallback: clientSocket.Connected");
                        }
                        if (bytesRead > 0)
                        {
                            UploadLog.Debug("~: ReceiveCallback: bytesRead > 0");
                            content += Encoding.ASCII.GetString(buffer, 0, bytesRead);

                            // If message contains "##EOS##", finish receiving
                            if (content.Length > 0)
                            {
                                // Convert byte array to string
                                string str = content.Substring(0, content.Length);
                                HsdFilledMessage response = new HsdFilledMessage();
                                UploadLog.Debug("~: " + String.Format("ReceiveCallback: New message received. Bytes read: {0}, File to Upload: {1}", bytesRead, str));

                                HsdFilledMessage Message = RestoreAsync<HsdFilledMessage>(str);                               
                                UploadLog.Debug("~: ReceiveCallback: Completed Datacontract serializer");
                                response.Message = "Sucess";
                                response.HSDStatus = HSDStatus.FAILED;
                                byte[] array = SaveAsync<HsdFilledMessage>(response);
                                UploadLog.Debug("~: Message to app ");
                                if (clientSocket.Connected)
                                {
                                    try
                                    {
                                        UploadLog.Debug("~: Socket: ClientSocket is connected");
                                        int res = clientSocket.Send(array);
                                        UploadLog.Debug("~: Send result: " + res);
                                    }
                                    catch (Exception ex)
                                    {
                                        UploadLog.Debug("~: Send method failed" + ex.Message.ToString());
                                    }

                                }

                                //Will receive another message for Registration file upload. so wait for data.                              
                                clientSocket.Dispose();
                            }
                        }
                    }
                }
                else
                {
                    UploadLog.Debug("~: ReceiveCallBack: IAsyncResult is received as null");
                }
            }
            catch (Exception ex)
            {
                UploadLog.Debug("~: ReceiveCallback Exception: " + ex.Message );
            }
        }

This is a Deserialize function, it will be convert and return the specified type object from string

public T RestoreAsync<T>(string xml)
        {
            using (Stream stream = new MemoryStream())
            {
                byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
                stream.Write(data, 0, data.Length);
                stream.Position = 0;
                DataContractSerializer deserializer = new DataContractSerializer(typeof(T));
                return (T)deserializer.ReadObject(stream);
            }
        }

This is a serialize function, it will be convert and return the byte from object

        public static byte[] SaveAsync<T>(object content)
        {
            try
            {
                byte[] array;
                using (MemoryStream memoryStream = new MemoryStream())
                using (StreamReader reader = new StreamReader(memoryStream))
                {
                    DataContractSerializer serializer;
                    serializer = new DataContractSerializer(typeof(T));
                    serializer.WriteObject(memoryStream, content);
                    memoryStream.Position = 0;
                    array = new byte[memoryStream.Length];

                    memoryStream.Read(array, 0, array.Length);
                }

                return array;
            }
            catch
            {
                throw;
            }

        }
Implementing and communicate Socket in Store Apps
Initialize port number and IP address for communicate server socket listener. First Connect socket service and write data in “OutputStream” for sending data from client to server. The Server socket listener will be Deserialize stream from request object and sent response stream to client service
private async void backButton_Click_1(object sender, RoutedEventArgs e)
        {
            try
            {
                StreamSocket clientSocket = new StreamSocket();

                string port = "39529";
                string host = "127.0.0.1";

                await clientSocket.ConnectAsync(new Windows.Networking.HostName(host), port, SocketProtectionLevel.PlainSocket);


                HsdFilledMessage message = new HsdFilledMessage();
                byte[] inputByte = SaveAsync<HsdFilledMessage>(message);
                DataWriter clientDataWriter = new DataWriter(clientSocket.OutputStream);
                clientDataWriter.WriteBytes(inputByte);
                await clientDataWriter.StoreAsync();
                await clientDataWriter.FlushAsync();

                string responseFromService = string.Empty;
                var readBuf = new Windows.Storage.Streams.Buffer(10240000);
                var readOp = await clientSocket.InputStream.ReadAsync(readBuf, 10240000, InputStreamOptions.Partial);
                DataReader readPacket = DataReader.FromBuffer(readBuf);
                uint buffLen = readPacket.UnconsumedBufferLength;
                responseFromService = readPacket.ReadString(buffLen);
                var response = RestoreAsync<HsdFilledMessage>(responseFromService);
            }
            catch (Exception ex)
            {
               
            }
        }

Download full source code here