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模型.简单的说就是两个进程间相互通信的过程.即通信双方一方作为服务器等待客户端提出请求并给以回应,另一方作为客户端向服务器提出请 ...
随机推荐
- AlphaGo:用机器学习技术古老的围棋游戏掌握AlphaGo: Mastering the ancient game of Go with Machine Learning
AlphaGo: Mastering the ancient game of Go with Machine Learning Posted by David Silver and Demis Has ...
- OpenCV学习(4) Mat的基本操作(1)
图像在OpenCV中都是通过Mat类来存储的,Mat可以用来表示N维矩阵,当然用的最多的还是二维矩阵. Mat类有两部分组成:第一部分是头信息,这些信息主要用来描述矩阵,比如矩 ...
- Windows Server 2012 R2搭建IIS服务器
1-单击宫格菜单的第一个“服务器管理器”: 2 2-在“快速启动(Q)”子菜单下,单击“2 添加角色和功能”: 3 3-点击左边“安装类型”,然后单击“基于角色或基于功能的安装”,再单击“下一步(N) ...
- Ubuntu简单搭建git私有服务
gitserver搭建过程 搭建gitserver过程记录 例如以下: 环境: serverUbuntu虚拟机(Boss),能通过网络訪问到(server地址:192.168.9.103). clie ...
- JS实现的MAP结构数据
Array.prototype.remove = function(s) { for (var i = 0; i < this.length; i++) { if (s == this[i]) ...
- SQL语法 之 基本查询
一.语法结构 SELECT select_list [ INTO new_table ] FROM table_source [ WHERE search_condition ] [ GROUP BY ...
- HDU 2222 AC自动机模版题
所学的AC自动机都源于斌哥和昀神的想法. 题意:求目标串中出现了几个模式串. 使用一个int型的end数组记录,查询一次. #include <cstdio> #include <c ...
- element-ui 源码学习
https://athena0304.github.io/element-analysis/ 1.模板字符串实现字符串拼接 typeClass() { return `el-alert--${ thi ...
- CompletableFuture 详解
转 http://www.jianshu.com/p/6f3ee90ab7d3 CompletableFuture类实现了CompletionStage和Future接口.Future是Java 5添 ...
- 03-maven学习-eclipse中创建maven项目
一,更改eclipse默认的maven配置 window->preference 选择本地maven目录 一直确定后 二,更改maven默认settings window->prefere ...