您的位置:首页 > 大数据 > 人工智能

Windows Phone开发(46):与Socket有个约会 转:http://blog.csdn.net/tcjiaan/article/details/7669315

2012-07-24 20:56 369 查看
不知道大家有没有“谈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代码。

[html] view plaincopyprint?

<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文件,完成后台代码逻辑。

[csharp] view plaincopyprint?

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代码。

[html] view plaincopyprint?

<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,输入以下代码。

[csharp] view plaincopyprint?

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地址,点“连接”,连接成功后,就可以发送指令了。



好的,就到这儿吧,示例的源码我会上专到“资源”中,有需要的话,大家可以按标题下载。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐