讓序列化與Socket合作愉快

來源:互聯網
上載者:User

我們在編寫與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應用程式時,不妨試試這種思路。本文的例子我稍候上傳到資源區。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.