wp网络编程资料

http://developer.nokia.com/community/wiki/WindowsPhone_Socket通信编程


WindowsPhone Socket通信编程

01、Windows Phone 套接字(Socket)实战之交互设计

这个 Demo 主要使用 WP 中提供的 Socket 对象,来与 PC 端进行文字、文件的互相传输。

概述

   因为在 WP 中系统对存储的操作限制的比较多,例如,你把 .doc、.txt、.zip 等常见的格式文件放到手机的存储(包括 SD卡)中,第三

方应用也是不能获取这些文件的。所以,当你的应用需要操作用户选择的文件的时候,其中的一个解决方案是当用户

连接到 Wifi 上时(不需要连接数据线),在 PC 端运行一个软件,让这个 PC 软件和 WP 使用 Socket 通过 TCP

协议进行文件的传输。既然可以传输文件,当然也可以传输文字,即 PC 和 WP 端进行文字聊天。

一、交互

这个 Demo 的实现思路图:


实际操作截图:

  1. 服务器端的启动截图:
  2. 客户端运行截图:
  3. 服务器端开始侦听,点击客户端的“连接” 按钮向服务器端发起连接请求,PC 端侦听到客户端的请求后,便建立通信使用的Socket,然后开始通信:


二、通信协议

     为了兼顾传递“文件”和“文字”数据使用同一个 Socket 对象,需要在客户端和 PC 端进行定义

同一个数据协议。并且在文件传输的时候,还需要传递文件的名称和文件的扩展名等额外的信息。因为

文字和文件数据,在进行 TCP 传输的时候,都是 byte 数组,所以这里在传输数据前,把这些额外

的描述信息(head)转换成 byte 数组后,放到文字或文件(body)的 byte 数组前面。因为这些描述

信息的长度是有限的文字,这里暂时定义 500 字节用来装这些描述信息,在这 500 字节后面放置真正的数据。


协议描述:


这里自定义一个 DataType 类,用来描述数据体的信息,这里暂时定义三个类型:

 public class DataType
{
bool isFile;
/// <summary>
/// 是否是文件类型,如果否,则是 string 类型的消息
/// </summary>
public bool IsFile
{
get { return isFile; }
set { isFile = value; }
}
 
string exten;
/// <summary>
/// 文件的后缀名,长度不能超过20个汉字字符(40个英文字符)
/// </summary>
public string Exten
{
get { return exten; }
set { exten = value; }
}
 
string fileName;
/// <summary>
/// 文件的名称,长度不能超过100个汉字字符(200个英文字符)
/// </summary>
public string FileName
{
get { return fileName; }
set { fileName = value; }
}
}



在处理这些描述信息的时候,需要定义一个静态的工具类,放一些静态常用方法。首先在工程中添加一个

CommonHelper.cs,然后添加两个把对象序列化和反序列化成字符串的方法:

添加命名空间:

using System.Runtime.Serialization.Json;

序列化和反序列化:

#region JSON序列化和反序列化
/// <summary>
/// JSON序列化
/// </summary>
public static string JsonSerializer<T>(T t)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, t);
byte[] buffer = ms.ToArray();
string jsonString = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
ms.Close();
return jsonString;
}
 
/// <summary>
/// JSON反序列化
/// </summary>
public static T JsonDeserialize<T>(string jsonString)
{
T jsonObject;
DataContractJsonSerializer jsonSel = new DataContractJsonSerializer(typeof(T));
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
sw.Write(jsonString);
sw.Flush();
ms.Position = 0;
jsonObject = (T)jsonSel.ReadObject(ms);
 
sw.Dispose();
ms.Dispose();
}
 
return jsonObject;
}
#endregion


在进行文件传输的时候,根据上面 DataType 类的定义,文件的后缀名,长度不能超过20个汉字字符(40个英文字符);

文件的名称,长度不能超过100个汉字字符(200个英文字符)。所以在文件传输前,需要对用户选择的文件的名字和后缀名

的长度进行判断,因为 1 个汉字占用两个字节,所以这里在 CommonHelper.cs 类中添加判断字数的方法,如果字数

不合格,则提示用户:

 #region  计算文本长度
/// <summary>
/// 计算文本长度,区分中英文字符,中文算两个长度,英文算一个长度
/// </summary>
/// <param name="Text">需计算长度的字符串</param>
/// <returns>int</returns>
public static int Text_Length(string Text)
{
int len = 0;
 
for (int i = 0; i < Text.Length; i++)
{
byte[] byte_len = System.Text.Encoding.UTF8.GetBytes(Text.Substring(i, 1));
if (byte_len.Length > 1)
len += 2; //如果长度大于1,是中文,占两个字节,+2
else
len += 1; //如果长度等于1,是英文,占一个字节,+1
}
 
return len;//len / 2 + len % 2;
}
#endregion


还需要在 CommonHelper.cs 文件中定义方法,用来把数据表述信息(head)和数据体(body)

来拼接成真正被发送的信息,和把接收到的信息翻转成描述信息(head)和数据体(body):

#region 数据转换
/// <summary>
/// 套接字发送和接收到的流都分别为两部分,前 500 字节为消息头,后面为消息体
/// </summary>
public const int HeaderLength = 500;
 
// 文件或文字暂时定义为最长 4MB
public const int FileLength = 1024 * 1024 * 4 + HeaderLength;
//const int MsgLength = 1024 * 2; // 消息文本最长字节数
 
/// <summary>
/// 把数据类型作为 head,把文字或文件数据作为 body,返回两者 byte[] 的组合结果
/// </summary>
/// <param name="dataType">作为 head,指示 body 的数据类型</param>
/// <param name="strPath">文件的路径,文字或文件只穿递一个,另一个为 null</param>
/// <param name="strMsg">文字的内容</param>
/// <returns></returns>
public static byte[] ConvertDataToByte(DataType dataType, string strPath, string strMsg)
{
byte[] byteResult;
 
if (dataType.IsFile == true)
{
// 文件的后缀名
dataType.Exten = Path.GetExtension(strPath);
 
// 文件的名称
dataType.FileName = Path.GetFileNameWithoutExtension(strPath);
 
if (CommonHelper.Text_Length(dataType.Exten) > 41) // 后缀名中包含一个 .
{
throw new Exception("文件的后缀名,长度不能超过20个汉字字符(40个英文字符)");
}
 
if (CommonHelper.Text_Length(dataType.FileName) > 200)
{
throw new Exception("文件的名称,长度不能超过100个汉字字符(200个英文字符)");
}
 
// 消息头
string strHeader = CommonHelper.JsonSerializer<DataType>(dataType);
byte[] byteHeader = Encoding.UTF8.GetBytes(strHeader + "<EOF>");
 
 
//通过文件流 读取文件内容
using (FileStream fs = new FileStream(strPath, FileMode.OpenOrCreate))
{
// 消息体
byte[] arrFile = new byte[FileLength];
 
//读取文件内容到字节数组,并 获得 实际文件大小
int fileLength = fs.Read(arrFile, 0, arrFile.Length);
 
if (fileLength >= CommonHelper.FileLength)
{
throw new Exception("文件的尺寸必须小于 4 MB");
}
 
byteResult = new byte[HeaderLength + fileLength];
 
// 拷贝字节数组的内容
Buffer.BlockCopy(byteHeader, 0, byteResult, 0, byteHeader.Length);
Buffer.BlockCopy(arrFile, HeaderLength, byteResult, HeaderLength, fileLength);
}
}
else
{
// 消息头
string strHeader = CommonHelper.JsonSerializer<DataType>(dataType);
 
// 添加 "<EOF>" 表示 head 的 json字符串结束
byte[] byteHeader = Encoding.UTF8.GetBytes(strHeader + "<EOF>");
 
 
byte[] byteMsg = Encoding.UTF8.GetBytes(strMsg);
byteResult = new byte[HeaderLength + byteMsg.Length];
 
Buffer.BlockCopy(byteHeader, 0, byteResult, 0, byteHeader.Length);
Buffer.BlockCopy(byteMsg, 0, byteResult, HeaderLength, byteMsg.Length);
}
 
return byteResult;
}
 
/// <summary>
/// 转换源 byte[] 数据内容,获取其中的 head 和 body 的实际内容
/// </summary>
/// <param name="byteSrc">数据源</param>
/// <param name="dataType">指示 body 的数据类型返回结果</param>
/// <param name="byteFile">文件内容返回结果</param>
/// <param name="strMsg">文字内容返回结果</param>
public static void ConvertByteToData(byte[] byteSrc, out DataType dataType, out byte[] byteFile, out string strMsg)
{
dataType = null;
byteFile = null;
strMsg = null;
 
// 初始化表示 head 的数组
byte[] byteHeader = new byte[HeaderLength];
Buffer.BlockCopy(byteSrc, 0, byteHeader, 0, HeaderLength);
 
// 获取 head 数组的 json 数据字符串
string strHeader = Encoding.UTF8.GetString(byteHeader);
 
if (strHeader.Contains("<EOF>"))
{
int index = strHeader.IndexOf("<EOF>");
string strHeaderValue = strHeader.Substring(0, index);
 
// 把 json 字符串转换成 DataType 对象
dataType = CommonHelper.JsonDeserialize<DataType>(strHeaderValue);
if (dataType != null)
{
if (dataType.IsFile == true)
{
byteFile = new byte[byteSrc.Length - HeaderLength];
Buffer.BlockCopy(byteSrc, HeaderLength, byteFile, 0, byteSrc.Length - HeaderLength);
}
else
{
byte[] byteMsg = new byte[byteSrc.Length - HeaderLength];
Buffer.BlockCopy(byteSrc, HeaderLength, byteMsg, 0, byteSrc.Length - HeaderLength);
 
strMsg = Encoding.UTF8.GetString(byteMsg);
strMsg = strMsg.Trim('\0');
}
}
}
}
 
#endregion


同时需要在这个文件中添加获取 PC 端 IP 的方法:

 #region 获取 PC 端的 IP 地址
/// <summary>
/// 获取本地的 IP 地址
/// </summary>
/// <returns></returns>
public static string GetIPAddress()
{
System.Net.IPAddress addr;
// 获得拨号动态分配IP地址
addr = new System.Net.IPAddress(Dns.GetHostByName(Dns.GetHostName()).AddressList[1].Address);
return addr.ToString();
}
 
 
public static string GetLocalIPAddress()
{
System.Net.IPAddress addr;
// 获得本机局域网IP地址
addr = new System.Net.IPAddress(Dns.GetHostByName(Dns.GetHostName()).AddressList[0].Address);
return addr.ToString();
}
#endregion

02、Windows Phone 套接字(Socket)实战之服务器端设计

这里主要写 PC 服务器端的逻辑,UI 使用的是 WPF,因为 WPF 比普通的 WinForm 的流式布局

更容易控制,显示截图:



一、页面 UI

     MainWindow.xaml 文件中布局的 XAML:
<Grid ShowGridLines="True">
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="110"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Margin" Value="10,0,10,0"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="7*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<!--本地 IP-->
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Width" Value="120"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</StackPanel.Resources>
<!--服务器端的 IP 和端口号-->
<TextBlock x:Name="txtIP" Margin="4,14,0,14" Width="144"/>
<TextBlock x:Name="txtPort" Text="5000" Margin="10, 0, 0, 0"/>
<Button x:Name="btnBeginListening" Content="开始侦听" HorizontalAlignment="Right" Click="btnBeginListening_Click"/>
<!--<Button x:Name="btnStop" Content="停止" HorizontalAlignment="Right" Click="btnStop_Click"/>-->
</StackPanel>
 
<!--消息窗口和消息发送窗口-->
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="scroll">
<!--显示连接状态、聊天消息等-->
<TextBlock x:Name="txtResult" TextWrapping="Wrap"/>
</ScrollViewer>
<!--聊天文字输入框-->
<TextBox x:Name="txtInput" Grid.Row="1" KeyDown="txtInput_KeyDown" TextWrapping="Wrap"/>
</Grid>
 
<!--操作按钮-->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<!--用户选择的文件的路径-->
<TextBox x:Name="txtFilePath" Width="150" Height="40"/>
<Button Content="选择文件" x:Name="btnChooseFile" Click="btnChooseFile_Click"/>
</StackPanel>
 
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Grid.Column="1">
<!--发送文件或者发送文字消息-->
<Button Content="发送文件" x:Name="btnSendFile" Click="btnSendFile_Click"/>
<Button Content="发送消息" x:Name="btnSendMsg" Click="btnSendMsg_Click"/>
</StackPanel>
</Grid>
</Grid>
 
<!--显示请求连接到服务器端的 WP 端的 IP 信息-->
<ListBox Grid.Column="1" x:Name="listbox">
</ListBox>
</Grid>



二、定义一个服务器端的 SocketClient 类,用来封装与 WP 端通讯所使用的 Socket

     虽然 WP 和 PC 端都是使用 C# 语言开发的,但是 WP 端和 PC 端的 Socket 类在使用方法上有一些差异,

并且 WP 端明显是经过精简过的,这里就不贴出来了。直接贴出 SocketClient 类:


namespace DesktopSocketServerDemo
{
/// <summary>
/// 与客户端的 连接通信类(包含了一个 与客户端 通信的 套接字,和线程)
/// </summary>
public class SocketClient
{
Socket sokMsg;
Thread threadMsg;
 
// 通信操作完成时调用,用来触发在操作完成时,在宿主页面显示消息提示
public event EventHandler<string> Completed;
 
public SocketClient(Socket sokMsg)
{
this.sokMsg = sokMsg;
 
this.threadMsg = new Thread(ReceiveMsg);
this.threadMsg.IsBackground = true;
this.threadMsg.Start();
}
 
bool isRec = true;
// 负责监听客户端发送来的消息
void ReceiveMsg()
{
while (isRec)
{
try
{
byte[] byteSrc = new byte[CommonHelper.FileLength];
 
// 从绑定的 Socket 套接字接收数据,将数据存入接收缓冲区。
int length = sokMsg.Receive(byteSrc);
 
DataType dataType;
byte[] bytesFile;
string strMsg;
 
// 转换源 byte[] 数据内容,获取其中的 head 和 body 的实际内容
CommonHelper.ConvertByteToData(byteSrc, out dataType, out bytesFile, out strMsg);
 
if (dataType.IsFile == true)
{
// 如果 body 的类型文件,则直接将文件存储到 PC 端的 D: 盘根路径下
using (FileStream file = new FileStream("d:\\" + dataType.FileName + dataType.Exten, FileMode.OpenOrCreate))
{
file.Write(bytesFile, 0, bytesFile.Length);
}
 
// 显示回调消息
OnCompleted("客户端发送的文件:" + dataType.FileName + dataType.Exten);
OnCompleted("该文件保存在 D: 盘的根目录下");
}
else
{
OnCompleted(">>客户端:" + strMsg);
}
 
}
catch (Exception ex)
{
// 如果抛异常,则释放该 Socket 对象所占用的资源
CloseConnection();
 
OnCompleted("SocketClient.ReceiveMsg() :" + ex.Message);
}
}
}
 
/// <summary>
/// 向客户端发送消息
/// </summary>
/// <param name="strMsg"></param>
public void Send(string strMsg)
{
byte[] arrMsgFinal = CommonHelper.ConvertDataToByte(new DataType { IsFile = false }, null, strMsg);
 
sokMsg.Send(arrMsgFinal);
}
 
// 向客户端发送文件数据
public void SendFile(string strPath)
{
try
{
byte[] arrFileFina = CommonHelper.ConvertDataToByte(new DataType { IsFile = true }, strPath, null);
 
//发送文件数据
sokMsg.Send(arrFileFina);//, 0, length + 1, SocketFlags.None);
OnCompleted("发送文件完成");
}
catch (Exception ex)
{
OnCompleted(ex.Message);
}
}
 
// 关闭与客户端连接
public void CloseConnection()
{
isRec = false;
sokMsg.Close();
sokMsg.Dispose();
}
 
 
void OnCompleted(string strMsg)
{
if (Completed != null)
{
Completed(null, strMsg);
}
}
 
}
}

三、在 MainWindow.xaml 文件中,定义侦听客户端连接的 Socket

     MainWindow.xaml 文件相应的 Codebehind 代码:
namespace DesktopSocketServerDemo
{
/// <summary>
/// PC 端服务器,用来接收连接 PC 的 Socket 连接,并创建 SocketClient 类与客户端通信
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
 
//MultiCast cast = new MultiCast();
//cast.Startup();
 
txtIP.Text += "本地 IP:" + strIP;
txtPort.Text = " 端口:" + Port;
}
 
int Port = 5000;
// 服务器端的 IP 地址
string strIP = CommonHelper.GetIPAddress();
 
//负责监听 客户端 的连接请求
Socket socketWatch = null;
 
// 执行 socketWatch 对象监听请求的线程
Thread threadWatch = null;
 
// 开启监听
private void btnBeginListening_Click(object sender, RoutedEventArgs e)
{
if (socketWatch == null)
{
//实例化 套接字 (ip4寻址协议,流式传输,TCP协议)
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 
IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(strIP), Port);
 
//将 监听套接字 绑定到 对应的IP和端口
socketWatch.Bind(endpoint);
 
//设置 监听队列 长度为10(同时能够处理 10个连接请求)
socketWatch.Listen(10);
threadWatch = new Thread(StartWatch);
threadWatch.IsBackground = true;
threadWatch.Start();
ShowMsg("启动服务器成功......\r\n");
}
else
{
ShowMsg("服务器已经启动");
}
}
 
// 循环侦听客户端的连接请求
bool isWatch = true;
 
// 负责与客户端通信的对象
SocketClient socketClient;
 
/// <summary>
/// 被线程调用 监听连接端口
/// </summary>
void StartWatch()
{
while (isWatch)
{
try
{
//监听 客户端 连接请求,但是,Accept会阻断当前线程
//监听到请求,立即创建负责与该客户端套接字通信的套接字
Socket socket = socketWatch.Accept();
 
if (socketClient != null)
socketClient.CloseConnection();
 
socketClient = new SocketClient(socket);
socketClient.Completed += connection_Completed;
 
this.Dispatcher.BeginInvoke(new Action(delegate
{
if (socket != null && socket.RemoteEndPoint != null)
//将 通信套接字的 客户端IP端口保存在下拉框里
listbox.Items.Add(new TextBlock { Text = socket.RemoteEndPoint.ToString() });
 
ShowMsg("接收一个客户端连接......");
}), null);
}
catch (Exception ex)
{
ShowMsg(ex.Message);
socketWatch.Close();
socketWatch.Dispose();
isWatch = false;
}
 
}
}
 
//发送消息到已经连接到 PC 的客户端
private void btnSendMsg_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInput.Text))
{
ShowMsg("输入内容不能为空");
return;
}
 
if (socketClient != null)
{
ShowMsg(txtInput.Text.Trim());
 
// 发送文字内容
socketClient.Send(txtInput.Text.Trim());
 
txtInput.Text = "";
}
else
{
MessageBox.Show("还没有建立连接");
}
}
 
//选择要发送的文件
private void btnChooseFile_Click(object sender, EventArgs e)
{
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
txtFilePath.Text = openFileDialog.FileName;
}
}
 
//发送文件
private void btnSendFile_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtFilePath.Text))
{
MessageBox.Show("请先选择一个文件");
return;
}
 
//connection = new SocketClient();
 
if (socketClient != null)
{
// 发送文件内容
socketClient.SendFile(txtFilePath.Text.Trim());
}
else
{
MessageBox.Show("还没有建立连接");
}
}
 
// 操作完成
void connection_Completed(object sender, string e)
{
ShowMsg(e);
}
 
// 向窗口追加消息
void ShowMsg(string strMsg)
{
this.Dispatcher.BeginInvoke(new Action(delegate
{
txtResult.Text += Environment.NewLine + strMsg + Environment.NewLine;
scroll.ScrollToBottom();
}), null);
}
 
// 当服务器关闭时触发
protected override void OnClosed(EventArgs e)
{
if (socketWatch != null)
{
socketWatch.Close();
socketWatch.Dispose();
}
base.OnClosed(e);
}
 
// 按“回车键”发送消息
private void txtInput_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
btnSendMsg_Click(null, null);
}
}
 
// 释放连接资源
//private void btnStop_Click(object sender, RoutedEventArgs e)
//{
// if (socketClient != null)
// {
// socketClient.CloseConnection();
// }
 
// if (sokWatch != null)
// {
// sokWatch.Close();
// sokWatch.Dispose();
// }
//}
}
}

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,具体设置可以参考 百度经验: http://jingyan.baidu.com/article/335530da4f774019cb41c3eb.html


一、概述

  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 = 0,
 
// 一个套接字 Accept 操作。
Accept = 1,
 
// 一个套接字 Connect 操作。
Connect = 2,
 
// 一个套接字 Receive 操作。
Receive = 4,
 
// 一个套接字 ReceiveFrom 操作。
ReceiveFrom = 5,
 
// 一个套接字 Send 操作。
Send = 7,
 
// 一个套接字 SendTo 操作。
SendTo = 9,
}
}


所以,在 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 = 1024 * 4;
 
// 当操作完成后,触发消息通知
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], 0, 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, 0, 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, 0, 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, 0, 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);
}
}
}
}


工程代码下载

File:PhoneSocketServerDemo.zip

This page was last modified on 4 July 2013, at 02:01.
354 page views in the last 30 days.

Comments

Hamishwillee - Don't delete the articlemetadata

The ArticleMetaData is useful. Please don't delete it.

Regards


你可能感兴趣的:(WindowsPhone)