Windows Phone開發(fā)(46):與Socket有個(gè)約會(huì)
不知道大家有沒(méi)有“談Socket色變”的經(jīng)歷?就像我一位朋友所說(shuō)的,Socket這家伙啊,不得已而用之。哈,Socket真的那么恐怖嗎?
其實(shí)這話一點(diǎn)也不假,Socket有時(shí)候真的不太好操控,也不好維護(hù),但不管怎么樣,我們還是要面對(duì)它的,沒(méi)準(zhǔn)Socket是一位大美女哦。
關(guān)于Socket的前世今生就不用我詳述了,關(guān)于她的歷史,已經(jīng)不少人仁志士為她立傳 寫著了,像我們國(guó)內(nèi)的百度百科、互動(dòng)百科等;全球著名的如維基百科之屬。而且,能加入WP開發(fā)的學(xué)習(xí)行列的,我想各位對(duì).NET的其它技術(shù)肯定是有一定基 礎(chǔ)的。我也相信,各位同仁過(guò)去一定也寫過(guò)與Socket打交道的程序。那么,WP中的Socket又將如何呢?
前提公布答案吧,在WP中使用Socket跟你在其它桌面應(yīng)用項(xiàng)目如WinForm,WPF等中是一樣的,而且說(shuō)白了,WP中的Socket只不過(guò)是從Silverlight框架中繼承過(guò)來(lái)的。
.NET的一大優(yōu)勢(shì)就是集成性和統(tǒng)一性都好,這不,你看,無(wú)論你是編寫桌面應(yīng)用程序,還是WP上的應(yīng)用程序,你會(huì)發(fā)現(xiàn),你的學(xué)習(xí)成本不高,很多東西都是一樣的,而且是相通的。顯然這也是Win8和WP8的應(yīng)用程序可以整合的原因吧。
在WP中使用Socket要注意以下幾點(diǎn):
1、WP客戶端應(yīng)用程序一般不被視為服務(wù)器端,因?yàn)椴荒苓M(jìn)行綁定本地終結(jié)點(diǎn)和監(jiān)聽(tīng)連接。但是,收發(fā)數(shù)據(jù)是沒(méi)問(wèn)題D。
2、在WP中的Socket操作(連接、接收以及發(fā)送)都是異步進(jìn)行的。如果希望UI線程和后前線程進(jìn)行同步,不妨使用System.Threading.ManualResetEvent類,這個(gè)東西不好講述,也不好理解。這樣吧,我舉一個(gè)例子。
有一天,NC和腦殘因?yàn)橐患∈卖[沖突,鬧來(lái)鬧去還是不能解決,怎么辦呢?于是,NC和腦殘決定來(lái)一場(chǎng)比試。兩人約定以跑步方式比試,誰(shuí)跑得快誰(shuí)就是勝利者。然而,NC這個(gè)人一向比較自負(fù),他相信腦殘絕對(duì)跑不過(guò)他。這樣,NC就修改了比賽規(guī)則:
NC讓腦殘先跑5秒,然后他才開始。
假設(shè)NC是主線程,腦殘是后臺(tái)線程,現(xiàn)在的情況是:主線程先等待一會(huì)兒,讓后臺(tái)線程先 執(zhí)行;后臺(tái)線程執(zhí)行5秒后向主線程發(fā)出信號(hào),主線程收到信號(hào)后再繼續(xù)往下執(zhí)行。按照故事里的情節(jié):NC先讓腦殘跑5秒鐘,他自己就在起跑線上等待,腦殘跑 了5秒后向NC發(fā)出信號(hào),NC看到信號(hào)后就開始跑。
下面介紹一個(gè)類——SocketAsyncEventArgs。
這個(gè)類作為啟動(dòng)異步操作時(shí)傳遞的參數(shù),它可以包含如接收數(shù)據(jù)的緩沖區(qū)、遠(yuǎn)程主機(jī)、用戶自定義對(duì)象等內(nèi)容,這個(gè)類并不復(fù)雜,打開“對(duì)象瀏覽器”看看就一目了然了。
要設(shè)置用于異步接收數(shù)據(jù)的緩沖區(qū),應(yīng)調(diào)用SetBuffer方法。
好,理論就扯到這兒,其實(shí)也沒(méi)有什么新的知識(shí)點(diǎn),我只是簡(jiǎn)單提一下罷了。
按照慣例,大家都會(huì)猜到,理論過(guò)后要干什么了,是的,付諸實(shí)踐。
在很多情況下,關(guān)于Socket的例子,都會(huì)做一個(gè)聊天程序的,不過(guò),聊天程序要求服務(wù)器端和客戶都具有發(fā)送和接收數(shù)據(jù)的功能,這樣會(huì)增加實(shí)例的難度和代碼長(zhǎng)度,不方便入門者閱讀。所以,想了一下,今天咱們不玩聊天的,今天咱們玩遙控飛機(jī),如何?
程序代碼較長(zhǎng),也不便于逐一來(lái)講解,這樣吧,為了保持代碼的可讀性,我會(huì)把完整的代碼都貼出來(lái),在代碼中我會(huì)適當(dāng)?shù)丶由献⑨尅?/p>
先說(shuō)一下原理,利用Socket進(jìn)行通訊這不用說(shuō)了,那是肯定的。功能是通過(guò)WP手機(jī)客戶端應(yīng)用程序來(lái)控制PC端播放、暫停和停止動(dòng)畫,而動(dòng)畫嘛,也不弄那么復(fù)雜了,就弄個(gè)矩形從左邊移到右邊的動(dòng)畫吧。
***部分 服務(wù)器端
既然要播放動(dòng)畫,少不了要用WPF了,而且,也方便貼界面布局的代碼。
1、新建WPF應(yīng)用程序項(xiàng)目。
2、打開MainWindow.xaml文件(默認(rèn)新建項(xiàng)目后自動(dòng)打開),輸入以下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="服務(wù)器端" 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文件,完成后臺(tái)代碼邏輯。
- 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讀出來(lái)
- std = this.Resources["std"] as Storyboard;
- // 聲明用于監(jiān)聽(tīng)連接請(qǐng)求的Socket
- Socket Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- IPEndPoint local = new IPEndPoint(IPAddress.Any, 1377); //監(jiān)聽(tīng)所有網(wǎng)絡(luò)接口上的地址
- Server.Bind(local);// 綁定本地終結(jié)點(diǎn)
- Server.Listen(100);// 偵聽(tīng)連接請(qǐng)求
- // 開始異步接受傳入的連接請(qǐng)求
- Server.BeginAccept(new AsyncCallback(this.AcceptSocketCallback), Server);
- }
- /// <summary>
- /// 接受傳入的Socket的回調(diào)
- /// </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
- {
- }
- // 繼續(xù)接受連接請(qǐng)求
- _socket.BeginAccept(new AsyncCallback(this.AcceptSocketCallback), _socket);
- }
- /// <summary>
- /// 接收消息的回調(diào)
- /// </summary>
- private void ReceiveCallback(IAsyncResult ia)
- {
- StateObject _so = ia.AsyncState as StateObject;
- Socket _socket = _so.theSocket;
- try
- {
- int n = _socket.EndReceive(ia);//n就是接收到的字節(jié)數(shù)
- string msg = Encoding.UTF8.GetString(_so.Buffer, 0, n);
- // 判斷客戶端發(fā)送了啥命令
- 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;
- // 繼續(xù)接收消息
- _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 控制動(dòng)畫的方法
- private void Play()
- {
- std.Begin();
- }
- private void Pause()
- {
- std.Pause();
- }
- private void Stop()
- {
- std.Stop();
- }
- #endregion
- }
- /// <summary>
- /// 用于異步Socket操作傳遞的狀態(tài)對(duì)象
- /// </summary>
- public class StateObject
- {
- private const int BUFFER_SIZE = 512;
- public byte[] Buffer { get; set; }
- public Socket theSocket { get; set; }
- /// <summary>
- /// 構(gòu)造函數(shù)
- /// </summary>
- public StateObject()
- {
- this.Buffer = new byte[BUFFER_SIZE];
- }
- }
- }
別走開,下頁(yè)為您介紹WP客戶端
#p#
第二部分 WP客戶端
1、新建Windows Phone應(yīng)用程序項(xiàng)目。
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 是包含所有頁(yè)面內(nèi)容的根網(wǎng)格-->
- <Grid x:Name="LayoutRoot" Background="Transparent">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="*"/>
- </Grid.RowDefinitions>
- <!--TitlePanel 包含應(yīng)用程序的名稱和頁(yè)標(biāo)題-->
- <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
- <TextBlock x:Name="ApplicationTitle" Text="我的應(yīng)用程序" Style="{StaticResource PhoneTextNormalStyle}"/>
- <TextBlock x:Name="PageTitle" Text="頁(yè)面名稱" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
- </StackPanel>
- <!--ContentPanel - 在此處放置其他內(nèi)容-->
- <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="服務(wù)器IP:" />
- <TextBox Name="txtServerIP" Grid.Column="1"/>
- <Button Grid.Column="2" Content="連接" Click="onConnect"/>
- </Grid>
- <StackPanel Grid.Row="1">
- <Button Content="放播動(dòng)畫" Click="onPlay"/>
- <Button Content="暫停動(dòng)畫" Click="onPause"/>
- <Button Content="停止動(dòng)畫" 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="菜單項(xiàng) 1"/>
- <shell:ApplicationBarMenuItem Text="菜單項(xiàng) 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;
- // 構(gòu)造函數(shù)
- 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();
- // 要連接的遠(yuǎn)程服務(wù)器
- connArg.RemoteEndPoint = new DnsEndPoint(this.txtServerIP.Text, 1377);
- // 操作完成后的回調(diào)
- connArg.Completed += (sendObj, arg) =>
- {
- if (arg.SocketError == SocketError.Success) //連接成功
- {
- Dispatcher.BeginInvoke(() => txtbInfo.Text = "連接成功。");
- }
- else
- {
- Dispatcher.BeginInvoke(() =>
- {
- txtbInfo.Text = "連接失敗,錯(cuò)誤:" + arg.SocketError.ToString();
- });
- }
- // 向調(diào)用線程報(bào)告操作結(jié)束
- MyEvent.Set();
- };
- // 重置線程等待事件
- MyEvent.Reset();
- txtbInfo.Text = "正在連接,請(qǐng)等候……";
- // 開始異連接
- 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);
- // 發(fā)送完成后的回調(diào)
- sendArg.Completed += (objSender, mArg) =>
- {
- // 如果操作成功
- if (mArg.SocketError == SocketError.Success)
- {
- Dispatcher.BeginInvoke(() => txtbInfo.Text = "發(fā)送成功。");
- }
- else
- {
- Dispatcher.BeginInvoke(() =>
- {
- this.txtbInfo.Text = "發(fā)送失敗,錯(cuò)誤:" + mArg.SocketError.ToString();
- });
- }
- // 報(bào)告異步操作結(jié)束
- MyEvent.Set();
- };
- // 重置信號(hào)
- MyEvent.Reset();
- txtbInfo.Text = "正在發(fā)送,請(qǐng)等候……";
- // 異步發(fā)送
- mySocket.SendAsync(sendArg);
- // 等待操作完成
- MyEvent.WaitOne(6000);
- }
- }
- }
- }
先運(yùn)行服務(wù)器端,再在WP模擬器或真實(shí)手機(jī)上運(yùn)行客戶端。
在手機(jī)客戶端中,輸入IP地址,點(diǎn)“連接”,連接成功后,就可以發(fā)送指令了。
好的,就到這兒吧,示例的源碼我會(huì)上專到“資源”中,有需要的話,大家可以按標(biāo)題下載。