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, , 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 = ;
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 = ; for (int i = ; i < Text.Length; i++)
{
byte[] byte_len = System.Text.Encoding.UTF8.GetBytes(Text.Substring(i, ));
if (byte_len.Length > )
len += ; //如果长度大于1,是中文,占两个字节,+2
else
len += ; //如果长度等于1,是英文,占一个字节,+1
} return len;//len / 2 + len % 2;
}
#endregion
还需要在 CommonHelper.cs 文件中定义方法,用来把数据表述信息(head)和数据体(body)
来拼接成真正被发送的信息,和把接收到的信息翻转成描述信息(head)和数据体(body):
#region 数据转换
/// <summary>
/// 套接字发送和接收到的流都分别为两部分,前 500 字节为消息头,后面为消息体
/// </summary>
public const int HeaderLength = ; // 文件或文字暂时定义为最长 4MB
public const int FileLength = * * + 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) > ) // 后缀名中包含一个 .
{
throw new Exception("文件的后缀名,长度不能超过20个汉字字符(40个英文字符)");
} if (CommonHelper.Text_Length(dataType.FileName) > )
{
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, , arrFile.Length); if (fileLength >= CommonHelper.FileLength)
{
throw new Exception("文件的尺寸必须小于 4 MB");
} byteResult = new byte[HeaderLength + fileLength]; // 拷贝字节数组的内容
Buffer.BlockCopy(byteHeader, , byteResult, , 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, , byteResult, , byteHeader.Length);
Buffer.BlockCopy(byteMsg, , 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, , byteHeader, , HeaderLength); // 获取 head 数组的 json 数据字符串
string strHeader = Encoding.UTF8.GetString(byteHeader); if (strHeader.Contains("<EOF>"))
{
int index = strHeader.IndexOf("<EOF>");
string strHeaderValue = strHeader.Substring(, 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, , byteSrc.Length - HeaderLength);
}
else
{
byte[] byteMsg = new byte[byteSrc.Length - HeaderLength];
Buffer.BlockCopy(byteSrc, HeaderLength, byteMsg, , 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[].Address);
return addr.ToString();
} public static string GetLocalIPAddress()
{
System.Net.IPAddress addr;
// 获得本机局域网IP地址
addr = new System.Net.IPAddress(Dns.GetHostByName(Dns.GetHostName()).AddressList[].Address);
return addr.ToString();
}
#endregion
01、Windows Phone 套接字(Socket)实战之交互设计的更多相关文章
- 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(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用. 一.简单回顾——什么是数据报套 ...
- 套接字socket 的地址族和类型、工作原理、创建过程
注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...
- [置顶] Java套接字Socket编程
1)概念 网络编程基本模型就客户端到服务器的模型,也就是我们常见的C/S模型.简单的说就是两个进程间相互通信的过程.即通信双方一方作为服务器等待客户端提出请求并给以回应,另一方作为客户端向服务器提出请 ...
- Java网络编程--套接字Socket
一.套接字Socket IP地址标志Internet上的计算机,端口号标志正在计算机上运行的进程(程序). 端口号被规定为一个16位的0--65535之间的整数,其中,0--1023被预先定义的服务通 ...
- 套接字编程,创建套接字socket
1.套接字地址结构: struct sockaddr { sa_family_t sa_family; char sa_data[14]; }; 其中,成员sa_family表示套接字的协议族类型,对 ...
随机推荐
- linux查找超过一定时间的文件,并批量删除
1.find . -maxdepth 4 -name "*-*" -mtime 3 -maxdepth的值决定是否对下面的子目录进行递归查找 -mtime 3表示查找刚好3天的: ...
- IE6支持min-width、max-width CSS样式属性
1.IE6支持max-width解决方法 IE6支持最大宽度,解决CSS代码: .yangshi{ max-width:1000px; _width:expression((document.docu ...
- 高性能WEB开发:深入理解页面呈现、重绘、回流
在讨论页面重绘.回流之前.需要对页面的呈现流程有些了解,页面是怎么把html结合css等显示到浏览器上的,下面的流程图显示了浏览器对页面的呈现的处理流程.可能不同的浏览器略微会有些不同.但基本上都是类 ...
- 使用apache-cxf-2.2.10来制作一个极简版WebService程序
原想拿最新版cxf来制作的,无奈Apache的zip包总下不下来,国内的apache-cxf-2.2.10却一蹴而就,也就用了这个版本.下载地址是:http://pan.baidu.com/s/1td ...
- ssh-keygen配置
1) 在本地机器中的~/.ssh/目录下执行下命令, ssh-keygen -t dsa 将生成两个文件,id_dsa和id_dsa.pub. 2) 将id_dsa.pub拷贝到远程机器,并且将id_ ...
- nDCG学习笔记
参考:http://en.wikipedia.org/wiki/Discounted_cumulative_gain Normalized Discounted Cumulative Gain:一种对 ...
- ajax交互Struts2的action(1)
1.客户端网页代码 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://w ...
- es5 - array - join
/** * join描述:将数组(或类数组对象)的所有元素连接到一个字符串中并返回此字符串. * join语法:arr .join([ separator]) * join参数:指定用于分隔数组的每对 ...
- WPF显示经常使用的几个显示文字控件TextBox, TextBlock, Lable
TextBox, TextBlock. Lable 当中TextBox 和Lable均继承了Control类 能够对其进行模板编辑. 而TextBlock没有继承Control所以不能对其进行模板编辑 ...
- 用sqldevelop进行连接linux中ret Hat 6.2 中的oracle步骤
1.下载Oracle Instant Client (32-bit) 因为PL/SQL只支持32位的Oracle,所以必须下载对应的32位的才可以. 只需要下载instantclient-basic- ...