2011-10-04 23 views
8

Voglio scrivere un semplice server socket, tuttavia mi piacerebbe che fosse scalabile verticalmente, ad esempio, non creando un thread per connessione o attività molto lunghe, che potrebbero consumare tutti i thread.Come si scrive un server socket scalabile usando C# 4.0?

Il server riceve una richiesta contenente una query e esegue lo streaming di un risultato di dimensioni arbitrarie.

Vorrei il modo idiomatico di farlo utilizzando le tecniche e le librerie disponibili in C# 4, con enfasi sul codice semplice, piuttosto che sulle prestazioni non elaborate.

Riapertura Un server socket è una parte utile di un sistema scalabile. Se vuoi scalare orizzontalmente, ci sono diverse tecniche. Probabilmente non sarai in grado di rispondere a questa domanda se non hai mai creato un server socket.

risposta

15

Ho lavorato a qualcosa di simile per una settimana o due, quindi spero di potervi aiutare un po '.

Se ci si concentra su un codice semplice, si consiglia di utilizzare le classi TcpClient e TcpListener. Entrambi rendono le prese molto più facili da utilizzare. Sebbene siano esistiti da .NET Framework 1.1, sono stati aggiornati e sono ancora la soluzione migliore.

In termini di come utilizzare .NET Framework 4.0 nella scrittura di codice semplicistico, le attività sono la prima cosa che viene in mente. Rendono la scrittura di codice asincrono molto meno doloroso e diventerà molto più facile migrare il codice quando uscirà C# 5 (new async and await keywords). Ecco un esempio di come attività possono semplificare il codice:

Invece di usare tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state); e fornendo un metodo di callback, che avrebbe chiamato EndAcceptTcpClient(); ed eventualmente lanciare la vostra oggetto di stato, C# 4 consente di utilizzare chiusure, lambda, e attività per rendere questo processo è molto più leggibile e scalabile. Ecco un esempio:

private void AcceptClient(TcpListener tcpListener) 
{ 
    Task<TcpClient> acceptTcpClientTask = Task.Factory.FromAsync<TcpClient>(tcpListener.BeginAcceptTcpClient, tcpListener.EndAcceptTcpClient, tcpListener); 

    // This allows us to accept another connection without a loop. 
    // Because we are within the ThreadPool, this does not cause a stack overflow. 
    acceptTcpClientTask.ContinueWith(task => { OnAcceptConnection(task.Result); AcceptClient(tcpListener); }, TaskContinuationOptions.OnlyOnRanToCompletion); 
} 

private void OnAcceptConnection(TcpClient tcpClient) 
{ 
    string authority = tcpClient.Client.RemoteEndPoint.ToString(); // Format is: IP:PORT 

    // Start a new Task to handle client-server communication 
} 

FromAsync è molto utile come Microsoft ha fornito molte sovraccarichi che possono semplificare le operazioni asincrone comuni. Ecco un altro esempio:

private void Read(State state) 
{ 
    // The int return value is the amount of bytes read accessible through the Task's Result property. 
    Task<int> readTask = Task<int>.Factory.FromAsync(state.NetworkStream.BeginRead, state.NetworkStream.EndRead, state.Data, state.BytesRead, state.Data.Length - state.BytesRead, state, TaskCreationOptions.AttachedToParent); 

    readTask.ContinueWith(ReadPacket, TaskContinuationOptions.OnlyOnRanToCompletion); 
    readTask.ContinueWith(ReadPacketError, TaskContinuationOptions.OnlyOnFaulted); 
} 

Stato è solo una classe definita dall'utente che di solito contiene solo l'istanza TcpClient, i dati (array di byte), e forse i byte letti pure.

Come potete vedere, ContinueWith può essere utilizzato per sostituire un sacco di ingombranti try-catches che fino ad ora erano un male necessario.

All'inizio del post hai menzionato di non voler creare un thread per connessione o creare attività a lunga durata e ho pensato di indirizzarlo a questo punto. Personalmente, non vedo il problema con la creazione di un thread per ogni connessione.

Ciò che occorre fare attenzione, tuttavia, è l'utilizzo di Attività (un'astrazione sul ThreadPool) per operazioni di lunga durata. Il ThreadPool è utile perché l'overhead della creazione di una nuova discussione non è trascurabile e per attività brevi come la lettura o la scrittura di dati e la gestione di una connessione client, le attività sono preferite.

È necessario ricordare che ThreadPool è una risorsa condivisa con una funzione specializzata (evitando il sovraccarico di dedicare più tempo alla creazione di un thread piuttosto che usarlo).Perché è condiviso, se hai usato un thread, un'altra risorsa non può e questo può portare rapidamente allo starvation del thread-pool e agli scenari deadlock.

+0

Grazie - questo è esattamente quello che stavo cercando! –

+5

Come circa 1 milione di connessioni durante l'utilizzo di un thread per connessione? – Shift

+0

Quindi è necessario utilizzare i thread del pool di thread correttamente. – vtortola