03、Windows Phone 套接字(Socket)实战之WP客户端设计
因为 PC 端和 WP 端进行通信时,采用的自定义的协议,所以也需要定义 DataType 类来判断
通信数据的类型,并且把数据的描述信息(head) 和数据的实际内容(body)进行拼接和反转,所以
在 WP 端也添加一个 CommonHelper.cs 文件。因为 PC 端的 CommonHelper 类的内容和 WP 端
的类功能基本相似,只是有一点点差别,这里就不再介绍 WP 端的 CommonHelper 类了。
注意事项:这个工程的 demo 是手机端通过 Wifi 或者 WP模拟器与 PC 端完成通信的,所以 WP手机或者
模拟器需要具有访问网络的权限时才能运行成功,如果 WP 端无法连接 PC 端,可能是 PC防火墙或者内部局域网
的防火墙禁用了此 TCP 的连接。
另外,如果运行的 PC 是笔记本的话,因为目前的主流笔记本都具有分享 Wifi 热点的功能,所以如果笔记本
能够联网的话,也可以让 WP8 的手机也连接到笔记本分享的 Wifi,具体设置可以参考 百度经验。
一、概述
WP 客户端使用一个 Pivot 页面,第一个 Pivot 项来显示 连接状态、聊天信息和异常信息等,
第二个 Pivot 项仅仅列出服务器端发送到 WP 端独立存储里面的文件,第三个 Pivot 项用来向 PC
端发送图片文件。
相应的操作截图:
1)状态和消息窗口
2)扫描 WP 独立存储里面,服务器端发送来的文件
3)向 PC 端发送图片文件:
4)PC 端接收到图片时,直接把图片保存到 D:盘的根目录下面:
二、WP 端页面的布局
这里直接贴出 MainPage 页面的 XAML :
<Grid x:Name="LayoutRoot" Background="Transparent">
<phone:Pivot Title="我的应用程序">
<!--枢轴项一-->
<phone:PivotItem Header="消息窗口">
<!--ContentPanel - 在此处放置其他内容-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="90"/>
<RowDefinition Height="60"/>
<RowDefinition Height="270"/>
<RowDefinition Height="90"/>
</Grid.RowDefinitions> <StackPanel Orientation="Horizontal">
<!-- PC 端的 IP 地址 -->
<TextBox HorizontalAlignment="Left" Text="" Height="72" Margin="5,5,0,0"
TextWrapping="Wrap" x:Name="txtRemoteIP" Width="289"/>
<!--连接 PC服务器端-->
<Button Margin="20,0,0,0" Content="连接" Width="98" Click="Button_Click"/>
</StackPanel>
<TextBlock x:Name="txtLocalIP" Grid.Row="1" Margin="10,5,0,0" TextWrapping="Wrap" Width="345"/>
<!--"10.239.201.36"--> <!--显示连接状态、聊天消息等-->
<ScrollViewer x:Name="scroll" Height="266" Margin="10,0,0,0" Grid.Row="2">
<TextBlock x:Name="labeMsg" TextWrapping="Wrap"/>
</ScrollViewer> <StackPanel Grid.Row="3" Orientation="Horizontal">
<!--聊天需要输入的文字-->
<TextBox x:Name="txtSendMsg" Height="72" Margin="5,5,0,0" TextWrapping="Wrap" Width="289"/> <!--发送聊天内容-->
<Button x:Name="btnSendMsg" Content="发送" Margin="20,0,0,0" Width="98" Click="btnSendMsg_Click"/>
</StackPanel>
</Grid>
</phone:PivotItem> <!--枢轴项二-->
<phone:PivotItem Header="文件窗口">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="90"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> <!--当服务器端有文件发送到 WP 端时,直接把文件存储到独立存储里面,点击按钮,查看这些文件-->
<Button x:Name="btnScanFiles" Content="扫描 Folder 里面的文件" Click="btnScanFiles_Click"/> <!--显示独立存储中,服务器端发送来的文件-->
<ListBox x:Name="listboxFiles" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Margin="5,5,5,30"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PivotItem> <!--枢轴项三-->
<phone:PivotItem Header="文件窗口">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="90"/>
<RowDefinition Height="90"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> <!--选择手机图片库中的图片,并且显示在下面的 imgPhoto 控件上-->
<Button x:Name="btnChoosePhoto" Content="选择图片" Click="btnChoosePhoto_Click"/> <!--向服务器端发送图片文件-->
<Button x:Name="btnSendPhoto" Content="向服务器发送图片" Grid.Row="1" Click="btnSendPhoto_Click"/>
<Image x:Name="imgPhoto" Grid.Row="2" Stretch="Uniform"/>
</Grid>
</phone:PivotItem>
</phone:Pivot>
</Grid>
二、WP 端的 SocketClient 类的设计
参考 MSDN 文档:http://msdn.microsoft.com/zh-cn/library/windowsphone/develop/hh202858(v=vs.105).aspx
该 MSDN 文章中,有一个简单的 Socket 操作的 Demo,这里把它进行重构了一下。
因为在 WP 端,Socket 进行操作时,主要使用 SocketAsyncEventArgs 类最为参数,并且 SocketAsyncEventArgs 参数
设置的 Socket 异步操作操作完成后的回调,都只调用在 socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;
上注册的方法,这些操作包括:
namespace System.Net.Sockets
{ // 最近使用此对象执行的异步套接字操作类型。
public enum SocketAsyncOperation
{
// 没有套接字操作。
None = , // 一个套接字 Accept 操作。
Accept = , // 一个套接字 Connect 操作。
Connect = , // 一个套接字 Receive 操作。
Receive = , // 一个套接字 ReceiveFrom 操作。
ReceiveFrom = , // 一个套接字 Send 操作。
Send = , // 一个套接字 SendTo 操作。
SendTo = ,
}
}
所以,在 SocketAsyncEventArgs 对象的 Competed 事件触发时,在回调中通过 Switch 进行
判断操作:
// 异步操作完成时调用
void socketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
// 获取最近使用此对象执行的异步套接字操作的类型。
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.ReceiveFrom:
ProcessReceiveFrom(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
case SocketAsyncOperation.SendTo:
ProcessSendTo(e);
break;
default:
throw new Exception("未知操作");
}
}
完整的 SocketClient 的定义:
namespace PhoneSocketServerDemo
{
/// <summary>
/// 封装 Socket对象,负责与 PC 服务器端进行通信的自定义类
/// </summary>
public class SocketClient
{
// 控制异步套接字的方法所使用的缓冲区的最大尺寸
const int Max_Buffer_Size = * ; // 当操作完成后,触发消息通知
public event EventHandler<string> Completed; // 负责与 PC端通信
Socket socket; /// <summary>
/// 建立与 PC 端通信的连接
/// </summary>
/// <param name="hostName">远程服务器的 IP地址</param>
/// <param name="portNumber">端口号</param>
public void Connect(string hostName, int portNumber)
{
//this.SocketShutDowm(); // 将网络终结点表示为主机名或 IP 地址和端口号的字符串表示形式。
DnsEndPoint dnsEndPoint = new DnsEndPoint(hostName, portNumber); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 表示异步套接字操作。
SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs(); // 远程 IP 或 DNS 终结点
socketAsyncEventArgs.RemoteEndPoint = dnsEndPoint; socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed; socketAsyncEventArgs.UserToken = null; // 开始一个对远程主机连接的异步请求。
socket.ConnectAsync(socketAsyncEventArgs);
} /// <summary>
/// 监听服务器端发来的数据
/// </summary>
/// <returns></returns>
public Task Receive()
{
return Task.Factory.StartNew(() =>
{
if (socket != null)
{
// 表示异步套接字操作。
SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs(); socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed; socketAsyncEventArgs.RemoteEndPoint = socket.RemoteEndPoint; socketAsyncEventArgs.UserToken = null; socketAsyncEventArgs.SetBuffer(new byte[Max_Buffer_Size], , Max_Buffer_Size); // 开始一个异步请求以便从连接的 Socket 对象中接收数据。
bool result = socket.ReceiveAsync(socketAsyncEventArgs);
}
else
{
OnCompleted("还没有建立连接");
}
});
} /// <summary>
/// 向服务器端发送文件
/// </summary>
/// <param name="dataType">body 的数据类型</param>
/// <param name="byteFile">文件的 byte[]内容</param>
/// <returns></returns>
public Task SendFile(DataType dataType, byte[] byteFile)
{
return Task.Factory.StartNew(() =>
{
if (socket != null)
{
SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs(); socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed; socketAsyncEventArgs.RemoteEndPoint = socket.RemoteEndPoint; socketAsyncEventArgs.UserToken = null; byte[] sendBytes = CommonHelper.ConvertFileToByte(dataType, byteFile); // 设置要用于异步套接字方法的数据缓冲区。
socketAsyncEventArgs.SetBuffer(sendBytes, , sendBytes.Length); // 将数据异步发送到连接的 Socket 对象
bool result = socket.SendAsync(socketAsyncEventArgs);
}
else
{
OnCompleted("还没有建立连接");
}
});
} /// <summary>
/// 向服务器端发送 文件 或者 文字 内容
/// </summary>
/// <param name="dataType">文件类型</param>
/// <param name="strPath">文件路径</param>
/// <param name="strMsg">文字消息</param>
/// <returns></returns>
public Task Send(DataType dataType, string strPath, string strMsg)
{
return Task.Factory.StartNew(() =>
{
if (socket != null)
{
SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs(); socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed; socketAsyncEventArgs.RemoteEndPoint = socket.RemoteEndPoint; socketAsyncEventArgs.UserToken = null; byte[] sendBytes = CommonHelper.ConvertDataToByte(dataType, strPath, strMsg); socketAsyncEventArgs.SetBuffer(sendBytes, , sendBytes.Length); // 将数据异步发送到连接的 Socket 对象
bool result = socket.SendAsync(socketAsyncEventArgs);
}
else
{
OnCompleted("还没有建立连接");
}
});
} // 异步操作完成时调用
void socketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
// 获取最近使用此对象执行的异步套接字操作的类型。
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.ReceiveFrom:
ProcessReceiveFrom(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
case SocketAsyncOperation.SendTo:
ProcessSendTo(e);
break;
default:
throw new Exception("未知操作");
}
} // 处理 socket连接 操作的回调
void ProcessConnect(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
OnCompleted("连接服务器成功");
//Socket socket = e.UserToken as Socket; Receive();
}
else
{
OnCompleted("连接服务器失败 :" + e.SocketError.ToString());
}
} // 处理服务器端发送来的数据
async void ProcessReceive(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
string strMsg = null;
byte[] byteFile = null;
DataType dataType = null;
CommonHelper.ConvertByteToData(e.Buffer, out dataType, out byteFile, out strMsg); if (dataType != null && dataType.IsFile == true)
{
await CommonHelper.SaveFile(dataType.FileName + dataType.Exten, byteFile); OnCompleted("已经保存服务器发送的文件:" + dataType.FileName + dataType.Exten);
}
else
{
OnCompleted(">>服务器:" + strMsg);
} // 处理完服务器发送的数据后,继续等待消息
Receive();
}
else
{
OnCompleted(e.SocketError.ToString());
}
} void ProcessReceiveFrom(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{ }
else
{
OnCompleted(e.SocketError.ToString());
}
} // 处理向服务器端发送数据后的回调
void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
string str = Encoding.UTF8.GetString(e.Buffer, , e.Buffer.Length); }
else
{
OnCompleted(e.SocketError.ToString());
}
} void ProcessSendTo(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{ }
else
{
OnCompleted(e.SocketError.ToString());
}
} // 关闭 Socket
public void SocketShutDowm()
{
if (socket != null)
{
socket.Close();
socket.Dispose();
}
} // 向宿主页面显示消息
void OnCompleted(string strMsg)
{
if (Completed != null)
{
Completed(null, strMsg);
}
}
}
}
三、MainPage 页面的 CodeBehind 代码:
namespace PhoneSocketServerDemo
{
public partial class MainPage : PhoneApplicationPage
{
// 构造函数
public MainPage()
{
InitializeComponent(); MyIPAddress finder = new MyIPAddress();
finder.Find((address) =>
{
Dispatcher.BeginInvoke(() =>
{
txtLocalIP.Text = "手机的 IP 地址:" + (address == null ? "Unknown" : address.ToString());
});
}); //txtLocalIP.Text = MyIPAddress.Find().ToString(); txtRemoteIP.Text = RemoteIP; socketClient = new SocketClient();
socketClient.Completed += client_Completed;
} void client_Completed(object sender, string e)
{
ShowMsg(e);
} // PC 端服务器的地址
string RemoteIP = "172.28.125.70";//"10.239.201.48";
int RemotePort = ; #region 枢轴项一
SocketClient socketClient;
// 连接服务器
private void Button_Click(object sender, RoutedEventArgs e)
{
socketClient.Connect(RemoteIP , RemotePort);
ShowMsg("正在连接服务器....");
//socketClient.Receive();
//client.Receive();
} // 发送文字
private void btnSendMsg_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(txtSendMsg.Text))
{
socketClient.Send(new DataType { IsFile = false }, null, txtSendMsg.Text);
ShowMsg(txtSendMsg.Text);
txtSendMsg.Text = "";
}
else
{
ShowMsg("请先输入内容");
}
} #endregion #region 枢轴项二
// 浏览独立存储中的文件,显示到 listbox 中
private async void btnScanFiles_Click(object sender, RoutedEventArgs e)
{
IReadOnlyList<Windows.Storage.StorageFile> files = await CommonHelper.ScanFiles();
if (files.Count > )
{
listboxFiles.ItemsSource = files;
}
else
{
MessageBox.Show("该文件夹中没有文件");
}
}
#endregion #region 枢轴项三 // 选择发送到服务器端的图片文件
private void btnChoosePhoto_Click(object sender, RoutedEventArgs e)
{
Microsoft.Phone.Tasks.PhotoChooserTask choooser = new Microsoft.Phone.Tasks.PhotoChooserTask(); choooser.Completed += choooser_Completed; choooser.Show();
} void choooser_Completed(object sender, Microsoft.Phone.Tasks.PhotoResult e)
{
if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.OK)
{
BitmapImage bitimage = new BitmapImage();
bitimage.SetSource(e.ChosenPhoto);
imgPhoto.Source = bitimage;
}
else
{
imgPhoto.Source = null;
}
} // 向服务器端发送图片
private void btnSendPhoto_Click(object sender, RoutedEventArgs e)
{
if (imgPhoto.Source != null)
{
BitmapImage bitmap = imgPhoto.Source as BitmapImage;
WriteableBitmap wb = new WriteableBitmap(bitmap); MemoryStream stream = new MemoryStream(); Extensions.SaveJpeg(wb, stream, wb.PixelWidth, wb.PixelHeight, , );
byte[] bytesPhoto = stream.GetBuffer(); // 发送文件,文件统一命名
socketClient.SendFile(new DataType { IsFile = true , Exten = ".jpg", FileName = "手机发送的图片"}, bytesPhoto);
}
else
{
MessageBox.Show("请先选择一张图片");
}
}
#endregion protected override void OnNavigatedTo(NavigationEventArgs e)
{
// 如果是重新导航到该页面,则重新建立连接
if (e.NavigationMode == NavigationMode.Back)
{
// 如果是重新导航到该应用,则重新连接服务器端
socketClient.Connect( RemoteIP, RemotePort);
//socketClient.Receive();
}
base.OnNavigatedTo(e);
} protected override void OnNavigatedFrom(NavigationEventArgs e)
{
//socketClient.SocketShutDowm();
base.OnNavigatedFrom(e);
} // 显示消息
void ShowMsg(string strMsg)
{
this.Dispatcher.BeginInvoke(delegate
{
labeMsg.Text += strMsg + Environment.NewLine + Environment.NewLine; // 滚动到底部
scroll.ScrollToVerticalOffset(scroll.VerticalOffset);
});
} }
}
03、Windows Phone 套接字(Socket)实战之WP客户端设计的更多相关文章
- 02、Windows Phone 套接字(Socket)实战之服务器端设计
这里主要写 PC 服务器端的逻辑,UI 使用的是 WPF,因为 WPF 比普通的 WinForm 的流式布局 更容易控制,而且比 WinForm 美观一些,显示截图: 一.页面 UI MainWind ...
- 面向对象之套接字(socket)和黏包
一丶套接字(socket) tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端 基于UDP协议的socket server端: import socket udp_sk = socke ...
- 网络编程 套接字socket TCP UDP
网络编程与套接字 网络编程 网络编程是什么: 网络通常指的是计算机中的互联网,是由多台计算机通过网线或其他媒介相互链接组成的 编写基于网络的应用程序的过程序称之为网络编程. 网络编程最主要的工 ...
- 第13讲 | 套接字Socket:Talk is cheap, show me the code
第13讲 | 套接字Socket:Talk is cheap, show me the code 基于 TCP 和 UDP 协议的 Socket 编程.在讲 TCP 和 UDP 协议的时候,我们分客户 ...
- Linux进程间通信(八):流套接字 socket()、bind()、listen()、accept()、connect()、read()、write()、close()
前面说到的进程间的通信,所通信的进程都是在同一台计算机上的,而使用socket进行通信的进程可以是同一台计算机的进程,也是可以是通过网络连接起来的不同计算机上的进程.通常我们使用socket进行网络编 ...
- Linux进程间通信(九):数据报套接字 socket()、bind()、sendto()、recvfrom()、close()
前一篇文章,Linux进程间通信——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用. 一.简单回顾——什么是数据报套 ...
- Java知多少(105)套接字(Socket)
网络应用模式主要有: 主机/终端模式:集中计算,集中管理: 客户机/服务器(Client/Server,简称C/S)模式:分布计算,分布管理: 浏览器/服务器模式:利用Internet跨平台. www ...
- 套接字socket 的地址族和类型、工作原理、创建过程
注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...
- [置顶] Java套接字Socket编程
1)概念 网络编程基本模型就客户端到服务器的模型,也就是我们常见的C/S模型.简单的说就是两个进程间相互通信的过程.即通信双方一方作为服务器等待客户端提出请求并给以回应,另一方作为客户端向服务器提出请 ...
随机推荐
- Linux进程间通信—消息队列
四.消息队列(Message Queue) 消息队列就是消息的一个链表,它允许一个或者多个进程向它写消息,一个或多个进程向它读消息.Linux维护了一个消息队列向量表:msgque,来表示系统中所有的 ...
- 在Spark中自定义Kryo序列化输入输出API(转)
原文链接:在Spark中自定义Kryo序列化输入输出API 在Spark中内置支持两种系列化格式:(1).Java serialization:(2).Kryo serialization.在默认情况 ...
- @使用javap反编译Java字节码文件
在Sun公司提供的JDK中,就已经内置了Java字节码文件反编译工具javap.exe(位于JDK安装目录的bin文件夹下). 我们可以在dos窗口中使用javap来反汇编指定的Java字节码文件.在 ...
- linux系统编程:守护进程详解及创建,daemon()使用
一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...
- 自建一个Java Spring MVC项目
用IDEA Intellij,本来创建的是SpringMVC项目,但是下载的时候,太慢了.所以还是用的Maven项目. 选择Maven 项目->Archetype->Web applica ...
- 【Python】Python中的深浅拷贝
[转]python 复制(拷贝)对象 -- ::| 分类: Python |举报|字号 订阅 下载LOFTER我的照片书 | 需求: 你想复制一个对象.因为在Python中,无论你把对象做为参数传递, ...
- centos版本7以上网卡名修改
1.初始状态网卡 2.首先,先编辑网卡的配置文件 vi /etc/sysconfig/network-scripts/ifcfg-eno16777736 将里面的NAME项修改为eth0 3.然后,禁 ...
- 循环栅栏:CyclicBarrier(司令要求任务) 读书笔记
可以理解为循环栅栏,栅栏就是一种障碍物.假如我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是循环栅栏的含义. 构造器: public Cycli ...
- xmpp 服务器配置 open fire for windows 及 spark 测试
xmpp 服务器配置 open fire for windows 此文章为 XMPP windows服务器配置,使用的是 open fire 3.9.1.exe 1: 下载 open fire ope ...
- 移动端HTML5框架
一:移动端HTML5框架 http://jquerymobile.com/jQuery Mobile http://jqtjs.com/jQTouch http://www.sencha.com/pr ...