2009-10-08 13 views
7

Desidero creare un socket socket asincrono utilizzando l'evento SocketAsyncEventArgs.Progettazione server tramite SocketAsyncEventArgs

Il server deve gestire circa 1000 connessioni allo stesso tempo. Qual è il modo migliore per gestire la logica per ogni pacchetto?

Il progetto del server è basato su this MSDN example, quindi ogni socket avrà il proprio SocketAsyncEventArgs per la ricezione dei dati.

  1. fare la roba logica all'interno della funzione ricezione. Nessun overhead verrà creato, ma poiché la prossima chiamata ReceiveAsync() non verrà eseguita prima che la logica sia stata completata, i nuovi dati non possono essere letti dal socket. Le due domande principali per me sono: Se il client invia molti dati e l'elaborazione logica è pesante, come sarà gestita dal sistema (pacchetti persi perché il buffer è pieno)? Inoltre, se tutti i client inviano dati allo stesso tempo, ci saranno 1000 thread, oppure esiste un limite interno e non è possibile avviare un nuovo thread prima che un altro completi l'esecuzione?

  2. Utilizzare una coda. La funzione di ricezione sarà molto breve ed eseguirà velocemente, ma si avrà un sovraccarico decente a causa della coda. I problemi sono, se i tuoi thread di lavoro non sono abbastanza veloci sotto il carico del server, la tua coda può essere piena, quindi forse devi forzare il calo dei pacchetti. Hai anche il problema Producer/Consumer, che probabilmente può rallentare l'intera coda con molti lock.

Quindi, quello che sarà il progetto migliore, la logica in funzione di ricezione, la logica nel thread di lavoro o qualsiasi cosa completamente diversa mi è mancata finora.

Un'altra missione riguardante l'invio di dati.

È meglio avere un SocketAsyncEventArgs legato a un socket (analogico all'evento di ricezione) e utilizzare un sistema di buffer per effettuare una chiamata di invio per alcuni pacchetti di piccole dimensioni (supponiamo che i pacchetti altrimenti sarebbero a volte! un altro) o utilizzare un SocketAsyncEventArgs diverso per ogni pacchetto e memorizzarli in un pool per riutilizzarli?

risposta

10

Per implementare efficacemente socket asincroni ogni socket richiede più di 1 SocketAsyncEventArgs. C'è anche un problema con il buffer byte [] in ogni SocketAsyncEventArgs. In breve, i buffer di byte verranno bloccati ogni volta che si verifica una transizione gestita nativa (invio/ricezione). Se si assegnano i SocketAsyncEventArgs e i buffer di byte secondo necessità, è possibile eseguire OutOfMemoryExceptions con molti client a causa della frammentazione e dell'incapacità del GC di compattare la memoria bloccata.

Il modo migliore per gestire ciò è creare una classe SocketBufferPool che allocherà un numero elevato di byte e SocketAsyncEventArgs all'avvio dell'applicazione, in questo modo la memoria bloccata sarà contigua. Quindi semplicemente riutilizzare i buffer dal pool in base alle esigenze.

In pratica, ho trovato il modo migliore di creare una classe wrapper attorno alla classe SocketAsyncEventArgs e SocketBufferPool per gestire la distribuzione delle risorse.

Come esempio, ecco il codice per un metodo BeginReceive:

private void BeginReceive(Socket socket) 
    { 
     Contract.Requires(socket != null, "socket"); 

     SocketEventArgs e = SocketBufferPool.Instance.Alloc(); 
     e.Socket = socket; 
     e.Completed += new EventHandler<SocketEventArgs>(this.HandleIOCompleted); 

     if (!socket.ReceiveAsync(e.AsyncEventArgs)) { 
      this.HandleIOCompleted(null, e); 
     } 
    } 

E qui è il metodo HandleIOCompleted:

private void HandleIOCompleted(object sender, SocketEventArgs e) 
    { 
     e.Completed -= this.HandleIOCompleted; 
     bool closed = false; 

     lock (this.sequenceLock) { 
      e.SequenceNumber = this.sequenceNumber++; 
     } 

     switch (e.LastOperation) { 
      case SocketAsyncOperation.Send: 
      case SocketAsyncOperation.SendPackets: 
      case SocketAsyncOperation.SendTo: 
       if (e.SocketError == SocketError.Success) { 
        this.OnDataSent(e); 
       } 
       break; 
      case SocketAsyncOperation.Receive: 
      case SocketAsyncOperation.ReceiveFrom: 
      case SocketAsyncOperation.ReceiveMessageFrom: 
       if ((e.BytesTransferred > 0) && (e.SocketError == SocketError.Success)) { 
        this.BeginReceive(e.Socket); 
        if (this.ReceiveTimeout > 0) { 
         this.SetReceiveTimeout(e.Socket); 
        } 
       } else { 
        closed = true; 
       } 

       if (e.SocketError == SocketError.Success) { 
        this.OnDataReceived(e); 
       } 
       break; 
      case SocketAsyncOperation.Disconnect: 
       closed = true; 
       break; 
      case SocketAsyncOperation.Accept: 
      case SocketAsyncOperation.Connect: 
      case SocketAsyncOperation.None: 
       break; 
     } 

     if (closed) { 
      this.HandleSocketClosed(e.Socket); 
     } 

     SocketBufferPool.Instance.Free(e); 
    } 

Il codice sopra è contenuto in una classe TCPSocket che aumenterà Dati ricevuti & Eventi DataSent. Una cosa da notare è il caso SocketAsyncOperation.ReceiveMessageFrom: block; se il socket non ha avuto un errore, avvia immediatamente un altro BeginReceive() che allocherà un altro SocketEventArgs dal pool.

Un'altra nota importante è la proprietà SequenceNumber SocketEventArgs impostata nel metodo HandleIOComplete. Sebbene le richieste asincrone vengano completate nell'ordine in coda, sei comunque soggetto ad altre condizioni di gara. Poiché il codice chiama BeginReceive prima di generare l'evento DataReceived, esiste la possibilità che il thread che serve l'IOCP originale si blocchi dopo aver chiamato BeginReceive ma prima di rasare l'evento mentre il secondo async viene completato su un nuovo thread che solleva prima l'evento DataReceived. Sebbene si tratti di un caso limite piuttosto raro, può verificarsi e la proprietà SequenceNumber consente all'app di consumo di garantire che i dati vengano elaborati nell'ordine corretto.

Un'altra area a conoscenza delle mosse asincrone. Spesso le richieste di invio asincrono vengono completate in modo sincrono (SendAsync restituisce false se la chiamata viene completata in modo sincrono) e può compromettere gravemente le prestazioni. Il sovraccarico aggiuntivo della chiamata asincrona che ritorna su un IOCP può in pratica causare prestazioni peggiori rispetto al semplice utilizzo della chiamata sincrona. La chiamata asincrona richiede due chiamate al kernel e un'allocazione dell'heap mentre la chiamata sincrona avviene nello stack.

Spero che questo aiuti, Bill

-1

Nel codice, si esegue questa operazione:

if (!socket.ReceiveAsync(e.AsyncEventArgs)) { 
    this.HandleIOCompleted(null, e); 
} 

Ma è un errore di farlo. C'è un motivo per cui la richiamata non viene invocata quando termina in modo sincrono, tale azione può riempire lo stack.

Immaginate che ogni ReceiveAsync ritorni sempre in modo sincrono. Se il tuo HandleIOCompleted era in un momento, è possibile elaborare il risultato che viene restituito in modo sincrono allo stesso livello di stack. Se non è ritornato in modo sincrono, si interrompe il tempo. Ma, facendo ciò che si fa, si finisce per creare un nuovo elemento nello stack ... quindi se si ha abbastanza sfortuna, si causeranno eccezioni di overflow dello stack.

Problemi correlati