剛接觸Silverlight的時候,除了其異步應用WCF、流媒體、動畫效果等方面外,Socket是最另我興奮的功能。
在Web上實作Socket雖然不是什麼新鮮事了,Activex,flash等都可以實作這樣的效果,但是Silverlight這樣友善的運用Socket讓伺服器與用戶端通信确是我之前沒有體驗過的。
用它可以做什麼?可以連線式的讓伺服器與用戶端互動,而且,是在Web上,那麼Web開發遊戲,語音,視訊聊天等都可以基于Socket功能實作,另外,伺服器端是獨立出來的,不依賴IIS程序,這樣讓資料之間的互動更自由。
廢話不說,下面來看看如何實作
首先,在進行資料交換之前,我們必須明白Silverlight Socket的一些規矩和原則。
Silverlight用戶端的Socket都是異步的,這點很容易明白,另外就是,考慮到Silverlight是應用到Web上的,而Silverlight的Socket自然就有一些安全限制。
每一個請求到伺服器端的新的Socket連接配接會話Silverlight都會先悄悄的用另一個Socket去請求政策檔案,這是很多剛接觸 Silverlight Socket的人感到郁悶的地方,請求政策時,Silverlight會自己發送一個字元串<policy-file-request/>到 伺服器的943端口,然後你必須在伺服器程式裡接收該請求,分析是否是政策請求後,發送一個政策檔案的字元串給用戶端,用戶端接收到政策檔案後自己分析完 後再發送程式員自己寫的資料請求。
用戶端的政策請求是自動發送的,政策檔案的接收和分析也是自動的,是Silverlight自發工作的,不需要程式員手工寫代碼進行發送接收和分析。
但是,伺服器端接收政策請求需要手工完成,程式員必須建立一個Socket監聽943端口(該端口是固定的,用戶端政策請求固定發送到該端口),然後分析請求過來的資料是否是政策請求,如果是的,那麼就讀取政策檔案,再将該政策檔案發送到用戶端就可以了。
另外一個限制,Silverlight Socket 資料交換端口必須在4502-4534範圍,也就是說,整個Socket将用到兩個端口,一個是943用于政策請求,另一個是4502-4534範圍的你指定的資料交換端口。
不管你的Socket代碼是如何工作,第一次在連接配接之前,Silverlight都會發送政策請求,隻有成功接收到伺服器傳回的政策檔案後,你的 Socket代碼才能進行工作,是以在第一次連接配接的時候,實際上Silverlight是進行了兩次Socket,第一次請求政策,成功才進行你的 Socket,是以,伺服器端必要監聽兩個端口,但是兩個監聽可以分開在兩個線程上工作(兩個線程,不是兩個程序)。每個會話請求一次政策後,之後的請求 就不會再請求政策了,是以他們不能是線性的工作,而是兩個獨立的監聽,否則會阻塞。
我的伺服器端的政策監聽和資料監聽是用的兩個子線程運作,而MS的示例是用的異步方法,都是為了不互相阻塞,用MS的方式也許更有效率些,而我是為了讓代碼更容易了解。
用戶端實作了将文本框的内容發送到伺服器端,然後伺服器收到後顯示出來,然後發回一句字元串,關閉連接配接,用戶端收到伺服器端的資訊後也關閉連接配接。就這麼簡單
好後,具體看看示例,說明很詳細。
用戶端
建立一個Silverlight項目
XAML
<UserControl x:Class="SilverlightTest.Socket1"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True">
<Grid.RowDefinitions >
<RowDefinition />
</Grid.RowDefinitions>
<TextBox x:Name="txtToSend" Grid.Row="0"/>
<Button Grid.Row="1" Click="OnSend" Content="Send" Margin="20" />
</Grid>
</UserControl>
代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace SilverlightTest
{
public partial class Socket1 : UserControl
{
public Socket1()
{
InitializeComponent();
}
//定義一個可在全局使用的Socket
System.Net.Sockets.Socket socket;
//定義一個同步上下文類,用來将子線程的操作排程到主線程上以可控制UI屬性。
SynchronizationContext syn;
//發送資訊按鈕的單擊事件
void OnSend(object sender, EventArgs args)
//定義一個位元組數組,并将文本框的的類容轉換為位元組數組後存入
byte[] bytes = Encoding.UTF8.GetBytes(txtToSend.Text);
//顯示資訊,可不要。
txtToSend.Text += "/r/nDnsSafeHost:"+Application.Current.Host.Source.DnsSafeHost;
//将同步上下文設定在目前上下文(線程,主線程,可控制UI的)
syn = SynchronizationContext.Current;
//為socket建立示例,并設定相關屬性。
socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
//定義并執行個體一個Socket參數
SocketAsyncEventArgs socketArgs = new SocketAsyncEventArgs();
//設定到遠端終節點屬性(4502端口,為什麼是4502,MS的SL通信安全上有)
socketArgs.RemoteEndPoint = new DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, 4502);
//設定好當Socket任何一個動作完成時的回調函數。
socketArgs.Completed += new EventHandler<SocketAsyncEventArgs>(socketArgs_Completed);
//Socket參數的使用者辨別,實際上就是一個可以傳遞的OBJECT參數。
socketArgs.UserToken = bytes;
//執行連接配接。
socket.ConnectAsync(socketArgs);
void socketArgs_Completed(object sender, SocketAsyncEventArgs e)
//當任何一個Socket動作完成,都回調該函數,然後對LastOperation進行判斷後繼續執行相應的部分
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
case SocketAsyncOperation.Send:
ProcessSend(e);
}
//将資料放入buffer并進行異步發送
void ProcessConnect(SocketAsyncEventArgs e)
//當連接配接成功後,擷取Socket參數 e傳遞過來的使用者辨別(也就是本示例中使用者輸入的字元串轉換的Byte位元組數組)
byte[] bytes = (byte[])e.UserToken;
//設定Socket參數的緩沖區參數,将我們的位元組數組設定為Socket的緩沖區。
e.SetBuffer(bytes, 0, bytes.Length);
//同步一下上下文,顯示一下目前的狀态資訊。
syn.Post(GetText,"States:"+e.SocketError.ToString()+","+e.LastOperation.ToString());
//發送資料
socket.SendAsync(e);
//發送完成後,執行等待接收伺服器發回的資料
void ProcessSend(SocketAsyncEventArgs e)
//定義個空的位元組數組,設定好其大小
byte[] bytes = new byte[1024];
//将前面定義位元組數組設定成緩沖區
//執行異步接收
socket.ReceiveAsync(e);
//當接收完成後
void ProcessReceive(SocketAsyncEventArgs e)
//在執行好接收後,本地SOCKET的緩沖區就會被伺服器發送的資料填充。
//顯示下資訊,當然也是用同步上下文的方式,在顯示資訊的時候,就直接将緩沖區的位元組數組轉換成字元串。
syn.Post(GetText, Encoding.UTF8.GetString(e.Buffer, 0,e.Buffer.Length)+" and Received" );
//關閉Socket連接配接
socket.Close();
//最後顯示下,Socket關閉。
syn.Post(GetText,"Socket Closed");
//同步上下文調用的方法。
void GetText(object str)
txtToSend.Text +="/r/n"+ str.ToString();
}
}
伺服器端,建立一個控制台項目
using System.IO;
namespace ConsoleApp
class Program
static void Main(string[] args)
Console.WriteLine("================Socket服務開啟======================");
//建立一個子線程,用于建立Socket來監聽政策請求和發送。
ThreadStart pcts = new ThreadStart(PolicyThread);
Thread policythread = new Thread(pcts);
policythread.Start();
//建立一個子線程,用于建立Socket來監聽資訊請求和發送。
ThreadStart infots = new ThreadStart(InfoThread);
Thread infothread = new Thread(infots);
infothread.Start();
//監聽政策請求和發送政策請求方法
static void PolicyThread()
//建立一個Socket用來監聽943(固定的)端口的政策請求
Socket policy = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
policy.Bind(new IPEndPoint(IPAddress.Any, 943));
policy.Listen(10);
//無限循環監聽
while (true)
if (policy.Blocking)//如果Socket是阻止模式的(這個東西實際上可以用不)
{
//建立Socket,用來擷取監聽Socket的第一個Socket連結
Socket _policy = policy.Accept();
//定義一個字元串,該字元串與Silverlight發送過來的請求字元串一樣。
string policyRequestString = "<policy-file-request/>";
//定義一個位元組數組
byte[] b = new byte[policyRequestString.Length];
//将用戶端發送過來,伺服器接收到的位元組數組存入b中
_policy.Receive(b);
//将接收到的位元組數組轉換成字元串
string requeststring = System.Text.Encoding.UTF8.GetString(b, 0, b.Length);
//顯示用戶端發送的字元串
Console.WriteLine(requeststring);
//比對用戶端發送過來的字元串是否和之前定義的額定好的政策請求字元串相同,如果相同,說明該請求是一個政策請求。
if (requeststring == policyRequestString)
{
//如果用戶端發送的是一個政策請求,伺服器發送政策檔案到用戶端
SendPolicy(_policy);
Console.WriteLine("Policy File have sended");
//關閉目前連接配接Socket
_policy.Close();
}
else// 否則,顯示錯誤
{
Console.WriteLine("not a sure request string!");
}
}
}
//監聽資訊請求和發送資訊方法
static void InfoThread()
//建立一個Socket用于監聽4502端口,擷取接收用戶端發送過來的資訊
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, 4502));
socket.Listen(10);
//無線循環監聽
//建立Socket,用來擷取監聽Socket的第一個Socket連結
Socket _socket = socket.Accept();
//建立一個空位元組數組
byte[] b2 = new byte[1024];
//将接受到的位元組數組存入到之前定義的b2位元組數組中。
_socket.Receive(b2);
//顯示接收到的資訊
Console.WriteLine(Encoding.UTF8.GetString(b2));
//發回一個資訊給用戶端,該資訊是位元組數組,是以我們将資訊字元串轉換成位元組數組
_socket.Send(Encoding.UTF8.GetBytes("This Send Over!!"));
//關閉目前Socket連接配接
_socket.Close();
//發送政策檔案的方法
//參數是傳遞進來的Socket連接配接
static void SendPolicy(Socket socket)
//建立一個檔案流,該檔案留指定代開一個政策檔案,至于政策檔案的格式,MS的Silverlight有詳細說明和配置方法
FileStream fs = new FileStream(@"D:/WebSites/SilverLight/ConsoleApp/bin/Debug/PolicyFile.xml",FileMode.Open);
int length = (int)fs.Length;
byte[] bytes = new byte[length];
//将政策檔案流讀到上面定義的位元組數組中
fs.Read(bytes,0,length);
//關閉檔案流
fs.Close();
//其政策檔案的位元組數組發送給用戶端
socket.Send(bytes,length,SocketFlags.None);
結束,這樣就可以建立一個簡單的Silverlight Socket收發程式了。其中還有許多需要改進的地方。但是已經很能說明Socket收發的過程和方法了,另外,伺服器端最還還是用異步方法進行監聽,這樣在多并發的時候比較有效率。