Silverlight Socket通信學習筆記


      之前因為項目的關系,涉及到與服務器實時通信,比如通過GPRS將GPS的位置信息等信息發送到服務器,然后再轉發給Silverlight應用程序,最后在地圖上標示出實時的地理位置,查了查相關的資料,網上給出的比較好的方法就是利用Socket與服務器通信。於是這兩天看了看Silverlight下的Socket通信,在此將學習的心得和實現過程作一個記錄,以供相互學習和交流。

      園子里關於這方面的內容已經有很多大神寫過了,這里小小的推薦一下:

      http://www.cnblogs.com/webabcd/archive/2008/12/22/1359551.html

      因此本文的重點知識說一下具體實現的過程,細節和原理性的東西不會太多,因為本人也是新手,所以就不賣弄了。之前說到和地圖結合,所以本文的后續工作將會把Silverlight的Socket通信與ArcGIS 的地圖結合,來實現一個小小的功能,而本篇則主要關於Socket的實現過程。下面就進入正題吧。

 

一.Silverlight的Socket通信和控制台、WinForm下的Socket通信有很大的區別。

對於后兩者的Socket通信,其過程就是開啟端口,綁定端口,監聽端口,連接,接收數據,發送數據。

而在Silverlight中則不太一樣,在Silverlight中,首先是Silverlight客戶端自動向943端口的服務器端發送一個“<policy-file-request/>”的語句請求,然后服務器端向客戶端發送策略文件:

clientaccesspolicy.xml,例如:

<?xml version="1.0" encoding="utf-8" ?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <socket-resource port="4502-4534" protocol="tcp"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

發送之后,才允許和服務器進行Socket通信,之后的過程則都是一樣。

二、服務器端

2.1、Silverligh中發送策略文件服務

上面說到,Silverlight中,服務器端會向客戶端發送策略文件,然后才能開始Socket通信,下面給出服務器端的發送策略文件服務的代碼,該代碼是在網上找的,是別人已經寫好的一個類,所以在編寫Silverlight 的Socket通信程序時,只要添加這個類就好了,代碼如下:

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using System.Windows.Forms;

namespace WindowsServer
{
    class PolicySocketServer
    {
        TcpListener _Listener = null;
        TcpClient _Client = null;
        static ManualResetEvent _TcpClientConnected = new ManualResetEvent(false);
        const string _PolicyRequestString = "<policy-file-request/>";
        int _ReceivedLength = 0;
        byte[] _Policy = null;
        byte[] _ReceiveBuffer = null;

        private void InitializeData()
        {
            string policyFile = Path.Combine(Application.StartupPath, "clientaccesspolicy.xml");
            using (FileStream fs = new FileStream(policyFile, FileMode.Open))
            {
                _Policy = new byte[fs.Length];
                fs.Read(_Policy, 0, _Policy.Length);
            }
            _ReceiveBuffer = new byte[_PolicyRequestString.Length];
        }
        public void StartSocketServer()
        {
            InitializeData();

            try
            {
                _Listener = new TcpListener(IPAddress.Any, 943);
                _Listener.Start();
                while (true)
                {
                    _TcpClientConnected.Reset();
                    _Listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null);
                    _TcpClientConnected.WaitOne();
                }
            }
            catch (Exception)
            {
            }
        }

        private void OnBeginAccept(IAsyncResult ar)
        {
            _Client = _Listener.EndAcceptTcpClient(ar);
            _Client.Client.BeginReceive(_ReceiveBuffer, 0, _PolicyRequestString.Length, SocketFlags.None,
                new AsyncCallback(OnReceiveComplete), null);
        }

        private void OnReceiveComplete(IAsyncResult ar)
        {
            try
            {
                _ReceivedLength += _Client.Client.EndReceive(ar);
                if (_ReceivedLength < _PolicyRequestString.Length)
                {
                    _Client.Client.BeginReceive(_ReceiveBuffer, _ReceivedLength,
                        _PolicyRequestString.Length - _ReceivedLength,
                        SocketFlags.None, new AsyncCallback(OnReceiveComplete), null);
                    return;
                }
                string request = System.Text.Encoding.UTF8.GetString(_ReceiveBuffer, 0, _ReceivedLength);
                if (StringComparer.InvariantCultureIgnoreCase.Compare(request, _PolicyRequestString) != 0)
                {
                    _Client.Client.Close();
                    return;
                }
                _Client.Client.BeginSend(_Policy, 0, _Policy.Length, SocketFlags.None,
                    new AsyncCallback(OnSendComplete), null);
            }
            catch (Exception)
            {
                _Client.Client.Close();
            }
            _ReceivedLength = 0;
            _TcpClientConnected.Set(); //Allow waiting thread to proceed 
        }

        private void OnSendComplete(IAsyncResult ar)
        {
            try
            {
                _Client.Client.EndSendFile(ar);
            }
            catch (Exception)
            {
            }
            finally
            {
                _Client.Client.Close();
            }
        } 
    }
}

2.2、啟動策略文件服務,聲明Socket,監聽端口,接收數據,發送數據。

啟動策略文件服務

#region Start The Policy Server 驗證策略文件
            PolicySocketServer StartPolicyServer = new PolicySocketServer();
            Thread th = new Thread(new ThreadStart(StartPolicyServer.StartSocketServer));
            th.IsBackground = true;
            th.Start();
            #endregion 

聲明Socket,綁定端口,開始監聽

 private void StartButton_Click(object sender, EventArgs e)
        {//創建Socket
            listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //獲取主機信息
            IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());

            //把IP和端口轉換化為IPEndPoint實例,端口號取4530
            //Win7 中開啟了IPV6的地址,因此0,1對應的是IPV6的地址,2,3對應IPV4地址,3對應本機的IP地址
            //XP中沒有開啟IPV6
            HostIPTextBox.Text = ipHostInfo.AddressList[3].ToString();

            if (!string.IsNullOrEmpty(PortTextBox.Text))
            {
                //獲取端口號
                int port = Convert.ToInt32(PortTextBox.Text.Trim());
                //獲得本機的IP地址
                localEP = new IPEndPoint(ipHostInfo.AddressList[3], port);
            }
            else
            {
               //默認4530端口
                ipAddress = IPAddress.Parse("127.0.0.1");
                localEP = new IPEndPoint(ipHostInfo.AddressList[3], 4530);
            }

            try
            {
                //綁定指定的終結點
                listener.Bind(localEP);
                //開始監聽
                listener.Listen(10);
                //一直循環接收客戶端的消息,開啟監聽端口線程
                ThreadStart threadwatchStart = new ThreadStart(WatchConnecting);
                threadWatch = new Thread(threadwatchStart);
                threadWatch.IsBackground = true;
                threadWatch.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Data.ToString());
            }
        }

連接端口,接收數據

   private void WatchConnecting()
        {
            ChangeStatue("等待Silverlight客戶端連接.....");
            while (true)  //持續不斷監聽客戶端發來的請求  
            {
                listener.BeginAccept(AcceptCallBack, listener);
                _flipFlop.WaitOne();
            }
        }
 private  void AcceptCallBack(IAsyncResult asyresult)
        {
            Socket listener = (Socket)asyresult.AsyncState;
            Socket socket = listener.EndAccept(asyresult);
            ChangeStatue("連接到Silverlight客戶端....");
            _flipFlop.Set();
            var state = new StateObject();
            state.Socket = socket;
            socket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, ReciverCallBack, state);
        }
  private void ReciverCallBack(IAsyncResult asyResult)
        {
            StateObject state = (StateObject)asyResult.AsyncState;
            Socket socket = state.Socket;
            int read = socket.EndReceive(asyResult);
            if (read > 0)
            {
                string chunk = Encoding.UTF8.GetString(state.Buffer, 0, read);
                state.StringBuilder.Append(chunk);
                if (state.StringBuilder.Length > 0)
                {
                    string result = state.StringBuilder.ToString();
                    ChangeStatue("成功接收到消息:"+result);
                    ChangeReciveText(result);
                    Send(socket, SendTextBox.Text);
                    AddListItems("接收消息:"+result+"\n");
                    AddListItems("發送消息:" + SendTextBox.Text + "\n");
                }
            }
        }

發送數據

 private void Send(Socket handler, String data)
        {
            byte[] byteData = Encoding.UTF8.GetBytes(data);
            handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallBack), handler);
        }

        private void SendCallBack(IAsyncResult asyResult)
        {
            try
            {
                Socket handler = (Socket)asyResult.AsyncState;
                int byteSent = handler.EndSend(asyResult);
                if (byteSent > 0)
                {
                    ChangeStatue("發送數據成功!");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Data.ToString());
            }
        }

StateObject類:

 public class StateObject
    {
        public Socket Socket;
        public StringBuilder StringBuilder = new StringBuilder();
        public const int BufferSize = 1024;
        public byte[] Buffer = new byte[BufferSize];
        public int TotalSize;
    }

客戶端:

 和服務器端類似,客戶端的操作包括:聲明Socket,指定服務器地址和端口,連接到指定的服務器端口,發送數據,接收數據。

下面是具體的實現代碼:

聲明Socket

 

private Socket socket;

 

指定服務器地址和端口,開始連接

  private void SendButton_Click(object sender, RoutedEventArgs e)
        {
            if(string.IsNullOrEmpty(IPTextBox.Text)||string.IsNullOrEmpty(PortTextBox.Text))
            {
                MessageBox.Show ("請輸入主機IP地址和端口號!");
                return;
            }
            //ip地址
            string host=IPTextBox.Text.Trim();
            //端口號
            int port=Convert.ToInt32(PortTextBox.Text.Trim());
            //建立終結點對象
            DnsEndPoint hostEntry=new DnsEndPoint(host,port);
            //創建一個Socket對象
            socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            //創建Socket異步事件參數
            SocketAsyncEventArgs socketEventArg=new SocketAsyncEventArgs ();
            //將消息轉化為發送的byte[]格式
            byte[]buffer=Encoding.UTF8.GetBytes(MessageTextBox.Text);
            //注冊Socket完成事件
            socketEventArg.Completed+=new EventHandler<SocketAsyncEventArgs>(socketEventArg_Completed);
            //設置Socket異步事件遠程終結點
            socketEventArg.RemoteEndPoint=hostEntry;
            //將定義好的Socket對象賦值給Socket異步事件參數的運行實例屬性
            socketEventArg.UserToken = buffer;
            try
            {
                socket.ConnectAsync(socketEventArg);
            }
            catch(SocketException ex)
            {
                throw new SocketException((int)ex.ErrorCode);
            }
        }

向服務器發送數據,並接受服務器回復的消息。

 private void socketEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
           //檢查是否發送出錯
            if (e.SocketError != SocketError.Success)
            {
                if (e.SocketError == SocketError.ConnectionAborted)
                {
                    Dispatcher.BeginInvoke(() => MessageBox.Show("連接超時....請重試!"));
                }
                else if (e.SocketError == SocketError.ConnectionRefused)
                {
                    Dispatcher.BeginInvoke(() => MessageBox.Show("無法連接到服務器端:"+e.SocketError));
                }else
                {
                    Dispatcher.BeginInvoke(() => MessageBox.Show("出錯了!"+e.SocketError));
                }
                return;
            }
           //如果連接上,則發送數據
            if (e.LastOperation == SocketAsyncOperation.Connect)
            {
                    byte[] userbytes = (byte[])e.UserToken;
                    e.SetBuffer(userbytes, 0, userbytes.Length);
                    socket.SendAsync(e);
                    
            }//如果已發送數據,則開始接收服務器回復的消息
            else if (e.LastOperation == SocketAsyncOperation.Send)
            {
                Dispatcher.BeginInvoke(() =>
                {
                    listBox1.Items.Add("客戶端在" + DateTime.Now.ToShortTimeString() + ",發送消息:" + MessageTextBox.Text);
                });
                byte[] userbytes = new byte[1024];
                e.SetBuffer(userbytes, 0, userbytes.Length);
                socket.ReceiveAsync(e);
            }//接收服務器數據
            else if (e.LastOperation == SocketAsyncOperation.Receive)
            {
                string RecevieStr = Encoding.UTF8.GetString(e.Buffer, 0, e.Buffer.Length).Replace("\0", "");
                Dispatcher.BeginInvoke(() =>
                {
                    listBox1.Items.Add("服務器在" + DateTime.Now.ToShortTimeString() + ",回復消息:" + RecevieStr);
                });
                socket.Close();
            }
        }
   

xaml代碼:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:esri="http://schemas.esri.com/arcgis/client/2009" x:Class="SilverlightSocket.MainPage"
    mc:Ignorable="d"
    d:DesignHeight="417" d:DesignWidth="530">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.868*"/>
            <ColumnDefinition Width="0.135*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <esri:Map Background="White" WrapAround="True" Grid.ColumnSpan="2">
            <esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
        </esri:Map>
        <StackPanel Grid.Column="1" Background="#7F094870">
            <StackPanel.Effect>
                <DropShadowEffect/>
            </StackPanel.Effect>
            <TextBlock x:Name="textBlock1" Text="主機IP" Grid.Column="1" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
            <TextBox x:Name="IPTextBox" Text="169.254.57.67" Grid.Column="1" d:LayoutOverrides="Width" Margin="5,5,0,0" HorizontalAlignment="Left"/>
            <TextBlock x:Name="textBlock2" Text="端口號" Grid.Column="1" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
            <TextBox x:Name="PortTextBox" Width="51" Text="4530" Grid.Column="1" Margin="5,5,0,0" HorizontalAlignment="Left"/>
            <TextBlock  x:Name="textBlock4" Text="消息記錄:" Height="23" Grid.Column="1" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
            <ListBox  x:Name="listBox1" Grid.Column="1" Margin="5,5,0,0" Height="150" />
            <TextBlock x:Name="textBlock3" Text="發送信息內容" Height="16" Grid.Column="1" d:LayoutOverrides="Width" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
            <TextBox x:Name="MessageTextBox" Grid.Column="1" Height="50" Margin="5,5,0,0" />
            <Button Content="發送" Height="23" x:Name="SendButton" Grid.Column="1" Margin="5,5,0,0" />
            <Button Content="清空" Height="23" x:Name="ClearButton" Grid.Column="1" Margin="5,5,0,0" />
        </StackPanel>
    </Grid>
</UserControl>

最后效果示意圖:

服務器端:

Silverlight客戶端:

后續工作中將結合地圖來實現模擬實時位置的顯示功能。。。。

 

 

(版權所有,轉載請標明出處)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM