2010-10-17 17 views
12

Per quanto riguarda il metodo C# e .NET System.Net.Sockets.Socket.AcceptAsync, sarebbe necessario gestire un valore restituito di "false" per gestire lo stato immediatamente disponibile SocketAsyncEventArgs dalla connessione elaborata in modo sincrono. Microsoft fornisce degli esempi (che si trovano sulla pagina della classe System.Net.Sockets.SocketAsyncEventArgs) che causerà un sovraccarico dello stack in presenza di una grande quantità di connessioni in sospeso, che possono essere sfruttate su qualsiasi sistema che implementa il loro modello di gestione.Overflow dello stack quando si utilizza System.Net.Sockets.Socket.AcceptAsync model

Altre idee per aggirare questo problema sono creare un ciclo che richiami il metodo del gestore, con la condizione che il valore Socket.AcceptAsync restituisca uguale a false e interrompa il ciclo (per consentire l'elaborazione posticipata) se il valore indica che l'operazione è in corso in modo asincrono (true). Tuttavia, questa soluzione causa anche una vulnerabilità di overflow dello stack a causa del fatto che il callback associato a SocketAsyncEventArgs passato a Socket.AcceptAsync ha alla fine del metodo una chiamata a Socket.AcceptAsync, che ha anche un ciclo per le connessioni immediatamente disponibili, accettate in modo sincrono, .

Come potete vedere, questo è un problema piuttosto solido, e devo ancora trovare una buona soluzione che non coinvolge System.Threading.ThreadPool e la creazione di tonnellate di altri metodi e l'elaborazione di pianificazione. Per quanto posso vedere, il modello di socket asincrono relativo a Socket.AcceptAsync richiede più di quello dimostrato negli esempi su MSDN.

Qualcuno ha una soluzione pulita ed efficiente per gestire immediatamente le connessioni in attesa accettate in modo sincrono da Socket.AcceptAsync senza dover creare thread separati per gestire le connessioni e senza ricorrere alla ricorsione?

+0

Sì, gli esempi su MSDN non sono sempre i migliori (o le migliori prassi, se è per questo). Puoi chiarire la tua domanda? Stai cercando un modo per lavorare con socket asyc che non causeranno StackOverflowExceptoin? – Oded

+0

Questo è corretto; Sto cercando un modo non ricorsivo per gestire immediatamente le connessioni in sospeso. –

+0

Vedere anche http://stackoverflow.com/questions/2002143/asynchronous-sockets-handling-false-socket-acceptasync-values ​​ – Lucero

risposta

7

Non userei AcceptAsync, ma piuttosto BeginAccept/EndAccept e implementare correttamente il modello asincrono comune, ovvero il controllo di CompletedSynchronously per evitare le richiamate nel thread di richiamata sulle operazioni completate.

Vedi anche AsyncCallBack CompletedSynchronously


Modifica per quanto riguarda l'obbligo di utilizzare AcceptAsync:

Il MSDN documentation dice esplicitamente che il callback non verrà richiamato per le operazioni che hanno completato in modo sincrono. Questo è diverso dal comune schema asincrono in cui la richiamata è sempre invocata.

Restituisce vero se l'operazione di I/O è in sospeso. L'evento SocketAsyncEventArgs.Completed sul parametro e verrà generato al termine dell'operazione . Restituisce false se l'operazione di I/O ha completato in modo sincrono. Il SocketAsyncEventArgs.Completed evento sul parametro e non verrà sollevata e l'oggetto e passato come parametro può essere esaminata immediatamente dopo la chiamata al metodo ritorna per recuperare il risultato dell'operazione.

Attualmente non vedo come un ciclo non risolva il problema di overflow dello stack. Forse puoi essere più specifico sul codice che causa il problema?


Edit 2: Sto pensando di codice come questo (solo per quanto riguarda AcceptAsync, il resto è stato solo per ottenere un'applicazione di lavoro per provarlo con):

static void Main(string[] args) { 
    Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
    listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 4444)); 
    listenSocket.Listen(100); 
    SocketAsyncEventArgs e = new SocketAsyncEventArgs(); 
    e.Completed += AcceptCallback; 
    if (!listenSocket.AcceptAsync(e)) { 
     AcceptCallback(listenSocket, e); 
    } 
    Console.ReadKey(true); 
} 

private static void AcceptCallback(object sender, SocketAsyncEventArgs e) { 
    Socket listenSocket = (Socket)sender; 
    do { 
     try { 
      Socket newSocket = e.AcceptSocket; 
      Debug.Assert(newSocket != null); 
      // do your magic here with the new socket 
      newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
      newSocket.Disconnect(false); 
      newSocket.Close(); 
     } catch { 
      // handle any exceptions here; 
     } finally { 
      e.AcceptSocket = null; // to enable reuse 
     } 
    } while (!listenSocket.AcceptAsync(e)); 
} 
+0

Sfortunatamente BeginAccept/EndAccept non si adatta alle mie esigenze, questo a causa del requisito di istanziare gli oggetti IAsyncResult per ogni operazione. Il mio server richiede un elevato volume di operazioni e la profilazione ha rivelato che questo era un aspetto stressante nell'applicazione. E anche in questo caso, la soluzione fornita sconfigge lo scopo del completamento sincrono richiamando la richiamata su un thread diverso o disponendo di uno strano sistema di accettazione delle chiamate fino alla velocità, da un thread separato. L'altra risposta fatta da qualcuno è essenzialmente identica alla tua. –

+0

In risposta alla modifica, il problema si presenta nel caso di gestire SocketAsyncEventArgs immediatamente disponibile chiamando il gestore AcceptAsync dallo stesso thread, ad esempio si controlla se il valore restituito è falso, quindi si chiama semplicemente il gestore con l'argomento di SocketAsyncEventArgs utilizzato in Socket.AcceptAsync.Quindi, all'interno del gestore di accettazione, si utilizza lo stesso modello alla fine per garantire che tutte le connessioni (sincrone o asincrone) vengano gestite correttamente. –

+0

In risposta alla seconda modifica con il codice di esempio, è esattamente quello che avevo in mente quando ho postato la mia risposta alla mia domanda. Grazie per aver pubblicato il codice, perché alcune persone potrebbero non aver capito cosa stavo cercando di trasmettere attraverso il testo. Credo che sia stata la tua risposta a far scattare il mio pensiero su come risolvere il problema in primo luogo; grazie ancora. Anche se devo dire che il lavoro di socket sincrono e asincrono è odioso, heh heh. Ma hey, ci viene l'idea. –

1

non ho guardato con attenzione, ma odora questo potrebbe essere utile (si veda la sezione chiamata "stack dive"):

http://blogs.msdn.com/b/mjm/archive/2005/05/04/414793.aspx

+0

Non stiamo parlando di Windows Communication Foundation. Anche se questo è un problema simile, è piuttosto irrilevante. Sembra anche che stiano semplicemente vanificando lo scopo di avere azioni complete in modo sincrono, avviando il gestore su un thread separato. A sua volta, averlo su un thread separato può solo causare un overflow dello stack sul thread del gestore. –

+0

Per essere chiari, la parte che ho richiamato non riguarda solo WCF, riguarda la scrittura di un loop asincrono con il pattern di inizio-fine. – Brian

+0

L'ho riletto alcune volte e l'ho verificato, quindi ho chiarito il mio commento. Le mie scuse se è venuto fuori come sprezzante. Non è proprio quello che sto cercando, e apprezzo il tuo contributo. –

3

ho risolto questo problema semplicemente cambiando il posizionamento del ciclo. Invece di chiamare in modo ricorsivo il gestore accept da se stesso, avvolgere il codice in un ciclo do-while con la condizione "! Socket.AcceptAsync (args)" impedisce un overflow dello stack.

Il ragionamento alla base di questo è che si utilizza il thread di richiamata per elaborare le connessioni che sono immediatamente disponibili, prima di preoccuparsi di attendere in modo asincrono altre connessioni. Sta riutilizzando un thread in pool, in modo efficace.

Apprezzo le risposte ma per qualche motivo nessuno di loro ha cliccato con me e non ha risolto il problema. Tuttavia, sembra che qualcosa lì dentro abbia spinto la mia mente a venire con questa idea. Evita di lavorare manualmente con la classe ThreadPool e non usa la ricorsione.

Ovviamente, se qualcuno ha una soluzione migliore o anche un'alternativa, sarei felice di sentirlo.

+0

I suoni sono molto simili a quello che ho hackerato insieme mentre tu scrivevi quel commento. Ecco perché ho chiesto come un ciclo non risolvesse il problema, dal momento che quella richiamata non viene chiamata mentre si sta effettuando il ciclo non c'è ricorsività ... – Lucero

+0

@Michael Mi rendo conto che questo ha quasi 4 anni, ma attualmente sto affrontando il problema stesso problema e non ho potuto davvero capire esattamente quello che hai fatto. Ti ricordi di ricordare? – Rotem

+0

@Rotem Sì, lo so! Fondamentalmente inizialmente chiamavo 'AcceptAsync' dall'interno della callback dell'evento' Completed' e nel caso in cui fosse completato in modo sincrono stavo solo invocando lo stesso callback dell'evento. Ciò causerà un overflow dello stack se molte chiamate vengono completate in modo sincrono, come è frequente nei carichi frequenti. Invece di farlo in questo modo, ho appena creato un ciclo come: 'while (! Acceptor.AcceptAsync (stato)) {/ * do stuff * /}' –

0
newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
newSocket.Disconnect(false); 
newSocket.Close(); 

Il problema con questo frammento di codice sopra è che questo bloccherà la tua prossima operazione di accettazione.

Un modo migliore è come questo:

while (true) 
{ 
    if (e.SocketError == SocketError.Success) 
    { 
     //ReadEventArg object user token 
     SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop(); 
     Socket socket = ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket; 

     if (!socket.ReceiveAsync(readEventArgs)) 
     ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessReceiveEx), readEventArgs); . 
    } 
    else 
    { 
     HadleBadAccept(e); 
    } 

    e.AcceptSocket = null; 

    m_maxNumberAcceptedClients.WaitOne(); 
    if (listenSocket.AcceptAsync(e)) 
     break; 
} 
+0

Questo è un commento per il mio downvote di 2 anni. Ho downvoted questo perché non ha senso per il contesto della domanda. Non ho pubblicato un esempio di codice, quindi sembra che tu stia rispondendo a una risposta a questa domanda e non alla domanda stessa. Questo dovrebbe essere un commento di due frasi sulla risposta accettata. –

Problemi correlati