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模型.简单的说就是两个进程间相互通信的过程.即通信双方一方作为服务器等待客户端提出请求并给以回应,另一方作为客户端向服务器提出请 ...
随机推荐
- qsort(),sort()排序函数
一.qsort()函数 功 能: 使用快速排序例程进行排序 头文件:stdlib.h 用 法: void qsort(void *base,int nelem,int width,int (*fcmp ...
- ECMA-262,第 5 版 最新 JavaScript 规范
ECMA-262,第 5 版 最新 JavaScript 规范 Rob Larsen, 界面架构师, IBM 简介: 了解 ECMAScript 规范的历史,查看它的众多重要新特性和新概念. 发布日期 ...
- UVA 10790 (13.08.06)
How Many Points of Intersection? We have two rows. There are a dots on the toprow andb dots on the ...
- TensorFlowIO操作(三)------图像操作
图像操作 图像基本概念 在图像数字化表示当中,分为黑白和彩色两种.在数字化表示图片的时候,有三个因素.分别是图片的长.图片的宽.图片的颜色通道数.那么黑白图片的颜色通道数为1,它只需要一个数字就可以表 ...
- 图片变换【Matrix】矩阵 简介
Matrix矩阵介绍 官方文档地址:https://developer.android.com/reference/android/graphics/Matrix.html 在Android中,对图片 ...
- 小课堂week16 编程范式巡礼第一季 三大基石
编程范式巡礼第一季 三大基石 最近迷上了一些哲史类书籍,回望过去.放眼未来,往往沉浸在其思维之美中无法自拔.计算机编程是一门非常年轻的学科,沉淀不足也是年轻的一个侧面,在编程领域,有足够思想深度的作品 ...
- 关于C#程序优化的五十种方法
关于C#程序优化的五十种方法 这篇文章主要介绍了C#程序优化的五十个需要注意的地方,使用c#开发的朋友可以看下 一.用属性代替可访问的字段 1..NET数据绑定只支持数据绑定,使用属性可以获 ...
- hdu 3065 AC自动机模版题
题意:输出每个模式串出现的次数,查询的时候呢使用一个数组进行记录就好. 同上题一样的关键点,其他没什么难度了. #include <cstdio> #include <cstring ...
- 转换和删除重复命令tr
前几篇文章介绍了几个用于处理字符的命令和工具,然而在处理大小写转换.删除重复字符等任务时,这些命令处理起来相对较为麻烦.这里将简单介绍Linux下自带的tr命令,相对于其他命令而言,其语法较为简单,比 ...
- 【转】使用python编写网络通信程序
文章主体现部分来自:http://openexperience.iteye.com/blog/145701 1. 背景知识 如果使用TCP协议来传递数据,客户端和服务器端需要分别经过以下步骤: ser ...