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模型.简单的说就是两个进程间相互通信的过程.即通信双方一方作为服务器等待客户端提出请求并给以回应,另一方作为客户端向服务器提出请 ...
 
随机推荐
- GPGPU OpenCL使用结构体数据
			
OpenCL编程中可以使用结构体,只需要在核函数kernel中提供同样的结构体申明就可以啦. 如果在主函数中定义了结构体: typedef struct studentNode{ int age; f ...
 - exchange 升级顺序导致的邮件被发不出的问题?
			
最近在做一个项目POC,准备升级过程目前的2007 环境到exchange 2010,由于客户环境是exchange 2007 sp1 不满足升级的基本条件,我们必须将exchange 2007 sp ...
 - JS 与Flex交互:html中的js 与flex中的actionScript通信
			
Flex与JavaScript交互的问题,这里和大家分享一下,主要包括Flex调用JavaScript中的函数和JavaScript调用Flex中的函数两大部分内容. Flex 与JavaScript ...
 - Log4j使用指南
			
1 概述 本文档是针对Log4j日志工具的使用指南.包括:日志介绍.日志工具介绍.Log4j基本使用.Log4j的高级使用.Spring与log4j的集成等.并进行了举例说明. 本文档 ...
 - 类的const和非const成员函数的重载
			
我们从一个例子说起,来看上一篇文章中的String类, 我们为它提供一个下标操作符([ ])以读写指定位置的字符(char). 只要了解过C++的操作符重载的语法,很快就可以写出下面这个[]操作符重载 ...
 - 【实践】require.js + r.js 代码打包压缩初体验
			
第二个分享的是学校项目所接触到的新知识,代码压缩 + 代码打包 这次的项目用了require.js 这个插件做模块化管理的工具,所谓模块化就是在开发的过程中将功能划分成一个独立的模块,使代码可读性更强 ...
 - 【饿了么】—— Vue2.0高仿饿了么核心模块&移动端Web App项目爬坑(三)
			
前言:接着上一篇项目总结,这一篇是学习过程记录的最后一篇,这里会梳理:评论组件.商家组件.优化.打包.相关资料链接.项目github地址:https://github.com/66Web/ljq_el ...
 - RS报内存错误XQE-ROL-0183
			
描述问题:RS开发了一个报表,里面涉及日期维度精细判断,还有FM里面做的权限处理,处理逻辑可能比较复杂,后面又加了一个case when的数据项结果就出现了下面的错误 下面就说一下处理方案 从字面意思 ...
 - PHP如何安装和配置Zend Studio
			
1 网上下载该软件,安装之后关闭三个东西 2 新建一个本地的PHP项目 3 展开左侧的资源树,发现多了一个PHP的相关东西 4 右击"基础知识",新建一个PHP文件并输入以下代码 ...
 - RMSE均方根误差学习笔记
			
1.均方根误差,它是观测值与真值偏差的平方和观测次数n比值的平方根,在实际测量中,观测次数n总是有限的,真值只能用最可信赖(最佳)值来代替.方根误差对一组测量中的特大或特小误差反映非常敏感,所以,均方 ...