Windows - Meziprocesní komunikace (Interprocess communication, IPC)
Meziprocesní komunikace (IPC) jsou mechanizmy umožňující
komunikaci a sdílení dat mezi procesy. Některé formy IPC fungují mezi
několika speciálními procesy, jiné mezi počítači na síti.
Typicky, aplikace používají IPC jako server nebo jako
klient. Klient je aplikace nebo proces, který požaduje
službu od jiné aplikace nebo procesu. Server je aplikace
nebo proces, který odpovídá na požadavky klientů. Spousta aplikací se
chová jako server i jako klient.
Win32 API poskytuje tyto mechanizmy IPC: schránku, Dynamic Data Exchange (DDE), mapování souboru, poštovní schránky, OLE, roury, RPC, Windows Sockets, WM_COPYDATA.
Aplikace většinou využívají více mechanizmů IPC. Minimálně schránku, pak DDE a OLE. Odpovědi na tyto otázky ukáží, zda je pro aplikaci výhodné použít nějaký mechanizmus IPC:
- má aplikace komunikovat s jinými aplikacemi na jiných počítačích na síti, nebo jen s aplikacemi na lokálním počítači?
- má aplikace komunikovat s jinými aplikacemi na jiných počítačích s jiným OS?
- má uživatel aplikace vybrat aplikace, se kterými bude aplikace komunikovat, nebo si má aplikace sama vybrat partnery?
- má aplikace komunikovat stejně se všemy jinými aplikacemi, nebo jen s vybranými aplikacemi omezeným způsobem?
- je výkon důležitý?, všechny mechanizmy IPC mají svoji režii
- má být aplikace GUI nebo konzolová?, některé IPC mechanizmy potřebují GUI
Mapování souboru (File mapping)
Mapování souboru umožňuje procesu nahlížet na obsah soubor
jako na kus paměti v jeho adresovém prostoru. Proces pak může jednoduše přes
ukazatele číst a modifikovat soubor. Když více procesů přistupuje ke
stejnému mapování souboru, každý dostane ukazatel do vlastní
paměti. Procesy musí používat synchronizaci, např. semafor, pro
vyvarování se poškození dat.
Speciální případ mapování souboru je pojmenovaná sdílená
paměť. Pokud se jako mapovaný soubor zadá systémový swapovací
soubor, je mapovaná paměť sdílenou pamětí.
Mapování souboru lze použít jen mezi procesy na lokálním počítači.
Operační systém vytvoří objekt mapování souboru (file-mapping
object). File view je kus paměti procesu,
zpřístupňující obsah souboru. Funkce mapování souboru dovolují procesu
vytvořit objekt i view. Mapovaný soubor může být jakýkoliv soubor nebo
swapovací soubor.
Objekt mapování souboru může obsahovat celý nebo jen část
souboru. View může obsahovat celý nebo jen část objektu. Proces může
vytvořit více view pro jeden objekt.
Mapování souboru má dvě výhody:
- sdílená paměť
- rychlejší a jednodušší přístup k souboru
Nejdříve je potřeba otevřít soubor pomocí funkce
CreateFile
. Aby ostatní procesy nemohly do tohoto souboru
zapisovat, v parametru fdwShareMode se zadá 0. Handle souboru musí být
otevřený tak dlouho, dokud bude potřeba objekt mapování.
CreateFileMapping |
|
HANDLE hMapFile; hMapFile = CreateFileMapping(hFile, // Current file handle. NULL, // Default security. PAGE_READWRITE, // Read/write permission. 0, // Max. object size. 0, // Size of hFile. "MyFileMappingObject"); // Name of mapping object. if (hMapFile == NULL) { ErrorHandler("Could not create file-mapping object."); }
Př. Napište program, který vytvoří objekt mapování souboru pro čtení i zápis.
MapViewOfFile MapViewOfFileEx
|
|
LPVOID lpMapAddress; lpMapAddress = MapViewOfFile(hMapFile, // Handle to mapping object. FILE_MAP_ALL_ACCESS, // Read/write permission 0, // Max. object size. 0, // Size of hFile. 0); // Map entire file. if (lpMapAddress == NULL) { ErrorHandler("Could not map view of file."); }
Př. Vytvořte view objektu pro zápis a něco do mapovaného souboru uložte.
FlushViewOfFile |
|
UnmapViewOfFile |
|
Pro zrušení objektu mapování se jednoduše použije funkce
CloseHandle
ještě před uzavřením souboru.
Př. Před ukončením programu zrušte view i objekt mapování souboru.
OpenFileMapping |
|
HANDLE hMapFile; LPVOID lpMapAddress; hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, // Read/write permission. FALSE, // Do not inherit the name "MyFileMappingObject"); // of the mapping object. if (hMapFile == NULL) { ErrorHandler("Could not open file-mapping object."); } lpMapAddress = MapViewOfFile(hMapFile, // Handle to mapping object. FILE_MAP_ALL_ACCESS, // Read/write permission. 0, // Max. object size. 0, // Size of hFile. 0); // Map entire file. if (lpMapAddress == NULL) { ErrorHandler("Could not map view of file."); }
Pokud má více procesů namapován stejný soubor, říká se mu pojmenovaná sdílená paměť. Musí koordinovat přístup k souboru pomocí semaforů, mutexů, událostí, atd.
Př. Napište druhý program, který otevře existující objekt mapování souboru (z předchozího programu), vytvoří view pro čtení, přečte, co tam první program zapsal a vypíše to.
Roury (Pipes)
Win32 API poskytuje dva druhy rour pro obousměrnou komunikaci: nepojmenované (unnamed, anonymous) a pojmenované (named) roury (pipes). Nepojmenované roury umožňují výměnu informací mezi dvěma příbuznými procesy. Typicky jsou používány pro přesměrování std. vstupu a výstupu potomka, takže může komunikovat s rodičem. Jeden proces může zapisovat a druhý číst z roury. Nepojmenované roury nelze použít přes síť ani mezi nepříbuznými procesy. Pojmenované roury se používají pro přenos dat mezi dvěma nepříbuznými procesy nebo mezi procesy na různých počítačích. Typicky named-pipe server vytvoří rouru se známým jménem. Named-pipe client ji na druhém konci otevře. Pak mohou oba číst i zapisovat do roury.
Roura má dva konce. Jednosměrná roura umožňuje procesu na jednom konci zapisovat do ní, procesu na druhém konci z ní číst. Obousměrná (duplex) roura umožňuje oběma procesům číst i zapisovat.
Nepojmenované roury (Anonymous pipes)
Nepojmenovaná roura je jednosměrná roura pro přenos dat mezi rodičem a potomkem.
CreatePipe |
|
Pro čtení z roury se používá funkce ReadFile
, pro zápis
WriteFile
. ReadFile
vrací 0, pokud je
uzavřený zapisovací konec roury. Zápis je bufferovaný, pokus chceme
zapsat více dat a buffer je plný, funkce WriteFile
čeká
dokud se něco nepřečte, pak se pošlou další data. Roura existuje dokud
nejsou uzavřeny oba její konce pomocí
CloseHandle
. Všechny handly rour jsou uzavřeny při
ukončení procesu. Dědění handlu lze určit ještě pomocí
DuplicateHandle
a CreateProcess
.
Pokud chceme přesměrovat např. std. výstup potomka do zapisovacího konce roury, uděláme to takto:
-
handle std. výstupu vrátí funkce
GetStdHandle
-
nastavíme handle std. výstupu na zapisovací konec roury pomocí
SetStdHandle
a vytvoříme potomka -
uzavřeme v rodiči zapisovací konec roury pomocí
CloseHandle
-
potomek pak zapisuje na std. výstup, jehož handle zjistí pomocí
GetStdHandle
Postup pro přesměrování std. vstupu potomka na čtecí konec roury je obdobný.
Pojmenované roury (Named pipes)
Pojmenovaná roura je jednosměrná nebo obousměrná roura pro komunikaci mezi jedním serverem a jedním nebo více klienty. Všechny instance pojmenované roury sdílejí jméno roury, ale každá má vlastní buffery a handly. Každý proces může vystupovat jako server nebo klient. Server je potom ten, který rouru vytvoří, klient se připojí na instanci roury.
Pro jméno roury se používá tvar
\\ServerName\pipe\PipeName
, kde ServerName je jmého
počítače nebo tečka (lokální počítač). Server nemůže vytvořit rouru na
jiném počítači.
CreateNamedPipe |
|
Klienti používají CreateFile
pro připojení k rouře. Musí
specifikovat příslušný přístupový mód v parametru
dwDesiredAccess. Např. GENERIC_READ pro rouru vytvořenou s
PIPE_ACCESS_OUTBOUND. Handle roury, který vrací
CreateFile
je vždy
PIPE_READMODE_BYTE. ReadFile
při blokujícím módu čeká, až
bude z roury co číst. WriteFile
při blokujícím módu čeká,
až bude v bufferu roury místo pro celý zápis. Při neblokujícím se
ihned vrátí a nezapíše nic nebo zapíše, co se do bufferu vleze.
ConnectNamedPipe
při blokujícím módu čeká, až se klient
připojí k rouře.
SetNamedPipeHandleState |
|
GetNamedPipeHandleState |
|
ConnectNamedPipe |
|
Když server komunikuje s více klienty přes více instancí roury, jedna ze strategií je vytvořit pro každou instanci roury samostatné vlákno, které zabezpečuje komunikaci s jedním klientem.
CallNamedPipe |
|
WaitNamedPipe |
|
PeekNamedPipe |
|
Při ukončení práce s instancí roury by server ještě před odpojením od
klienta měl zavolat funkci FlushFileBuffers
, která čeká,
až klient přečte zbytek obsahu roury.
DisconnectNamedPipe |
|
GetNamedPipeInfo |
|
Př. Napište program (server), který vytvoří pojmenovanou rouru, ze které bude číst a vypisovat proud bytů. Pak napište klienta, který do roury něco zapíše.
Další funkce týkající se rour jsou:
- TransactNamedPipe
Jan Outrata
outrata@phoenix.inf.upol.cz