×
Namespaces

Variants
Actions

WindowsPhone Socket通信编程

From Nokia Developer Wiki
Jump to: navigation, search
WP Metro Icon Wifi.png
SignpostIcon XAML 40.png
WP Metro Icon WP8.png
Article Metadata

代码示例
兼容于
文章
段博琼 在 01 Jul 2013 创建
最后由 hamishwillee 在 04 Jul 2013 编辑

Contents

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

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

概述

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

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

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

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

一、交互

这个 Demo 的实现思路图:

26081142-bc857aa76bd6405892a4752ff634ef23.jpg


实际操作截图:

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


二、通信协议

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

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

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

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

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


协议描述:

13.PNG


这里自定义一个 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 的流式布局

更容易控制,显示截图:


09.PNG


一、页面 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. 状态和消息窗口
    06.PNG
  2. 扫描 WP 独立存储里面,服务器端发送来的文件
    07.PNG
  3. 向 PC 端发送图片文件:
    08.PNG
  4. PC 端接收到图片时,直接把图片保存到 D:盘的根目录下面:
    10.PNG


二、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 类的设计

设置的 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.
345 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×