Silverlight Socket通信學習筆記
之前因為項目的關系,涉及到與服務器實時通信,比如通過GPRS將GPS的位置信息等信息發(fā)送到服務器,然后再轉發(fā)給Silverlight應用程序,最后在地圖上標示出實時的地理位置,查了查相關的資料,網(wǎng)上給出的比較好的方法就是利用Socket與服務器通信。于是這兩天看了看Silverlight下的Socket通信,在此將學習的心得和實現(xiàn)過程作一個記錄,以供相互學習和交流。
園子里關于這方面的內(nèi)容已經(jīng)有很多大神寫過了,這里小小的推薦一下:
http://www.cnblogs.com/webabcd/archive/2008/12/22/1359551.html
因此本文的重點知識說一下具體實現(xiàn)的過程,細節(jié)和原理性的東西不會太多,因為本人也是新手,所以就不賣弄了。之前說到和地圖結合,所以本文的后續(xù)工作將會把Silverlight的Socket通信與ArcGIS 的地圖結合,來實現(xiàn)一個小小的功能,而本篇則主要關于Socket的實現(xiàn)過程。下面就進入正題吧。
一.Silverlight的Socket通信和控制臺、WinForm下的Socket通信有很大的區(qū)別。
對于后兩者的Socket通信,其過程就是開啟端口,綁定端口,監(jiān)聽端口,連接,接收數(shù)據(jù),發(fā)送數(shù)據(jù)。
而在Silverlight中則不太一樣,在Silverlight中,首先是Silverlight客戶端自動向943端口的服務器端發(fā)送一個“<policy-file-request/>”的語句請求,然后服務器端向客戶端發(fā)送策略文件:
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>
發(fā)送之后,才允許和服務器進行Socket通信,之后的過程則都是一樣。
二、服務器端
2.1、Silverligh中發(fā)送策略文件服務
上面說到,Silverlight中,服務器端會向客戶端發(fā)送策略文件,然后才能開始Socket通信,下面給出服務器端的發(fā)送策略文件服務的代碼,該代碼是在網(wǎng)上找的,是別人已經(jīng)寫好的一個類,所以在編寫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,監(jiān)聽端口,接收數(shù)據(jù),發(fā)送數(shù)據(jù)。
啟動策略文件服務
- #region Start The Policy Server 驗證策略文件
- PolicySocketServer StartPolicyServer = new PolicySocketServer();
- Thread th = new Thread(new ThreadStart(StartPolicyServer.StartSocketServer));
- th.IsBackground = true;
- th.Start();
- #endregion
聲明Socket,綁定端口,開始監(jiān)聽
- private void StartButton_Click(object sender, EventArgs e)
- {//創(chuàng)建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);
- //開始監(jiān)聽
- listener.Listen(10);
- //一直循環(huán)接收客戶端的消息,開啟監(jiān)聽端口線程
- ThreadStart threadwatchStart = new ThreadStart(WatchConnecting);
- threadWatch = new Thread(threadwatchStart);
- threadWatch.IsBackground = true;
- threadWatch.Start();
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Data.ToString());
- }
- }
連接端口,接收數(shù)據(jù)
- private void WatchConnecting()
- {
- ChangeStatue("等待Silverlight客戶端連接.....");
- while (true) //持續(xù)不斷監(jiān)聽客戶端發(fā)來的請求
- {
- 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("發(fā)送消息:" + SendTextBox.Text + "\n");
- }
- }
- }
發(fā)送數(shù)據(jù)
- 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("發(fā)送數(shù)據(jù)成功!");
- }
- }
- 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,指定服務器地址和端口,連接到指定的服務器端口,發(fā)送數(shù)據(jù),接收數(shù)據(jù)。
下面是具體的實現(xiàn)代碼:
聲明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);
- //創(chuàng)建一個Socket對象
- socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
- //創(chuàng)建Socket異步事件參數(shù)
- SocketAsyncEventArgs socketEventArg=new SocketAsyncEventArgs ();
- //將消息轉化為發(fā)送的byte[]格式
- byte[]buffer=Encoding.UTF8.GetBytes(MessageTextBox.Text);
- //注冊Socket完成事件
- socketEventArg.Completed+=new EventHandler<SocketAsyncEventArgs>(socketEventArg_Completed);
- //設置Socket異步事件遠程終結點
- socketEventArg.RemoteEndPoint=hostEntry;
- //將定義好的Socket對象賦值給Socket異步事件參數(shù)的運行實例屬性
- socketEventArg.UserToken = buffer;
- try
- {
- socket.ConnectAsync(socketEventArg);
- }
- catch(SocketException ex)
- {
- throw new SocketException((int)ex.ErrorCode);
- }
- }
向服務器發(fā)送數(shù)據(jù),并接受服務器回復的消息。
- private void socketEventArg_Completed(object sender, SocketAsyncEventArgs e)
- {
- //檢查是否發(fā)送出錯
- 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;
- }
- //如果連接上,則發(fā)送數(shù)據(jù)
- if (e.LastOperation == SocketAsyncOperation.Connect)
- {
- byte[] userbytes = (byte[])e.UserToken;
- e.SetBuffer(userbytes, 0, userbytes.Length);
- socket.SendAsync(e);
- }//如果已發(fā)送數(shù)據(jù),則開始接收服務器回復的消息
- else if (e.LastOperation == SocketAsyncOperation.Send)
- {
- Dispatcher.BeginInvoke(() =>
- {
- listBox1.Items.Add("客戶端在" + DateTime.Now.ToShortTimeString() + ",發(fā)送消息:" + MessageTextBox.Text);
- });
- byte[] userbytes = new byte[1024];
- e.SetBuffer(userbytes, 0, userbytes.Length);
- socket.ReceiveAsync(e);
- }//接收服務器數(shù)據(jù)
- 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="發(fā)送信息內(nèi)容" 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="發(fā)送" 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客戶端:
后續(xù)工作中將結合地圖來實現(xiàn)模擬實時位置的顯示功能。。。。
原文鏈接:http://www.cnblogs.com/potential/archive/2013/01/23/2873035.html