.Net Socket編程基礎 -1

Socket Basic Concepts 

首先介紹Socket的一些基本概念

Socket是操作系統提供的一系列網絡編程接口。

網絡模型分若干層,也有一些協議,比如TCP協議,UDP協議等,這些都是抽象的定義,在硬件以及操作系統級別上有一些對應的實現,Socket可以看做操作系統爲開發人員提供的一系列網絡編程接口,它封裝了一些協議的細節,比如怎麼組織數據包,怎麼發送數據之類的。

Socket編程的幾個基本概念 

Endpoint
Endpoin指定要連接到哪裏,Endpoint包括兩部分內容,IP和Port,IP地址和端口組合起來才能唯一指定遠程的通信端。

AddressFamily
怎麼尋址,有了IP地址之後就是如何尋址的問題,常用的尋址方案是IP V4和IP V6兩種類型,windows操作系統從VISTA和Windows 20008起默認支持IPV6。

Protocol
使用什麼協議進行通信,比如TCP協議或者UDP協議,下面介紹Socket類型的時候還會涉及TCP和UDP等協議的介紹。

Socket類型

Socket有三種常用類型:Stream, Dgram, Raw

Stream流類型,支持可靠、雙向、基於連接的字節流,使用TCP協議。

Dgram數據報類型,支持數據報,即最大長度固定的無連接、不可靠消息。消息可能會丟失或重複並可能在到達時不按順序排列,使用UDP協議。

Raw類型支持對基礎傳輸協議的訪問,需要自己生成數據包。網上有一些RAW的例子,比如D.O.S攻擊,ARP攻擊,網絡監控之類的。

本文只討論Stream類型的Socket編程,RAW和Dgram不在討論之列,也就是隻討論基於TCP協議的編程。

一些常見的概念問題

Socket和TCP/IP有什麼關係?

Socket和TCP/IP不是一個層面的概念,Socket是操作系統提供的操作TCP數據的編程接口。

Sockets V4、Sockets V5有什麼區別?

經常看到一些軟件可以設置Sockets4/Sockets5代理,簡單說他們是客戶端與外網服務器之間通訊的協議,Sockets是位於應用層與傳輸層之間的中間層。 Sockets V4支持TCP, Sockets V5支持TCP/UDP,支持安全認證,支持IPV6。

Socket能夠同時接受和發送數據嗎?

TCP協議是雙工的

Socket如何保證數據按順序到達?

TCP協議來保證

Socket的基本通信模型模型

客戶端:

Socket()
Connect
Send
Close

服務器端:

Socket()
Bind
Listen
Accept
Receive
Send
Close

客戶端和服務器端模型是不一樣的,兩邊是非對稱的。

 .Net Socket API


下面是.Net Socket編程最基本的幾個類,位於命名空間System.Net.Sockets

Socket Socket接口類
TcpClient TCP客戶端類
TcpListener TCP偵聽類
NetworkStream 用於網絡訪問的基礎數據流

其他經常用到的輔助類,位於命名空間System.Net

Dns 域名解析
EndPoint  標識網絡地址
IPAddress  IP地址。
NetworkCredential 基於密碼的身份驗證方案,不支持基於公鑰的身份驗證方法(比如ssl)

一個Socket的簡單例子

輸入網址,獲得HTML頁面的一段演示代碼,只是演示Socket對象的幾個主要功能,不具有實用價值。

基本步驟爲:建立Socket對象,連接服務器,發送數據,然後接受數據,對應上一章介紹的Socket通信模型。

代碼

private string DownloadPage(string path)
        {
            Uri uri = new Uri(path);
            Encoding encoding = Encoding.UTF8;// .GetEncoding("gb2312");

            string requestHeader = BuildRequestHeader(uri);
            byte[] requestBytes = encoding.GetBytes(requestHeader);
            byte[] receivedBytes = new byte[1024 * 100];

            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect(uri.Host,uri.Port);
            socket.Send(requestBytes);

            int receivedBytesLength = socket.Receive(receivedBytes);
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();

            string html = string.Empty;
            if (receivedBytesLength > 0)
            {
                html = encoding.GetString(receivedBytes, 0, receivedBytesLength);
            }
            return html;
        }


構造HTTP Header的代碼如下,注意不要忘了Http頭模板的最後一行

private string BuildRequestHeader(Uri uri)
        {
            string httpHeaderTemplate = @"GET {url} HTTP/1.1
Connection: Close
Host: {host}

";
            return httpHeaderTemplate.Replace("{url}", uri.AbsolutePath)
                .Replace("{host}", uri.Host);

        }


30秒思考題:這段簡單代碼有什麼問題?

我們定義的用來接收數據的數組大小是固定的,如果要接受的數據超過數組大小怎麼辦? 可以定義一個緩衝區,每次接受固定大小的數據,直到接收完成爲止。示例代碼如下:

MemoryStream ms = new MemoryStream();
            while (true)
            {
                Console.WriteLine("Available :{0}", socket.Available);
                int receivedBytesLength = socket.Receive(receivedBytes, 0, receivedBytes.Length, SocketFlags.None);
                if (receivedBytesLength > 0)
                {
                    ms.Write(receivedBytes, 0, receivedBytesLength);
                }
                else
                {
                    break;
                }
            }

            string html = string.Empty;
            if (ms.Length > 0)
            {
                html = encoding.GetString(ms.ToArray(), 0, (int)ms.Length);
            }


Socket的緩衝區

Socket接收數據時,操作系統先把數據接收到緩衝區,然後通知程序,socket.Available 是從已經從網絡接收的、可供讀取的數據的字節數,這個值是指緩衝區中已接收數據的字節數,不是實際的數據大小。而且如果網絡有延遲,Send之後馬上讀取Available屬性不一定能讀到正確的值,所以不能利用socket.Available來判斷總共要接受的字節數。

在上面的方法中,如果沒有可讀取的數據,則 Receive 方法將一直處於阻止狀態,直到有數據可用,如果Server端也沒有正確關閉連接,程序很容易死在這裏,可以通過Socket.ReceiveTimeout來設置Socket對象接受數據的超時時間。

30秒思考題:爲什麼這樣下載的頁面有時候和瀏覽器下載的頁面不一樣?
>>gzip,chunked編碼,重定向等

NetworkStream的例子

前面講過基於TCP協議的Socket是Steam類型的,在操作系統中,爲了簡化編程,把設備、文件等都看作流對象,統一編程接口。NetworkStream類提供了在阻止模式下通過Socket套接字發送和接收數據的方法,.Net還提供了TcpClient和TcpListener類,用於簡化同步阻止模式下通過TCP協議連接、發送和接收流數據。下面的例子是這幾個對象的簡單介紹,省略了一些細節,也不具有實用價值。

這個例子模擬計算機遠程控制,先新起一個線程模擬服務進程,在這個線程中創建一個TcpListener對象,等待客戶端連接。用戶在客戶端界面點了“連接”按鈕後,UI線程創建TcpClient對象,等待用戶輸入dos命令,用戶輸入dos命令,按執行按鈕,這時TcpClient對象把用戶輸入的命令發送給TcpListener對象,服務進程執行完命令後,將執行結果反饋給TcpClient對象。

部分代碼。
       

private void StartServer()
        {
            TcpListener server = new TcpListener(IPAddress.Any, 10000);
            server.Start();
            Debug.WriteLine("Server: start");

            TcpClient client = server.AcceptTcpClient();
            Debug.WriteLine("Server : connection accept");
            NetworkStream stream = client.GetStream();

            Process process = new Process();
            process.StartInfo.FileName = "cmd.exe";
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardInput = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.CreateNoWindow = false;
            process.Start();

            process.OutputDataReceived += (Object sender, DataReceivedEventArgs e) =>
            {
                byte[] bytes = Encoding.GetEncoding("gb2312").GetBytes(e.Data + "\r\n");
                stream.Write(bytes, 0, bytes.Length);
            };
            process.BeginOutputReadLine();
            
            while (true)
            {
                Byte[] buffer = new Byte[1024 * 10];
                int length = stream.Read(buffer, 0, buffer.Length);
                if (length == 0)
                {
                    Debug.WriteLine("Server: read 0 byte");
                    break;
                }

                string command = Encoding.GetEncoding("gb2312").GetString(buffer, 0, length);
                Debug.WriteLine("Server: receive {0} ", command);

                StreamWriter Writer = process.StandardInput;
                Writer.WriteLine(command);
                Writer.Flush();
            }

            server.Stop();
            process.WaitForExit(1000);
            process.Close();
            Debug.WriteLine("Server: close");
        }

        TcpClient client;
        private void ConnectButton_Click(object sender, EventArgs e)
        {
            client = new TcpClient();
            client.Connect("localhost", 10000);
            Console.WriteLine("Client: connect");
            StartButton.Enabled = true;
        }

        private void StartButton_Click(object sender, EventArgs e)
        {
            if (CommandTextBox.Text.Trim().Length == 0)
            {
                return;
            }

            string request = CommandTextBox.Text.Trim();
            byte[] bytes = Encoding.GetEncoding("gb2312").GetBytes(request);
            NetworkStream stream = client.GetStream();
            stream.Write(bytes, 0, bytes.Length);
            
            Console.WriteLine("Client: request {0}", request);
            MessageTextBox.AppendText("\r\nresponse from server:\r\n");
            
            byte[] buffer = new byte[1024];
            do{
                int receivedBytesLength = stream.Read(buffer, 0, buffer.Length);
                if(receivedBytesLength > 0)
                {
                    string text = Encoding.GetEncoding("gb2312").GetString(buffer, 0, receivedBytesLength);
                    MessageTextBox.AppendText(text);
                    MessageTextBox.AppendText("\r\n");
                }
                else
                {
                    break;
                }
            }while(stream.DataAvailable);
        }




下一章介紹Socket異步編程模式

 

.Net Socket編程基礎 異步編程

同步socket併發性很差,特別是對於服務器端來說,要處理很多客戶端連接,同步Socket力不從心,要提高系統的併發處理能力,就要藉助.Net異步編程模式。

.Net Socket的異步編程模式和.Net 通用的APM編程模式是一致的,調用BeginXXX方法開始異步操作,系統系統處理完成後調用回調函數,在回調函數中調用EndXXX結束操作。

常用的Socket對象的異步API有
接受客戶端連接 BeginAccept, EndAccept
接收數據 BeginReceive, EndReceive
發送數據 BeginSend, EndSend

下面是簡單的Socket服務端實現的部分,演示Socket異步API的使用。

首先New一個Socket對象,綁定到特定端口,開始監聽客戶端發來的連接請求。

Socket socket;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(address, port));
socket.Listen(backlog); 
// backlog 參數指定隊列中最多可容納的等待接受的傳入連接數,不同操作系統的backlog參數的上限是不一樣的

//然後調用BeginAccept開始等待客戶端連接。
while (true)
{
    acceptDone.Reset();
    socket.BeginAccept(new AsyncCallback(AcceptCallback), socket);
    acceptDone.WaitOne();
}

//acceptDone是信號量
private ManualResetEvent acceptDone = new ManualResetEvent(false);

//AcceptCallback是回調函數,下面是這個函數的部分代碼

private void AcceptCallback(IAsyncResult ar)
{
    acceptDone.Set();
    // EndAccept 將會返回Windows自動分配的 Socket 對象實例
   Socket handler = listener.EndAccept(ar);
    log.InfoFormat("{0} connected", handler.RemoteEndPoint.ToString());

    //可以限制允許的最大連接數連接,保存當前對話的session信息等
   SocketState state = new SocketState(handler);
    SocketError errorCode;
    handler.BeginReceive(state.Buffer, 0, state.BufferSize, SocketFlags.None, out errorCode, new AsyncCallback(ReceiveCallback), state);

    if ((errorCode != SocketError.Success) && (errorCode != SocketError.IOPending))
    {
        ProcessError(handler, errorCode, "BeginReceive");
    }
}
BeginXXX的重載列表中一般有一個函數重載允許你傳一個SocketError 類型的out參數,可以根據這個參數判斷是否出錯。

SocketState對象是爲了方便寫代碼構造的用來保存當前Socket狀態的類,比如上次接受數據是什麼時間,一共接受了多少數據等。

public class SocketState : IDisposable
{
private Socket socket = null;
private const int bufferSize = 1024;
private byte[] buffer = new byte[bufferSize];
	。。。
}
操作系統接受到數據後會調用回調函數ReceiveCallback 

private void ReceiveCallback(IAsyncResult ar)
{
    SocketState state = (SocketState)ar.AsyncState;
    Socket handler = state.Socket;

    int bytesRead = handler.EndReceive(ar);
	
    if (bytesRead > 0)
    {
       //處理接受的數據
        。。。
        //如果還有數據,繼續接受
        if (handler.Available > 0 || handler.Connected )
          {
              SocketError errorCode;
              handler.BeginReceive(state.Buffer, 0, state.BufferSize, SocketFlags.None, out errorCode, new AsyncCallback(ReceiveCallback), state);
         }
         else
        {
            handler.Close();
            //其他處理代碼,比如重置連接計數器等
      }
     }
}
上面代碼只是簡單介紹API的用法,很多地方沒有考慮,完整的Socket服務代碼比這個要複雜得多。

BeginXXX、EndXXX這種異步模式比同步方式要高效得多,但是每次需要New一個IAsyncResult對象和state對象,併發高的情況下對GC也是不小的壓力,頻繁的申請、釋放小塊內存,容易產生很多小的內存碎片,內存碎片多的話內存利用率低,而且可能出現即使內存沒有全部用完,但是.Net Runtime還是會報告沒有可分配的內存的情況。另一方便,編程模型比較複雜。

爲了進一步簡化Socket異步編程,.Net3.5引入了新的編程模型。
 .Net 3.x 的Socket異步編程

在.Net 3.5中引入了一組增強功能,提供可供專用的高性能套接字應用程序使用的可選異步模式,並且簡化了Socket異步編程複雜度。SocketAsyncEventArgs類是這組增強功能中最常用的一個類,專爲需要高性能的網絡服務器應用程序而設計。它主要的成員如下:

OnCompleted事件:在異步操作完成後,系統會觸發OnCompleted事件,在事件處理代碼中可以進行後續異步套接字操作的處理。
SetBuffer方法:初始化要用於異步套接字方法的數據緩衝區。從上面的一些例子可以看到,不管同步還是異步,都需要一個緩衝數組來接受數據,使用SetBuffer可以簡化緩衝區的設置。
LastOperation屬性:獲取最近使用此上下文對象執行的套接字操作類型。Accept、Receive、Send都可以用SocketAsyncEventArgs類,需要通過這個屬性來判斷上次進行的是什麼操作。
SocketError:異步套接字操作的結果, SocketError.Success 表示操作成功完成

.Net 3.X這些Socket增強功能的主要特點是可以避免在異步套接字 I/O 量非常大時發生重複的對象分配和同步,怎麼實現呢?就是利用很通用的對象緩衝池技術。

一般需要對兩類對象使用對象池,一個對象池存放SocketAsyncEventArgs對象實例,一個對象池存放緩存byte[]實例,每次需要SocketAsyncEventArgs對象或者緩存數組時,就從對象池中取一個,用完了再放回對象池。對象池方式管理分配的對象屬於通用的高效內存使用方式,不是Socket編程特有的模式。

這兩個對象池實現的例子可以參考MSDN上的示例代碼。
	
使用此組執行異步套接字操作的模式包含以下步驟:(來自MSDN)
1.分配一個新的 SocketAsyncEventArgs 上下文對象,或者從應用程序池中獲取一個空閒的此類對象。
2.將該上下文對象的屬性設置爲要執行的操作(例如,完成回調方法、數據緩衝區、緩衝區偏移量以及要傳輸的最大數據量)。
3.調用適當的套接字方法 (xxxAsync) 以啓動異步操作。
4.如果異步套接字方法 (xxxAsync) 返回 true,則在回調中查詢上下文屬性來獲取完成狀態。
5.如果異步套接字方法 (xxxAsync) 返回 false,則說明操作是同步完成的。可以查詢上下文屬性來獲取操作結果。
6.將該上下文重用於另一個操作,將它放回到應用程序池中,或者將它丟棄。

用代碼來說明更直接一些,我們來改寫上面的例子

//創建Socket對象,開始監聽
//創建Accept用的SocketAsyncEventArgs對象實例,指定事件處理函數
SocketAsyncEventArgs  acceptArgs = new SocketAsyncEventArgs();
acceptArgs.Completed += Process_Accept;       

//開始等待客戶端的連接 
if (!socket.AcceptAsync(e))
{
    Process_Accept(this, e);
}

//在事件處理函數Process_Accept中
///。。。

//繼續等待其他客戶端連接
Socket acceptSocket = e.AcceptSocket;
StartAccept(e);

//開始異步接收數據
SocketAsyncEventArgs  socketReceiveArgs = new SocketAsyncEventArgs();
socketReceiveArgs.Completed += Process_Receive;
socketReceiveArgs.SetBuffer(receiveBuffer, 0, bufferSize);
if (!socket.ReceiveAsync(socketReceiveArgs))
{
    Process_Receive(this, socketReceiveArgs);
}

//在事件處理函數Process_Receive中處理接收到的數據

if (e.SocketError == SocketError.Success)
{
    if (e.BytesTransferred > 0)
    {
        byte[] data = new byte[e.BytesTransferred];
        Buffer.BlockCopy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);
        //處理數據
      。。。
      //繼續接受數據
      socketReceiveArgs.SetBuffer(receiveBuffer, 0, bufferSize);
        if (!socket.ReceiveAsync(socketReceiveArgs))
        {
             Process_Receive(this, socketReceiveArgs);
        }
    }
    else
    {
        //關閉連接
    }
}
else
{
    //錯誤處理
}
同樣以上代碼僅供觀賞,只演示了API的用法,很多情況沒考慮,不具有實用價值

下一節介紹Socket的錯誤處理
Socket錯誤檢測

這裏只介紹一些基本的錯誤檢測概念,不涉及一些具體類型的錯誤處理,比如檢測客戶端異常斷開。

Socket編程常見的錯誤類型是SocketException,ScoketException是從System.ComponentModel.Win32Exception繼承下來的,所以也有ErrorCode屬性,NativeErrorCode屬性和GetObjectData方法。NativeErrorCode對應Windows的Scoket錯誤代碼,具體含義可以在MSDN上查找。

程序出現了Socket異常也不一定出現了嚴重的網絡錯誤,需要關閉Socket連接,比如NativeErrorCode爲10035,代表操作沒法馬上完成,比如緩衝區已滿,或者緩衝區暫時沒有數據可以接受。

另外一個會遇到的異常是ObjectDisposedException,Socket連接斷開端口已經釋放時會引發這個異常。

在Socket異步編程模型中,有些錯誤是不能通過Try Catche這樣的方式檢測的,爲了判斷是否發生錯誤,在.Net 2.0的異步編程模型裏可以在調用BeginXXX或者EndXXX時使用SocketError類型的參數來檢測,SocketError是一個枚舉類型,表示Socket操作的處理結果,SocketError.Success代表成功處理,除此之外代表可能發生了錯誤,錯誤代碼的含義應該和上面SocketException異常的NativeErrorCode是一致的。

.Net 3.0提供的Socket異步編程可以用SocketAsyncEventArgs類的SocketError屬性來檢測是否發生錯誤,SocketError屬性是SocketError枚舉類型,在OnCompleted的事件處理函數中應該先檢測是否有錯誤發生,如果有錯誤要先根據錯誤類型處理錯誤。

接下來簡單介紹Silverlight中的Scoket編程。
 

Silverlight中的socket編程

 

如果沒有特聲明,我們下面所討論的內容都是基於Silverlight 3.0的。

Silverlight Socket編程和前面所述.net的socket編程類似,但是因爲web的特殊環境,出於安全等因素的考慮,Silverlight Socket有一些特殊的限制。

首先,Silverlight Socket都是異步的,也就是說,需要用前面講的.net 3.5的異步編程模式開發。

其次,Silverlight Socket只支持Tcp協議,並且Silverlight Socket限制了可使用端口的範圍,端口必須在4502-4534範圍之內。

另外一個比較重要的限制是Silverlight的安全策略系統。

 

Silverlight網絡安全策略

 

出於安全方面的原因,Silverlight socket發起一個新的連接請求時,需要先向遠端服務器請求一個策略文件,之後才允許網絡連接訪問該目標域下的網絡資源。

請求策略文件時,Silverlight發送一個字符串<policy-file-request/>到服務器的943端口,服務器程序需要接收該請求,分析是否是策略請求後,發送一個策略文件的字符串給客戶端。這個策略文件定義了Silverlight能訪問那些資源以及允許使用什麼樣的方式訪問。

 

Silverlight支持兩種網絡安全策略文件。

 

Flash 策略文件,此策略文件只可由 System.Net 命名空間中的 WebClient 和 HTTP 類使用。

Silverlight 策略文件,既可由 System.Net 命名空間中的 WebClient 和 HTTP 類使用,也可由 System.Net.Sockets 命名空間中的套接字類使用的 Silverlight 策略文件。

在連接某個網絡資源之前,Silverlight會嘗試從目標域下載安全策略文件。具體哪種類型取決於連接請求是來自WebClient或HTTP 類,還是來自Socket。客戶端請求策略文件的過程是Silverlight自動處理的,不需要你寫代碼去控制。

 

如果Silverlight收到了服務器返回的策略文件,在Silverlight應用程序的整個會話期間,該文件將用作Socket針對該目標站點的所有後續請求的策略文件。

 

如果沒有收到策略文件或者策略文件分析失敗,Silverlight將拒絕到網絡資源的連接,任何連接請求都將失敗。

 

下面的內容使用 DTD 介紹 Silverlight 策略文件格式, 每個元素的含義請參考msdn

http://msdn.microsoft.com/zh-cn/library/cc645032(VS.95).aspx

<?xml version=”1.0″ encoding=”ISO-8859-1″?>

<!– A DTD for the Silverlight Policy File –>

<!ELEMENT access-policy (cross-domain-access)>

<!ELEMENT cross-domain-access (policy+)>

<!ELEMENT policy (allow-from)>

<!ELEMENT policy (grant-to)>

<!ELEMENT allow-from (domain+)>

<!ATTLIST allow-from http-request-headers CDATA>

<!ELEMENT domain EMPTY >

<!ATTLIST domain uri CDATA #REQUIRED>

<!ELEMENT allow-from http-methods CDATA>

<!ELEMENT grant-to (resource+)>

<!ELEMENT grant-to (socket-resource+)>

<!ELEMENT grant-to EMPTY>

<!ATTLIST resource path CDATA #REQUIRED>

<!ATTLIST resource include-subpaths (true|false) “false”>

<!ATTLIST socket-resource port CDATA #REQUIRED protocol #REQUIRED>

<!– End of file. –>

 

下面是一個Socket策略文件的示例(MSDN的例子)

<?xml version=”1.0″ encoding =”utf-8″?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri=”file:///” />
</allow-from>
<grant-to>
<socket-resource port=”4502-4506″ protocol=”tcp” />
</grant-to>
</policy>

</cross-domain-access>

</access-policy>

Socket策略請求服務器端的實現比較簡單,可以參考MSDN Silverlight文檔中《Silverlight中的網絡安全訪問限制》,網上的例子也很多,不再累述。

 

Web上其他的通信方式

 

除了Silverlight Socket之外,還有很多種web端和服務器的通信方式。

 

HTTP Polling

基於AJAX的輪詢。比較容易理解,Web端以輪詢方式向服務器發請求。這種方式從直觀來看,實現起來最簡單,性能也有問題,但是簡單和複雜,快和慢都是相對的。

爲了提高效率,可以減少每次request和reponse傳輸數據量,比如簡化http header,簡化cookie,採用Restful web service等等。

好像web qq和google wave都是HTTP Polling機制。

 

Comet

基於HTTP長連接的”服務器推”技術,大概兩種類型:一種是服務器端阻塞異步AJAX http請求直到有數據或超時才返回,另外一種是利用Iframe服務器端將數據推到web端,與第一種不同的是,服務器端並不直接返回數據,而是返回對客戶端Javascript函數的調用。

Comnet效率比HTTP Polling高,但是架構比較複雜。web客戶端角度要注意瀏覽器對HTTP長連接數的限制,另外客戶端的控制請求和數據請求應該使用不同的HTTP連接。服務器端因

爲要維護大量的長連接需要注意服務的性能和可擴展性,有些web服務器專門針對comet優化過。

長連接通信的通用模式,服務器端和客戶端之間需要實現心跳算法來檢測另一方是否在線。

 

Flash XMLSocket

利用Flash的socket機制與Server通信。

 

Wcf HTTP Duplex Services

Silverlight 3.0可以使用Wcf HTTP Duplex Services實現Web端和服務器端的雙向通信,網上有很多例子,HTTP Duplex Services是基於HTTP Polling的。

 

Web Sockets API

還只是草案,參考地址:http://dev.w3.org/html5/websockets/

現在google chrome最新的開發版已經支持Web Sockets API,測試代碼如下

if ("WebSocket" in window) {	
  var ws = new WebSocket("ws://example.com/service");
  ws.onopen = function() {
    ws.send("message to send"); 
  };
  ws.onmessage = function (evt) {alert(evt.data); };
  ws.onclose = function() { alert("closed");};
} else {
  alert("sad");
}

http tunnel

講到HTTP,順便講HTTP 隧道(http tunnel)。

因爲很多防火牆或者代理服務器只支持http的相關端口,爲了能夠繞過這些限制,就要想辦法利用http(https)協議。

一般防火牆和代理服務器只是簡單的檢查http頭信息,可以要把要傳輸的數據僞裝成符合http協議的文本數據。

另外防火牆和代理服務器一般不會檢查https協議傳輸的內容,所以可以利用這一點來繞開端口限制,具體方法就是先發送一個http頭:

CONNECT xxxxx.com:443 HTTP/1.0
HOST xxxxx.com:443
一些http頭信息
//空行結束

代理服務器看到是https協議就會放行,返回

HTTP/1.0 200 Connection Established
http 頭信息//空行結束

連接就建立起來了。

 

通過配置文件來配置System.Net

 

詳細配置元素信息參考

msdn 網絡設置架構 http://msdn.microsoft.com/zh-cn/library/dacty7ed(VS.80).aspx
msdn system.Net 元素(網絡設置)
http://msdn.microsoft.com/zh-cn/library/6484zdc1(VS.80).aspx

 

Socket Trace

要Trace Socket的信息,可以自己寫日誌文件,也可以利用.Net自帶的Trace功能

配置節例子

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>

<system.diagnostics>

<trace autoflush="true" />

<sources>

<source name="System.Net" maxdatasize="1024">

<listeners>

<add name="MyTraceFile"/>

</listeners>

</source>

<source name="System.Net.Sockets">

<listeners>

<add name="MyTraceFile"/>

</listeners>

</source>

<sharedListeners>

<add

name="MyTraceFile"

type="System.Diagnostics.TextWriterTraceListener"

initializeData="System.Net.trace.log"

/>

</sharedListeners>

<switches>

<add name="System.Net" value="Verbose" />

</switches>

</system.diagnostics>

</configuration>

.Net 4.0中Socket編程新功能

 

DnsEndPoint

silverlight中已經有DnsEndPoint了,.Net 4.0也會增加。
IPAddress[] IPs = Dns.GetHostAddresses(“www.contoso.com”);

 

IP Version Neutrality

設置Socket的屬性爲IPv6Only爲false就能兼容IPv4,可以不用分別爲IPv4和IPv6創建socket對象了。

socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);

 

NAT Traversal

 

TcpListener的示例代碼

var listener = new TcpListener(IPAddress.IPv6Any, 8000);
listener.AllowNatTraversal(true);
listener.Start();

 

使用Socket的示例代碼

Socket新增了一個方法
public void SetIPProtectionLevel (IPProtectionLevel);

枚舉IPProtectionLevel的定義如下

public enum IPProtectionLevel
{
    Unspecified = –1,    // platform default
    Unrestricted = 10,   // global with NAT traversal
    EdgeRestricted = 20, // global without NAT traversal
    Restricted = 30,     // site local
}

或者通過配置文件來設置

<system.net>

<settings>

<!– default is platform defined (Unspecified) –>

<socket ipProtectionLevel=”Unrestricted | EdgeRestricted | Restricted | Unspecified”/>

</settings>

</system.net>

詳細信息參考End-to-end connectivity with NAT traversal

http://blogs.msdn.com/ncl/archive/2009/07/27/end-to-end-connectivity-with-nat-traversal-.aspx

 

Silverlight 4中的網絡新功能

UDP Multicast

Silverlight 4增加了兩個新類:UdpSingleSourceMulticastClient和UdpAnySourceMulticastClient來處理UDP Multicast。

 

Net.TCP Port Sharing Service

按照官方的說法,性能和比HTTP Polling Duplex有極大的提高。

吞吐量:對於UI線程來說提升了5.5倍,對於worker線程來說提升了870倍。
客戶端數量數:服務器可支持的連接客戶端數量是之前的5-6倍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章