之前因為項目的關系,涉及到與服務器實時通信,比如通過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客戶端:
后續工作中將結合地圖來實現模擬實時位置的顯示功能。。。。
(版權所有,轉載請標明出處)