Finestre.  Virus.  I Quaderni.  Internet.  ufficio.  Utilità.  Autisti

Lo scopo di questo articolo è discutere la terminologia. L'articolo non riguarda come e perché, ma solo esclusivamente sull'uso della terminologia. L'articolo riflette l'opinione dell'autore e non pretende di essere scientifico.

introduzione

Se lavori nel campo della programmazione sistemi distribuiti o dentro integrazione dei sistemi, quindi la maggior parte di ciò che viene presentato qui non è nuovo per te.

Il problema sorge quando le persone si incontrano usando diverse tecnologie e quando quelle persone iniziano conversazioni tecniche. In questo caso, c'è spesso un malinteso reciproco dovuto alla terminologia. Qui cercherò di riunire le terminologie utilizzate in diversi contesti.

Terminologia

Non esiste una terminologia e una classificazione chiare in quest'area. La terminologia utilizzata di seguito riflette il modello dell'autore, cioè è strettamente soggettiva. Qualsiasi critica e qualsiasi discussione sono i benvenuti.

Ho suddiviso la terminologia in tre aree: RPC (Remote Procedure Call), Messaging e REST. Queste aree hanno radici storiche.

RPC

RPC le tecnologie sono le tecnologie più antiche. I rappresentanti più brillanti di RPC sono: CORBA E DCOM.

A quei tempi, dovevi principalmente collegare i sistemi in modo veloce e relativamente affidabile reti locali. L'idea principale alla base di RPC era rendere le chiamate ai sistemi remoti molto simili alle funzioni di chiamata all'interno di un programma. Tutti i meccanismi di chiamata remota erano nascosti al programmatore. Almeno hanno cercato di nasconderlo. I programmatori in molti casi dovevano lavorare a un livello più profondo, dove apparivano i termini marshalling ( marshalling) E unmarshalling(com'è in russo?), che essenzialmente significava serializzazione. Le normali chiamate di funzione all'interno dei processi sono state gestite dal chiamante in delega, e dalla parte del sistema che svolge la funzione, in Spedizioniere. Idealmente, né il sistema di chiamata né il sistema di elaborazione gestivano le complessità del trasferimento dei dati tra i sistemi. Tutte queste sottigliezze erano concentrate nel bundle Proxy - Dispatcher, il cui codice veniva generato automaticamente.

Quindi non noterai, non dovresti notare, alcuna differenza tra chiamare una funzione locale e chiamare una funzione remota.
Ora c'è una sorta di rinascita RPC, i cui rappresentanti più importanti sono: Google ProtoBuf, Thrift, Avro.

Messaggistica

Nel tempo si è scoperto che un tentativo di proteggere il programmatore dal fatto che la funzione chiamata fosse ancora diversa da quella locale non portava al risultato sperato. I dettagli di implementazione e le differenze fondamentali tra i sistemi distribuiti erano troppo grandi per essere risolti utilizzando il codice proxy generato automaticamente. A poco a poco, si è capito che il fatto che i sistemi sono collegati da un ambiente inaffidabile, lento ea bassa velocità dovrebbe riflettersi esplicitamente nel codice del programma.

Sono apparse le tecnologie servizi web. Abbiamo iniziato a parlare ABC: indirizzo, legame, contratto. Non è del tutto chiaro il motivo per cui sono comparsi i contratti, che sono essenzialmente buste (buste) per argomenti di input. I contratti spesso complicano l'intero modello piuttosto che semplificarlo. Ma non importa.

Ora il programmatore ha creato esplicitamente servizio(Servizio) O cliente(cliente) che chiama il servizio. Il servizio era un set operazioni (operazione), ognuno dei quali all'input ha preso richiesta(Richiesta) ed emesso risposta(Risposta). cliente in modo esplicito inviato(Santo) richiesta, il servizio esplicitamente ricevuto ( ricevere) e ha risposto (Inviato), inviando la risposta. Il cliente ha ricevuto (Ricevi) la risposta e su di essa la chiamata è giunta al termine.

Proprio come in RPC, Proxy e Dispatcher hanno funzionato da qualche parte qui. E come prima, il loro codice veniva generato automaticamente e il programmatore non doveva capirlo. A meno che il client non abbia utilizzato in modo esplicito le classi di Proxy.

Le richieste e le risposte vengono convertite in modo esplicito nel formato wire. Molto spesso è un array di byte. La trasformazione è chiamata Serializzazione E Deserializzazione e talvolta si nasconde nel codice proxy.
Il culmine della messaggistica si è manifestato nell'emergere di un paradigma ESB (Enterprise Service Bus). Nessuno può davvero formulare cosa sia, ma tutti concordano sul fatto che i dati sull'ESB si muovono sotto forma di messaggi.

RIPOSO

In una lotta costante con la complessità del codice, i programmatori hanno fatto il passo successivo e hanno creato RIPOSO.

Il principio di base di REST è che le operazioni delle funzioni sono fortemente limitate e lasciano solo un insieme di operazioni. CRUD: Crea - Leggi - Aggiorna - Elimina. In questo modello, tutte le operazioni vengono sempre applicate ad alcuni dati. Le operazioni disponibili in CRUD sono sufficienti per la maggior parte delle applicazioni. Poiché le tecnologie REST nella maggior parte dei casi implicano l'uso del protocollo HTTP, i comandi CRUD si riflettevano nei comandi http (inviare - Ottenere - Mettere - Eliminare) . Si sostiene costantemente che REST non sia necessariamente legato a HTTP. Ma in pratica, il riflesso delle firme delle operazioni sulla sintassi dei comandi HTTP è ampiamente utilizzato. Ad esempio, una chiamata di funzione

EntityAddress ReadEntityAddress(string param1, string param2)

Espresso così:

GET: entityAddress?param1=value1¶m2=value2

Conclusione

Prima di iniziare una discussione sui sistemi distribuiti o sull'integrazione, definisci un po' di terminologia. Se Proxy significa sempre la stessa cosa in contesti diversi, allora, ad esempio, richiesta significherà poco in termini di RPC e il marshalling causerà sconcerto quando si discute delle tecnologie REST.



I programmi che comunicano su una rete necessitano di un meccanismo di comunicazione. Al livello inferiore, all'arrivo dei pacchetti, viene inviato ed elaborato un segnale programma di rete elaborazione del segnale. Al livello superiore funziona il meccanismo di rendezvous (rendezvous) adottato nel linguaggio Ada. NFS utilizza un meccanismo RPC (Remote Procedure Call) in cui il client comunica con il server (vedere la figura 1). Secondo questo processo, il client prima chiama una procedura che invia una richiesta al server. All'arrivo di un pacchetto con una richiesta, il server invoca la sua procedura di apertura, esegue il servizio richiesto, invia una risposta e il controllo viene restituito al client.

L'interfaccia RPC può essere pensata come costituita da tre livelli:

  1. Il livello superiore è completamente "trasparente". Un programma a questo livello potrebbe, ad esempio, contenere una chiamata alla procedura rnusers(), che restituisce il numero di utenti sulla macchina remota. Non è necessario conoscere l'utilizzo del meccanismo RPC finché si effettua la chiamata nel programma.
  2. Livello medio progettato per le applicazioni più comuni. Le chiamate RPC a questo livello sono gestite dalle subroutine registerrpc() e callrpc(): registerrpc() ottiene il codice a livello di sistema e callrpc() esegue una chiamata di procedura remota. La chiamata a rnusers() è implementata usando queste due subroutine.
  3. Il livello inferiore viene utilizzato per attività più complesse che modificano i valori predefiniti ai valori dei parametri della procedura. A questo livello è possibile manipolare in modo esplicito i socket utilizzati per inviare messaggi RPC.

Come regola generale, dovresti usare il livello superiore ed evitare di usare i livelli inferiori a meno che non sia assolutamente necessario.

Anche se in questo tutorial trattiamo solo l'interfaccia C, le chiamate di procedure remote possono essere effettuate da qualsiasi linguaggio. Il funzionamento del meccanismo RPC per organizzare la comunicazione tra processi su macchine diverse non differisce dal suo funzionamento sulla stessa macchina.

RPC (Remote Procedure Call, Remote Procedure Call Service) è un'interfaccia tra utenti remoti e alcuni programmi host che vengono eseguiti su richiesta di questi utenti. Il servizio RPC di un host in genere fornisce una serie di programmi ai client. Ciascuno di questi programmi, a sua volta, è costituito da una o più procedure remote. Ad esempio, un servizio remoto sistema di file NFS, che è basato su chiamate RPC, può consistere solo di due programmi: ad esempio, un programma interagisce con programmi di alto livello interfacce utente e l'altro con funzioni di I/O di basso livello.

Ci sono due parti coinvolte in ogni chiamata di procedura remota: il client attivo, che invia una richiesta di chiamata di procedura al server, e il server, che invia una risposta al client.

Nota. Si noti che i termini "client" e "server" in questo caso fare riferimento a una transazione specifica Un host specifico o Software(processo o programma) può funzionare sia come client che come server. Ad esempio, un programma che fornisce un servizio di procedura remota può anche essere un client nel lavorare con un file system di rete.

Il protocollo RPC si basa su un modello di chiamata di procedura remota simile a quello delle chiamate di procedura locale. Quando chiami una procedura locale, spingi gli argomenti in una posizione specifica nella memoria, nello stack o variabili ambientali e trasferire il controllo del processo a un indirizzo specifico. Una volta che hai finito, leggi i risultati a un indirizzo specifico e continua con il tuo processo.

Nel caso di lavorare con una procedura remota, la differenza principale è che la chiamata di funzione remota è servita da due processi: il processo client e il processo server.

Il processo client invia un messaggio al server, che include i parametri della procedura chiamata, e attende un messaggio di risposta con i risultati del suo lavoro. Quando viene ricevuta una risposta, il risultato viene letto e il processo continua. Sul lato server, il processo del gestore delle chiamate è in stato di attesa e, quando arriva un messaggio, legge i parametri della procedura, la esegue, invia una risposta e rimane in attesa della chiamata successiva.

Il protocollo RPC non impone alcun requisito sulle comunicazioni aggiuntive tra processi e non richiede il sincronismo delle funzioni svolte, ovvero le chiamate possono essere asincrone e reciprocamente indipendenti, in modo che il client possa eseguire altre procedure in attesa di una risposta. Il server RPC può allocare un processo separato o una macchina virtuale per ogni funzione, quindi, senza attendere il completamento delle richieste precedenti, può accettare immediatamente quelle successive.

Tuttavia, ci sono diverse importanti differenze tra le chiamate di procedure locali e remote:

  1. Elaborazione degli errori. Il client dovrebbe in ogni caso essere informato degli errori che si verificano quando si effettuano chiamate di procedura remota sul server o sulla rete.
  2. Variabili globali. Poiché il server non ha accesso allo spazio degli indirizzi del client, le chiamate di procedura remota non possono utilizzare parametri nascosti come variabili globali.
  3. Prestazione. La velocità di esecuzione delle procedure remote, di norma, è inferiore di uno o due ordini di grandezza rispetto alla velocità di esecuzione di procedure locali simili.
  4. Autenticazione. Poiché le chiamate di procedura remota si verificano sulla rete, è necessario utilizzare i meccanismi di autenticazione del client.

Principi di costruzione del protocollo.

Il protocollo RPC può utilizzare diversi protocolli di trasporto diversi. La responsabilità del protocollo RPC è solo quella di fornire standard e interpretare il passaggio di messaggi. L'affidabilità e l'affidabilità della trasmissione dei messaggi è interamente fornita dal livello di trasporto.

Tuttavia, RPC può controllare la scelta e alcune caratteristiche del protocollo di trasporto. Come esempio di interazione tra RPC e un protocollo di trasporto, consideriamo la procedura di assegnazione di una porta RPC per un processo applicativo tramite RPC - Portmapper.

Questa funzione assegna dinamicamente (su richiesta) una porta specifica a una connessione RPC. La funzione Portmapper viene utilizzata abbastanza spesso, perché l'insieme delle porte di trasporto riservate per RPC è limitato e il numero di processi che possono potenzialmente essere eseguiti contemporaneamente è molto elevato. Portmapper, ad esempio, viene chiamato quando si scelgono le porte per la comunicazione client e server Sistemi NFS.

Il servizio Portmapper utilizza il meccanismo dei messaggi broadcast RPC su una porta specifica - III. Su questa porta, il client invia un messaggio broadcast di richiesta porta per uno specifico servizio RPC. Il servizio Portmapper elabora il messaggio, determina l'indirizzo del servizio RPC locale e invia una risposta al client. Il servizio RPC Portmapper può funzionare con entrambi i protocolli TCP e UDP.

RPC può funzionare con vari protocolli di trasporto, ma non duplica mai le loro funzioni, ad es. se RPC funziona su TCP, tutte le preoccupazioni sull'affidabilità e l'affidabilità della connessione RPC vengono assegnate a TCP. Tuttavia, se RPC è installato su UDP, può fornire funzionalità native aggiuntive per garantire la consegna dei messaggi.

Nota.

Le attività dell'applicazione possono considerare il protocollo RPC come una procedura specifica per chiamare una funzione su una rete JSR (Jump Subroutine Instruction).

Affinché il protocollo RPC funzioni, devono essere soddisfatte le seguenti condizioni:

  1. Identificazione univoca di tutte le procedure chiamate in remoto su un determinato host. Le richieste RPC contengono tre campi identificativi: il numero del programma remoto (servizio), il numero di versione del programma remoto e il numero della procedura remota il programma specificato. Il numero di programma è assegnato dal produttore del servizio, il numero di procedura indica la funzione specifica di questo servizio
  2. Identificazione della versione del protocollo RPC. I messaggi RPC contengono un campo della versione del protocollo RPC. Viene utilizzato per concordare i formati dei parametri trasmessi quando il client sta lavorando diverse versioni RPC.
  3. Fornire meccanismi per autenticare il client al server. Il protocollo RPC fornisce una procedura per autenticare il client nel servizio e, se necessario, con ogni richiesta o inviare una risposta al client. Inoltre, RPC consente l'utilizzo di vari meccanismi di sicurezza aggiuntivi.

RPC può utilizzare quattro tipi di meccanismi di autenticazione:

  • AUTH_NULL - nessuna autenticazione
  • AUTH_UNIX - Autenticazione standard UNIX
  • AUTH_SHORT - Autenticazione standard UNIX con propria struttura di codifica
  • AUTH_DES - Autenticazione DES
  1. Identificazione dei messaggi di risposta alle richieste corrispondenti. I messaggi di risposta RPC contengono l'ID richiesta da cui sono stati creati. Questo ID può essere chiamato ID transazione della chiamata RPC. Questo meccanismo è particolarmente necessario quando si lavora in modalità asincrona e quando si esegue una sequenza di più chiamate RPC.
  2. Identificazione degli errori di protocollo. Tutti gli errori di rete o del server hanno identificatori univoci, mediante i quali ciascuno dei partecipanti alla connessione può determinare la causa dell'errore.

Strutture dei messaggi di protocollo

Quando si trasmettono messaggi RPC su un protocollo di trasporto, più messaggi RPC possono risiedere all'interno di un singolo pacchetto di trasporto. Per separare un messaggio dall'altro viene utilizzato un marcatore di record (RM - Record Marker). Ogni messaggio RPC è "etichettato" con esattamente un RM.

Un messaggio RPC può essere costituito da diversi frammenti. Ogni frammento è costituito da quattro byte di intestazione e (da 0 a 2**31-1) dati. Il primo bit dell'intestazione indica se il frammento dato è l'ultimo e i restanti 31 bit indicano la lunghezza del pacchetto di dati.

La struttura di RPC è formalmente descritta nel linguaggio di descrizione e rappresentazione dei formati di dati - XDR con aggiunte riguardanti la descrizione delle procedure. Si potrebbe anche dire che il linguaggio di descrizione RPC è un'estensione di XDR, integrato con il lavoro con le procedure.

La struttura di un pacchetto RPC è simile a questa:


La struttura della risposta (reply_body) può contenere sia la struttura passata in caso di errore (nel qual caso contiene il codice di errore), sia la struttura dell'elaborazione della richiesta andata a buon fine (nel qual caso contiene i dati restituiti).

Interfaccia di programmazione di alto livello.

L'uso di subroutine in un programma è il modo tradizionale per strutturare un compito, per renderlo più chiaro. Le routine più comunemente utilizzate sono raccolte in librerie dove possono essere utilizzate da diversi programmi. In questo caso noi stiamo parlando su una chiamata locale (locale), ad es. sia l'oggetto chiamante che quello chiamato funzionano all'interno dello stesso programma sullo stesso computer.

Nel caso di una chiamata remota, un processo in esecuzione su un computer avvia un processo sul computer remoto (ovvero esegue effettivamente il codice della procedura sul computer remoto). È ovvio che una chiamata di procedura remota differisce in modo significativo da una locale tradizionale, ma dal punto di vista del programmatore non ci sono praticamente tali differenze, ovvero l'architettura di una chiamata di procedura remota consente di simulare una chiamata locale.

Tuttavia, se nel caso di una chiamata locale, il programma passa i parametri alla procedura chiamata e riceve il risultato del lavoro attraverso lo stack o le aree di memoria condivise, allora nel caso di una chiamata remota, il passaggio dei parametri si trasforma nell'invio di una richiesta tramite la rete, e il risultato del lavoro è nella risposta in arrivo.

Questo approccio è una possibile base per la creazione di applicazioni distribuite, e anche se molte sistemi moderni non utilizzare questo meccanismo, i concetti e i termini di base vengono mantenuti in molti casi. Quando si descrive il meccanismo RPC, ci si riferirà tradizionalmente al processo chiamante come al client e al processo remoto che implementa la procedura come al server.

Una chiamata di procedura remota include i seguenti passaggi:

  1. Il programma client effettua una chiamata locale a una procedura chiamata stub. Allo stesso tempo, al client "sembra" che, chiamando lo stub, chiami effettivamente la procedura del server. Infatti, il client passa i parametri necessari allo stub e restituisce il risultato. Tuttavia, la situazione non è esattamente come immagina il cliente. Il compito dello stub è accettare argomenti destinati alla procedura remota, forse convertirli in un formato standard e formare una richiesta di rete. L'impacchettamento degli argomenti e l'esecuzione di una richiesta di rete si chiama marshalling.
  2. La richiesta di rete viene inviata tramite la rete a sistema remoto. Per fare ciò, lo stub utilizza chiamate appropriate, come quelle discusse nelle sezioni precedenti. Si noti che in questo caso possono essere utilizzati vari protocolli di trasporto e non solo le famiglie TCP/IP.
  3. Sull'host remoto accade di tutto ordine inverso. Lo stub del server attende una richiesta e, una volta ricevuta, recupera i parametri, gli argomenti della chiamata alla procedura. L'estrazione (unmarshalling) può includere le trasformazioni necessarie (ad esempio, il riordino dei byte).
  4. Lo stub effettua una chiamata alla procedura del real server a cui è indirizzata la richiesta del client, passandole gli argomenti ricevuti in rete.
  5. Dopo aver eseguito la procedura, il controllo ritorna allo stub del server, passandogli i parametri richiesti. Come lo stub del client; lo stub del server converte i valori restituiti dalla procedura per formare un messaggio di risposta di rete che viene inviato in rete al sistema da cui è arrivata la richiesta.
  6. Il sistema operativo passa il messaggio ricevuto allo stub del client, il quale, dopo la necessaria trasformazione, passa i valori (che sono i valori restituiti dalla procedura remota) al client, che lo interpreta come un normale ritorno dal procedura.

Quindi, dal punto di vista del cliente, effettua una chiamata di procedura remota, proprio come farebbe per una locale. Lo stesso si può dire del server: la procedura viene chiamata in modo standard, qualche oggetto (server stub) effettua una chiamata alla procedura locale e riceve i valori da essa restituiti. Il client tratta lo stub come una procedura server richiamabile e il server prende il proprio stub come client.

Pertanto, gli stub sono al centro del sistema RPC, responsabili di tutti gli aspetti della generazione e del passaggio di messaggi tra un client e un server remoto (procedura), anche se sia il client che il server considerano le chiamate da effettuare localmente. Questo è il concetto principale di RPC: nascondere completamente la natura distribuita (di rete) dell'interazione nel codice stub. I vantaggi di questo approccio sono evidenti: sia il client che il server sono indipendenti dall'implementazione della rete, entrambi lavorano all'interno di una determinata rete distribuita. macchina virtuale e le chiamate di procedura hanno un'interfaccia standard.

Parametri di passaggio

Passare i parametri del valore non è difficile. In questo caso, lo stub del client inserisce il valore del parametro nella richiesta di rete, magari eseguendo conversioni standard (ad esempio, modificando l'endianness). La situazione è molto più complicata quando si passano puntatori quando il parametro è l'indirizzo dei dati, non il suo valore. Il passaggio di un indirizzo nella richiesta non ha senso perché la procedura remota è in esecuzione in uno spazio di indirizzi completamente diverso. al massimo soluzione semplice utilizzato in RPC serve a impedire ai client di passare parametri diversi dal valore, sebbene ciò imponga certamente serie restrizioni.

Legame

Prima che un client possa chiamare una procedura remota, deve essere associato a un sistema remoto che dispone del server richiesto. Pertanto, il compito di vincolare è diviso in due:

  1. Trovare un host remoto con un server desiderato
  2. Trovare il processo server richiesto su un determinato host

Vari approcci possono essere utilizzati per trovare un host. Possibile variante- creazione di una directory centralizzata in cui gli host annunciano i propri server e dove il cliente, se lo desidera, può scegliere l'host e l'indirizzo della procedura adatti a lui.

Ciascuna procedura RPC è identificata in modo univoco da un numero di programma e di procedura. Il numero di programma definisce un gruppo di procedure remote, ognuna delle quali ha il proprio numero. A ciascun programma viene inoltre assegnato un numero di versione, in modo che se si apportano modifiche minori al programma (ad esempio, l'aggiunta di una procedura), non è necessario modificarne il numero. Tipicamente, diverse procedure funzionalmente simili sono implementate in un modulo di programma, che, una volta avviato, diventa il server di queste procedure e che è identificato dal numero di programma.

Pertanto, quando un client desidera chiamare una procedura remota, deve conoscere i numeri del programma, della versione e della procedura che forniscono il servizio richiesto.

Per inviare una richiesta, il client deve anche conoscere l'indirizzo di rete dell'host e il numero di porta associato al programma server che fornisce le procedure richieste. Questo viene fatto usando il demone portmap(IM) (chiamato rpcbind(IM) su alcuni sistemi). Il daemon viene eseguito su un host che fornisce un servizio di procedura remota e utilizza un numero di porta noto. Quando un processo server viene inizializzato, registra le sue procedure ei numeri di porta in portmap(IM). Ora, quando il client ha bisogno di conoscere il numero di porta per chiamare una particolare procedura, invia una richiesta al server portmap(IM), che a sua volta restituisce il numero di porta o inoltra la richiesta direttamente al server di procedura remota e restituisce una risposta al client quando viene eseguito. In ogni caso, se la procedura richiesta esiste, il client riceve il numero di porta della procedura dal server portmap(IM) e può effettuare ulteriori richieste direttamente a questa porta.

Gestione di situazioni speciali (eccezione)

La gestione delle eccezioni quando si chiamano le procedure locali non è un grosso problema. UNIX fornisce la gestione degli errori di processo come la divisione per zero, l'accesso a un'area di memoria non valida, ecc. Nel caso di una chiamata di procedura remota, la probabilità di situazioni di errore aumenta. Agli errori del server e dello stub si aggiungono gli errori relativi, ad esempio, alla ricezione di un messaggio di rete errato.

Ad esempio, quando si utilizza UDP come protocollo di trasporto, i messaggi vengono ritrasmessi dopo un certo timeout. Viene restituito un errore al client se, dopo un certo numero di tentativi, non è pervenuta alcuna risposta dal server. Nel caso in cui venga utilizzato il protocollo TCP, viene restituito un errore al client se il server ha terminato la connessione TCP.

Chiama Semantica

La chiamata inequivocabile di una procedura locale porta alla sua esecuzione, dopodiché il controllo ritorna al programma principale. La situazione è diversa quando si chiama una procedura remota. È impossibile determinare quando verrà eseguita esattamente la procedura, se verrà eseguita e, in tal caso, quante volte? Ad esempio, se la richiesta viene ricevuta dal sistema remoto dopo che il programma del server è andato in crash, la procedura non verrà eseguita affatto. Se il client non riceve una risposta dopo un certo periodo di tempo (timeout), invia nuovamente la richiesta, potrebbe verificarsi una situazione in cui la risposta è già trasmessa sulla rete e la richiesta ripetuta viene nuovamente accettata per l'elaborazione dalla procedura remota . In questo caso, la procedura verrà eseguita più volte.

Pertanto, l'esecuzione di una procedura remota può essere caratterizzata dalla seguente semantica:

  • Una e una sola volta. Questo comportamento (in alcuni casi il più desiderabile) è difficile da richiedere a causa di possibili arresti anomali del server.
  • Tempi massimi. Ciò significa che la procedura non è stata eseguita affatto o è stata eseguita solo una volta. Un'asserzione simile può essere fatta quando si riceve un errore invece di una normale risposta.
  • Almeno una volta. La procedura è stata probabilmente eseguita una volta, ma è possibile di più. Per il normale funzionamento in una situazione del genere, la procedura remota deve avere la proprietà idempotent (dall'inglese idemponent). Questa proprietà ha una procedura, la cui esecuzione ripetuta non provoca modifiche cumulative. Ad esempio, la lettura di un file è idempotente, ma l'aggiunta di testo a un file non lo è.

Rappresentazione dei dati

Quando il client e il server vengono eseguiti sullo stesso sistema sullo stesso computer, non ci sono problemi di incompatibilità dei dati. Sia per il client che per il server, i dati in forma binaria sono presentati allo stesso modo. Nel caso di una chiamata remota, la questione è complicata dal fatto che il client e il server possono essere eseguiti su sistemi con architetture diverse che hanno una diversa rappresentazione dei dati (ad esempio, la rappresentazione di un valore in virgola mobile, endianness, ecc.)

La maggior parte delle implementazioni del sistema RPC definisce alcune rappresentazioni di dati standard in cui devono essere convertiti tutti i valori passati nelle richieste e nelle risposte.

Ad esempio, il formato di rappresentazione dei dati in Sun Microsystems RPC è il seguente:

  1. Ordine dei byte - Il più grande per ultimo
  2. Rappresentazione di valori in virgola mobile - IEEE
  3. Rappresentazione dei caratteri - ASCII

Netto

Nella sua funzionalità, il sistema RPC occupa una posizione intermedia tra il livello applicativo e il livello di trasporto. Secondo il modello OSI, questa disposizione corrisponde ai livelli di presentazione e di sessione. Pertanto, RPC è teoricamente indipendente dall'implementazione della rete, in particolare dai protocolli di rete del livello di trasporto.

Le implementazioni software del sistema, di norma, supportano uno o due protocolli. Ad esempio, il sistema RPC di Sun Microsystems supporta il passaggio di messaggi utilizzando i protocolli TCP e UDP. La scelta dell'uno o dell'altro protocollo dipende dai requisiti dell'applicazione. La scelta del protocollo UDP è giustificata per applicazioni con le seguenti caratteristiche:

  • Le procedure chiamate sono idempotenti
  • La dimensione degli argomenti passati e il risultato restituito è inferiore alla dimensione del pacchetto UDP - 8 KB.
  • Il server fornisce lavoro con diverse centinaia di client. Poiché il server è costretto a mantenere una connessione con ciascuno dei client attivi quando lavora con i protocolli TCP, questo occupa una parte significativa delle sue risorse. Il protocollo UDP è meno dispendioso in termini di risorse a questo proposito.

D'altra parte, TCP consente il funzionamento efficiente delle applicazioni con le seguenti caratteristiche:

  • L'applicazione richiede un protocollo di trasferimento affidabile
  • Le procedure chiamate non sono idemponenti
  • La dimensione degli argomenti o del risultato restituito supera gli 8 KB

La scelta del protocollo di solito spetta al cliente e il sistema organizza la formazione e la trasmissione dei messaggi in modi diversi. Quindi, quando si utilizza il protocollo TCP, per il quale i dati trasmessi sono un flusso di byte, è necessario separare i messaggi l'uno dall'altro. Per fare ciò, ad esempio, viene utilizzato il protocollo di marcatura dei record descritto in RFC1057 "RPC: Remote Procedure Call Protocol specified version 2", in cui viene inserito un numero intero a 32 bit all'inizio di ogni messaggio, specificando la dimensione del messaggio in byte.

La situazione è diversa con la semantica della chiamata. Ad esempio, se RPC viene eseguito utilizzando un protocollo di trasporto inaffidabile (UDP), il sistema ritrasmette il messaggio a brevi intervalli (timeout). Se l'applicazione client non riceve una risposta, allora è sicuro dire che la procedura è stata eseguita zero o Di più una volta. Se è stata ricevuta una risposta, l'applicazione può dedurre che la procedura è stata eseguita almeno una volta. Con Trusted Transport Protocol (TCP), se si riceve una risposta, si può dire che la procedura è stata eseguita una volta. Se la risposta non viene ricevuta, è impossibile dire con certezza che la procedura non è stata eseguita3.

Come funziona?

Essenzialmente, il sistema RPC stesso è integrato nel programma client e nel programma server. La buona notizia è che quando si sviluppano applicazioni distribuite, non è necessario approfondire i dettagli del protocollo RPC o programmare l'elaborazione dei messaggi. Il sistema presuppone l'esistenza di un ambiente di sviluppo appropriato, che semplifica enormemente la vita dei creatori di software applicativo. Uno dei punti chiave in RPC è che lo sviluppo di un'applicazione distribuita inizia con la definizione di un'interfaccia oggetto - una descrizione formale delle funzioni del server, fatta su linguaggio speciale. Sulla base di questa interfaccia, gli stub client e server vengono quindi generati automaticamente. L'unica cosa da fare dopo è scrivere il codice della procedura vera e propria.

Ad esempio, si consideri RPC di Sun Microsystems. Il sistema si compone di tre parti principali:

  • rpcgen(1) è un compilatore RPC che, in base alla descrizione dell'interfaccia di procedura remota, genera stub client e server sotto forma di programmi C.
  • Libreria XDR (eXternal Data Representation), che contiene funzioni per la trasformazione vari tipi dati in una forma indipendente dalla macchina, consentendo lo scambio di informazioni tra sistemi eterogenei.
  • Una libreria di moduli che assicurano il funzionamento del sistema nel suo complesso.

Consideriamo un esempio della più semplice applicazione di registrazione eventi distribuita. Il client, all'avvio, chiama una procedura remota per scrivere un messaggio nel file di registro del computer remoto.

Per fare ciò, dovrai creare almeno tre file: la specifica delle interfacce della procedura remota log.x (nel linguaggio di descrizione dell'interfaccia), il testo effettivo delle procedure remote log.c e il testo della procedura principale del client program main() - client.c (nel linguaggio C).

Il compilatore rpcgen(l) genera tre file basati sulla specifica log.x: il testo degli stub del client e del server in linguaggio C (log clnt.c e log svc.c) e il file di descrizione log.h utilizzato da entrambi gli stub .

Quindi, diamo un'occhiata al codice sorgente dei programmi.

Questo file specifica i parametri di registrazione della procedura remota - numeri di programma, versione e procedura, e definisce anche l'interfaccia di chiamata - argomenti di input e valori restituiti. Viene così definita la procedura RLOG, prendendo come argomento una stringa (che verrà scritta nel log), e il valore restituito, per default, indica il successo o il fallimento dell'operazione richiesta.


programma LOG_PROG( versione LOG_VER( int RLOG (stringa) = 1; ) = 1; ) = 0x31234567;

Il compilatore rpcgen(l) crea un header file log.h, dove, in particolare, sono definite le procedure:


Diamo un'occhiata più da vicino a questo file. Il compilatore traduce il nome RLOG definito nel file di definizione dell'interfaccia in rlog_1, sostituendo i caratteri maiuscoli con caratteri minuscoli e aggiungendo il numero di versione del programma con un carattere di sottolineatura. Il tipo restituito è cambiato da int a int *. Questa è la regola: RPC consente di inviare e ricevere solo gli indirizzi dei parametri dichiarati nella descrizione dell'interfaccia. La stessa regola si applica alla stringa passata come argomento. Sebbene il file print.h non lo implichi, infatti, anche l'indirizzo della riga viene passato come argomento alla funzione rlog_l().

Oltre al file di intestazione, il compilatore rpcgen(l) genera i moduli client stub e server stub. In sostanza, il testo di questi file contiene tutto il codice della chiamata remota.

Lo stub del server è il programma host che gestisce tutte le interazioni di rete con il client (più precisamente, con il suo stub). Per eseguire l'operazione, lo stub del server effettua una chiamata di funzione locale, il cui testo deve essere scritto:


Lo stub del client accetta l'argomento passato alla procedura remota, effettua le conversioni necessarie, emette una richiesta al server portmap(1M), comunica con il server della procedura remota e infine passa il valore restituito al client. Per il client, chiamare una procedura remota si riduce a chiamare uno stub e non è diverso da una normale chiamata locale.

cliente.c


#includere #includere"log.h" principale(int argc, char*argv) ( CLIENTE *cl; char*server, *miastringa, *clnttime; time_tbintime; int*risultato; Se(argc != 2) ( fprintf(stderr, "Formato chiamata: %s HostAddr\n", argv ); exit (1) ; ) server = argv ; /*Ottieni l'handle del client. In caso di fallimento, segnaleremo che è impossibile stabilire una connessione con il server */ Se((c1 = clnt_create (server, LOG_PROG, LOG_VER, "udp")) == NULL) ( clnt_pcreateerror (server); exit (2); ) /*alloca un buffer per la stringa*/ miastringa = ( char*)maloc(100); /*Determiniamo l'ora dell'evento*/ bintime = tempo((tempo_t *) NULL); clnttime = ctime(&bintime); sprintf(mystring, "%s - Client avviato", clnttime); /*Invieremo un messaggio per il log - l'ora in cui il client ha iniziato a funzionare. In caso di fallimento, segnaleremo l'errore */ Se((risultato = rlog_l(&miastringa, cl)) == NULLO) ( fprintf(stderr, "errore2\n"); clnt_perror(cl, server); exit(3); ) /*In caso di errore sul computer remoto, segnala un errore*/ Se(*risultato !=0) fprintf(stderr, "Errore durante la scrittura nel log\n"); /*0libera la maniglia*/ cint distruggere(cl); uscita(0); )

Lo stub del client log_clnt.c viene compilato con il modulo client.c per produrre un eseguibile del client.


Ora su alcuni host server.nowhere.ru devi avviare il processo del server:


$ registratore

Successivamente, quando il client rlog viene avviato su un'altra macchina, il server aggiungerà la voce appropriata al file di registro.

Lo schema del funzionamento RPC in questo caso è mostrato in Fig. 1. I moduli interagiscono come segue:

  1. Quando un processo del server viene avviato, crea un socket UDP e collega qualsiasi porta locale a quel socket. Successivamente, il server chiama la funzione di libreria svc_register(3N) per registrare i numeri e le versioni del programma. Per fare ciò, la funzione chiama il processo portmap(IM) e passa i valori richiesti. Il server portmap (IM) di solito si avvia quando il sistema viene inizializzato e si collega a una porta nota. Ora portmap(3N) conosce il numero di porta per il nostro programma e la versione. Il server è in attesa di ricevere una richiesta. Si noti che tutte le azioni descritte vengono eseguite dallo stub del server creato dal compilatore rpcgen(IM).
  2. Quando il programma rlog si avvia, la prima cosa che fa è chiamare la funzione di libreria clnt_create(3N), fornendogli l'indirizzo del sistema remoto, i numeri del programma e della versione e il protocollo di trasporto. La funzione invia una richiesta al server portmap(IM) del sistema remoto server.nowhere.m e ottiene il numero di porta remota per il server di registro.
  3. Il client chiama la procedura rlog_1() definita nello stub del client e trasferisce il controllo allo stub. Questo, a sua volta, forma una richiesta (convertendo gli argomenti in formato XDR) sotto forma di pacchetto UDP e la invia alla porta remota ricevuta dal server portmap (IM). Quindi attende per un po' una risposta e, se non ricevuta, invia nuovamente la richiesta. In circostanze favorevoli, la richiesta viene accettata dal server logger (modulo stub del server). Lo stub determina quale funzione è stata chiamata (dal numero di procedura) e chiama la funzione rlog_1() del modulo log.c. Dopo aver restituito il controllo allo stub, lo stub converte il valore restituito dalla funzione rlog_1() in formato XDR e genera una risposta anch'essa sotto forma di pacchetto UDP. Dopo aver ricevuto la risposta, lo stub del client estrae il valore restituito, lo converte e lo restituisce al programma principale del client.

I programmi che comunicano su una rete necessitano di un meccanismo di comunicazione. Al livello inferiore, all'arrivo dei pacchetti, viene inviato un segnale, che viene elaborato dal programma di elaborazione del segnale di rete. Al livello superiore funziona il meccanismo di rendezvous (rendezvous) adottato nel linguaggio Ada. NFS utilizza un meccanismo RPC (Remote Procedure Call) in cui il client comunica con il server (vedere la figura 1). Secondo questo processo, il client prima chiama una procedura che invia una richiesta al server. All'arrivo di un pacchetto con una richiesta, il server invoca la sua procedura di apertura, esegue il servizio richiesto, invia una risposta e il controllo viene restituito al client.

L'interfaccia RPC può essere pensata come costituita da tre livelli:

Il livello superiore è completamente "trasparente". Un programma a questo livello potrebbe, ad esempio, contenere una chiamata alla procedura rnusers(), che restituisce il numero di utenti sulla macchina remota. Non è necessario conoscere l'utilizzo del meccanismo RPC finché si effettua la chiamata nel programma.

Il livello intermedio è per le applicazioni più comuni. Le chiamate RPC a questo livello sono gestite dalle subroutine registerrpc() e callrpc(): registerrpc() ottiene il codice a livello di sistema e callrpc() esegue una chiamata di procedura remota. La chiamata a rnusers() è implementata usando queste due subroutine.

Il livello inferiore viene utilizzato per attività più complesse che modificano i valori predefiniti ai valori dei parametri della procedura. A questo livello è possibile manipolare in modo esplicito i socket utilizzati per inviare messaggi RPC.

Come regola generale, dovresti usare il livello superiore ed evitare di usare i livelli inferiori a meno che non sia assolutamente necessario.

Anche se in questo tutorial trattiamo solo l'interfaccia C, le chiamate di procedure remote possono essere effettuate da qualsiasi linguaggio. Il funzionamento del meccanismo RPC per organizzare la comunicazione tra processi su macchine diverse non differisce dal suo funzionamento sulla stessa macchina.

RPC (Remote Procedure Call, Remote Procedure Call Service) è un'interfaccia tra utenti remoti e alcuni programmi host che vengono eseguiti su richiesta di questi utenti. Il servizio RPC di un host in genere fornisce una serie di programmi ai client. Ciascuno di questi programmi, a sua volta, è costituito da una o più procedure remote. Ad esempio, un servizio NFS remoto basato su chiamate RPC può essere costituito solo da due programmi: ad esempio, un programma interagisce con interfacce utente di alto livello e l'altro con funzioni di I/O di basso livello.

Ci sono due parti coinvolte in ogni chiamata di procedura remota: il client attivo, che invia una richiesta di chiamata di procedura al server, e il server, che invia una risposta al client.

Nota. Tieni presente che i termini "client" e "server" in questo caso si riferiscono a una transazione specifica.Un particolare host o software (processo o programma) può fungere sia da client che da server. Ad esempio, un programma che fornisce un servizio di procedura remota può anche essere un client nel lavorare con un file system di rete.

Il protocollo RPC si basa su un modello di chiamata di procedura remota simile a quello delle chiamate di procedura locale. Quando si chiama una procedura locale, si collocano gli argomenti in una posizione specifica nella memoria, nello stack o nelle variabili di ambiente e si trasferisce il controllo del processo a un indirizzo specifico. Una volta che hai finito, leggi i risultati a un indirizzo specifico e continua con il tuo processo.

Nel caso di lavorare con una procedura remota, la differenza principale è che la chiamata di funzione remota è servita da due processi: il processo client e il processo server.

Il processo client invia un messaggio al server, che include i parametri della procedura chiamata, e attende un messaggio di risposta con i risultati del suo lavoro. Quando viene ricevuta una risposta, il risultato viene letto e il processo continua. Sul lato server, il processo del gestore delle chiamate è in stato di attesa e, quando arriva un messaggio, legge i parametri della procedura, la esegue, invia una risposta e rimane in attesa della chiamata successiva.

Il protocollo RPC non impone alcun requisito sulle comunicazioni aggiuntive tra processi e non richiede il sincronismo delle funzioni svolte, ovvero le chiamate possono essere asincrone e reciprocamente indipendenti, in modo che il client possa eseguire altre procedure in attesa di una risposta. Il server RPC può allocare un processo separato o una macchina virtuale per ogni funzione, quindi, senza attendere il completamento delle richieste precedenti, può accettare immediatamente quelle successive.

Tuttavia, ci sono diverse importanti differenze tra le chiamate di procedure locali e remote:

1. Elaborazione degli errori. Il client dovrebbe in ogni caso essere informato degli errori che si verificano quando si effettuano chiamate di procedura remota sul server o sulla rete.

2. Variabili globali. Poiché il server non ha accesso allo spazio degli indirizzi del client, le chiamate di procedura remota non possono utilizzare parametri nascosti come variabili globali.

3. Prestazione. La velocità di esecuzione delle procedure remote, di norma, è inferiore di uno o due ordini di grandezza rispetto alla velocità di esecuzione di procedure locali simili.

4. Autenticazione. Poiché le chiamate di procedura remota si verificano sulla rete, è necessario utilizzare i meccanismi di autenticazione del client.

Principi di costruzione del protocollo.

Il protocollo RPC può utilizzare diversi protocolli di trasporto diversi. La responsabilità del protocollo RPC è solo quella di fornire standard e interpretare il passaggio di messaggi. L'affidabilità e l'affidabilità della trasmissione dei messaggi è interamente fornita dal livello di trasporto.

Tuttavia, RPC può controllare la scelta e alcune caratteristiche del protocollo di trasporto. Come esempio dell'interazione tra RPC e il protocollo di trasporto, si consideri la procedura per l'assegnazione della porta RPC di un processo applicativo tramite RPC - Mappatore portuale.

Questa funzione assegna dinamicamente (su richiesta) una porta specifica a una connessione RPC. Funzione Mappatore portuale viene utilizzato abbastanza spesso, poiché l'insieme delle porte di trasporto riservate per RPC è limitato e il numero di processi che possono potenzialmente funzionare contemporaneamente è molto elevato. Mappatore portuale, ad esempio, viene chiamato quando vengono selezionate le porte di comunicazione client e server del sistema NFS.

Servizio Mappatore portuale utilizza il meccanismo dei messaggi broadcast RPC su una porta specifica - III. Su questa porta, il client invia un messaggio broadcast di richiesta porta per uno specifico servizio RPC. Servizio Mappatore portuale elabora il messaggio, determina l'indirizzo del servizio RPC locale e invia una risposta al client. Servizio RPC Mappatore portuale può funzionare con entrambi i protocolli TCP e UDP.

RPC può funzionare con vari protocolli di trasporto, ma non duplica mai le loro funzioni, ad es. se RPC funziona su TCP, tutte le preoccupazioni sull'affidabilità e l'affidabilità della connessione RPC vengono assegnate a TCP. Tuttavia, se RPC è installato su UDP, può fornire funzionalità native aggiuntive per garantire la consegna dei messaggi.

Nota. Le attività dell'applicazione possono considerare il protocollo RPC come una procedura specifica per chiamare una funzione su una rete JSR (Jump Subroutine Instruction).

Affinché il protocollo RPC funzioni, devono essere soddisfatte le seguenti condizioni:

1. Identificazione univoca di tutte le procedure chiamate in remoto su un determinato host. Le richieste RPC contengono tre campi identificativi: il numero del programma remoto (servizio), il numero di versione del programma remoto e il numero della procedura remota del programma specificato. Il numero di programma è assegnato dal produttore del servizio, il numero di procedura indica la funzione specifica di questo servizio

2. Identificazione della versione del protocollo RPC. I messaggi RPC contengono un campo della versione del protocollo RPC. Viene utilizzato per coordinare i formati dei parametri trasmessi quando il client lavora con diverse versioni di RPC.

3. Fornire meccanismi per l'autenticazione del client sul server. Il protocollo RPC fornisce una procedura per autenticare il client nel servizio e, se necessario, con ogni richiesta o inviare una risposta al client. Inoltre, RPC consente l'utilizzo di vari meccanismi di sicurezza aggiuntivi.

RPC può utilizzare quattro tipi di meccanismi di autenticazione:

AUTH_NULL - nessuna autenticazione

AUTH_UNIX - Autenticazione standard UNIX

AUTH_SHORT - Autenticazione standard UNIX con propria struttura di codifica

AUTH_DES - Autenticazione DES

4. Identificazione dei messaggi di risposta alle richieste corrispondenti. I messaggi di risposta RPC contengono l'ID richiesta da cui sono stati creati. Questo ID può essere chiamato ID transazione della chiamata RPC. Questo meccanismo è particolarmente necessario quando si lavora in modalità asincrona e quando si esegue una sequenza di più chiamate RPC.

5. Identificazione degli errori di funzionamento del protocollo. Tutti gli errori di rete o del server hanno identificatori univoci, mediante i quali ciascuno dei partecipanti alla connessione può determinare la causa dell'errore.

Strutture dei messaggi di protocollo

Quando si trasmettono messaggi RPC su un protocollo di trasporto, più messaggi RPC possono risiedere all'interno di un singolo pacchetto di trasporto. Per separare un messaggio dall'altro viene utilizzato un marcatore di record (RM - Record Marker). Ogni messaggio RPC è "etichettato" con esattamente un RM.

Un messaggio RPC può essere costituito da diversi frammenti. Ogni frammento è costituito da quattro byte di intestazione e (da 0 a 2**31-1) dati. Il primo bit dell'intestazione indica se il frammento dato è l'ultimo e i restanti 31 bit indicano la lunghezza del pacchetto di dati.

La struttura di RPC è formalmente descritta nel linguaggio di descrizione e rappresentazione dei formati di dati - XDR con aggiunte riguardanti la descrizione delle procedure. Si potrebbe anche dire che il linguaggio di descrizione RPC è un'estensione di XDR, integrato con il lavoro con le procedure.

La struttura di un pacchetto RPC è simile a questa:

struttura rpc_msg (

unsigned int xid;

interruttore di unione (msg_type mtype) (

call_body ccorpo;

risposta corpo rcorpo;

dove xid è l'identificatore della transazione corrente, call_body è il pacchetto di richiesta, reply_body è il pacchetto di risposta. La struttura della richiesta è simile a questa:

struct chiamata corpo(

unsigned int rpcvers;

unsigned int prog;

unsigned int vers;

unsigned int proc;

opaque_auth cred;

opaco_authverf;

/* parametri della procedura */

La struttura della risposta (reply_body) può contenere sia la struttura passata in caso di errore (nel qual caso contiene il codice di errore), sia la struttura dell'elaborazione della richiesta andata a buon fine (nel qual caso contiene i dati restituiti).

Interfaccia di programmazione di alto livello.

L'uso di subroutine in un programma è il modo tradizionale per strutturare un compito, per renderlo più chiaro. Le routine più comunemente utilizzate sono raccolte in librerie dove possono essere utilizzate da diversi programmi. In questo caso si tratta di una chiamata locale (locale), ad es. sia l'oggetto chiamante che quello chiamato funzionano all'interno dello stesso programma sullo stesso computer.

Nel caso di una chiamata remota, un processo in esecuzione su un computer avvia un processo sul computer remoto (ovvero esegue effettivamente il codice della procedura sul computer remoto). È ovvio che una chiamata di procedura remota differisce in modo significativo da una locale tradizionale, ma dal punto di vista del programmatore non ci sono praticamente tali differenze, ovvero l'architettura di una chiamata di procedura remota consente di simulare una chiamata locale.

Tuttavia, se nel caso di una chiamata locale, il programma passa i parametri alla procedura chiamata e riceve il risultato del lavoro attraverso lo stack o le aree di memoria condivise, allora nel caso di una chiamata remota, il passaggio dei parametri si trasforma nell'invio di una richiesta tramite la rete, e il risultato del lavoro è nella risposta in arrivo.

Questo approccio è una possibile base per la creazione di applicazioni distribuite e, sebbene molti sistemi moderni non utilizzino questo meccanismo, i concetti e i termini di base in molti casi rimangono. Quando si descrive il meccanismo RPC, ci si riferirà tradizionalmente al processo chiamante come al client e al processo remoto che implementa la procedura come al server.

Una chiamata di procedura remota include i seguenti passaggi:

1. Il programma client effettua una chiamata locale a una procedura chiamata stub. Allo stesso tempo, al client "sembra" che, chiamando lo stub, chiami effettivamente la procedura del server. Infatti, il client passa i parametri necessari allo stub e restituisce il risultato. Tuttavia, la situazione non è esattamente come immagina il cliente. Il compito dello stub è accettare argomenti destinati alla procedura remota, forse convertirli in un formato standard e formare una richiesta di rete. L'impacchettamento degli argomenti e l'esecuzione di una richiesta di rete si chiama marshalling.

2. La richiesta di rete viene inviata tramite la rete al sistema remoto. Per fare ciò, lo stub utilizza chiamate appropriate, come quelle discusse nelle sezioni precedenti. Si noti che in questo caso possono essere utilizzati vari protocolli di trasporto e non solo le famiglie TCP/IP.

3. Sull'host remoto, tutto avviene in ordine inverso. Lo stub del server attende una richiesta e, una volta ricevuta, recupera i parametri, gli argomenti della chiamata alla procedura. L'estrazione (unmarshalling) può includere le trasformazioni necessarie (ad esempio, il riordino dei byte).

4. Lo stub effettua una chiamata alla procedura real server a cui è indirizzata la richiesta del client, passandole gli argomenti ricevuti in rete.

5. Dopo aver eseguito la procedura, il controllo ritorna allo stub del server, passandogli i parametri richiesti. Come lo stub del client; lo stub del server converte i valori restituiti dalla procedura per formare un messaggio di risposta di rete che viene inviato in rete al sistema da cui è arrivata la richiesta.

6. Il sistema operativo passa il messaggio ricevuto allo stub del client, il quale, dopo la necessaria trasformazione, passa i valori (che sono i valori restituiti dalla procedura remota) al client, che lo interpreta come un normale ritorno dalla procedura.

Quindi, dal punto di vista del cliente, effettua una chiamata di procedura remota, proprio come farebbe per una locale. Lo stesso si può dire del server: la procedura viene chiamata in modo standard, qualche oggetto (server stub) effettua una chiamata alla procedura locale e riceve i valori da essa restituiti. Il client tratta lo stub come una procedura server richiamabile e il server prende il proprio stub come client.

Pertanto, gli stub sono al centro del sistema RPC, responsabili di tutti gli aspetti della generazione e del passaggio di messaggi tra un client e un server remoto (procedura), anche se sia il client che il server considerano le chiamate da effettuare localmente. Questo è il concetto principale di RPC: nascondere completamente la natura distribuita (di rete) dell'interazione nel codice stub. I vantaggi di questo approccio sono evidenti: sia il client che il server sono indipendenti dall'implementazione della rete, entrambi lavorano all'interno di una macchina virtuale distribuita e le chiamate di procedura hanno un'interfaccia standard.

Parametri di passaggio

Passare i parametri del valore non è difficile. In questo caso, lo stub del client inserisce il valore del parametro nella richiesta di rete, magari eseguendo conversioni standard (ad esempio, modificando l'endianness). La situazione è molto più complicata quando si passano puntatori quando il parametro è l'indirizzo dei dati, non il suo valore. Il passaggio di un indirizzo nella richiesta non ha senso perché la procedura remota è in esecuzione in uno spazio di indirizzi completamente diverso. La soluzione più semplice utilizzata in RPC è quella di impedire ai client di passare parametri diversi dal valore, anche se questo impone certamente delle serie restrizioni.

Legame

Prima che un client possa chiamare una procedura remota, deve essere associato a un sistema remoto che dispone del server richiesto. Pertanto, il compito di vincolare è diviso in due:

Trovare un host remoto con un server desiderato

Trovare il processo server richiesto su un determinato host

Vari approcci possono essere utilizzati per trovare un host. Un'opzione possibile è creare una sorta di directory centralizzata in cui gli host pubblicizzano i propri server e dove il cliente, se lo desidera, può scegliere l'host e l'indirizzo della procedura che gli si addice.

Ciascuna procedura RPC è identificata in modo univoco da un numero di programma e di procedura. Il numero di programma definisce un gruppo di procedure remote, ognuna delle quali ha il proprio numero. A ciascun programma viene inoltre assegnato un numero di versione, in modo che se si apportano modifiche minori al programma (ad esempio, l'aggiunta di una procedura), non è necessario modificarne il numero. Tipicamente, diverse procedure funzionalmente simili sono implementate in un modulo di programma, che, una volta avviato, diventa il server di queste procedure e che è identificato dal numero di programma.

Pertanto, quando un client desidera chiamare una procedura remota, deve conoscere i numeri del programma, della versione e della procedura che forniscono il servizio richiesto.

Per inviare una richiesta, il client deve anche conoscere l'indirizzo di rete dell'host e il numero di porta associato al programma server che fornisce le procedure richieste. Questo viene fatto usando il demone portmap(IM) (chiamato rpcbind(IM) su alcuni sistemi). Il daemon viene eseguito su un host che fornisce un servizio di procedura remota e utilizza un numero di porta noto. Quando un processo server viene inizializzato, registra le sue procedure ei numeri di porta in portmap(IM). Ora, quando il client ha bisogno di conoscere il numero di porta per chiamare una particolare procedura, invia una richiesta al server portmap(IM), che a sua volta restituisce il numero di porta o inoltra la richiesta direttamente al server di procedura remota e restituisce una risposta al client quando viene eseguito. In ogni caso, se la procedura richiesta esiste, il client riceve il numero di porta della procedura dal server portmap(IM) e può effettuare ulteriori richieste direttamente a questa porta.

Gestione di situazioni speciali (eccezione)

La gestione delle eccezioni quando si chiamano le procedure locali non è un grosso problema. UNIX fornisce la gestione degli errori di processo come la divisione per zero, l'accesso a un'area di memoria non valida, ecc. Nel caso di una chiamata di procedura remota, la probabilità di situazioni di errore aumenta. Agli errori del server e dello stub si aggiungono gli errori relativi, ad esempio, alla ricezione di un messaggio di rete errato.

Ad esempio, quando si utilizza UDP come protocollo di trasporto, i messaggi vengono ritrasmessi dopo un certo timeout. Viene restituito un errore al client se, dopo un certo numero di tentativi, non è pervenuta alcuna risposta dal server. Nel caso in cui venga utilizzato il protocollo TCP, viene restituito un errore al client se il server ha terminato la connessione TCP.

Chiama Semantica

La chiamata inequivocabile di una procedura locale porta alla sua esecuzione, dopodiché il controllo ritorna al programma principale. La situazione è diversa quando si chiama una procedura remota. È impossibile determinare quando verrà eseguita esattamente la procedura, se verrà eseguita e, in tal caso, quante volte? Ad esempio, se la richiesta viene ricevuta dal sistema remoto dopo che il programma del server è andato in crash, la procedura non verrà eseguita affatto. Se il client non riceve una risposta dopo un certo periodo di tempo (timeout), invia nuovamente la richiesta, potrebbe verificarsi una situazione in cui la risposta è già trasmessa sulla rete e la richiesta ripetuta viene nuovamente accettata per l'elaborazione dalla procedura remota . In questo caso, la procedura verrà eseguita più volte.

Pertanto, l'esecuzione di una procedura remota può essere caratterizzata dalla seguente semantica:

- Una e una sola volta. Questo comportamento (in alcuni casi il più desiderabile) è difficile da richiedere a causa di possibili arresti anomali del server.

- Tempi massimi. Ciò significa che la procedura non è stata eseguita affatto o è stata eseguita solo una volta. Un'asserzione simile può essere fatta quando si riceve un errore invece di una normale risposta.

- Almeno una volta. La procedura è stata probabilmente eseguita una volta, ma è possibile di più. Per il normale funzionamento in una situazione del genere, la procedura remota deve avere la proprietà idempotent (dall'inglese idemponent). Questa proprietà ha una procedura, la cui esecuzione ripetuta non provoca modifiche cumulative. Ad esempio, la lettura di un file è idempotente, ma l'aggiunta di testo a un file non lo è.

Rappresentazione dei dati

Quando il client e il server vengono eseguiti sullo stesso sistema sullo stesso computer, non ci sono problemi di incompatibilità dei dati. Sia per il client che per il server, i dati in forma binaria sono presentati allo stesso modo. Nel caso di una chiamata remota, la questione è complicata dal fatto che il client e il server possono essere eseguiti su sistemi con architetture diverse che hanno una diversa rappresentazione dei dati (ad esempio, la rappresentazione di un valore in virgola mobile, endianness, ecc.)

La maggior parte delle implementazioni del sistema RPC definisce alcune rappresentazioni di dati standard in cui devono essere convertiti tutti i valori passati nelle richieste e nelle risposte.

Ad esempio, il formato di rappresentazione dei dati in Sun Microsystems RPC è il seguente:

Ordine dei byte - Il più grande per ultimo

Rappresentazione di valori in virgola mobile - IEEE

Rappresentazione dei caratteri - ASCII

Nella sua funzionalità, il sistema RPC occupa una posizione intermedia tra il livello applicativo e il livello di trasporto. Secondo il modello OSI, questa disposizione corrisponde ai livelli di presentazione e di sessione. Pertanto, RPC è teoricamente indipendente dall'implementazione della rete, in particolare dai protocolli di rete del livello di trasporto.

Le implementazioni software del sistema, di norma, supportano uno o due protocolli. Ad esempio, il sistema RPC di Sun Microsystems supporta il passaggio di messaggi utilizzando i protocolli TCP e UDP. La scelta dell'uno o dell'altro protocollo dipende dai requisiti dell'applicazione. La scelta del protocollo UDP è giustificata per applicazioni con le seguenti caratteristiche:

Le procedure chiamate sono idempotenti

La dimensione degli argomenti passati e il risultato restituito è inferiore alla dimensione del pacchetto UDP - 8 KB.

Il server fornisce lavoro con diverse centinaia di client. Poiché il server è costretto a mantenere una connessione con ciascuno dei client attivi quando lavora con i protocolli TCP, questo occupa una parte significativa delle sue risorse. Il protocollo UDP è meno dispendioso in termini di risorse a questo proposito.

D'altra parte, TCP consente il funzionamento efficiente delle applicazioni con le seguenti caratteristiche:

L'applicazione richiede un protocollo di trasferimento affidabile

Le procedure chiamate non sono idemponenti

La dimensione degli argomenti o del risultato restituito supera gli 8 KB

La scelta del protocollo di solito spetta al cliente e il sistema organizza la formazione e la trasmissione dei messaggi in modi diversi. Quindi, quando si utilizza il protocollo TCP, per il quale i dati trasmessi sono un flusso di byte, è necessario separare i messaggi l'uno dall'altro. Per fare ciò, ad esempio, viene utilizzato il protocollo di marcatura dei record descritto in RFC1057 "RPC: Remote Procedure Call Protocol specified version 2", in cui viene inserito un numero intero a 32 bit all'inizio di ogni messaggio, specificando la dimensione del messaggio in byte.

La situazione è diversa con la semantica della chiamata. Ad esempio, se RPC viene eseguito utilizzando un protocollo di trasporto inaffidabile (UDP), il sistema ritrasmette il messaggio a brevi intervalli (timeout). Se l'applicazione client non riceve una risposta, è possibile affermare che la procedura è stata eseguita zero o più volte. Se è stata ricevuta una risposta, l'applicazione può dedurre che la procedura è stata eseguita almeno una volta. Con Trusted Transport Protocol (TCP), se si riceve una risposta, si può dire che la procedura è stata eseguita una volta. Se la risposta non viene ricevuta, è impossibile dire con certezza che la procedura non è stata eseguita3.

Come funziona?

Essenzialmente, il sistema RPC stesso è integrato nel programma client e nel programma server. La buona notizia è che quando si sviluppano applicazioni distribuite, non è necessario approfondire i dettagli del protocollo RPC o programmare l'elaborazione dei messaggi. Il sistema presuppone l'esistenza di un ambiente di sviluppo appropriato, che semplifica enormemente la vita dei creatori di software applicativo. Uno dei punti chiave in RPC è che lo sviluppo di un'applicazione distribuita inizia con la definizione di un'interfaccia oggetto, una descrizione formale delle funzioni del server, realizzata in un linguaggio speciale. Sulla base di questa interfaccia, gli stub client e server vengono quindi generati automaticamente. L'unica cosa da fare dopo è scrivere il codice della procedura vera e propria.

Ad esempio, si consideri RPC di Sun Microsystems. Il sistema si compone di tre parti principali:

Rpcgen(1) è un compilatore RPC che, in base alla descrizione dell'interfaccia di procedura remota, genera stub client e server sotto forma di programmi C.

La libreria XDR (eXternal Data Representation), che contiene funzioni per convertire vari tipi di dati in un formato indipendente dalla macchina, che consente lo scambio di informazioni tra sistemi eterogenei.

Una libreria di moduli che assicurano il funzionamento del sistema nel suo complesso.

Consideriamo un esempio della più semplice applicazione di registrazione eventi distribuita. Il client, all'avvio, chiama una procedura remota per scrivere un messaggio nel file di registro del computer remoto.

Per fare ciò, dovrai creare almeno tre file: la specifica delle interfacce della procedura remota log.x (nel linguaggio di descrizione dell'interfaccia), il testo effettivo delle procedure remote log.c e il testo della procedura principale del client program main() - client.c (nel linguaggio C).

Il compilatore rpcgen(l) genera tre file basati sulla specifica log.x: il testo degli stub del client e del server in linguaggio C (log clnt.c e log svc.c) e il file di descrizione log.h utilizzato da entrambi gli stub .

Quindi, diamo un'occhiata al codice sorgente dei programmi.

Questo file specifica i parametri di registrazione della procedura remota - numeri di programma, versione e procedura, e definisce anche l'interfaccia di chiamata - argomenti di input e valori restituiti. Viene così definita la procedura RLOG, prendendo come argomento una stringa (che verrà scritta nel log), e il valore restituito, per default, indica il successo o il fallimento dell'operazione richiesta.

programmaLOG_PROG(

versione LOG_VER(

int RLOG(stringa) = 1;

) = 0x31234567;

Il compilatore rpcgen(l) crea un header file log.h, dove, in particolare, sono definite le procedure:

log.h

* Si prega di non modificare questo file.

* È stato generato utilizzando rpcgen.

#ifndef _LOG_H_RPCGEN

#define _LOG_H_RPCGEN

#includere

/* Numero programma */

#define LOG_PROG ((senza segno lungo) (0x31234567))

#define LOG_VER ((unsigned long) (1)) /*Numero di versione*/

#define RLOG ((unsigned long) (1)) /*Numero procedura*/

extern int *rlog_l ();

/* Procedura interna - non dobbiamo usarla */ extern int log_prog_l_freeresult();

#endif /* !_LOG_H_RPCGEN */

Diamo un'occhiata più da vicino a questo file. Il compilatore traduce il nome RLOG definito nel file di definizione dell'interfaccia in rlog_1, sostituendo i caratteri maiuscoli con caratteri minuscoli e aggiungendo il numero di versione del programma con un carattere di sottolineatura. Il tipo restituito è cambiato da int a int *. Questa è la regola: RPC consente di inviare e ricevere solo gli indirizzi dei parametri dichiarati nella descrizione dell'interfaccia. La stessa regola si applica alla stringa passata come argomento. Sebbene il file print.h non lo implichi, infatti, anche l'indirizzo della riga viene passato come argomento alla funzione rlog_l().

Oltre al file di intestazione, il compilatore rpcgen(l) genera i moduli client stub e server stub. In sostanza, il testo di questi file contiene tutto il codice della chiamata remota.

Lo stub del server è il programma host che gestisce tutte le interazioni di rete con il client (più precisamente, con il suo stub). Per eseguire l'operazione, lo stub del server effettua una chiamata di funzione locale, il cui testo deve essere scritto:

log.c

#includere

#includere

#includere

#include "log.h"

int *rlog_1(char **arg)

/*Il valore restituito deve essere definito come statico*/

risultato int statico;

int fd; /*Descrittore del file di registro*/

/*0apri il file di log (crealo se non esiste), restituisce un codice di errore se fallisce risultato == 1.*/

if ((fd=open("./server .log",

O_CREAT | O_RDWR | O_APPEND))< 0) return (&result);

len = strlen(*arg);

if (write(fd, *arg, strlen(*arg)) != len)

return(&risultato); /*Restituisce il risultato - il risultato dell'indirizzo*/

Lo stub del client accetta l'argomento passato alla procedura remota, effettua le conversioni necessarie, emette una richiesta al server portmap(1M), comunica con il server della procedura remota e infine passa il valore restituito al client. Per il client, chiamare una procedura remota si riduce a chiamare uno stub e non è diverso da una normale chiamata locale.

cliente.c

#includere

#include "log.h"

main(int argc, char *argv)

char *server, *mystring, *clnttime;

se (argc != 2) (

fprintf(stderr, "Formato chiamata: %s HostAddress\n",

/*Ottieni l'handle del client. In caso di fallimento, vi informeremo su

impossibilità di stabilire la connessione con il server*/

if ((c1 = clnt_create (server,

LOG_PROG, LOG_VER, "udp")) == NULL) (

clnt_pcreateerror(server);

/*alloca un buffer per la stringa*/

miastringa = (char*)malloc(100);

/*Determiniamo l'ora dell'evento*/

bintime = tempo((tempo_t *) NULL);

clnttime = ctime(&bintime);

sprintf(mystring, "%s - Client avviato", clnttime);

/*Invieremo un messaggio per il log - l'ora in cui il client ha iniziato a funzionare. In caso di fallimento, segnaleremo l'errore */

if ((risultato = rlog_l(&mystring, cl)) == NULL) (

fprintf(stderr, "errore2\n");

clnt_perror(cl, server);

/*In caso di errore sul computer remoto, segnala un errore*/

se (*risultato !=0)

fprintf(stderr, "Errore durante la scrittura nel log\n");

/*0libera la maniglia*/

cint distruggere(cl);

Lo stub del client log_clnt.c viene compilato con il modulo client.c per produrre un eseguibile del client.

cc -o rlog client.c log_clnt.c -Insl

Lo stub del server log_svc.c e la procedura log.c vengono compilati per produrre un eseguibile del server.

cc -o logger log_svc.c log.c -Insl

Ora su alcuni host server.nowhere.ru devi avviare il processo del server:

Successivamente, quando il client rlog viene avviato su un'altra macchina, il server aggiungerà la voce appropriata al file di registro.

Lo schema del funzionamento RPC in questo caso è mostrato in Fig. 1. I moduli interagiscono come segue:

1. Quando un processo del server viene avviato, crea un socket UDP e collega qualsiasi porta locale a quel socket. Successivamente, il server chiama la funzione di libreria svc_register(3N) per registrare i numeri e le versioni del programma. Per fare ciò, la funzione chiama il processo portmap(IM) e passa i valori richiesti. Il server portmap (IM) di solito si avvia quando il sistema viene inizializzato e si collega a una porta nota. Ora portmap(3N) conosce il numero di porta per il nostro programma e la versione. Il server è in attesa di ricevere una richiesta. Si noti che tutte le azioni descritte vengono eseguite dallo stub del server creato dal compilatore rpcgen(IM).

2. Quando il programma rlog si avvia, la prima cosa che fa è chiamare la funzione di libreria clnt_create(3N), fornendogli l'indirizzo del sistema remoto, il numero del programma e della versione e il protocollo di trasporto. La funzione invia una richiesta al server portmap(IM) del sistema remoto server.nowhere.m e ottiene il numero di porta remota per il server di registro.

3. Il client chiama la procedura rlog_1() definita nello stub del client e trasferisce il controllo allo stub. Questo, a sua volta, forma una richiesta (convertendo gli argomenti in formato XDR) sotto forma di pacchetto UDP e la invia alla porta remota ricevuta dal server portmap (IM). Quindi attende per un po' una risposta e, se non ricevuta, invia nuovamente la richiesta. In circostanze favorevoli, la richiesta viene accettata dal server logger (modulo stub del server). Lo stub determina quale funzione è stata chiamata (dal numero di procedura) e chiama la funzione rlog_1() del modulo log.c. Dopo aver restituito il controllo allo stub, lo stub converte il valore restituito dalla funzione rlog_1() in formato XDR e genera una risposta anch'essa sotto forma di pacchetto UDP. Dopo aver ricevuto la risposta, lo stub del client estrae il valore restituito, lo converte e lo restituisce al programma principale del client.


Remote Procedure Call RPC Il concetto di Remote Procedure Call - RPC consiste nell'estendere il noto e ben compreso meccanismo per il passaggio di controllo e dati all'interno di un programma in esecuzione sulla stessa macchina al trasferimento di controllo e dati su una rete. Gli strumenti di chiamata di procedura remota sono progettati per facilitare l'organizzazione del calcolo distribuito.La massima efficienza nell'utilizzo di RPC si ottiene in quelle applicazioni in cui esiste una connessione interattiva tra componenti remoti con un tempo di risposta breve e una quantità relativamente piccola di dati trasferiti.

Tali applicazioni sono chiamate orientate a RPC. I tratti caratteristici delle procedure locali chiamanti sono l'asimmetria, cioè una delle parti che interagiscono è l'iniziatore, la sincronicità, cioè l'esecuzione della procedura chiamante si interrompe dal momento in cui viene emessa la richiesta e riprende solo al ritorno dal chiamato L'implementazione di chiamate remote è molto più complicata dell'implementazione di chiamate di procedure locali.

Per cominciare, poiché le procedure chiamanti e chiamate sono in esecuzione su macchine diverse, hanno spazi di indirizzi diversi e questo crea problemi durante il passaggio di parametri e risultati, soprattutto se le macchine non sono identiche.Poiché RPC non può fare affidamento sulla memoria condivisa, ciò significa che i parametri RPC non devono contenere puntatori a posizioni di memoria non stack e che i valori dei parametri devono essere copiati da un computer all'altro.

La differenza successiva tra RPC e chiamata locale è che utilizza necessariamente il sistema di comunicazione sottostante, tuttavia, questo non dovrebbe essere visto esplicitamente né nella definizione delle procedure né nelle procedure stesse. La lontananza introduce ulteriori problemi. L'esecuzione del programma chiamante e della procedura locale chiamata nella stessa macchina è implementata nell'ambito di un singolo processo, ma almeno due processi partecipano all'implementazione RPC, uno in ciascuna macchina.

Nel caso in cui una di esse vada in crash, possono verificarsi le seguenti situazioni: quando la procedura chiamante va in crash, le procedure chiamate da remoto diventano orfane, e quando le procedure remote vanno in crash, le procedure chiamanti diventano genitori indigenti, che aspetteranno una risposta dal procedure remote inutilmente Inoltre, ci sono una serie di problemi associati all'eterogeneità dei linguaggi di programmazione e ambienti operativi le strutture dati e le strutture di chiamata di procedura supportate in un linguaggio di programmazione non sono supportate allo stesso modo in tutti gli altri linguaggi.

Questi e alcuni altri problemi sono risolti dalla diffusa tecnologia RPC che è alla base di molti distribuiti sistemi operativi. Operazioni di base RPC Per capire come funziona RPC, consideriamo prima di tutto l'esecuzione di una chiamata di procedura locale su una normale macchina in esecuzione offline. Sia, ad esempio, la chiamata di sistema count read fd,buf,nbytes dove fd è un numero intero, buf è un array di caratteri e nbytes è un numero intero.

Per effettuare la chiamata, la procedura chiamante inserisce i parametri nello stack in ordine inverso Figura 3.1. Dopo che la chiamata a read è stata eseguita, inserisce il valore restituito in un registro, sposta l'indirizzo di ritorno e restituisce il controllo alla procedura chiamante, che recupera i parametri dallo stack, reimpostandolo.Si noti che in C, i parametri possono essere chiamati sia per riferimento per nome, sia per valore per valore. Rispetto alla procedura chiamata, i parametri di valore sono variabili locali inizializzabili.

La procedura chiamata può modificarle e ciò non influirà sul valore degli originali di queste variabili nella procedura chiamante. Se un puntatore a una variabile viene passato alla procedura chiamata, la modifica del valore di questa variabile da parte della procedura chiamata comporta la modifica del valore di questa variabile per la procedura chiamante, fatto molto significativo per RPC. Esiste anche un altro meccanismo di passaggio dei parametri che non viene utilizzato nel linguaggio C. Si chiama ripristino call-by-copy, che richiede al programma chiamante di copiare le variabili nello stack come valori e quindi copiarle nuovamente dopo che la chiamata è stata eseguita. fatto sui valori originali della procedura chiamante.

La scelta del meccanismo di passaggio dei parametri da utilizzare spetta ai progettisti del linguaggio. A volte dipende dal tipo di dati che vengono passati: in C, ad esempio, gli interi e altri dati scalari vengono sempre passati per valore, mentre gli array vengono sempre passati per riferimento.

Riso. 3.1. a Stack prima della chiamata di lettura b Stack durante l'esecuzione della procedura a Stack dopo il ritorno al programma chiamante L'idea alla base di RPC è di far sembrare una chiamata di procedura remota il più possibile simile a una chiamata di procedura locale. In altre parole, per rendere RPC trasparente, la procedura chiamante non ha bisogno di sapere che la procedura chiamata si trova su un'altra macchina e viceversa RPC ottiene la trasparenza nel modo seguente.

Quando la procedura chiamata è effettivamente una procedura remota, un'altra versione della procedura, chiamata client stub, viene inserita nella libreria invece della procedura locale. Come la procedura originale, lo stub viene chiamato utilizzando la sequenza di chiamata come nella Figura 3.1 e si verifica un interrupt quando si accede al kernel. Solo che a differenza della procedura originale, non inserisce parametri nei registri e non richiede dati al kernel, ma forma un messaggio da inviare al kernel della macchina remota. Fasi di esecuzione RPC L'interazione dei componenti software durante l'esecuzione di una chiamata di procedura remota è illustrata nella Figura 3.2. Dopo che lo stub del client è stato chiamato dal programma client, il suo primo compito è riempire il buffer con il messaggio che invia.

Su alcuni sistemi, lo stub del client ha un unico buffer di lunghezza fissa che viene riempito dall'inizio ogni volta che arriva una nuova richiesta. Su altri sistemi, il buffer dei messaggi è un pool di buffer per i singoli campi dei messaggi, alcuni dei quali sono già pieni.

Questo metodo è particolarmente adatto quando il pacchetto ha un formato costituito da un gran numero di campi, ma i valori di molti di questi campi non cambiano da chiamata a chiamata. I parametri devono quindi essere convertiti nel formato appropriato e inseriti nel buffer dei messaggi A questo punto il messaggio è pronto per essere trasmesso, quindi viene eseguito un kernel interrupt. Riso. 3.2. Chiamata di procedura remota Quando il kernel riceve il controllo, cambia contesto, salva i registri del processore e i descrittori della pagina della mappa di memoria, imposta nuova carta memoria da utilizzare per la modalità kernel. Poiché i contesti del kernel e dell'utente sono diversi, il kernel deve copiare il messaggio esattamente nel proprio spazio di indirizzi, in modo che possa accedervi, ricordare l'indirizzo di destinazione e possibilmente altri campi di intestazione, e deve passarlo all'interfaccia di rete.

Questo completa il lavoro sul lato client.

Il timer di trasferimento si avvia e il kernel può eseguire un sondaggio round-robin per una risposta o trasferire il controllo allo scheduler, che sceglierà un altro processo da eseguire. Nel primo caso, l'esecuzione della query è accelerata, ma non c'è multiprogrammazione. Sul lato server, i bit in entrata vengono inseriti dall'hardware ricevente in un buffer integrato o in RAM.Quando tutte le informazioni sono state ricevute, viene generato un interrupt.

Il gestore dell'interrupt controlla che i dati del pacchetto siano corretti e determina a quale stub deve passarlo.Se nessuno stub si aspetta il pacchetto, il gestore dell'interrupt deve bufferizzarlo o scartarlo del tutto. Se è presente uno stub in attesa, il messaggio viene copiato in esso. Infine, viene eseguito un cambio di contesto, che ripristina i registri e la mappa di memoria ai valori che avevano quando lo stub ha effettuato la chiamata di ricezione.

Ora lo stub del server inizia a funzionare. Decomprime i parametri e li inserisce in modo appropriato nello stack. Quando tutto è pronto, viene effettuata la chiamata al server. Al termine della procedura, il server invia i risultati al client, per fare ciò vengono eseguiti tutti i passaggi sopra descritti, solo in ordine inverso. La Figura 3.3 mostra la sequenza di comandi che devono essere eseguiti per ogni chiamata RPC e la Figura 3.4 mostra quale proporzione del tempo totale di esecuzione di RPC viene impiegata in ciascuna delle 14 fasi descritte.

La ricerca è stata condotta su un multiprocessore postazione di lavoro DEC Firefly, e sebbene la presenza di cinque processori abbia necessariamente influito sui risultati delle misurazioni, l'istogramma mostrato in figura dà un'idea generale del processo di esecuzione di RPC. Riso. 3.3. Fasi della procedura RPC Fig. 3.4. Distribuzione del tempo tra 14 fasi di esecuzione RPC 1. Chiama lo stub 2. Prepara il buffer 3. Impacchetta i parametri 4. Compila il campo di intestazione 5. Calcola il checksum nel messaggio 6. Interrompi al kernel 7. Accoda il pacchetto per l'esecuzione 8. Invia il messaggio al controllore sul bus QBUS 9. Tempo di trasferimento Ethernet 10. Ricevi pacchetto dal controllore 11. Routine di interruzione 12. Calcolo checksum 13. Passaggio contesto a spazio utente 14. Esecuzione stub server

Un modo per risolvere questo problema consiste nell'utilizzare direttamente l'indirizzo di rete del server nel programma client.

Lo svantaggio di questo approccio è che è estremamente poco flessibile quando si sposta un server, o quando si aumenta il numero di server, o quando si cambia l'interfaccia in tutti questi e molti altri casi, è necessario ricompilare tutti i programmi che utilizzavano hard impostazione dell'indirizzo del server.Al fine di evitare tutti questi problemi, in alcuni sistemi distribuiti utilizzare qualcosa chiamato collegamento dinamico.

Il punto di partenza per il collegamento dinamico è la definizione formale della specifica del server. La specifica contiene il nome del file server, il numero di versione e un elenco di procedure di servizio fornite da questo server per i client (Figura 3.5). Per ogni procedura viene fornita una descrizione dei suoi parametri, indicando se questo parametro è di input o di output relativo al server.Alcuni parametri possono essere sia di input che di output, ad esempio viene modificato un array inviato dal client al server lì, quindi l'operazione viene restituita al client copia ripristino . Riso. 3.5. Specifica del server RPC La specifica formale del server viene utilizzata come input per il programma generatore di stub che crea sia stub client che server.

Vengono quindi inseriti nelle rispettive librerie. Quando un programma client utente richiama una procedura definita nella specifica del server, la corrispondente procedura stub viene associata al binario del programma.

Allo stesso modo, quando un server viene compilato, gli stub del server vengono associati ad esso. Quando il server si avvia, la prima azione è passare la sua interfaccia del server programma speciale, chiamato legante ohm. Questo processo, noto come processo di registrazione del server, prevede che il server fornisca il proprio nome, numero di versione, identificatore univoco e un handle per la posizione del server.L'handle è indipendente dal sistema e può essere un IP, Ethernet, X.500 o altro indirizzo.

Inoltre, può contenere altre informazioni, ad esempio relative all'autenticazione. Quando il client chiama per la prima volta una delle procedure remote, ad esempio read, lo stub del client vede che non è ancora connesso al server e invia un messaggio al programma binder con la richiesta di importare l'interfaccia del versione desiderata del server desiderato.Se tale server esiste, il binder passa il descrittore e l'identificatore univoco per lo stub del client.

Lo stub del client, quando invia un messaggio con una richiesta, utilizza un descrittore come indirizzo. Il messaggio contiene parametri e un identificatore univoco che il motore del server utilizza per indirizzare il messaggio in arrivo al server corretto se ce ne sono diversi su questa macchina. Questo metodo di importazione ed esportazione delle interfacce è altamente flessibile: ad esempio, potrebbero esserci più server che supportano la stessa interfaccia ei client vengono assegnati in modo casuale ai server.

Nell'ambito di questo metodo, diventa possibile interrogare periodicamente i server, analizzarne le prestazioni e, in caso di guasto, spegnimento automatico, che aumenta la tolleranza ai guasti complessiva del sistema. Questo metodo può anche supportare l'autenticazione del client. Ad esempio, un server può determinare che può essere utilizzato solo dai client di un determinato elenco, ma l'associazione dinamica presenta degli svantaggi, come l'overhead aggiuntivo delle interfacce di esportazione e importazione.

L'entità di questi costi può essere significativa, poiché esistono molti processi client poco tempo, e ad ogni avvio del processo è necessario ripetere la procedura di importazione dell'interfaccia. Inoltre, nei grandi sistemi distribuiti, il programma binder può diventare un collo di bottiglia e la creazione di più programmi con lo stesso scopo aumenta anche il sovraccarico di creazione e sincronizzazione dei processi Semantica RPC in caso di guasti Idealmente, RPC dovrebbe funzionare correttamente in caso di fallimenti.

Considera le seguenti classi di errore 1. Il client non è in grado di determinare la posizione del server, ad esempio, se il server desiderato si guasta o perché il programma client è stato compilato molto tempo fa e utilizzato vecchia versione interfaccia del server. In questo caso, in risposta ad una richiesta del client, viene ricevuto un messaggio contenente un codice di errore. 2. La richiesta dal client al server è andata persa La soluzione più semplice è attraverso certo tempo ripetere la richiesta. 3. Messaggio di risposta perso dal server al client.

Questa opzione è più complicata della precedente, poiché alcune procedure non sono idempotenti. Una procedura idempotente è una procedura la cui richiesta di esecuzione può essere ripetuta più volte senza modificare il risultato. La lettura di un file può servire da esempio di tale procedura, tuttavia la procedura per prelevare un determinato importo da un conto bancario non è idempotente e, in caso di perdita della risposta, una richiesta ripetuta può modificare in modo significativo lo stato del conto del cliente.

Una possibile soluzione è rendere tutte le procedure idempotenti. Tuttavia, in pratica ciò non è sempre possibile, quindi è possibile utilizzare un altro metodo: la numerazione sequenziale di tutte le richieste da parte del core del client. Il core del server ricorda il numero della richiesta più recente da ciascuno dei client e, al ricevimento di ciascuna richiesta, analizza se questa richiesta è primaria o ripetuta. 4. Il server si è bloccato dopo aver ricevuto la richiesta Anche qui è importante la proprietà di idempotenza, ma sfortunatamente l'approccio della numerazione delle richieste non può essere applicato.

In questo caso, è importante quando si è verificato l'errore, prima o dopo l'operazione. Ma il core del client non è in grado di riconoscere queste situazioni, sa solo che la risposta è scaduta. Esistono tre approcci a questo problema Attendere il riavvio del server e riprovare l'operazione.Questo approccio garantisce che l'RPC sia stato completato almeno una volta, e possibilmente più volte. Segnalare immediatamente l'errore all'applicazione.

Questo approccio garantisce che l'RPC sia stato eseguito al massimo una volta. Il terzo approccio non garantisce nulla. Quando il server fallisce, non viene fornito alcun supporto al client. L'RPC potrebbe non essere eseguito affatto o potrebbe essere eseguito molte volte. In ogni caso, questo metodo è molto facile da implementare. Nessuno di questi approcci è molto allettante e l'opzione ideale, che garantirebbe esattamente un'esecuzione RPC, nel caso generale non può essere implementata per motivi fondamentali.

Supponiamo, ad esempio, che un'operazione remota stampi del testo, il che comporta il caricamento di un buffer della stampante e l'impostazione di un bit in un registro di controllo della stampante, che provoca l'avvio della stampante.Un arresto anomalo del server può verificarsi un microsecondo prima o un microsecondo dopo il bit di controllo è impostato. Il momento del fallimento determina interamente la procedura di ripristino, ma il cliente non può conoscere il momento del fallimento.

In breve, la possibilità di un crash del server cambia radicalmente la natura di RPC e riflette chiaramente la differenza tra centralizzata e sistema distribuito. Nel primo caso, un arresto anomalo del server provoca un arresto anomalo del client e il ripristino è impossibile. Nel secondo caso, le azioni di ripristino del sistema sono possibili e necessarie. 1. Il client si è arrestato in modo anomalo dopo aver inviato una richiesta. In questo caso, i calcoli vengono eseguiti su risultati che nessuno si aspetta, tali calcoli sono chiamati orfani. La presenza di orfani può causare vari problemi: sovraccarico del tempo CPU, blocco delle risorse, sostituzione della risposta alla richiesta corrente con la risposta alla richiesta emessa dalla macchina client prima del riavvio del sistema.

Come comportarsi con gli orfani? Considera 4 possibili soluzioni. Distruzione. Prima che uno stub del client invii un messaggio RPC, crea una voce di registro che gli dice cosa farà dopo.Il registro viene memorizzato su disco o altra memoria a tolleranza d'errore.

Dopo il crash, il sistema si riavvia, il registro viene analizzato e gli orfani vengono eliminati. Gli svantaggi di questo approccio sono, in primo luogo, l'aumento del costo associato alla scrittura di ogni RPC su disco e, in secondo luogo, la possibile inefficienza dovuta alla comparsa di orfani di seconda generazione generati da RPC rilasciati da orfani di prima generazione. Reincarnazione In questo caso, tutti i problemi vengono risolti senza utilizzare la scrittura su disco. Il metodo consiste nel dividere il tempo in periodi numerati progressivamente. Quando il client si riavvia, trasmette un messaggio a tutte le macchine sull'inizio di un nuovo periodo.

Alla ricezione di questo messaggio, tutti i calcoli remoti vengono terminati. Naturalmente, se la rete è segmentata, alcuni orfani potrebbero sopravvivere. La reincarnazione soft è simile al caso precedente, tranne per il fatto che non tutti i calcoli remoti vengono trovati e distrutti, ma solo i calcoli del client che si riavvia. Scadenza Ogni richiesta ha un tempo standard, T, entro il quale deve essere completata.

Se la richiesta non viene completata entro il tempo assegnato, viene allocato un quantum aggiuntivo. Sebbene ciò richieda lavoro aggiuntivo, se, dopo un arresto anomalo del client, il server attende un intervallo T prima che il client venga riavviato, allora tutti gli orfani vengono necessariamente distrutti.In pratica, nessuno di questi approcci è auspicabile; Si supponga, ad esempio, che un orfano abbia bloccato uno o più file di database.

Se un orfano viene improvvisamente distrutto, questi blocchi rimarranno, inoltre, gli orfani distrutti potrebbero rimanere in piedi in varie code di sistema, in futuro potrebbero causare l'esecuzione di nuovi processi e così via.

Cosa faremo con il materiale ricevuto:

Se questo materiale ti è stato utile, puoi salvarlo sulla tua pagina sui social network:

Lezione 4

4.1 Concetto di chiamata di procedura remota

L'idea di chiamare procedure remote (Chiamata procedura remota - RPC) consiste nell'estendere il noto e ben compreso meccanismo per il trasferimento di controllo e dati all'interno di un programma in esecuzione sulla stessa macchina al trasferimento di controllo e dati su una rete. Gli strumenti di chiamata a procedura remota sono progettati per facilitare l'organizzazione del calcolo distribuito. L'uso più efficiente di RPC si ottiene in quelle applicazioni in cui esiste una comunicazione interattiva tra componenti remoti con un tempo di risposta ridotto e una quantità relativamente piccola di dati trasferiti. Tali applicazioni sono chiamate orientate a RPC.

I tratti caratteristici della chiamata di procedure locali sono: l'asimmetria, cioè una delle parti che interagiscono è l'iniziatore; sincrono, cioè l'esecuzione della procedura chiamante si interrompe dal momento in cui viene emessa la richiesta e riprende solo dopo il rientro dalla procedura chiamata.

L'implementazione di chiamate remote è molto più complicata dell'implementazione di chiamate di procedure locali. Tanto per cominciare, poiché le procedure chiamanti e chiamate vengono eseguite su macchine diverse, hanno spazi di indirizzi diversi e questo crea problemi quando si passano parametri e risultati, soprattutto se le macchine non sono identiche. Poiché RPC non può fare affidamento sulla memoria condivisa, ciò significa che i parametri RPC non devono contenere puntatori a posizioni di memoria non stack e che i valori dei parametri devono essere copiati da un computer all'altro. La differenza successiva tra RPC e chiamata locale è che utilizza necessariamente il sistema di comunicazione sottostante, tuttavia, questo non dovrebbe essere visto esplicitamente né nella definizione delle procedure né nelle procedure stesse. La lontananza introduce ulteriori problemi. L'esecuzione del programma chiamante e della procedura locale chiamata sulla stessa macchina è implementata all'interno di un singolo processo. Ma ci sono almeno due processi coinvolti nell'implementazione RPC, uno su ciascuna macchina. Se una di esse va in crash, si possono verificare le seguenti situazioni: se la procedura chiamante va in crash, le procedure chiamate da remoto diventeranno orfane, e se le procedure remote vanno in crash, le procedure chiamanti diventeranno dei "genitori indigenti" che aspetteranno una risposta dal procedure remote inutilmente.

Inoltre, ci sono una serie di problemi associati all'eterogeneità dei linguaggi di programmazione e degli ambienti operativi: le strutture dati e le strutture di chiamata delle procedure supportate in un qualsiasi linguaggio di programmazione non sono supportate allo stesso modo in tutti gli altri linguaggi.


Questi e altri problemi sono risolti dalla diffusa tecnologia RPC che è alla base di molti sistemi operativi distribuiti.

Operazioni RPC di base

Per capire come funziona RPC, consideriamo prima di tutto l'esecuzione di una chiamata di procedura locale su una normale macchina in esecuzione offline. Lascia che questa, ad esempio, sia una chiamata di sistema

count=read(fd,buf,nbytes);

dove fd è un numero intero;

buf è un array di caratteri;

nbyte è un numero intero.

Per effettuare la chiamata, la procedura chiamante inserisce i parametri nello stack in ordine inverso. Al termine della chiamata a read, inserisce il valore di ritorno in un registro, sposta l'indirizzo di ritorno e restituisce il controllo alla procedura chiamante, che recupera i parametri dallo stack, reimpostandolo. Si noti che in C i parametri possono essere chiamati per riferimento (per nome) o per valore (per valore). Rispetto alla procedura chiamata, i parametri di valore sono variabili locali inizializzabili. La procedura chiamata può modificarle e ciò non influirà sul valore degli originali di queste variabili nella procedura chiamante.

Se un puntatore a una variabile viene passato alla procedura chiamata, la modifica del valore di questa variabile da parte della procedura chiamata comporta la modifica del valore di questa variabile per la procedura chiamante. Questo fatto è molto significativo per RPC.

Esiste anche un altro meccanismo per passare i parametri che non è usato nel linguaggio C. Si chiama call-by-copy/restore e consiste nella necessità per il programma chiamante di copiare le variabili nello stack come valori, e poi copiarle indietro dopo che la chiamata è stata effettuata sui valori originali della procedura chiamante.

La scelta del meccanismo di passaggio dei parametri da utilizzare spetta ai progettisti del linguaggio. A volte dipende dal tipo di dati trasferiti. In C, ad esempio, gli interi e altri dati scalari vengono sempre passati per valore e gli array vengono sempre passati per riferimento.

L'idea alla base di RPC è di far sembrare una chiamata di procedura remota uguale a una chiamata di procedura locale, se possibile. In altre parole, rendere RPC trasparente: la procedura chiamante non ha bisogno di sapere che la procedura chiamata si trova su un'altra macchina e viceversa.

RPC raggiunge la trasparenza nel modo seguente. Quando la procedura chiamata è effettivamente remota, un'altra versione della procedura, chiamata client stub, viene inserita nella libreria invece della procedura locale. Come la procedura originale, lo stub viene chiamato utilizzando la sequenza di chiamata e si verifica un interrupt quando si accede al kernel. Solo che a differenza della procedura originale, non inserisce parametri nei registri e non richiede dati al kernel, ma forma un messaggio da inviare al kernel della macchina remota.

Passaggi di esecuzione RPC

L'interazione dei componenti software durante l'esecuzione di una chiamata di procedura remota è illustrata nella Figura 2.

Figura 2. Chiamata di procedura remota

Dopo che lo stub del client è stato chiamato dal programma client, il suo primo compito è riempire il buffer con il messaggio che invia. Su alcuni sistemi, lo stub del client ha un unico buffer di lunghezza fissa che viene riempito dall'inizio ogni volta che arriva una nuova richiesta. Su altri sistemi, il buffer dei messaggi è un pool di buffer per i singoli campi dei messaggi, alcuni dei quali sono già pieni. Questo metodo è particolarmente adatto quando il pacchetto ha un formato costituito da un gran numero di campi, ma i valori di molti di questi campi non cambiano da chiamata a chiamata.

I parametri devono quindi essere convertiti nel formato appropriato e inseriti nel buffer dei messaggi. A questo punto il messaggio è pronto per essere trasmesso, quindi viene eseguito un interrupt del kernel.

Quando il kernel ottiene il controllo, cambia contesto, salva i registri del processore e la mappa di memoria (descrittori di pagina), imposta una nuova mappa di memoria da utilizzare per il funzionamento in modalità kernel. Poiché i contesti del kernel e dell'utente sono diversi, il kernel deve copiare il messaggio esattamente nel proprio spazio di indirizzi, in modo che possa accedervi, ricordare l'indirizzo di destinazione (e possibilmente altri campi di intestazione) e deve passarlo alla rete interfaccia. Questo completa il lavoro sul lato client. Il timer di trasferimento si avvia e il kernel può eseguire un sondaggio round-robin per una risposta o trasferire il controllo allo scheduler, che sceglierà un altro processo da eseguire. Nel primo caso, l'esecuzione della query è accelerata, ma non c'è multiprogrammazione.

Sul lato server, i bit in ingresso vengono inseriti dall'hardware ricevente in un buffer integrato o nella RAM. Quando tutte le informazioni sono state ricevute, viene generato un interrupt. Il gestore di interrupt controlla la validità dei dati del pacchetto e determina quale stub deve passarlo. Se nessuno degli stub si aspetta questo pacchetto, il gestore deve bufferizzarlo o scartarlo del tutto. Se è presente uno stub in attesa, il messaggio viene copiato in esso. Infine, viene eseguito un cambio di contesto, che ripristina i registri e la mappa di memoria ai valori che avevano quando lo stub ha effettuato la chiamata di ricezione.

Ora lo stub del server inizia a funzionare. Decomprime i parametri e li inserisce in modo appropriato nello stack. Quando tutto è pronto, viene effettuata la chiamata al server. Dopo aver eseguito la procedura, il server invia i risultati al client. Per fare ciò, vengono eseguiti tutti i passaggi sopra descritti, solo in ordine inverso.

La Figura 3 mostra la sequenza di comandi che devono essere eseguiti per ogni chiamata RPC.

Figura 3. Passaggi della procedura RPC

Se noti un errore, seleziona una parte di testo e premi Ctrl + Invio
CONDIVIDERE: