让序列化与Socket合作愉快
来源:互联网 发布:新版淘宝不能指纹支付 编辑:程序博客网 时间:2024/06/02 02:12
我们在编写与Socket有关的应用程序时,在发送软为复杂的数据时,可能我们最常做的是把各个部分的数据转换为字符串,然后将这些字符串用一个分隔符连接起来进行发送。不过,不知道你有没有想过这样做还是有问题的。
比如,我用#来分隔各个字符串,在根据客户端输入的内容到服务器端进行查找,然后返回结果,万一用户输入的查找关键字中就包含#,那么就会影响我们对字符串进行分割了。
不知道各位有没有想过,把序列化和反序列化的技术也用到socket上?先定义一个封装数据的类,发送前将该类的对象序列化,然后发送;接收时,先接收字节流,然后再反序列化,得到某个对象。
这样一来是不是比单单发送字符串好多了。
下面我举的这个例子,服务器端用WPF开发,客户端是Windows Store App,当然我这里只是举例,其实这可以用于所有类型的应用程序,包括Windows Phone应用,原理是不变的。
一、服务器端
首先我们定义一个用于封装数据的类,这里就以一个产品信息类做演示,这个类在服务器端和客户端都要定义一遍,这样才方便序列化和反序列化,你也可以特立写到一个类库中,然后服务器端和客户端都引用该类库。
[DataContract(Name = "product")] public class Product { /// <summary> /// 产品编号 /// </summary> [DataMember(Name = "product_no")] public string ProductNo { get; set; } /// <summary> /// 产品名称 /// </summary> [DataMember(Name = "product_name")] public string ProductName { get; set; } /// <summary> /// 产品单价 /// </summary> [DataMember(Name = "product_price")] public decimal ProductPrice { get; set; } }
WPF窗口的XAML
<Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Grid Grid.Row="0" Margin="11"> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <TextBlock Text="产品编号:" Grid.Column="0" Grid.Row="0" Style="{DynamicResource tbLabel}"/> <TextBlock Text="产品名称:" Grid.Column="0" Grid.Row="1" Style="{DynamicResource tbLabel}"/> <TextBlock Text="产品单价:" Grid.Column="0" Grid.Row="2" Style="{DynamicResource tbLabel}"/> <TextBox x:Name="txtProductNo" Grid.Column="1" Grid.Row="0" Style="{DynamicResource txtInput}"/> <TextBox x:Name="txtProductName" Grid.Column="1" Grid.Row="1" Style="{DynamicResource txtInput}"/> <TextBox x:Name="txtProductPrice" Grid.Column="1" Grid.Row="2" Style="{DynamicResource txtInput}"/> </Grid> <StackPanel Grid.Row="1" Margin="9" Orientation="Horizontal"> <Button x:Name="btnStartListen" Content="开始侦听" Padding="10,6" Click="OnStartListen"/> <Button x:Name="btnStopListen" Content="停止侦听" Padding="10,6" IsEnabled="False" Margin="18,0,0,0" Click="OnStopListen"/> </StackPanel> </Grid>
处理代码如下。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;using System.IO;using System.Net;using System.Net.Sockets;using System.Runtime.Serialization;using System.Runtime.Serialization.Json;namespace ServerApp{ /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { TcpListener listener = null; const int LOCAL_PORT = 2785;//监听端口 public MainWindow() { InitializeComponent(); } private void OnStartListen(object sender, RoutedEventArgs e) { this.listener = new TcpListener(IPAddress.Any, LOCAL_PORT); this.listener.Start(); btnStartListen.IsEnabled = false; btnStopListen.IsEnabled = true; // 接受传入连接 decimal d; if (decimal.TryParse(txtProductPrice.Text, out d) == false) { d = 0.00M; } Product prd = new Product { ProductNo = txtProductNo.Text == "" ? "000" : txtProductNo.Text, ProductName = txtProductName.Text == "" ? "No Name" : txtProductName.Text, ProductPrice = d }; listener.BeginAcceptTcpClient(new AsyncCallback(EndAcceptClientMethod), prd); MessageBox.Show("正在接受连接。"); } private void EndAcceptClientMethod(IAsyncResult ar) { Product prd = ar.AsyncState as Product; TcpClient client = null; // 发送消息 byte[] sendBuffer = this.SerializeObject<Product>(prd); try { client = listener.EndAcceptTcpClient(ar); var networkStream = client.GetStream(); // 先发送数据长度 byte[] bfLen = BitConverter.GetBytes(sendBuffer.Length); networkStream.Write(bfLen, 0, 4); // 发送数据 networkStream.Write(sendBuffer, 0, sendBuffer.Length); } catch (SocketException ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } catch (ObjectDisposedException dex) { System.Diagnostics.Debug.WriteLine("对象已释放。" + dex.Message); } catch (Exception eex) { System.Diagnostics.Debug.WriteLine(eex.Message); } finally { if (client != null) { client.Close(); } } // 继续接受连接 try { listener.BeginAcceptTcpClient(new AsyncCallback(EndAcceptClientMethod), prd); } catch { } } private void OnStopListen(object sender, RoutedEventArgs e) { if (this.listener != null) { this.listener.Stop(); } btnStartListen.IsEnabled = true; btnStopListen.IsEnabled = false; } /// <summary> /// 将对象序列化 /// </summary> /// <typeparam name="T">要进行序列化的类型</typeparam> /// <param name="t">要序列化的对象</param> /// <returns>序列化后的字节数组</returns> private byte[] SerializeObject<T>(T t) where T : class { byte[] buffer = null; // 开始序列化 using (MemoryStream ms = new MemoryStream()) { DataContractJsonSerializer ss = new DataContractJsonSerializer(t.GetType()); ss.WriteObject(ms, t); buffer = ms.ToArray(); } return buffer; } } [DataContract(Name = "product")] public class Product { /// <summary> /// 产品编号 /// </summary> [DataMember(Name = "product_no")] public string ProductNo { get; set; } /// <summary> /// 产品名称 /// </summary> [DataMember(Name = "product_name")] public string ProductName { get; set; } /// <summary> /// 产品单价 /// </summary> [DataMember(Name = "product_price")] public decimal ProductPrice { get; set; } }}
由于只做演示,接受连接后只发送一次数据就关闭连接。
二、客户端
这里只放出核心代码。
namespace ClientApp{ /// <summary> /// 可用于自身或导航至 Frame 内部的空白页。 /// </summary> public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } /// <summary> /// 在此页将要在 Frame 中显示时进行调用。 /// </summary> /// <param name="e">描述如何访问此页的事件数据。Parameter /// 属性通常用于配置页。</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } /// <summary> /// 反序列化对象 /// </summary> /// <typeparam name="T">要反序列化的类型</typeparam> /// <param name="buffer">字节数组</param> /// <returns>反序列化后的对象</returns> private T DeSerializeObject<T>(byte[] buffer) where T : class { T t = default(T); using (MemoryStream ms = new MemoryStream(buffer)) { ms.Position = 0; DataContractJsonSerializer ss = new DataContractJsonSerializer(typeof(T)); t = (T)ss.ReadObject(ms); } return t; } private async void OnClick(object sender, RoutedEventArgs e) { StreamSocket socket = new StreamSocket(); HostName host = new HostName(txtHost.Text); // 连接服务器 await socket.ConnectAsync(host, txtPort.Text); // 读取数据 DataReader reader = new DataReader(socket.InputStream); reader.ByteOrder = ByteOrder.LittleEndian; // 加载4字节,读取长度 await reader.LoadAsync(sizeof(int)); int len = reader.ReadInt32(); // 加载剩余字节 await reader.LoadAsync((uint)len); IBuffer readBuffer = reader.ReadBuffer((uint)len); // 反序列化 Product prd = this.DeSerializeObject<Product>(readBuffer.ToArray()); if (prd != null) { this.tbContent.Text = string.Format("产品编号:{0}\n产品名称:{1}\n产品单价:{2}", prd.ProductNo, prd.ProductName, prd.ProductPrice.ToString("C2")); } } } [DataContract(Name = "product")] public class Product { /// <summary> /// 产品编号 /// </summary> [DataMember(Name = "product_no")] public string ProductNo { get; set; } /// <summary> /// 产品名称 /// </summary> [DataMember(Name = "product_name")] public string ProductName { get; set; } /// <summary> /// 产品单价 /// </summary> [DataMember(Name = "product_price")] public decimal ProductPrice { get; set; } }}
下在是测试结果。
本例我使用的是JSON序列化和反序列化。
这个例子只是知识与技巧的综合,没有涉及到新的概念,所以就不多解释了。用socket收发数据比较好的做法是先将要发送的数据的长度先发送给对方,然后再发数据内容,这样可以保证正确的读取数据。
大家在编写socket应用程序时,不妨试试这种思路。本文的例子我稍候上传到资源区。
- 让序列化与Socket合作愉快
- Android:规范命名,让合作更加愉快
- 如何让周一更愉快
- 让.Net验证控件与自定义验证合作无间
- Socket编程(四)--对象序列化与反序列化
- 序列化与反序列化 Socket中处理方法
- 愉快
- 使用 ant 让你愉快编程(1)
- 使用 ant 让你愉快编程(2)
- 使用 ant 让你愉快编程(3)
- 使用 ant 让你愉快编程(4)
- 使用 ant 让你愉快编程(5)
- 使用 ant 让你愉快编程(6)
- 使用 ant 让你愉快编程(7)
- 使用 ant 让你愉快编程
- 使用 ant 让你愉快编程(1)
- 使用 ant 让你愉快编程(2)
- 使用 ant 让你愉快编程(3)
- Java 学习笔记——字符串
- sudoers文件解析
- java MD5加密类
- Access by Offset
- Maven和Tycho
- 让序列化与Socket合作愉快
- javaWeb得到上传的文件
- Vbox虚拟机 所选虚拟电脑不能正常访问
- Beyond Compare 关于回车换行的问题
- 使用Preferencescreen启动Activity
- WinCE Display驱动开发介绍
- css3 transition split(分裂旋转效果)
- 随机生成ID属性值数字大小写字母
- 两个表关联更新