Projektowanie biblioteki komunikacyjnej wymaga wzięcia pod uwagę wielu problemów związanych z komunikacją sieciową i dokonania określonych wyborów dotyczących sposobu działania biblioteki, czyli określenia semantyki biblioteki.
W kolejnych punktach przedstawione zostaną zagadnienia mające wpływ na projektowanie bibliotek komunikacyjnych.
Działanie każdej biblioteki wymaga obustronnej komunikacji z użytkownikiem biblioteki. Z jednej strony biblioteka otrzymuje od użytkownika polecenia wykonania określonego zadania, z drugiej zaś sygnalizuje mu zajście określonych zdarzeń.
W przypadku wydawania poleceń sprawa jest prosta -- użytkownik wywołuje określoną funkcję biblioteczną i w ten sposób sygnalizuje żądanie wykonania określonego zadania.
Natomiast powiadamianie o zdarzeniach jest trudniejsze, ponieważ zdarzenia mogą wystąpić w dowolnym momencie, bez inicjatywy ze strony użytkownika. Musi więc istnieć mechanizm pozwalający bibliotece zasygnalizować wystąpienie zdarzenia.
Przykładami zdarzeń, które mogą wystąpić w bibliotece komunikacyjnej są:
Model synchroniczny jest standardowym, najprostszym modelem powiadamiania o zdarzeniach.
Istotą tego modelu jest to, że wątek użytkownika zostaje zablokowany w wywołaniu funkcji bibliotecznej do czasu wystąpienia oczekiwanego zdarzenia. Przerwanie oczekiwania może nastąpić również w celu zasygnalizowania innego zdarzenia -- wystąpienia błędu lub zajścia innych okoliczności (np. upływu czasu przeznaczonego na oczekiwanie, zajścia innego zdarzenia związanego z procesem).
W tym modelu wątek wywołujący funkcję biblioteki może się zablokować (zasnąć) w oczekiwaniu na zdarzenie. Oznacza to, że taki sposób może zostać wykorzystany tylko wówczas, gdy odwołujemy się do biblioteki z wątku i zaśnięcie jest w tej sytuacji dozwolone.
Sposobem uniknięcia blokowania jest przepytywanie (ang. polling), czyli nieblokujące sprawdzenie, czy zdarzenie zaszło. Unika się w ten sposób blokowania, ale ten sposób ma również znaczące wady. Najważniejszą jest to, że aplikacja musi sama dbać o odpowiednio częste przepytywanie, czy zdarzenie zaszło. Przepytywanie może odbywać się w sposób ciągły lub okresowo.
W przypadku ciągłego przepytywania aplikacja, bądź też jeden z jej wątków, nieustannie zajmuje się przepytywaniem. Niewątpliwą wadą takiego rozwiązania jest marnotrawienie czasu procesora, który mógłby być wykorzystany na działanie innych aplikacji. Zaletą jest bardzo krótki czas reakcji na zdarzenie. W niektórych zastosowaniach może być on czynnikiem krytycznym i wtedy użycie ciągłego przepytywania jest uzasadnione. Niemniej jednak w większości zastosowań nie można sobie pozwolić na takie szafowanie czasem procesora i wtedy trzeba zastosować okresowe przepytywanie.
Okresowe przepytywanie oznacza, że aplikacja zajmuje się swoim działaniem i co pewien czas sprawdza, czy nie zaszło jakieś zdarzenie. Może to następować co określony czas lub przy okazji wystąpienia innego zdarzenia. Na przykład w bibliotece komunikacyjnej przy wysyłaniu danych można sprawdzić, czy nie zostały odebrane nowe dane.
W przypadku przepytywania co określony czas, może on być ustalony statycznie lub zmieniany dynamicznie w zależności od stanu aplikacji lub systemu. Na przykład aplikacja wykorzystująca dużo pamięci może celowo opóźnić odbieranie danych w celu zmniejszenia swojego obciążenia i uniknięcia efektu migotania.
Wadą okresowego przepytywania jest długi czas reakcji na zdarzenie i związana z tym możliwość gubienia niektórych zdarzeń z powodu przepełnienia wewnętrznych buforów lub, w przypadku stosowania technik sterowania przepływem danych, spowolnienie działania komunikacji między nadawcą i odbiorcą.
Alternatywą dla blokowania i przepytywania jest asynchroniczne powiadamianie. Ten sposób sygnalizacji zdarzenia polega na wywołaniu określonej przez użytkownika funkcji w momencie wystąpienia zdarzenia.
Najważniejszą dla użytkownika zaletą tego modelu jest to, że nie musi się on w sposób aktywny zajmować oczekiwaniem na zdarzenie. Nie musi czekać na wystąpienie zdarzenia w wywołaniu funkcji ani też pamiętać o przepytywaniu. Zajmuje się swoim własnym zadaniem i zostaje powiadomiony bez swojego udziału, kiedy zdarzenie zajdzie.
Inną zaletą tego rozwiązania jest szybkość, z jaką może nastąpić reakcja na zdarzenie: wywołanie procedury obsługi może nastąpić bardzo szybko po zajściu zdarzenia. W skrajnym przypadku bibliotek pracujących w trybie jądra może to się odbyć nawet bezpośrednio w procedurze obsługi przerwania.
Ważną zaletą przy pracy w trybie jądra jest brak konieczności posiadania wątku, który musiałby czekać na wystąpienie zdarzenia. Większość zadań jądra jest realizowana właśnie w oparciu o asynchroniczne wywoływanie funkcji, więc dostosowanie się do takiego modelu ułatwia integrację z pozostałą częścią jądra.
W przypadku systemów wieloprocesorowych dodatkową zaletą jest to, że procedury obsługi zdarzeń mogą się wykonywać równolegle na kilku procesorach. Umożliwia to zrównoleglenie pracy i osiągnięcie większej wydajności.
Jak każde rozwiązanie, asynchroniczne powiadamianie nie jest pozbawione swoich wad.
Zrównoleglenie wywołania procedur obsługi powoduje konieczność zapewnienia synchronizacji dostępu do współdzielonych struktur aplikacji w procedurach obsługi zdarzeń. W przypadku pracy w jądrze jest to wymóg standardowy, natomiast aplikacje pracujące w trybie użytkownika są przeważnie pisane z założeniem sekwencyjnego wykonywania i nie posiadają mechanizmów synchronizacji dostępu do struktur danych.
Możliwość bezpośredniego wywołania procedur obsługi zdarzeń przez bibliotekę wymaga przestrzegania w procedurze obsługi określonych ograniczeń narzuconych przez bibliotekę. Na przykład standardowym wymogiem jest zakaz zasypiania w procedurze obsługi. Wynika on z implementacji biblioteki i założeń, jakie ona czyni przy wywołaniu procedury obsługi zdarzenia. Na przykład procedura obsługi może być wykonywana w kontekście wątku biblioteki i wówczas zaśnięcie spowoduje wstrzymanie pracy tego wątku i potencjalne zakleszczenie (ang. deadlock). Może to wstrzymać pracę całej biblioteki lub obsługę tego jednego kanału komunikacji. Natomiast dużo gorsze konsekwencje ma zaśnięcie w procedurze obsługi wywołanej z procedury obsługi przerwania. W tym wypadku zaśnięcie prawie na pewno spowoduje niestabilność pracy lub zatrzymanie systemu operacyjnego. Takie same konsekwencje może mieć niezastosowanie się do dodatkowych wymagań dotyczących pracy w jądrze, np. dostępu tylko do pamięci nie podlegającej stronicowaniu (ang. non-paged memory).
Problem zachowania procedury obsługi można rozwiązać trojako:
Oprócz tych trzech podstawowych sposobów sygnalizacji zdarzeń, stosuje się również inne podejścia, wykorzystujące elementy tych podstawowych sposobów.
Jedna biblioteka może udostępniać kilka sposobów powiadamiania o zdarzeniach. Na przykład transmisja danych odbywa się synchronicznie, a powiadamianie o błędach asynchronicznie. Często spotykanym rozwiązaniem jest umieszczanie procedur blokujących i ich nieblokujących odpowiedników, służących do przepytywania.
W przypadku istnienia procedur blokujących i nieblokujących, w celu poprawienia czasu odpowiedzi i minimalizacji marnotrawienia czasu procesora stosuje się rozwiązanie polegające na aktywnym przepytywaniu przez określony czas i, jeżeli w tym czasie zdarzenie nie wystąpi, zaśnięciu w oczekiwaniu na to zdarzenie.
Efektywność takiego rozwiązanie wynika z faktu, że przeważnie zdarzenia określonego typu występują seriami. Przepytywanie przez określony czas po zakończeniu obsługi zdarzenia daje szansę szybkiego odebrania następnego zdarzenia i osiągnięcia efektu przetwarzania potokowego, czyli ciągłego przetwarzania zdarzeń bez zasypiania.
W przypadku, gdy kolejne zdarzenie nie pojawi się w określonym czasie, proces zasypia lub przechodzi do stanu okresowego przepytywania. Unika się w ten sposób zużycia czasu procesora w okresach przestoju komunikacji, zapewniając jednocześnie dobry czas odpowiedzi w przypadku wzmożonej aktywności.
Dane są wysyłane w sieć i odbierane z niej przez kartę sieciową. W związku z tym biblioteka musi brać pod uwagę możliwości i ograniczenia karty sieciowej. Mają one wpływ na to, w jaki sposób ma ona zarządzać swoimi zasobami i jaką funkcjonalność oraz semantykę operacji może udostępniać.
W kolejnych punktach są opisane różne zagadnienia mające wpływ na projektowanie bibliotek komunikacyjnych.
Karta sieciowa przekazuje odebrane dane lub pobiera dane do wysłania bezpośrednio z pamięci. Pamięć, której karta sieciowa używa do wysyłania bądź odbierania danych, tworzy bufor do komunikacji z kartą i musi być traktowana w specjalny sposób.
Przede wszystkim karta sieciowa, jak każde urządzenie sprzętowe, operuje na pamięci fizycznej, a nie wirtualnej. W związku z tym trzeba zapewnić, że zawartość ramek pamięci używanych do transmisji nie zostanie w czasie transmisji podmieniona na zawartość innej strony w wyniku działania mechanizmu stronicowania.
Skutkiem takiej podmiany w czasie transmisji byłoby uszkodzenie danych procesu dokonującego transmisji, lub, co gorsza, innego procesu działającego na tej samej maszynie. Jeśli podmiana nastąpiłaby w czasie odbierania danych z sieci, uszkodzone zostałyby dane w pamięci innego procesu, któremu ta ramka pamięci została w danej chwili przydzielona. W przypadku wysyłania danych przesłano by złe dane, pochodzące częściowo z pamięci innego procesu.
Tak więc mechanizm stronicowania musi być zablokowany dla stron tworzących bufor, przynajmniej na czas transmisji. W przypadku działania w jądrze systemu można to łatwo osiągnąć przydzielając na bufor strony z puli stron nie wymiatanych na dysk (ang. non-pagable pool). W przypadku bufora znajdującego się w przestrzeni użytkownika wymaga to przypięcia stron, czyli zaznaczenia w tablicy stron procesu, że dane strony z pamięci procesu nie mogą być wymiecione na dysk i że nie mogą zmienić swego położenia w pamięci fizycznej.
Kolejną rzeczą, którą trzeba wziąć pod uwagę jest to, że procesor operuje adresami logicznymi pamięci, a karta sieciowa adresami fizycznymi. Adres logiczny bufora komunikacyjnego musi więc zostać przetłumaczony na adres fizyczny.
Takiego tłumaczenia można dokonać tylko w jądrze, gdyż tylko tam jest dostępna tablica stron pozwalająca na odwzorowanie adresu logicznego na adres fizyczny. Oczywiście trzeba to zrobić już po przypięciu strony, inaczej adres może się zmienić.
Dodatkowym problemem jest to, że bufor ciągły w przestrzeni adresów logicznych może się składać z kilku ramek pamięci, rozrzuconych w różnych miejscach pamięci fizycznej. Jednemu buforowi może więc odpowiadać ciąg adresów ramek.
Proces przypinania stron w pamięci i przekazania karcie sieciowej jej położenia jest nazywany w dalszej części pracy rejestracją pamięci w karcie sieciowej.
Karta sieciowa może mieć również inne wymagania względem bufora.
Użycie transmisji DMA (ang. Direct Memory Access -- bezpośredni dostęp do pamięci) do transmisji danych do i z pamięci, wymaga by ta pamięć znajdowała się w obszarze adresowalnym przez kontroler DMA. W przypadku starszych systemów komputerowych zaadresowany mógł być tylko pewien obszar pamięci fizycznej i bufor musiał w całości znajdować się w tym obszarze pamięci.
Spotyka się również wymaganie, by cały bufor był ciągły w pamięci fizycznej. Upraszcza to działanie z punktu widzenia karty sieciowej, ale jednocześnie wymaga od systemu operacyjnego zarządzania przydziałem ciągłych obszarów pamięci fizycznej, czego stara się on zazwyczaj unikać.
Z powodu tych wszystkich problemów, najprostszym i najczęściej stosowanym rozwiązaniem jest używanie przez cały czas jednego stałego bufora do komunikacji z kartą sieciową.
Kiedy dane mają zostać wysłane, karta sieciowa musi zostać powiadomiona, że w buforze znajdują się dane do wysłania i że trzeba zacząć transmisję. Zazwyczaj realizuje się to poprzez zapisanie odpowiednich wartości do rejestrów karty. Rodzi to problem synchronizacji dostępu do rejestrów karty i dlatego przeważnie inicjowaniem transmisji zajmują się zaufane procedury systemu operacyjnego. Wymaga to przejścia do trybu jądra, co w niektórych wypadkach jest niepożądane.
Możliwe jest również zasygnalizowanie rozpoczęcia transmisji z poziomu użytkownika, na przykład poprzez sprawdzanie przez kartę sieciową odpowiedniej flagi umieszczonej w pamięci. Jest to jednak trudniejsze w realizacji.
Powiadomienie o odebraniu danych najczęściej jest realizowane za pomocą przerwań. Jest to mechanizm uniwersalny i używany przez wszystkie urządzenia sprzętowe do sygnalizacji zdarzeń asynchronicznych.
Obsługa przerwania jest jednak kosztowna, ponieważ wymaga przejścia do trybu jądra i dodatkowych czynności już w trybie jądra. W przypadku zgłaszania przerwania przy każdym odbiorze danych, co w przypadku kart sieciowych o przepustowości 1000Mb pracujących z pełną prędkością mogłoby oznaczać setki tysięcy na sekundę, koszt obsługi przerwań byłby nie do zaakceptowania. Z tego powodu stosuje się inne techniki sygnalizacji odebrania danych, na przykład poprzez ustawienie flagi w pamięci.
Rozwiązanie z ustawianiem flagi w pamięci ma tę dodatkową zaletę, że umożliwia sprawdzenie odebrania danych bez przechodzenia do trybu jądra. Niestety wymaga to przepytywania w oczekiwaniu na odebranie danych, ponieważ zaśnięcie w oczekiwaniu na odebranie danych wymaga przejścia do trybu jądra w celu uśpienia procesu.
Większość sposobów komunikacji wymaga utworzenia połączenia między nadawcą i odbiorcą danych. Połączenie identyfikuje nadawcę i odbiorcę oraz pozwala ustalić parametry komunikacji.
Zarządzanie połączeniem musi zawierać mechanizm ustanowienia połączenia i jego zerwania. Dodatkowo może istnieć możliwość zmiany parametrów połączenia w trakcie jego trwania.
Do nawiązywania połączenia stosuje się różne modele, z których dwa najpopularniejsze, klient-serwer i równy-z-równym (ang. peer-to-peer), są opisane w punktach 2.4.1 i 2.4.2.
Wydajność ustanawiania i zrywania połączenia nie ma w większości przypadków zasadniczego znaczenia dla działania aplikacji, natomiast biblioteka powinna zapewniać wygodny sposób nawiązywania i wykrywania zerwania połączenia.
W niektórych sposobach komunikacji ustanawianie połączenia nie jest niezbędne. Użytkownik przy każdej transmisji danych określa adres docelowy, a przy odebraniu danych otrzymuje za każdym razem adres nadawcy. Taki sposób komunikacji jest nazywany komunikacją bezpołączeniową.
Standardowym, najbardziej rozpowszechnionym modelem nawiązywania połączenia jest model klient-serwer, w którym serwer oczekuje na połączenie pod adresem znanym klientom, natomiast klienci inicjują połączenie z tym adresem. Serwer otrzymuje informacje o kolejnych klientach, którzy chcą się połączyć i decyduje czy nawiązać połączenie, czy odrzucić żądanie.
W modelu równy-z-równym nie ma rozróżnienia pomiędzy stronami nawiązującymi połączenie. Do nawiązania połączenia potrzebna jest obustronna jednoczesna deklaracja chęci połączenia. W praktyce wygląda to w ten sposób, że aplikacja deklaruje, że chce się połączyć z drugą stroną i przez określony czas czeka, czy połączenie się powiodło. Jeśli w tym czasie druga strona w analogiczny sposób zgłosi chęć połączenia, połączenie zostaje nawiązane.
Taki model połączenia stosuje się w przypadku, kiedy trudno jest wyróżnić którąś ze stron połączenia, na przykład jeśli mają się ze sobą połączyć równorzędne węzły klastra.
Podstawowymi cechami charakteryzującymi sposób transportu danych jest: rodzaj transmisji (strumieniowa lub pakietowa), niezawodność dostarczania danych i zachowanie porządku danych.
Są dwa sposoby traktowania przesyłanych danych: mogą one być przesyłane w pakietach lub jako strumień bajtów.
W przypadku transmisji pakietowej, nadawca wysyła określoną porcję danych (pakiet) i odbiorca tę porcję danych odbiera jako jedną całość.
Natomiast jeżeli transmisja jest strumieniowa, to wysyłane dane łączone są w jeden strumień bajtów, bez możliwości rozróżnienia granic wyznaczonych przez poszczególne operacje wysłania danych. Odbiór danych również następuje w sposób ciągły -- odbiorca widzi jeden ciągły strumień bajtów, z którego pobiera dane w określonych przez siebie porcjach.
Niezawodność jest jedną z najważniejszych cech transmisji danych.
Dane są dostarczane niezawodnie, jeżeli biblioteka zapewnia, że albo dane wysłane przez nadawcę zostaną odebrane przez odbiorcę, albo niedostarczenie danych zostanie wykryte. Ponadto nie może wystąpić duplikacja danych, czyli każda porcja danych może zostać odebrana co najwyżej jeden raz.
Przy komunikacji niezawodnej niedostarczenie danych najczęściej jest traktowane jako poważny błąd i sygnalizowane zerwaniem całego połączenia. W przypadku odebrania danych przychodzących po raz drugi nie ma potrzeby informowania użytkownika -- dane te są po prostu po cichu porzucane.
Niezawodność można realizować na poziomie sprzętu, protokołu komunikacyjnego lub biblioteki komunikacyjnej. Im bliżej sprzętu się to odbywa, tym jest bardziej efektywne, ponieważ unika się narzutu protokołów niższego poziomu.
Dla zapewnienia kontroli dostarczania danych najczęściej stosuje się pozytywne potwierdzanie z retransmisją. Odbiorca ma obowiązek potwiedzić odebranie każdej porcji danych. Potwierdzenie nie musi dotyczyć każdej porcji danych z osobna, można na przykład potwierdzić spójny fragment składający się z kilku porcji. Jeśli po określonym czasie od wysłania nie nadejdzie potwierdzenie odebrania danych, to następuje retransmisja, czyli ponowne wysłanie tych samych danych. Takie rozwiązanie jest stosowane na przykład w protokole TCP.
Do wykrywania duplikacji danych używa się numerowania porcji danych. Numer porcji danych pozwala stwierdzić, czy te dane zostały już kiedyś odebrane i czy w związku z tym powinny zostać porzucone.
Komunikacja, w której nie zapewnia się niezawodności jest nazywana zawodną. Użycie komunikacji zawodnej ogranicza się do bardzo specyficznych zastosowań w których ważne jest jak najszybsze dostarczenie danych, natomiast strata informacji jest tolerowana. Przykładem takich zastosowań może być przesyłanie danych audio lub wideo w czasie rzeczywistym czy interaktywne gry sieciowe.
Zachowanie porządku danych oznacza, że dane zostaną odebrane przez odbiorcę w takim samym porządku, w jakim zostały nadane.
W celu zapewnienia porządku przesyłania danych stosuje się numerację porcji danych. Jeżeli dane zostaną odebrane w nieodpowiednim porządku, to ich dostarczenie jest wstrzymywane do czasu aż nadejdą wcześniejsze dane.
Większość bibliotek komunikacyjnych używa bufora pośredniego w celu zebrania danych przed ich wysłaniem w sieć oraz odebrania danych z sieci. Operacja wysłania powoduje skopiowanie wysyłanych danych z bufora użytkownika do bufora pośredniego i dopiero z niego dane są wysyłane w sieć. Natomiast dane przychodzące z sieci wstawia się do bufora pośredniego i dopiero z niego kopiuje do bufora użytkownika w momencie, kiedy chce on te dane odebrać.
Użycie bufora pośredniego bardzo ułatwia komunikację z kartą sieciową i pozwala w prosty i bezpieczny sposób zaimplementować operacje transmisji. Nie narzuca też zbyt wysokich wymagań na funkcjonalność udostępnianą przez kartę sieciową. Bufor pośredni stanowi jedyne miejsce na dane przesyłane do i z karty sieciowej. Wystarczy raz przygotować pamięć i wskazać jej położenie, a dalej operować już tylko na tej pamięci. Od karty wymaga się jedynie obsługiwania jednego ciągłego obszaru pamięci fizycznej. Zarządzanie tym buforem jest scentralizowane, zajmują się tym zaufane procedury w jądrze systemu operacyjnego.
W przypadku wysoko wydajnych aplikacji koszt skopiowania danych przed wysłaniem może mieć zasadniczy wpływ na wydajność. Dlatego stosuje się techniki pozwalające uniknąć pośredniego kopiowania (kopiowania do i z bufora pośredniego) i wysyłać oraz odbierać dane we wskazanym przez użytkownika biblioteki miejscu. Wymaga to jednak określonego wsparcia sprzętowego ze strony karty sieciowej i systemu operacyjnego, zwłaszcza jeśli dane mają być wysyłane bezpośrednio z poziomu użytkownika, a nie jądra systemu operacyjnego. Wiąże się to również z wieloma problemami.
Dane do wysłania (lub bufory na odebrane dane) mogą być porozrzucane po całej pamięci w miejscach wskazanych przez użytkownika biblioteki. Karta sieciowa musi mieć możliwość zlokalizowania tych fragmentów i skojarzenia ich z odpowiednią transmisją.
Pojawia się tu problem zarządzania zasobami karty sieciowej, takimi jak mapa fragmentów pamięci służących do komunikacji, które przecież nie są nieograniczone. Muszą one być w jakiś sposób współdzielone pomiędzy wszystkich chętnych do komunikacji.
Ponadto powstaje kwestia bezpieczeństwa systemu, ponieważ karta sieciowa, jako urządzenie sprzętowe, ma dostęp do całej pamięci fizycznej. Wskazanie karcie dowolnego adresu pamięci fizycznej jest niebezpieczne, ponieważ karta sieciowa mogłaby odczytać dane innego procesu z pamięci i wysłać je w sieć (na przykład fragment pamięci z zapisanymi hasłami użytkowników) lub zamazać nie swoje dane (na przykład fragment kodu systemu operacyjnego). Dlatego karta sieciowa musi posiadać mechanizmy pozwalające na zabezpieczenie się przed tego typu działaniem, tzn. zezwolenie na transmisję tylko z i do pamięci procesu, który inicjuje transmisję.
Samo przypięcie pamięci również nie może być dozwolone bezwarunkowo. Pamięć fizyczna jest wspólnym zasobem systemu i jeden proces nie może zagarnąć dla siebie całej pamięci fizycznej poprzez przypięcie jej, gdyż mogłoby to poważnie zagrozić działaniu systemu.
Do zarządzania transmisjami danych stosuje się dwie metody:
Synchroniczne przetwarzanie transmisji danych polega na rozciągnięciu wszystkich operacji związanych z transmisją na jedno wywołanie funkcji transmisji danych. Powrót z funkcji transmisji następuje dopiero wtedy, kiedy transmisja danych się zakończy (z punktu widzenia użytkownika -- fizycznie dane nie muszą jeszcze zostać wysłane).
Rozwiązanie to ma tę zaletę, że wszystkie zdarzenia związane z daną transmisją mogą zostać zasygnalizowane poprzez przekazanie odpowiedniego kodu powrotu z funkcji transmisji danych. Nie ma problemu z określeniem, do której transmisji danych odnosi się dane zdarzenie.
Najpoważniejszą wadą jest to, że jeden wątek może obsługiwać naraz tylko jedną transmisję.
Można zwiększać liczbę jednoczesnych transmisji poprzez zwiększanie liczby wątków, jednak jest to rozwiązanie kosztowne z powodu znaczącego zużycia zasobów systemowych na każdy wątek.
Alternatywnym rozwiązaniem jest użycie bufora pośredniego do buforowania transmitowanych danych. Właściwym przetwarzaniem transmisji, a zwłaszcza obsługą związanych z nią asynchronicznych zdarzeń, zajmuje się warstwa protokołu komunikacyjnego zarządzająca buforem pośrednim. Natomiast funkcja wysyłająca lub odbierająca dane operuje na gotowych danych znajdujących się w buforze pośrednim. Użycie bufora pośredniego wyklucza jednak uniknięcie pośredniego kopiowania.
Użycie żądań transmisji pozwala w wygodny sposób obsługiwać jednocześnie wiele transmisji. Funkcja rozpoczynająca transmisję tworzy tylko żądanie transmisji i rozpoczyna jego obsługę. Wątek może zainicjować wiele transmisji i następnie zajmować się tylko przetwarzaniem zdarzeń pochodzących z tych wszystkich transmisji. Nie ma nawet potrzeby posiadania wątku -- funkcja inicjująca transmisję może być wywołana poza kontekstem jakiegokolwiek wątku.
Żądania transmisji mogą również służyć do obejścia ograniczeń związanych z samą transmisją danych. Liczba jednocześnie odbywających się fizycznych transmisji oczywiście nie jest nieograniczona i zależy od możliwości sprzętu czy ograniczeń samej biblioteki. W przypadku braku zasobów potrzebnych do przeprowadzenia transmisji, żądanie transmisji może zostać ustawione w kolejce w oczekiwaniu na zwolnienie zasobów.
Użycie żądań transmisji ułatwia również zrealizowanie transmisji bez pośredniego kopiowania. Nie ma potrzeby używania bufora pośredniego w celu zwiększenia przepustowości, ponieważ jednocześnie może być prowadzonych bardzo wiele transmisji.
Kontrola przepływu danych ma na celu takie sterowanie wysyłaniem danych, by odbiorca zawsze miał na nie miejsce. W przypadku braku miejsca u odbiorcy nadawanie zostaje wstrzymane.
Kontrolę przeływu danych realizuje się poprzez przesyłanie dodatkowej informacji kontrolnej o ilości dostępnego miejsca u odbiorcy. Taka informacja może zostać doklejona do przesyłanych danych (tzw. jazda na barana, ang. piggybacking), wysłana oddzielną transmisją danych bądź dostarczona do nadawcy inną drogą.
Niektóre biblioteki komunikacyjne nie zapewniają co prawda kontroli przepływu danych, ale umożliwiają wykrycie wysłania danych, które nie mogą zostać odebrane. Najczęściej nadejście do odbiorcy nieoczekiwanych danych jest wówczas traktowane jako poważny błąd i powoduje zerwanie połączenia. Takie rozwiązanie przerzuca na użytkownika biblioteki troskę o sterowanie przepływem danych, ale zapewnia niezawodność połączenia, gdyż dane nie mogą zostać stracone w sposób niezauważony.
Krzysztof Lichota 2002-06-24