让序列化与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应用程序时,不妨试试这种思路。本文的例子我稍候上传到资源区。

 

原创粉丝点击