Windows Phone開發(46):與Socket有個約會

來源:互聯網
上載者:User

不知道大家有沒有“談Socket色變”的經曆?就像我一位朋友所說的,Socket這傢伙啊,不得已而用之。哈,Socket真的那麼恐怖嗎?

其實這話一點也不假,Socket有時候真的不太好操控,也不好維護,但不管怎麼樣,我們還是要面對它的,沒準Socket是一位大美女哦。

關於Socket的前世今生就不用我詳述了,關於她的曆史,已經不少人仁志士為她立傳寫著了,像我們國內的百度百科、互動百科等;全球著名的如維基百科之屬。而且,能加入WP開發的學習行列的,我想各位對.NET的其它技術肯定是有一定基礎的。我也相信,各位同仁過去一定也寫過與Socket打交道的程式。那麼,WP中的Socket又將如何呢?

前提公布答案吧,在WP中使用Socket跟你在其它案頭應用項目如WinForm,WPF等中是一樣的,而且說白了,WP中的Socket只不過是從Silverlight架構中繼承過來的。

.NET的一大優勢就是整合性和統一性都好,這不,你看,無論你是編寫傳統型應用程式,還是WP上的應用程式,你會發現,你的學習成本不高,很多東西都是一樣的,而且是相通的。顯然這也是Win8和WP8的應用程式可以整合的原因吧。

在WP中使用Socket要注意以下幾點:

1、WP用戶端應用程式一般不被視為伺服器端,因為不能進行綁定本地終結點和監聽串連。但是,收發資料是沒問題D。

2、在WP中的Socket操作(串連、接收以及發送)都是非同步進行的。如果希望UI線程和後前線程進行同步,不妨使用System.Threading.ManualResetEvent類,這個東西不好講述,也不好理解。這樣吧,我舉一個例子。

有一天,NC和腦殘因為一件小事鬧衝突,鬧來鬧去還是不能解決,怎麼辦呢?於是,NC和腦殘決定來一場比試。兩人約定以跑步方式比試,誰跑得快誰就是勝利者。然而,NC這個人一向比較自負,他相信腦殘絕對跑不過他。這樣,NC就修改了比賽規則:

NC讓腦殘先跑5秒,然後他才開始。

假設NC是主線程,腦殘是後台線程,現在的情況是:主線程先等待一會兒,讓後台線程先執行;後台線程執行5秒後向主線程發出訊號,主線程收到訊號後再繼續往下執行。按照故事裡的情節:NC先讓腦殘跑5秒鐘,他自己就在起跑線上等待,腦殘跑了5秒後向NC發出訊號,NC看到訊號後就開始跑。

下面介紹一個類——SocketAsyncEventArgs。

這個類作為啟動非同步作業時傳遞的參數,它可以包含如接收資料的緩衝區、遠程主機、使用者自訂對象等內容,這個類並不複雜,開啟“物件瀏覽器”看看就一目瞭然了。

要設定用於非同步接收資料的緩衝區,應調用SetBuffer方法。

好,理論就扯到這兒,其實也沒有什麼新的知識點,我只是簡單提一下罷了。

按照慣例,大家都會猜到,理論過後要幹什麼了,是的,付諸實踐。

 

在很多情況下,關於Socket的例子,都會做一個聊天程式的,不過,聊天程式要求伺服器端和客戶都具有發送和接收資料的功能,這樣會增加執行個體的難度和代碼長度,不方便入門者閱讀。所以,想了一下,今天咱們不玩聊天的,今天咱們玩遙控飛機,如何?

程式碼較長,也不便於逐一來講解,這樣吧,為了保持代碼的可讀性,我會把完整的代碼都貼出來,在代碼中我會適當地加上注釋。

先說一下原理,利用Socket進行通訊這不用說了,那是肯定的。功能是通過WP手機用戶端應用程式來控制PC端播放、暫停和停止動畫,而動畫嘛,也不弄那麼複雜了,就弄個矩形從左邊移到右邊的動畫吧。

 

第一部分  伺服器端

既然要播放動畫,少不了要用WPF了,而且,也方便貼介面布局的代碼。

1、建立WPF應用程式項目。

2、開啟MainWindow.xaml檔案(預設建立項目後自動開啟),輸入以下XAML代碼。

<Window x:Class="MYServer.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        Title="伺服器端" Height="350" Width="525">        <Window.Resources>        <Storyboard x:Key="std">            <DoubleAnimation Duration="0:0:5"                                 Storyboard.TargetName="rect"                                 Storyboard.TargetProperty="(Rectangle.RenderTransform).(TranslateTransform.X)"                                 To="400"/>        </Storyboard>    </Window.Resources>        <Grid>        <Grid.RowDefinitions>            <RowDefinition />            <RowDefinition Height="Auto" />        </Grid.RowDefinitions>        <Rectangle x:Name="rect" Grid.Row="0" Width="50" Height="50" Fill="Orange" HorizontalAlignment="Left" VerticalAlignment="Center">            <Rectangle.RenderTransform>                <TranslateTransform X="0" Y="0"/>            </Rectangle.RenderTransform>        </Rectangle>        <TextBlock Name="txtDisplay" Grid.Row="1"/>    </Grid></Window>

3、開啟MainWindow.xaml.cs檔案,完成後台代碼邏輯。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;using System.Windows.Media.Animation;using System.IO;using System.Net;using System.Net.Sockets;namespace MYServer{    /// <summary>    /// MainWindow.xaml 的互動邏輯    /// </summary>    public partial class MainWindow : Window    {        Storyboard std = null; //示範圖板        public MainWindow()        {            InitializeComponent();            // 從資源中把Key為std的Storyboard讀出來            std = this.Resources["std"] as Storyboard;            // 聲明用於監聽串連請求的Socket            Socket Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            IPEndPoint local = new IPEndPoint(IPAddress.Any, 1377); //監聽所有網路介面上的地址            Server.Bind(local);// 綁定本地終結點            Server.Listen(100);// 偵聽串連請求            // 開始非同步接受傳入的串連請求            Server.BeginAccept(new AsyncCallback(this.AcceptSocketCallback), Server);        }        /// <summary>        /// 接受傳入的Socket的回調        /// </summary>        private void AcceptSocketCallback(IAsyncResult ia)        {            Socket _socket = ia.AsyncState as Socket;            Socket accptSocket = _socket.EndAccept(ia);            try            {                IPEndPoint remote = (IPEndPoint)accptSocket.RemoteEndPoint;                // 顯示用戶端的IP                Dispatcher.BeginInvoke(new Action<string>(this.SetIPForText), remote.Address.ToString());                StateObject so = new StateObject();                so.theSocket = accptSocket;                // 開始非同步接收訊息                accptSocket.BeginReceive(so.Buffer, 0, so.Buffer.Length, SocketFlags.None, new AsyncCallback(this.ReceiveCallback), so);            }            catch            {            }            // 繼續接受串連請求            _socket.BeginAccept(new AsyncCallback(this.AcceptSocketCallback), _socket);        }        /// <summary>        /// 接收訊息的回調        /// </summary>        private void ReceiveCallback(IAsyncResult ia)        {            StateObject _so = ia.AsyncState as StateObject;            Socket _socket = _so.theSocket;            try            {                int n = _socket.EndReceive(ia);//n就是接收到的位元組數                string msg = Encoding.UTF8.GetString(_so.Buffer, 0, n);                // 判斷用戶端發送了啥命令                switch (msg)                {                    case "play":                        Dispatcher.BeginInvoke(new Action(this.Play), null);                        break;                    case "pause":                        Dispatcher.BeginInvoke(new Action(this.Pause), null);                        break;                    case "stop":                        Dispatcher.BeginInvoke(new Action(this.Stop), null);                        break;                    default:                        break;                }            }            catch             {            }            _so = new StateObject();            _so.theSocket = _socket;            // 繼續接收訊息            _socket.BeginReceive(_so.Buffer,                                0,                                _so.Buffer.Length,                                SocketFlags.None,                                new AsyncCallback(this.ReceiveCallback),                                _so);        }        /// <summary>        /// 顯示用戶端的IP        /// </summary>        private void SetIPForText(string ip)        {            this.txtDisplay.Text = "用戶端IP:" + ip;        }        #region 控制動畫的方法        private void Play()        {            std.Begin();        }        private void Pause()        {            std.Pause();        }        private void Stop()        {            std.Stop();        }        #endregion    }    /// <summary>    /// 用於非同步Socket操作傳遞的狀態物件    /// </summary>    public class StateObject    {        private const int BUFFER_SIZE = 512;        public byte[] Buffer { get;  set; }        public Socket theSocket { get; set; }        /// <summary>        /// 建構函式        /// </summary>        public StateObject()        {            this.Buffer = new byte[BUFFER_SIZE];        }    }}

 

 

第二部分  WP用戶端

1、建立Windows Phone應用程式項目。

2、開啟MainPage.xaml檔案,參考下面的XAML代碼。

<phone:PhoneApplicationPage     x:Class="WPClient.MainPage"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"    FontFamily="{StaticResource PhoneFontFamilyNormal}"    FontSize="{StaticResource PhoneFontSizeNormal}"    Foreground="{StaticResource PhoneForegroundBrush}"    SupportedOrientations="Portrait" Orientation="Portrait"    shell:SystemTray.IsVisible="True">    <!--LayoutRoot 是包含所有頁面內容的根網格-->    <Grid x:Name="LayoutRoot" Background="Transparent">        <Grid.RowDefinitions>            <RowDefinition Height="Auto"/>            <RowDefinition Height="*"/>        </Grid.RowDefinitions>        <!--TitlePanel 包含應用程式的名稱和網頁標題-->        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">            <TextBlock x:Name="ApplicationTitle" Text="我的應用程式" Style="{StaticResource PhoneTextNormalStyle}"/>            <TextBlock x:Name="PageTitle" Text="頁面名稱" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>        </StackPanel>        <!--ContentPanel - 在此處放置其他內容-->        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">            <Grid.RowDefinitions>                <RowDefinition Height="auto"/>                <RowDefinition Height="*"/>            </Grid.RowDefinitions>            <Grid Grid.Row="0">                <Grid.ColumnDefinitions>                    <ColumnDefinition Width="Auto" />                    <ColumnDefinition />                    <ColumnDefinition Width="Auto" />                </Grid.ColumnDefinitions>                <TextBlock Grid.Column="0" VerticalAlignment="Center" Text="伺服器IP:" />                <TextBox Name="txtServerIP" Grid.Column="1"/>                <Button Grid.Column="2" Content="串連" Click="onConnect"/>            </Grid>            <StackPanel Grid.Row="1">                <Button Content="放播動畫" Click="onPlay"/>                <Button Content="暫停動畫" Click="onPause"/>                <Button Content="停止動畫" Click="onStop"/>                <TextBlock Name="txtbInfo" Margin="3,18,3,0"/>            </StackPanel>        </Grid>    </Grid>     <!--示範 ApplicationBar 用法的範例程式碼-->    <!--<phone:PhoneApplicationPage.ApplicationBar>        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="按鈕 1"/>            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="按鈕 2"/>            <shell:ApplicationBar.MenuItems>                <shell:ApplicationBarMenuItem Text="功能表項目 1"/>                <shell:ApplicationBarMenuItem Text="功能表項目 2"/>            </shell:ApplicationBar.MenuItems>        </shell:ApplicationBar>    </phone:PhoneApplicationPage.ApplicationBar>--></phone:PhoneApplicationPage>

3、開啟MainPage.xaml.cs,輸入以下代碼。

using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Windows;using System.Windows.Controls;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Animation;using System.Windows.Shapes;using Microsoft.Phone.Controls;using System.Net.Sockets;using System.IO;using System.Threading;namespace WPClient{    public partial class MainPage : PhoneApplicationPage    {        Socket mySocket = null;        ManualResetEvent MyEvent = null;        // 建構函式        public MainPage()        {            InitializeComponent();        }        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)        {            base.OnNavigatedTo(e);            if (mySocket == null)            {                mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            }            if (MyEvent == null)            {                MyEvent = new ManualResetEvent(false);            }        }        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)        {            if (mySocket != null)            {                mySocket.Shutdown(SocketShutdown.Both);                mySocket.Close();            }            base.OnNavigatedFrom(e);        }        private void onConnect(object sender, RoutedEventArgs e)        {            if (mySocket != null)            {                SocketAsyncEventArgs connArg = new SocketAsyncEventArgs();                // 要串連的遠程伺服器                connArg.RemoteEndPoint = new DnsEndPoint(this.txtServerIP.Text, 1377);                // 操作完成後的回調                connArg.Completed += (sendObj, arg) =>                {                    if (arg.SocketError == SocketError.Success) //串連成功                    {                        Dispatcher.BeginInvoke(() => txtbInfo.Text = "串連成功。");                    }                    else                    {                        Dispatcher.BeginInvoke(() =>                        {                            txtbInfo.Text = "串連失敗,錯誤:" + arg.SocketError.ToString();                        });                    }                    // 向調用線程報告操作結束                    MyEvent.Set();                };                // 重設線程等待事件                MyEvent.Reset();                txtbInfo.Text = "正在串連,請等候……";                // 開始異串連                mySocket.ConnectAsync(connArg);                // 等待串連完成                MyEvent.WaitOne(6000);            }        }        private void onPause(object sender, RoutedEventArgs e)        {            SendCommand("pause");        }        private void onStop(object sender, RoutedEventArgs e)        {            SendCommand("stop");        }        private void onPlay(object sender, RoutedEventArgs e)        {            SendCommand("play");        }        private void SendCommand(string txt)        {            if (mySocket != null && mySocket.Connected)            {                SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(txt);                sendArg.SetBuffer(buffer, 0, buffer.Length);                // 發送完成後的回調                sendArg.Completed += (objSender, mArg) =>                    {                        // 如果操作成功                        if (mArg.SocketError == SocketError.Success)                        {                            Dispatcher.BeginInvoke(() => txtbInfo.Text = "發送成功。");                        }                        else                        {                            Dispatcher.BeginInvoke(() =>                                {                                    this.txtbInfo.Text = "發送失敗,錯誤:" + mArg.SocketError.ToString();                                });                        }                        // 報告非同步作業結束                        MyEvent.Set();                    };                // 重設訊號                MyEvent.Reset();                txtbInfo.Text = "正在發送,請等候……";                // 非同步發送                mySocket.SendAsync(sendArg);                // 等待操作完成                MyEvent.WaitOne(6000);            }        }    }}

 

先運行伺服器端,再在WP模擬器或真實手機上運行用戶端。

在手機用戶端中,輸入IP地址,點“串連”,串連成功後,就可以發送指令了。

 

 好的,就到這兒吧,樣本的源碼我會上專到“資源”中,有需要的話,大家可以按標題下載。

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.