Utilizzo di un socket client asincrono

Con un socket client asincrono non è possibile sospendere l'applicazione in attesa del completamento delle operazioni di rete, ma è possibile utilizzare il modello standard di programmazione asincrona di .NET Framework per elaborare la connessione di rete su un thread mentre l'applicazione continua a essere eseguita sul thread originale. L'utilizzo dei socket asincroni è adatto alle applicazioni che richiedono un utilizzo intensivo delle risorse di rete o la cui esecuzione deve continuare senza attendere il completamento delle operazioni di rete.

Nella classe Socket si segue lo schema di denominazione di .NET Framework per i metodi asincroni. Il metodo sincrono Receive, ad esempio, corrisponde ai metodi asincroni BeginReceive e EndReceive.

Per la restituzione del risultato di operazioni asincrone deve essere utilizzato un metodo di callback. Se non è necessario che l'applicazione riceva il risultato non è obbligatorio utilizzare il metodo di callback. Nel codice di esempio riportato in questa sezione viene utilizzato un metodo per avviare la connessione a una periferica di rete e un metodo callback per completare la connessione, un metodo per avviare l'invio dei dati e un metodo di callback per completare l'invio, infine un metodo per avviare la ricezione dei dati e un metodo di callback per terminare la ricezione dei dati.

Per l'elaborazione delle connessioni di rete con i socket asincroni vengono utilizzati più thread del pool di thread di sistema. L'inizio dell'invio o della ricezione dei dati viene eseguito tramite un thread, mentre altri thread consentono il completamento della connessione alla periferica di rete e l'invio o la ricezione dei dati. Nell'esempio riportato di seguito vengono utilizzate istanze della classe System.Threading.ManualResetEvent per sospendere l'esecuzione del thread principale e segnalare quando possa essere ripresa.

Nell'esempio che segue, per connettere un socket asincrono a una periferica di rete, viene utilizzato il metodo Connect che inizializza un Socket e chiama il metodo BeginConnect, passando un endpoint remoto che rappresenta la periferica di rete, il metodo di callback per la connessione e un oggetto di stato, ovvero il Socket client, utilizzato per passare le informazioni di stato tra chiamate asincrone. Nell'esempio viene implementato il metodo Connect per connettere il Socket specificato all'endpoint indicato. Si presuppone l'impiego di un'istanza dell'oggetto globale ManualResetEvent denominata connectDone.

Public Shared Sub Connect(remoteEP As EndPoint, client As Socket)
    client.BeginConnect(remoteEP, _
       AddressOf ConnectCallback, client)
    
    connectDone.WaitOne()
End Sub 'Connect
[C#]
public static void Connect(EndPoint remoteEP, Socket client) {
    client.BeginConnect(remoteEP, 
        new AsyncCallback(ConnectCallback), client );

   connectDone.WaitOne();
}

Il metodo di callback per la connessione ConnectCallback consente di implementare il delegato AsyncCallback. Tramite tale metodo viene effettuata la connessione alla periferica remota quando questa è disponibile e viene segnalato quindi al thread dell'applicazione l'avvenuto completamento della connessione impostando l'istanza di ManualResetEvent denominata connectDone. Mediante il codice riportato di seguito viene implementato il metodo ConnectCallback.

Private Shared Sub ConnectCallback(ar As IAsyncResult)
    Try
        ' Retrieve the socket from the state object.
        Dim client As Socket = CType(ar.AsyncState, Socket)

        ' Complete the connection.
        client.EndConnect(ar)

        Console.WriteLine("Socket connected to {0}", _
            client.RemoteEndPoint.ToString())

        ' Signal that the connection has been made.
        connectDone.Set()
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'ConnectCallback


[C#]
private static void ConnectCallback(IAsyncResult ar) {
    try {
        // Retrieve the socket from the state object.
        Socket client = (Socket) ar.AsyncState;

        // Complete the connection.
        client.EndConnect(ar);

        Console.WriteLine("Socket connected to {0}",
            client.RemoteEndPoint.ToString());

        // Signal that the connection has been made.
        connectDone.Set();
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

Il metodo Send utilizzato nell'esempio consente di codificare i dati della stringa specificata in formato ASCII e di inviarli in modalità asincrona alla periferica di rete rappresentata dal socket specificato. Nell'esempio riportato di seguito viene implementato il metodo Send.

Private Shared Sub Send(client As Socket, data As [String])
    ' Convert the string data to byte data using ASCII encoding.
    Dim byteData As Byte() = Encoding.ASCII.GetBytes(data)

    ' Begin sending the data to the remote device.
    client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, _
        AddressOf SendCallback, client)
End Sub 'Send

[C#]
private static void Send(Socket client, String data) {
    // Convert the string data to byte data using ASCII encoding.
    byte[] byteData = Encoding.ASCII.GetBytes(data);

    // Begin sending the data to the remote device.
    client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,
        new AsyncCallback(SendCallback), client);
}

Il metodo di callback per l'invio SendCallback consente di implementare il delegato AsyncCallback e di eseguire l'invio dei dati quando la periferica di rete è pronta a riceverli. Nell'esempio riportato di seguito viene illustrata l'implementazione del metodo SendCallback. Si presuppone l'impiego di un'istanza dell'oggetto globale ManualResetEvent denominata sendDone.

Private Shared Sub SendCallback(ar As IAsyncResult)
    Try
        ' Retrieve the socket from the state object.
        Dim client As Socket = CType(ar.AsyncState, Socket)

        ' Complete sending the data to the remote device.
        Dim bytesSent As Integer = client.EndSend(ar)
        Console.WriteLine("Sent {0} bytes to server.", bytesSent)

        ' Signal that all bytes have been sent.
        sendDone.Set()
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'SendCallback

[C#]
private static void SendCallback(IAsyncResult ar) {
    try {
        // Retrieve the socket from the state object.
        Socket client = (Socket) ar.AsyncState;

        // Complete sending the data to the remote device.
        int bytesSent = client.EndSend(ar);
        Console.WriteLine("Sent {0} bytes to server.", bytesSent);

        // Signal that all bytes have been sent.
        sendDone.Set();
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

Per la lettura dei dati da un socket client è necessaria la presenza di un oggetto di stato che consenta di passare i valori tra le chiamate asincrone. La classe riportata di seguito è un esempio di oggetto di stato per la ricezione dei dati da un socket client. Essa contiene un campo per il socket client, un buffer per i dati ricevuti e uno StringBuilder per conservare la stringa di dati in ingresso. Grazie all'inserimento di tali campi nell'oggetto di stato, i valori in essi specificati possono essere conservati tra una chiamata e l'altra per la lettura dei dati dal socket client.

Public Class StateObject
    ' Client socket.
    Public workSocket As Socket = Nothing 
    ' Size of receive buffer.
    Public BufferSize As Integer = 256
    ' Receive buffer.
    Public buffer(256) As Byte 
    ' Received data string.
    Public sb As New StringBuilder()
End Class 'StateObject

[C#]
public class StateObject {
    // Client socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 256;
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder sb = new StringBuilder();
}

Il metodo Receive utilizzato nell'esempio di seguito consente di impostare l'oggetto di stato e di chiamare quindi il metodo BeginReceive per leggere i dati dal socket client in modo asincrono. In questo esempio viene implementato il metodo Receive.

Private Shared Sub Receive(client As Socket)
    Try
        ' Create the state object.
        Dim state As New StateObject()
        state.workSocket = client
            
        ' Begin receiving the data from the remote device.
        client.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
            AddressOf ReceiveCallback, state)
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'Receive

[C#]
private static void Receive(Socket client) {
    try {
        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = client;

        // Begin receiving the data from the remote device.
        client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReceiveCallback), state);
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

Il metodo di callback per la ricezione ReceiveCallback consente di implementare il delegato AsyncCallback, di ricevere i dati dalla periferica di rete e di generare una stringa di messaggio. Tramite tale metodo vengono letti uno o più byte di dati dalla rete nel buffer di dati e viene chiamato il metodo BeginReceive fino a che l'invio di dati dal client non sia stato completato. Una volta letti i dati provenienti dal client, tramite il metodo ReceiveCallback si segnala al thread dell'applicazione il completamento della ricezione dei dati impostando l'istanza di ManualResetEvent denominata sendDone.

Nell'esempio di codice riportato di seguito viene implementato il metodo ReceiveCallback. In questo esempio si presuppone l'impiego di una stringa globale denominata response che contiene la stringa ricevuta e un'istanza dell'oggetto globale ManualResetEvent denominata receiveDone. Perché la sessione di rete possa terminare è necessario che la chiusura del socket client sia eseguita dal server in modo normale.

Private Shared Sub ReceiveCallback(ar As IAsyncResult)
    Try
        ' Retrieve the state object and the client socket 
        ' from the asynchronous state object.
        Dim state As StateObject = CType(ar.AsyncState, StateObject)
        Dim client As Socket = state.workSocket

        ' Read data from the remote device.
        Dim bytesRead As Integer = client.EndReceive(ar)

        If bytesRead > 0 Then
            ' There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, _
                bytesRead))

            '  Get the rest of the data.
            client.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
                AddressOf ReceiveCallback, state)
        Else
            ' All the data has arrived; put it in response.
            If state.sb.Length > 1 Then
                response = state.sb.ToString()
            End If
            ' Signal that all bytes have been received.
            receiveDone.Set()
        End If
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'ReceiveCallback

[C#]
private static void ReceiveCallback( IAsyncResult ar ) {
    try {
        // Retrieve the state object and the client socket 
        // from the asynchronous state object.
        StateObject state = (StateObject) ar.AsyncState;
        Socket client = state.workSocket;
        // Read data from the remote device.
        int bytesRead = client.EndReceive(ar);
        if (bytesRead > 0) {
            // There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
                //  Get the rest of the data.
            client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
                new AsyncCallback(ReceiveCallback), state);
        } else {
            // All the data has arrived; put it in response.
            if (state.sb.Length > 1) {
                response = state.sb.ToString();
            }
            // Signal that all bytes have been received.
            receiveDone.Set();
        }
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

Vedere anche

Utilizzo di un socket client sincrono | Attesa mediante socket | Programmazione asincrona | Utilizzo di socket client