天天看點

一起談.NET技術,模拟IIS向Silverlight輸出政策檔案

  最近的Silverlight開發中,由于部分需求對實時性和資料量下載下傳速度有要求,部分WCF服務配置成了netTcpBinding,這種方式跟普通的service.svc寄宿IIS不同的是,Silverlight需要的政策檔案需要放置在本機IIS的根下,也就是wwwroot檔案夾下,以滿足Silverlight在以TCP協定調用本機WCF服務時請求政策檔案。(注:Silverlight通過TCP協定調用WCF服務時,會以http方式請求主機的一個政策檔案,位址是http://localhost/clientaccesspolicy.xml)

  這其實是個不太好的選擇,程式運作的所需的環境被分成了兩部分,同僚的機器上并未安裝IIS,為了大家開發簡便,不用在額外安裝IIS,也為了讓程式更加獨立,我就想能不能寫代碼監控80端口模拟IIS向Silverlight輸出這個政策檔案。

  有了這個想法之後,首先想到的是通過Socket進行監聽,因為此前在MSDN上看到過這種方式,但很無奈,将代碼轉移過來之後,并未成功。相信做過Silverlight在Socket方面應用的朋友對下面這個PolicyServer類很熟悉吧。

using System;

using System.IO;

using System.Net;

using System.Net.Sockets;

namespace PolicyServer

{

// Encapsulate and manage state for a single connection from a client

class PolicyConnection

private Socket m_connection;

// buffer to receive the request from the client

private byte[] m_buffer;

private int m_received;

// the policy to return to the client

private byte[] m_policy;

// the request that we're expecting from the client

private static string s_policyRequestString = "<policy-file-request/>";

public PolicyConnection(Socket client, byte[] policy)

m_connection = client;

m_policy = policy;

m_buffer = new byte[s_policyRequestString.Length];

m_received = 0;

try

// receive the request from the client

m_connection.BeginReceive(m_buffer, 0, s_policyRequestString.Length, SocketFlags.None, new AsyncCallback(OnReceive), null);

}

catch (SocketException)

m_connection.Close();

// Called when we receive data from the client

private void OnReceive(IAsyncResult res)

m_received += m_connection.EndReceive(res);

// if we haven't gotten enough for a full request yet, receive again

if (m_received < s_policyRequestString.Length)

m_connection.BeginReceive(m_buffer, m_received, s_policyRequestString.Length - m_received, SocketFlags.None, new AsyncCallback(OnReceive), null);

return;

// make sure the request is valid

string request = System.Text.Encoding.UTF8.GetString(m_buffer, 0, m_received);

if (StringComparer.InvariantCultureIgnoreCase.Compare(request, s_policyRequestString) != 0)

// send the policy

m_connection.BeginSend(m_policy, 0, m_policy.Length, SocketFlags.None, new AsyncCallback(OnSend), null);

// called after sending the policy to the client; close the connection.

public void OnSend(IAsyncResult res)

m_connection.EndSend(res);

finally

// Listens for connections on port 943 and dispatches requests to a PolicyConnection

class PolicyServer

private Socket m_listener;

// pass in the path of an XML file containing the socket policy

public PolicyServer(string policyFile)

// Load the policy file

FileStream policyStream = new FileStream(policyFile, FileMode.Open);

m_policy = new byte[policyStream.Length];

policyStream.Read(m_policy, 0, m_policy.Length);

policyStream.Close();

m_listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);

m_listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);

m_listener.Bind(new IPEndPoint(IPAddress.IPv6Any, 943));

m_listener.Listen(10);

m_listener.BeginAccept(new AsyncCallback(OnConnection), null);

public void OnConnection(IAsyncResult res)

Socket client = null;

client = m_listener.EndAccept(res);

// handle this policy request with a PolicyConnection

PolicyConnection pc = new PolicyConnection(client, m_policy);

// look for more connections

public void Close()

m_listener.Close();

public class Program

static void Main(string[] args)

if (args.Length == 0)

Console.WriteLine("usage: PolicyServer.exe PolicyFile.xml");

PolicyServer ps = new PolicyServer(args[0]);

System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);

  此路不通之後,又想起使用HttpListener類,看看是否能夠監聽http請求,果然能夠截獲HTTP的請求。

HttpListener listener = new HttpListener();

listener.Prefixes.Add(http://localhost/);

listener.Start();Console.WriteLine("開始監聽…");

HttpListenerContext context = listener.GetContext();

HttpListenerRequest request = context.Request;

HttpListenerResponse response = context.Response;

  但是這種方式有個明顯的缺點,就是線程是阻塞的。于是,又想到使用線程池。

System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(Listen));

private static void Listen(object state)

while (httpListener.IsListening)

httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), httpListener);

listenForNextRequest.WaitOne();

  這樣的話,每接收一個請求便會異步處理這個請求。在請求的處理上,接收請求後需要向外輸出政策檔案流,供silverlight端接收驗證。

using (System.Net.HttpListenerResponse response = context.Response)

System.Threading.Thread.Sleep(1000);

string responseString = "<?xml version=\"1.0\" encoding=\"utf-8\"?> "

+ " <access-policy> "

+ " <cross-domain-access> "

+ " <policy> "

+ " <allow-from http-request-headers=\"*\">"

+ " <domain uri=\"*\" /> "

+ " </allow-from> "

+ " <grant-to> "

+ " <socket-resource port=\"4502-4534\" protocol=\"tcp\" /> "

+ " </grant-to> "

+ " </policy> "

+ " </cross-domain-access>"

+ " </access-policy>";

byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);

response.ContentLength64 = buffer.LongLength;

response.OutputStream.Write(buffer, 0, buffer.Length);

  啟動這個模拟服務,将clientaccesspolicy從wwwroot中移除後再運作一下程式,OK,我們不再需要将政策檔案放到IIS下了。

  如果你的機器裝了IIS,請還是放一個政策檔案到wwwroot吧,否則就停掉IIS再使用這個類,因為IIS和這個類隻能有一方監聽80端口。

繼續閱讀