Socket Server in Windows Phone 8

從Windows Phone 8 開始,Socket 的支援擴大到允許開發者撰寫Socket Server,也就是說,在不透過中介服務的情況下,開發者可以撰寫一個Windows Phone Server Application來允許其它裝置連入,

這在開發對戰連線遊戲時是個非常有用的機制。

 

文/黃忠成

 

 

Socket Support in Windows Phone 8

 

  從Windows Phone 7.1開始便支援了Socket Programming,開發者可以直接使用Socket來開發需要快速通訊的網路應用程式,不過那時Socket僅支援到Client-Side,簡單的說就是你無法在Windows Phone上建立一個Socket Server

來接入其它的Windows Phone App連線,必須透過一個由其它平台所提供的中介服務(像是Windows Client、網站等等),這大大的限制了Windows Phone Apps Socket的應用範圍。

  從Windows Phone 8 開始,Socket 的支援擴大到允許開發者撰寫Socket Server,也就是說,在不透過中介服務的情況下,開發者可以撰寫一個Windows Phone Server Application來允許其它裝置連入,

這在開發對戰連線遊戲時是個非常有用的機制。

 

A Simple Socket Listener

 

  用法很簡單,你只需要利用StreamSocketListener來建立一個Server Socket,就可以接入來自其它裝置的連線,如以下程式。


using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Navigation;

using Microsoft.Phone.Controls;

using Microsoft.Phone.Shell;

using SocketServer.Resources;

using Windows.Networking.Sockets;

using Windows.Storage.Streams;

using Windows.Networking;

using Windows.Networking.Connectivity;

using System.Text;


namespace SocketServer

{

    public class ListenInformation

    {

        public HostName Host { get; set; }

        public string DisplayName { get; set; }

    }


    public partial class MainPage : PhoneApplicationPage

    {

        private StreamSocketListener _listener = new StreamSocketListener();

        private List _ips = new List();

        // Constructor

        public MainPage()

        {

            InitializeComponent();

            var hostNames = NetworkInformation.GetHostNames();

            foreach (HostName hn in hostNames)

            {

                ListenInformation info = new ListenInformation() { Host = hn };

                if (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71) //Wi-Fi

                    info.DisplayName = hn.CanonicalName + "(Wi-Fi)";

                else if (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 244) //cellular

                    info.DisplayName = hn.CanonicalName + "(cellular)";

                else

                    info.DisplayName = hn.CanonicalName;

                _ips.Add(info);

            }

            lstIPs.DisplayMemberPath = "DisplayName";

            lstIPs.ItemsSource = _ips;

        }


        async private void WaitForData(StreamSocket socket)

        {

            var dr = new DataReader(socket.InputStream);

            var dataReaded = await dr.LoadAsync(sizeof(uint));


            if (dataReaded == 0)

                return;


            uint strLen = dr.ReadUInt32();

            await dr.LoadAsync(strLen);

            string msg = dr.ReadString(strLen);

            if (msg.ToLower().Equals("bye"))

            {

                socket.Dispose();

                return;

            }

            Dispatcher.BeginInvoke(() =>

                {

                    lstOutput.Items.Add("recv:" + msg);

                });

            WaitForData(socket);

        }




        void listener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)

        {           

           WaitForData(args.Socket);

        }


        private async void Button_Click(object sender, RoutedEventArgs e)

        {

            if (lstIPs.SelectedItem != null)

            {

                ((Button)sender).IsEnabled = false;

                _listener.ConnectionReceived += listener_ConnectionReceived;

                await _listener.BindEndpointAsync(((ListenInformation)lstIPs.SelectedItem).Host, "1067");


                appTitle.Text = "listen on " + _listener.Information.LocalPort;

            }

            else

                MessageBox.Show("please select a ip to listen.");

        }

    }

}

 

讓我稍微解釋一下這個程式幾個關鍵片段。


public MainPage()

        {

            InitializeComponent();

            var hostNames = NetworkInformation.GetHostNames();

            foreach (HostName hn in hostNames)

            {

                ListenInformation info = new ListenInformation() { Host = hn };

                if (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71) //Wi-Fi

                    info.DisplayName = hn.CanonicalName + "(Wi-Fi)";

                else if (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 244) //cellular

                    info.DisplayName = hn.CanonicalName + "(cellular)";

                else

                    info.DisplayName = hn.CanonicalName;

                _ips.Add(info);

            }

            lstIPs.DisplayMemberPath = "DisplayName";

            lstIPs.ItemsSource = _ips;

        }

 

理論上,每個Windows Phone 8裝置應該都會有一個以上的網路位址,除非該裝置沒有數據連線及Wi-Fi,因此要起始一個Socket Server,得先決定要繫結至哪個網路位址,這裡透過GetHostNames()來取得所有可用的網路位址,

然後透過IanaInterfaceType來標示該網路位址的型態,一般來說, 71是Wi-Fi、244是數據連線。


private async void Button_Click(object sender, RoutedEventArgs e)

        {

            if (lstIPs.SelectedItem != null)

            {

                ((Button)sender).IsEnabled = false;

                _listener.ConnectionReceived += listener_ConnectionReceived;

                await _listener.BindEndpointAsync(((ListenInformation)lstIPs.SelectedItem).Host, "1067");


                appTitle.Text = "listen on " + _listener.Information.LocalPort;

            }

            else

                MessageBox.Show("please select a ip to listen.");

        }

 

當使用者選擇了要繫結的網路位址後,這裡透過BindEndpointAsync來啟用Socket Server,第一個參數是HostName,第二個則是要聆聽的Port資訊。

當連線接入時,ConnectionReceived事件會觸發,在此處透過DataReader來讀入由Client端傳入的資料。

 


async private void WaitForData(StreamSocket socket)

        {

            var dr = new DataReader(socket.InputStream);

            var dataReaded = await dr.LoadAsync(sizeof(uint));


            if (dataReaded == 0)

                return;


            uint strLen = dr.ReadUInt32();

            await dr.LoadAsync(strLen);

            string msg = dr.ReadString(strLen);

            if (msg.ToLower().Equals("bye"))

            {

                socket.Dispose();

                return;

            }

            Dispatcher.BeginInvoke(() =>

                {

                    lstOutput.Items.Add("recv:" + msg);

                });

            WaitForData(socket);

        }




        void listener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)

        {           

           WaitForData(args.Socket);

        }

 

 

Client: Windows Phone 8

 

  完成Server端後,Client端相對簡單多了。


using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Navigation;

using Microsoft.Phone.Controls;

using Microsoft.Phone.Shell;

using ClientSocketDemo.Resources;

using Windows.Networking.Sockets;

using Windows.Networking;

using Windows.Storage.Streams;


namespace ClientSocketDemo

{

    public partial class MainPage : PhoneApplicationPage

    {

        private StreamSocket _socket = null;


        // Constructor

        public MainPage()

        {

            InitializeComponent();


            // Sample code to localize the ApplicationBar

            //BuildLocalizedApplicationBar();

        }


        private async void Button_Click(object sender, RoutedEventArgs e)

        {

            _socket = new StreamSocket();

            await _socket.ConnectAsync(new HostName(txtIP.Text), "1067");

            ((Button)sender).IsEnabled = false;

            txtIP.IsEnabled = false;

            btnSend.IsEnabled = true;

            txtSendData.IsEnabled = true;           

        }


        private async void btnSend_Click(object sender, RoutedEventArgs e)

        {

            DataWriter dr = new DataWriter(_socket.OutputStream);

            string data = txtSendData.Text;

            dr.WriteUInt32((uint)data.Length);

            await dr.StoreAsync();

            dr.WriteString(data);

            await dr.StoreAsync();

            dr.DetachStream();

        }

    }

}

 

透過ConnectAsync可以連接至Socket Server,第一個參數是HostName(IP),第二個是Port。

圖1

 

Client: Windows 8 App

 

  同理,你也可以撰寫Windows Store App的客戶端來連入Windows Phone。


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;


namespace WinSocketClient
{
  
    public sealed partial class MainPage : Page
    {
        private StreamSocket _socket = null;

        public MainPage()
        {
            this.InitializeComponent();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            _socket = new StreamSocket();
            await _socket.ConnectAsync(new HostName(txtIP.Text), "1067");
            ((Button)sender).IsEnabled = false;
            txtIP.IsEnabled = false;
            btnSend.IsEnabled = true;
            txtSendData.IsEnabled = true;
        }

        private async void btnSend_Click(object sender, RoutedEventArgs e)
        {
            DataWriter dr = new DataWriter(_socket.OutputStream);
            string data = txtSendData.Text;
            dr.WriteUInt32((uint)data.Length);
            await dr.StoreAsync();
            dr.WriteString(data);
            await dr.StoreAsync();
            dr.DetachStream();
        }

    }
}


 

圖2