原文:Windows Phone开发(46):与Socket有个约会

不知道大家有没有“谈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地址,点“连接”,连接成功后,就可以发送指令了。

好的,就到这儿吧,示例的源码我会上专到“资源”中,有需要的话,大家可以按标题下载。

Windows Phone开发(46):与Socket有个约会的更多相关文章

  1. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  2. Windows Phone开发(47):轻松调用Web Service

    原文:Windows Phone开发(47):轻松调用Web Service 众所周知(除了没用过VS的),在VS里面调用Web Service是一件很愉快的事情,不解释,相信很多朋友在以前的项目中肯 ...

  3. Windows Phone开发(45):推送通知大结局——Raw通知

    原文:Windows Phone开发(45):推送通知大结局--Raw通知 为什么叫大结局呢?因为推送通知服务就只有三种,前面扯了两种,就剩下一种--Raw通知. 前面我们通过两节的动手实验,相信大家 ...

  4. Windows Phone开发(6):处理屏幕方向的改变

    原文:Windows Phone开发(6):处理屏幕方向的改变 俺们都知道,智能手机可以通过旋转手机来改变屏幕的显示方向,更多的时候,对于屏幕方向的改变,我们要做出相应的处理,例如,当手机屏幕方向从纵 ...

  5. Windows下C语言的Socket编程例子(TCP和UDP)

    原文:Windows下C语言的Socket编程例子(TCP和UDP) 刚刚学windows编程,所以想写学习笔记,这是一个简单的Socket程序例子,开发环境是vc6: 首先是TCP server端: ...

  6. Windows 10 开发人员预览版中的新增功能(转自 IT之家)

    Windows 10 开发人员预览版中的新增功能 在Win10预览版中安装工具与SDK后,即可着手创建Windows通用应用或先浏览目前的环境与此前相比都发生了什么变化. 应用建模 文件资源管理器: ...

  7. Kinect for Windows SDK开发学习相关资源

    Kinect for Windows SDK(K4W)将Kinect的体感操作带到了平常的应用学习中,提供了一种不同于传统的鼠标,键盘及触摸的无接触的交互方式,在某种程度上实现了自然交互界面的理想,即 ...

  8. Erlang在Windows上开发环境搭建全过程讲解目录

    我会按照下面的列表来一步一步讲解,在windows来开发Erlang所用到的一些工具,和知识.我会不停的添加和修正. Erlang运行时环境 Erlang开发工具选择 Rebar来构建,编译,测试,发 ...

  9. Windows Service 开发,安装与调试

    Visual Studio.net 2010 Windows Service 开发,安装与调试 本示例完成一个每隔一分钟向C:\log.txt文件写入一条记录为例,讲述一个Windows Servic ...

随机推荐

  1. 主从mysql 同步设置

    GRANT REPLICATION SLAVE ON *.* TO 'root'@'%' IDENTIFIED BY 'zhoubt';grant all privileges on *.* to  ...

  2. windows线程同步的总结

    一 线程 1)如果你正在编写C/C++代码,决不应该调用CreateThread.相反,应该使用VisualC++运行期库函数_beginthreadex,退出也应该使用_endthreadex.如果 ...

  3. QT5.6,5.7,5.8的新特征以及展望

    https://wiki.qt.io/New_Features_in_Qt_5.6 (跨平台High-DPI,改进WebEngine到45,支持WIN 10,Canvas3D,3D) https:// ...

  4. Java经典面试题及详解

    Java基础方面:   1.作用域public,private,protected,以及不写时的区别 答:区别如下:  作用域           当前类       同一package       ...

  5. 论javascript模块化的优缺

    如今backbone.emberjs.spinejs.batmanjs 等MVC框架侵袭而来.CommonJS.AMD.NodeJS.RequireJS.SeaJS.curljs等模块化的JavaSc ...

  6. Android Studio IDE Out of Memory

    场景: 尝试过各种方式,IDE重装,重新启动,设置IDE MEMORY大小JDK MEMORY大小都无效 终于在FILE->INVALIDATE CACHES/RESTART 中点击重新启动之后 ...

  7. Mqtt协议IOS移植完1

    MQTTClient.h #import <Foundation/Foundation.h> @protocol MQTTDelegate <NSObject> /** * @ ...

  8. [C++]指针浅析

    Date: 2014-1-4 summary: 指针的简单理解,概念性的东西会比较多(100个人有100种理解,此处只代表我个人看法) Contents: 1.什么是指针 c++ primer plu ...

  9. 上Https 和 http 差分

    HTTPS 和 HTTP 差协议 超文本传输协定HTTP 对于web 浏览器和现场服务之间传递消息,HTTP 以纯文本协议 发送内容 无论不提供数据加密方法 假设拦截攻击web 浏览器和网站serve ...

  10. 关于JAVA Project.waitfor()返回值是1

    Project.waitfor()返回值是1,找了很久从网上没有发现关于1的说明. 这时对源代码调试了一下,发现Project=null.而去根目录下点击被调用的bat文件发现也可以被正确执行. 这时 ...