天天看點

使用異步伺服器套接字程式設計指南(微軟MSDN)

異步伺服器套接字使用 .NET Framework 異步程式設計模型處理網絡服務請求。Socket 類遵循标準 .NET Framework 異步命名模式;例如,同步 Accept 方法對應異步 BeginAccept 和 EndAccept 方法。

異步伺服器套接字需要一個開始接受網絡連接配接請求的方法,一個處理連接配接請求并開始接收網絡資料的回調方法以及一個結束接收資料的回調方法。本節将進一步讨論所有這些方法。

在下面的示例中,為開始接受網絡連接配接請求,方法 StartListening 初始化 Socket,然後使用 BeginAccept 方法開始接受新連接配接。當套接字上接收到新連接配接請求時,将調用接受回調方法。它負責擷取将處理連接配接的 Socket 執行個體,并将 Socket 送出給将處理請求的線程。接受回調方法實作 AsyncCallback 委托;它傳回 void,并帶一個 IAsyncResult 類型的參數。下面的示例是接受回調方法的外殼程式。

[Visual Basic]

Sub acceptCallback(ar As IAsyncResult)

    ' Add the callback code here.

End Sub 'acceptCallback

[C#]

void acceptCallback( IAsyncResult ar) {

    // Add the callback code here.

}

BeginAccept 方法帶兩個參數:指向接受回調方法的 AsyncCallback 委托和一個用于将狀态資訊傳遞給回調方法的對象。在下面的示例中,偵聽 Socket 通過狀态參數傳遞給回調方法。本示例建立一個 AsyncCallback 委托并開始接受網絡連接配接。

listener.BeginAccept( _

    New AsyncCallback(SocketListener.acceptCallback),_

    listener)

listener.BeginAccept(

    new AsyncCallback(SocketListener.acceptCallback), 

    listener);

異步套接字使用系統線程池中的線程處理傳入的連接配接。一個線程負責接受連接配接,另一線程用于處理每個傳入的連接配接,還有一個線程負責接收連接配接資料。這些線程可以是同一個線程,具體取決于線程池所配置設定的線程。在下面的示例中,System.Threading.ManualResetEvent 類挂起主線程的執行并在執行可以繼續時發出信号。

下面的示例顯示在本地計算機上建立異步 TCP/IP 套接字并開始接受連接配接的異步方法。它假定以下内容:存在一個名為 allDone 的全局 ManualResetEvent,該方法是一個名為 SocketListener 的類的成員,以及定義了一個名為 acceptCallback 的回調方法。

Public Sub StartListening()

    Dim ipHostInfo As IPHostEntry = Dns.Resolve(Dns.GetHostName())

    Dim localEP = New IPEndPoint(ipHostInfo.AddressList(0), 11000)

    Console.WriteLine("Local address and port : {0}", localEP.ToString())

    Dim listener As New Socket(localEP.Address.AddressFamily, _

       SocketType.Stream, ProtocolType.Tcp)

    Try

        listener.Bind(localEP)

        s.Listen(10)

        While True

            allDone.Reset()

            Console.WriteLine("Waiting for a connection...")

            listener.BeginAccept(New _

                AsyncCallback(SocketListener.acceptCallback), _

                listener)

            allDone.WaitOne()

        End While

    Catch e As Exception

        Console.WriteLine(e.ToString())

    End Try

    Console.WriteLine("Closing the listener...")

End Sub 'StartListening

public void StartListening() {

    IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());

    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0],11000);

    Console.WriteLine("Local address and port : {0}",localEP.ToString());

    Socket listener = new Socket( localEP.Address.AddressFamily,

        SocketType.Stream, ProtocolType.Tcp );

    try {

        listener.Bind(localEP);

        s.Listen(10);

        while (true) {

            allDone.Reset();

            Console.WriteLine("Waiting for a connection...");

            listener.BeginAccept(

                new AsyncCallback(SocketListener.acceptCallback), 

                listener );

            allDone.WaitOne();

        }

    } catch (Exception e) {

        Console.WriteLine(e.ToString());

    }

    Console.WriteLine( "Closing the listener...");

接受回調方法(即前例中的 acceptCallback)負責向主應用程式發出信号,讓它繼續執行處理、建立與用戶端的連接配接并開始異步讀取用戶端資料。下面的示例是 acceptCallback 方法實作的第一部分。該方法的此節向主應用程式線程發出信号,讓它繼續處理并建立與用戶端的連接配接。它采用一個名為 allDone 的全局 ManualResetEvent。

Public Sub acceptCallback(ar As IAsyncResult)

    allDone.Set()

    Dim listener As Socket = CType(ar.AsyncState, Socket)

    Dim handler As Socket = listener.EndAccept(ar)

    ' Additional code to read data goes here.

public void acceptCallback(IAsyncResult ar) {

    allDone.Set();

    Socket listener = (Socket) ar.AsyncState;

    Socket handler = listener.EndAccept(ar);

    // Additional code to read data goes here.  

從用戶端套接字讀取資料需要一個在異步調用之間傳遞值的狀态對象。下面的示例實作一個用于從遠端用戶端接收字元串的狀态對象。它包含以下各項的字段:用戶端套接字,用于接收資料的資料緩沖區,以及用于建立用戶端發送的資料字元串的 StringBuilder。将這些字段放在該狀态對象中,使這些字段的值在多個調用之間得以保留,以便從用戶端套接字讀取資料。

Public Class StateObject

    Public workSocket As Socket = Nothing

    Public BufferSize As Integer = 1024

    Public buffer(BufferSize) As Byte

    Public sb As New StringBuilder()

End Class 'StateObject

public class StateObject {

    public Socket workSocket = null;

    public const int BufferSize = 1024;

    public byte[] buffer = new byte[BufferSize];

    public StringBuilder sb = new StringBuilder();

開始從用戶端套接字接收資料的 acceptCallback 方法的此節首先初始化 StateObject 類的一個執行個體,然後調用 BeginReceive 方法以開始從用戶端套接字異步讀取資料。

下面的示例顯示了完整的 acceptCallback 方法。它假定以下内容:存在一個名為 allDone 的 ManualResetEvent,定義了 StateObject 類,以及在名為 SocketListener 的類中定義了 readCallback 方法。

Public Shared Sub acceptCallback(ar As IAsyncResult)

    ' Get the socket that handles the client request.

    ' Signal the main thread to continue.

    ' Create the state object.

    Dim state As New StateObject()

    state.workSocket = handler

    handler.BeginReceive(state.buffer, 0, state.BufferSize, 0, _

        AddressOf AsynchronousSocketListener.readCallback, state)

    public static void acceptCallback(IAsyncResult ar) {

        // Get the socket that handles the client request.

        Socket listener = (Socket) ar.AsyncState;

        Socket handler = listener.EndAccept(ar);

        // Signal the main thread to continue.

        allDone.Set();

        // Create the state object.

        StateObject state = new StateObject();

        state.workSocket = handler;

        handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,

            new AsyncCallback(AsynchronousSocketListener.readCallback), state);

需要為異步套接字伺服器實作的 final 方法是傳回用戶端發送的資料的讀取回調方法。與接受回調方法一樣,讀取回調方法也是一個 AsyncCallback 委托。該方法将來自用戶端套接字的一個或多個位元組讀入資料緩沖區,然後再次調用 BeginReceive 方法,直到用戶端發送的資料完成為止。從用戶端讀取整個消息後,在控制台上顯示字元串,并關閉處理與用戶端的連接配接的伺服器套接字。

下面的示例實作 readCallback 方法。它假定定義了 StateObject 類。

Public Shared Sub readCallback(ar As IAsyncResult)

    Dim state As StateObject = CType(ar.AsyncState, StateObject)

    Dim handler As Socket = state.workSocket

    ' Read data from the client socket. 

    Dim read As Integer = handler.EndReceive(ar)

    ' Data was read from the client socket.

    If read > 0 Then

        state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, read))

        handler.BeginReceive(state.buffer, 0, state.BufferSize, 0, _

            AddressOf readCallback, state)

    Else

        If state.sb.Length > 1 Then

            ' All the data has been read from the client;

            ' display it on the console.

            Dim content As String = state.sb.ToString()

            Console.WriteLine("Read {0} bytes from socket." + _

                ControlChars.Cr + " Data : {1}", content.Length, content)

        End If

    End If

End Sub 'readCallback

public void readCallback(IAsyncResult ar) {

    StateObject state = (StateObject) ar.AsyncState;

    Socket handler = state.WorkSocket;

    // Read data from the client socket.

    int read = handler.EndReceive(ar);

    // Data was read from the client socket.

    if (read > 0) {

        state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,read));

        handler.BeginReceive(state.buffer,0,StateObject.BufferSize, 0,

            new AsyncCallback(readCallback), state);

    } else {

        if (state.sb.Length > 1) {

            // All the data has been read from the client;

            // display it on the console.

            string content = state.sb.ToString();

            Console.WriteLine("Read {0} bytes from socket./n Data : {1}",

               content.Length, content);

        handler.Close();