2011-01-25 11 views
6

Ho un progetto piuttosto vecchio: client e server DCOM, entrambi in C++ \ ATL, solo piattaforma Windows. Tutto funziona bene: i client locali e remoti si connettono al server e lavorano simultaneamente senza alcun problema.DCOM: Come chiudere la connessione nel server in caso di crash del client?

Ma quando il client remoto si blocca o viene ucciso dal Task Manager o dal comando "taskkill" o spegnimento - ho un problema. Il mio server non sa nulla del crash del client e tenta di inviare nuovi eventi a tutti i client (anche a quelli già in crash). Come risultato ho una pausa (il server non può inviare dati al client già in crash) e la sua durata è proporzionale al numero di client remoti che si sono arrestati. Dopo 5 arresti anomali, le pause dei client sono così lunghe che è uguale alla completa interruzione del server.

Conosco il meccanismo "ping" DCOM (DCOM deve disconnettere i client che non rispondono a "ogni 2 minuti di ping" dopo 6 minuti di silenzio). E davvero, dopo 6 minuti di sospensione ho un piccolo periodo di normale lavoro, ma poi il server sta tornando allo stato "in pausa".

Cosa posso fare con tutto questo? Come fare DCOM "ping" funziona bene? Se implementerò il mio codice "ping", è possibile disconnettere manualmente la connessione dei vecchi client DCOM? Come farlo?

+0

Avete considerato di inviare gli eventi dai thread del thread thread, per mitigare il blocco in misura maggiore? – bdonlan

+0

@bdonlan: Questa potrebbe essere una soluzione, tuttavia questo complicherà significativamente il server - dovrà prendersi cura di questi thread extra di durata. – sharptooth

+0

Non proprio - puoi semplicemente usare il pool di thread win32 integrato. Se stai già utilizzando un MTA, è piuttosto semplice colpire QueueUserWorkItem. Se si è in uno STA, è necessario eseguire il marshalling dell'interfaccia remota nel MTA, ma non è ancora troppo difficile da utilizzare con CoMarshalInterThreadInterfaceInStream ecc. – bdonlan

risposta

1

Non sono sicuro del sistema di ping DCOM, ma un'opzione per voi sarebbe semplicemente la generazione delle notifiche da un pool di thread separato. Ciò contribuirà a mitigare l'effetto di avere un piccolo numero di client di blocco: inizierai ad avere problemi quando ce ne sono troppi, naturalmente.

Il modo semplice per farlo è utilizzare QueueUserWorkItem - questo richiamerà la richiamata passata sul pool di thread di sistema dell'applicazione. Supponendo che si sta utilizzando un MTA, questo è tutto quello che dovete fare:

static InfoStruct { 
    IRemoteHost *pRemote; 
    BSTR someData; 
}; 

static DWORD WINAPI InvokeClientAsync(LPVOID lpInfo) { 
    CoInitializeEx(COINIT_MULTITHREADED); 

    InfoStruct *is = (InfoStruct *)lpInfo; 
    is->pRemote->notify(someData); 
    is->pRemote->Release(); 
    SysFreeString(is->someData); 
    delete is; 

    CoUninitialize(); 
    return 0; 
} 

void InvokeClient(IRemoteHost *pRemote, BSTR someData) { 

    InfoStruct *is = new InfoStruct; 
    is->pRemote = pRemote; 
    pRemote->AddRef(); 

    is->someData = SysAllocString(someData); 
    QueueUserWorkItem(InvokeClientAsync, (LPVOID)is, WT_EXECUTELONGFUNCTION); 
} 

Se il thread principale è in una STA, questo è un po 'più complessa solo; basta usare CoMarshalInterThreadInterfaceInStream e CoGetInterfaceAndReleaseStream per passare il puntatore di interfaccia tra appartamenti:

static InfoStruct { 
    IStream *pMarshalledRemote; 
    BSTR someData; 
}; 

static DWORD WINAPI InvokeClientAsync(LPVOID lpInfo) { 
    CoInitializeEx(COINIT_MULTITHREADED); // can be STA as well 

    InfoStruct *is = (InfoStruct *)lpInfo; 
    IRemoteHost *pRemote; 
    CoGetInterfaceAndReleaseStream(is->pMarshalledRemote, __uuidof(IRemoteHost), (LPVOID *)&pRemote); 

    pRemote->notify(someData); 
    pRemote->Release(); 
    SysFreeString(is->someData); 
    delete is; 

    CoUninitialize(); 

    return 0; 
} 

void InvokeClient(IRemoteHost *pRemote, BSTR someData) { 
    InfoStruct *is = new InfoStruct; 
    CoMarshalInterThreadInterfaceInStream(__uuidof(IRemoteHost), pRemote, &is->pMarshalledRemote); 

    is->someData = SysAllocString(someData); 
    QueueUserWorkItem(InvokeClientAsync, (LPVOID)is, WT_EXECUTELONGFUNCTION); 
} 

Si noti che il controllo degli errori è stato eliso per chiarezza - si, naturalmente, vuole errore di controllo tutte le chiamate - in particolare, si vuole sta controllando per RPC_S_SERVER_UNAVAILABLE e altri tali errori di rete e rimuovere i client incriminati.

Alcune varianti più sofisticate che si possono prendere in considerazione includono l'assicurazione che solo una richiesta è in volo per client alla volta (riducendo ulteriormente l'impatto di un client bloccato) e memorizzando nella cache il puntatore dell'interfaccia marshalled nel MTA (se il proprio thread principale è uno STA) - poiché credo che CoMarshalInterThreadInterfaceInStream possa eseguire richieste di rete, sarebbe preferibile occuparsene prima del momento in cui è noto al client, anziché rischiare di bloccare il thread principale.

0

Una soluzione consiste nell'eliminare gli eventi: invitare i client a interrogare il server per verificare se ci sia qualcosa di interessante.

+1

Davvero, è impossibile. Ho centinaia di eventi ed è molto importante informare il cliente sugli eventi il ​​più velocemente possibile. Se il client chiederà al server nuovi eventi anche ogni 1 secondo, non sarà abbastanza veloce. Se il client chiederà al server 100 volte al secondo, i 5 client bloccheranno totalmente server, rete e CPU. – Ezh

+0

@Ezh: Sembra ragionevole, ma l'hai provato? Quanto velocemente esegue un milione di chiamate "fai niente"? – sharptooth

+1

Sì, l'ho provato. Chiamata DCOM remota non è "non fare nulla". È la comunicazione di rete, la sicurezza di Windows, il marshalling ecc. Alcuni millisecondi più il carico della rete e della CPU. – Ezh

0

Utilizzare DCOM per stabilire una notifica denominata pipe. La disconnessione viene gestita meglio con i tubi. L'ascoltatore risponde (quasi) istantaneamente ai messaggi. ad es. Server-> Client (qual è il nome della pipa?). Client-> Server risponde con nome che include la macchina. Il client crea named pipe e ascolta. Il server apre la pipe immediatamente o quando necessario.

0

È possibile implementare il proprio meccanismo di ping in modo che i client chiamino il metodo ping del server di volta in volta. Hai già una sorta di contenitore per i tuoi clienti sul lato server. In quella mappa, contrassegna ciascun cliente con un timestamp dell'ultimo ping. Quindi controlla se il client è vivo prima di inviare eventi a quel client. È possibile personalizzare una strategia di quando interrompere l'invio di eventi, magari in base al tempo o al numero di ping mancanti o al tipo di evento o ad altri fattori. Probabilmente non devi preoccuparti di cancellare i client - questo può aspettare finché DCOM non si rende conto che un determinato client è morto. Questo schema potrebbe non eliminare completamente il problema dal momento che un cliente potrebbe morire poco prima che un evento debba essere inviato, ma si avrà il controllo completo sul numero di tali client che possono esistere modificando il periodo di ping. Più piccolo è questo periodo meno clienti morti, anche se paghi con il traffico.

Problemi correlati