R09-03.DOC

(223 KB) Pobierz
Szablon dla tlumaczy

1

 

Rozdział 9.                 Programowanie zagadnień telekomunikacyjnych

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Żaden człowiek nie jest samotną wyspą, nie jest nią również współczesny komputer. Podobnie jak sprawne komunikowanie się ludzi niezbędne jest dla prawidłowego funkcjonowania społeczeństwa, tak też integralnym elementem współczesnych zastosowań informatyki staje się sprawna komunikacja pomiędzy programami komputerowymi – zarówno w swej najprostszej formie poprzez porty szeregowe, jak i w postaci zaawansowanych technologii wyrosłych na gruncie Internetu.

Rozdział ten rozpoczniemy od przedstawienia podstaw komunikacji szeregowej, jako historycznie najstarszej i koncepcyjnie najprostszej formy komunikacji komputerów z urządzeniami zewnętrznymi. Druga, obszerniejsza część rozdziału poświęcona jest internetowym protokołom komunikacyjnym i ich wykorzystaniu w aplikacjach tworzonych z użyciem C++Buildera.

Komunikacja szeregowa

Komunikacja komputera z urządzeniami zewnętrznymi za pośrednictwem portów szeregowych jest techniką najprostszą i najdawniej stosowaną, zarazem jednak wystarczającą w bardzo wielu zastosowaniach. W dalszej części rozdziału zajmiemy się protokołami powstałymi w związku z Internetem – w tym miejscu wspominamy o nim z jednej istotnej przyczyny: otóż swą popularność zawdzięcza on między innymi solidnym podstawom teoretycznym, niezawodnym technologiom oraz obszernej dokumentacji. Nie należy o tym zapominać, tworząc rozwiązania o charakterze bardziej „kameralnym”, jak m.in. opisywana tu komunikacja szeregowa.

Protokoły komunikacyjne

Pojęcie „protokołu” w rozumieniu potocznym oznacza porozumienie co do uzgodnionych sposobów porozumiewania się. Nawet w komunikacji werbalnej brak mniej lub bardziej formalnego protokołu porozumiewania się znacznie utrudnia osiągnięcie założonych celów. Oczywiście możliwe jest zaprogramowanie komunikacji poprzez port szeregowy według zasad arbitralnie ustalonych przez autora aplikacji, bez trzymania się jakichś zdefiniowanych zasad, aplikacja taka nie będzie jednak w stanie wykonać niemal żadnych użytecznych czynności.

Komunikowanie się dwóch programów komputerowych może być postrzegane z różnym stopniem szczegółowości – i tak na przykład dla użytkownika „ściągającego” plik z serwera obojętne są raczej zjawiska elektromagnetyczne, zachodzące w kablu łączącym z owym serwerem komputer lokalny, dla inżyniera odpowiedzialnego za zaprojektowanie czy utrzymanie sieci zjawiska te są jednak zagadnieniem pierwszoplanowym. W przełożeniu na szeroko rozumiane oprogramowanie komunikacyjne koncepcja ta przyjmuje postać tzw. modelu warstwowego, zgodnie z którym hierarchicznie zorganizowane warstwy (ang. layers) odpowiedzialne są za obsługę poszczególnych aspektów komunikacji; w najbardziej elementarnym modelu komunikacyjnym wydzielić można trzy warstwy, poczynając od najbardziej elementarnej: fizyczną, transportową i aplikacyjną.

I tak warstwa fizyczna odpowiedzialna jest za prawidłową transmisję poszczególnych bajtów informacji. Fizyczna postać służących temu celowi sygnałów elektrycznych zdefiniowana może być (i najczęściej jest) zgodnie z międzynarodowym standardem przemysłowym RS-232, a więc jako transmisja znaków ośmiobitowych, z jednym bitem stopu, bez kontroli parzystości. Proste to i nie budzące wątpliwości.

Zadaniem warstwy transportowej jest pośredniczenie pomiędzy warstwami z nią sąsiadującymi. Tak więc poszczególne bajty otrzymywane z warstwy fizycznej formowane są w określone porcje („ramki”) zrozumiałe dla warstwy aplikacyjnej i vice versa – ramki otrzymywane z warstwy aplikacyjnej rozformowywane są do pojedynczych bajtów, wysyłanych następnie do warstwy fizycznej. Warstwa transportowa odpowiedzialna jest także za ocenę poprawności danych otrzymanych z warstwy fizycznej i oczywiście uwzględnienie tego w treści produkowanych ramek tak, by warstwa aplikacyjna mogła jednoznacznie rozróżnić, czy otrzymała kolejną porcję poprawnych danych, czy też komunikat o błędzie.

W warstwie aplikacyjnej otrzymane ramki interpretowane są zgodnie z logiką konkretnego programu – i tak na przykład porcja danych, stanowiąca ciąg bajtów składających się na ramkę, może być rozpatrywana w rozbiciu na pola konkretnego rekordu, zawierającego informację o konkretnym pracowniku, zaś rozpoznanie ramki jako niosącej komunikat o błędzie spowoduje wypisanie stosownego komunikatu, sformułowanego w kategoriach zrozumiałych przez użytkownika (np. „brak papieru w drukarce sieciowej nr 2”).

Jest rzeczą oczywistą, iż każda z wymienionych warstw funkcjonować musi według ściśle zdefiniowanego protokołu – inaczej przecież współdziałanie sąsiadujących warstw nie byłoby po prostu możliwe. Mniej oczywisty, i jednocześnie bardziej interesujący dla programisty, jest natomiast fakt, iż ów hierarchiczny podział znaleźć może odzwierciedlenie w hierarchii klas reprezentujących poszczególne warstwy. Warstwa fizyczna, jako zdecydowanie najmniej specjalizowana, znajdzie więc najprawdopodobniej odzwierciedlenie w postaci najbardziej ogólnej klasy bazowej. Na bazie tej ostatniej budować można następnie rozmaite klasy, definiujące zróżnicowane formaty ramek (choć najprawdopodobniej dana aplikacja zadowoli się tylko jednym lub kilkoma takimi formatami); najbardziej specjalizowane klasy pochodne, reprezentujące warstwę aplikacyjną, realizować będą wymianę danych w kategoriach oprogramowanego zastosowania, przesyłając na przykład do określonej lokalizacji komplet rekordów, stanowiących informację kadrową grupy pracowników – jakże to odległe od beznamiętnych bajtów w warstwie fizycznej…

Generalnie rzecz biorąc, w procesie tworzenia oprogramowania realizującego zadania protokołów poszczególnych warstw modelu komunikacyjnego wyodrębnić można 10 poniższych etapów:

 

1.       rozpoznanie wymagań protokołu;

2.       zrozumienie natury potencjalnych błędów;

3.       określenie wymagań efektywności (jeżeli takowe są niezbędne);

4.       oszacowanie wielkości strumieni przesyłanych danych w obydwu kierunkach;

5.       ogólne określenie struktury i treści przesyłanych komunikatów;

6.       zdefiniowanie architektury aplikacji;

7.       określenie metod testowania;

8.       implementowanie protokołu wg przyjętych założeń;

9.       testowanie zaimplementowanych rozwiązań;

10.    w razie potrzeby powrót do punktu 8. lub 9.

 

Definiowanie protokołów jest czynnością o zróżnicowanym stopniu trudności, zależnie od konkretnego projektu, niewątpliwie jednak zrozumienie zasad działania przedmiotowej aplikacji i przynajmniej ogólne określenie postaci przesyłanych danych wydaje się niezbędnym minimum. Podobnie jak w przypadku wszelkich nietrywialnych prac projektowych, tak i tutaj projektant zmuszony jest do pewnych kompromisów pod względem szczegółowych cech implementowanych rozwiązań.

I tak na przykład przesyłane dane mogą być formowane w „drukowalne” znaki ASCII, bądź przesyłane w postaci binarnej, czyli ośmiobitowych bajtów o dowolnej wartości. Protokoły „znakowe” są generalnie łatwiejsze w weryfikacji, bowiem wyniki ich pracy podejrzeć można z łatwością za pomocą powszechnie dostępnych programów terminalowych, jak np. HyperTerminal. Są one jednak mniej efektywne i trudniejsze w zarządzaniu od protokołów binarnych i jako takie niezbyt przydają się do przesyłania dużych strumieni danych; ponadto czytelna postać przesyłanej informacji stoi w oczywistej sprzeczności z wymogami bezpieczeństwa i poufności transmisji. Powoduje to, iż najczęściej stosowane są protokoły binarne, łatwiejsze w implementacji i zarządzaniu, choć bardziej kłopotliwe w śledzeniu.

Podobnym dylematem jest wybór pomiędzy synchronicznym a asynchronicznym charakterem transmisji. W transmisji synchronicznej każda wysyłana porcja danych musi zostać potwierdzona przed wysłaniem kolejnej porcji; przy transmisji asynchronicznej kolejne porcje danych wysyłane są niezależnie od otrzymywanych potwierdzeń, co czyni ją bardziej efektywną, lecz jednocześnie trudniejszą w implementacji – oprogramowanie protokołu odpowiedzialne jest za kojarzenie każdej wysłanej porcji z jej potwierdzeniem, przy czym kolejność otrzymywania potwierdzeń może być różna od kolejności wysyłania danych, niektóre potwierdzenia mogą nawet w ogóle nie nadejść.

Protokoły warstwy aplikacji

Zadaniem warstwy aplikacji jest specyficzna (dla danej aplikacji) interpretacja danych dostarczanych przez warstwę transportową, tak więc protokół tej warstwy określa przede wszystkim drobiazgowe reguły tej interpretacji, jak również sposób odwzorowania specyficznych dla aplikacji informacji w zunifikowane ramki warstwy transportowej. W ramach wspomnianej interpretacji dokonuje się więc przede wszystkim odróżnienia ewentualnych komunikatów o błędach od użytecznych danych, które następnie podlegają zazwyczaj dalszej klasyfikacji, obejmującej (na przykład) podział na polecenia, dane sterujące i dane zawierające zasadniczą informację.

Jeżeli więc na przykład aplikacja zamierza odczytać ustawienie wewnętrznego zegara jakiegoś urządzenia zewnętrznego lub sprawdzić stan buforów tegoż urządzenia, odzwierciedleniem tego w jej kodzie źródłowym będzie wywołanie określonych metod obiektu, reprezentującego warstwę aplikacyjną. Metody te odpowiedzialne są za sformułowanie przedmiotowych żądań w kategoriach zrozumiałych dla urządzenia, czego konkretnym przejawem jest przekazanie odpowiednio skonstruowanych ramek do metod odziedziczonych z klasy przodka, reprezentującej warstwę transportową.

Jakkolwiek protokół warstwy aplikacyjnej musi być przygotowany na obsługę błędnych ramek („błędnych”, czyli nie spełniających reguł integralności narzuconych przez tenże protokół), to jednak prawdopodobieństwo takich sytuacji powinno być minimalizowane poprzez eliminowanie błędnych danych w dwóch niższych warstwach.

Nic nie stoi oczywiście na przeszkodzie, by warstwa aplikacji realizowana była w ramach nie jednej klasy, lecz całej hierarchii klas, co wynikać może z hierarchicznej organizacji przetwarzanych danych i niosących te dane komunikatów. Umiejętne zaprojektowanie takiej hierarchii umożliwia tworzenie aplikacji bardziej niezawodnych, bezpieczniejszych i łatwiejszych w utrzymywaniu.

Protokół transportowy

Pośredniczący charakter warstwy transportowej objawia się z jednej strony w formowaniu pojedynczych bajtów, otrzymywanych z warstwy fizycznej, w zunifikowane ramki komunikatów, z drugiej natomiast w rozformowywaniu tychże ramek.

Każda ze wspomnianych ramek ma najczęściej ustalony format – składając się najczęściej z nagłówka (ang. header), bloku danych (ang. data block) i „końcówki” (ang. tail). Do najważniejszych części nagłówka należy identyfikator komunikatu (reprezentowanego przez ramkę) oraz rozmiar bloku danych; najistotniejszą częścią końcówki jest zazwyczaj suma kontrolna (lub wartość określona przez bardziej zaawansowany mechanizm kontroli, na przykład CRC). „Wyławianie” kolejnych ramek ze strumienia płynącego z warstwy fizycznej staje się łatwiejsze, jeżeli początek każdego bloku sygnalizowany jest przez bajt o pewnej wyróżnionej wartości lub predefiniowaną sekwencję bajtów – co jest rozwiązaniem nader często stosowanym w komunikacji szeregowej. W przypadku zaistnienia błędu transmisji (lub jej przerwania) należy konsekwentnie ignorować nadchodzące bajty, aż do napotkania wspomnianej sekwencji – powrót do „normalności” nie jest więc specjalnie trudny. Całą sprawę mogłaby dodatkowo ułatwić wyróżniona sekwencja kończąca, następująca bezpośrednio po końcówce komunikatu; w prawidłowym strumieniu danych powinna bezpośrednio po niej następować sekwencja początkowa – wszelkie inne dane są objawem błędu transmisji.

Przykładowy format ramki (w przełożeniu na strumień płynący z warstwy fizycznej) mógłby więc wyglądać następująco:

 

<sekwencja początkowa>
<długość danych>
<identyfikator>
<blok danych>
<suma kontrolna>
<sekwencja kończąca>

 

Wspomnieliśmy przed chwilą o sumie kontrolnej, zawartej w końcówce komunikatu. Jest to pojedyncza – najczęściej dwu- lub czterobajtowa liczba całkowita obliczona jako funkcja zawartości bloku danych, w najprostszym przypadku stanowiąca sumę modulo 2 (XOR) poszczególnych słów lub dwusłów całego komunikatu. Wartość ta obliczana jest na podstawie oryginalnej postaci komunikatu i przesyłana wraz z nim; po otrzymaniu komunikatu jest ona obliczana ponownie i porównywana z wartością oryginalną.

Niezgodność porównywanych sum z pewnością wskazuje na błąd transmisji, odwrotne twierdzenie nie jest jednak prawdziwe – zgodność sum kontrolnych nie świadczy bynajmniej o bezbłędnej transmisji. Aby zmniejszyć prawdopodobieństwo tego rodzaju „przeoczeń”, zalecane jest posłużenie się bardziej zaawansowanymi metodami kontroli, na przykład wykorzystanie kodu kontroli cyklicznej (ang. CRC – Cyclic Redundancy Code).

 

Protokoły jako maszyny z pamięcią stanu

Jest oczywiste, iż oprogramowanie protokołu musi sprawować pełną kontrolę nad wysyłaną i otrzymywaną informacją, by po prostu prawidłowo wybierać kolejne porcję do wysyłki oraz właściwie interpretować otrzymane dane. Kontrola taka może być efektywnie zrealizowana poprzez zaprogramowanie protokołu w postaci obiektów z pamięcią stanu – formalnie bowiem postać wysłanej i otrzymanej (w danej chwili) informacji składa się na stan protokołu jako automatu skończonego. Repertuar rozróżnialnych stanów jest przy tym specyfiką konkretnego zastosowania i może ograniczać się do pojedynczej porcji informacji (np. „inicjuję odczyt – czytam – weryfikuję – w porządku”), albo odzwierciedlać całość transmisji (przełączenie stanu następuje po odczytaniu (wysłaniu) każdej z porcji).

Wyraźne rozróżnianie stanów protokołu ułatwia także jego współpracę z protokołami warstw sąsiednich, każdemu z możliwych stanów można bowiem przypisać określony zbiór komunikatów zapewniających klarowne sprzężenie zwrotne z aplikacją.

Notabene projektanci i użytkownicy aplikacji często spotykają się z różnorodnymi „maszynami stanowymi”, nie zawsze zdając sobie z tego sprawę – czymże bowiem innym jest selektywne udostępnianie poszczególnych opcji menu czy przycisków formularzy w zależności od stanu prowadzonego dialogu czy określonych zasobów?

Efektywność a niezawodność

Wybór pomiędzy efektywnością a niezawodnością rozwiązań telekomunikacyjnym jest dylematem niemal tak powszechnym, jak słynny kompromis „czas-pamięć”, odnoszący się do oprogramowania. Nieosiągalnym marzeniem jest oczywiście pełna niezawodność przy największej możliwej szybkości – nie można jednak zjeść przysłowiowego ciastka i mieć je w dalszym ciągu; gdzie wobec tego znajduje się granica rozsądnego kompromisu?

To, ile będziemy musieli poświęcić efektywności, by zyskać żądaną niezawodność (lub vice versa), zależy w pierwszym rzędzie od rozwiązań sprzętowych. Dla komunikacji szeregowej parytet ten określony jest głownie długością stosowanych kabli – prawdzie kłopoty zaczynają się gdzieś na granicy 50 metrów, bowiem standard RS-232 nie był tworzony na potrzeby komunikacji długodystansowej (w przeciwieństwie np. do standardu RS422 dopuszczającego kabel czterokilometrowy). Dla łączności modemowej pierwszorzędne znaczenie ma jakość używanej linii telefonicznej, nie bez znaczenia są również cechy konstrukcyjne samego modemu.

W przypadku gdy odczyt i zapis danych rozdzielone są pomiędzy różne części aplikacji, dysproporcja rozmiarów strumieni płynących w obydwu kierunkach przesądzać może, które z owych części należy optymalizować: wysyłanie sporych porcji danych potwierdzanych krótkimi odpowiedziami każe więc skupić uwagę programisty przede wszystkim na procedurach organizujących wysyłkę, podczas gdy np. otrzymywanie monstrualnych danych w odpowiedzi na krótkie żądania wymaga w pierwszym rzędzie zapewnienia sprawnego ich odbioru.

Wreszcie – niezależnie od staranności zaprojektowania, zaimplementowania i przetestowania protokołów – należy liczyć się z błędami, tymi tkwiącymi wciąż w oprogramowaniu i (lub) tymi spowodowanymi czynnikami zewnętrznymi. Całkowite wyeliminowanie błędów jest po prostu niemożliwe – zrozumienie tej prostej prawdy i uczynienie obsługi błędów integralną częścią projektu pozwoli zaoszczędzić wielu rozpaczliwych wysiłków zmierzających do „wpychania” tej obsługi na siłę do gotowych już projektów.

Architektura aplikacji

Wśród wielu elementów składających się na enigmatyczne pojęcie „architektury aplikacji” wśród aplikacji komunikacyjnych kluczowe znaczenie ma ich model wątkowy (ang. threading model). Wielowątkowość pomyślana została jako środek zapewniający aplikacjom Windows większą mobilność, lepszą organizację i efektywniejsze wykorzystanie zasobów systemu – co zrealizowane zostało dzięki sprowadzeniu do granic pojedynczej aplikacji tych mechanizmów, które dotąd z powodzeniem wykorzystywano do optymalizacji systemu komputerowego jako całości.

Faktyczne korzyści osiągane dzięki zastosowaniu wielowątkowości uwarunkowane są – jak w przypadku każdego silnego narzędzia – umiejętnym jej zorganizowaniem. W aplikacji telekomunikacyjnej (lub wykorzystującej telekomunikację jako jeden z elementów swych możliwości) zalecane jest bezwzględnie wydzielenie w postaci odrębnych wątków tych fragmentów kodu, które odpowiedzialne są za transmisję danych. W przypadku transmisji synchronicznej naprzemienne operacje wysyłania i odbioru mogą być realizowane w ramach wspólnego wątku. Przy transmisji asynchronicznej, wobec niezależnego wysyłania i odbierania danych, naturalny wydaje się podział na dwa odrębne wątki – „piszący” i „czytający”, co dodatkowo przyczynia się do polepszenia efektywności; ów dwuwątkowy model może więc okazać się pożyteczny także w transmisji synchronicznej, niezbędną synchronizację danych zapewnić można bowiem za pomocą funkcji Win32 API, realizujących synchronizację wątków.

Techniki synchronizacji wątków

Synchronizacja wątków w Windows opiera się na pojęciu tzw. stanu sygnalnego (ang. signaled state) – oczekujący („niegotowy”) wątek przechodzi do stanu sygnalnego wówczas, gdy system poinformuje go („zasygnalizuje”) o wystąpieniu okoliczności, na które wątek ów oczekuje.

Zdarzeniem, na które oczekuje wątek wysyłający dane, jest skompletowanie danych do wysyłki w jego wewnętrznym buforze; po wykonaniu tej kompletacji oprogramowanie protokołu sygnalizuje ten fakt wątkowi wysyłającemu, który staje się gotowy i przystępuje do fizycznej transmisji. Jako że w czasie tej transmisji niedopuszczalne jest dopisywanie do buforu jakiejkolwiek porcji nowych danych, bufor ten jest dodatkowo chroniony za pomocą związanej z nim sekcji krytycznej – zapewniającej, iż w danej chwili dostęp do buforu ma co najwyżej jeden z wątków: „napełniający” albo „transmitujący”.

Fakt wysłania danych może być następnie sygnalizowany (przez wątek wysyłający) wątkowi czytającemu, który rozpoczyna tym samym oczekiwanie na potwierdzenie wysyłki. Ponieważ elementy synchronizacji Win32 API umożliwiają określenie czasowych limitów oczekiwania, można za ich pomocą łatwo zrealizować „przeterminowanie” transmisji, czyli obsługę braku potwierdzenia w założonym „oknie” czasowym.

W przypadku odczytywania danych system operacyjny sygnalizuje wątkowi czytającemu, iż do aplikacji skierowany jest komunikat; wątek czytający powinien wówczas wczytać do swego wewnętrznego buforu porcję danych zawartą w komunikacie i zasygnalizować wątkowi głównemu protokołu (np. za pomocą funkcji PostMessage()) fakt, iż w buforze oczekuje kompletna porcja danych do odczytu. Koncepcja ta komplikuje się nieco, gdy rozmiar nadchodzących danych nie jest określony a priori, a jedynie sygnalizowany jest koniec strumienia; buforowanie danych przyjmuje wówczas postać bardziej wykoncypowaną.

Cały ten scenariusz powinien się oczywiście odbywać w postaci „odizolowanej” od reszty aplikacji, czemu znakomicie sprzyja zrealizowanie protokołów komunikacyjnych w postaci hierarchicznie powiązanych klas, zgodnie z koncepcją zakreśloną na wstępie. Ma to tę dodatkową zaletę, iż aplikacja odizolowana od szczegółów implementacyjnych obiektów, reprezentujących warstwy modelu komunikacyjnego, nie musi troszczyć się o gospodarowanie zasobami na potrzeby tychże obiektów – na przykład przydział i zwalnianie pamięci przeznaczonej na bufory.

Buforowanie

Zagadnienie buforowania danych często jest zagadnieniem niedocenianym, jednakże należy ono do tej grupy zagadnień, które powinny być uwzględniane już w początkowych etapach projektu. W przypadku „ściągania” danych ich buforowanie umożliwia minimalizację interakcji obiektu transmisyjnego z głównym wątkiem protokołu – po (być może czasochłonnym) zapełnianiu buforu dane pobierane są z niego jednorazowo, nie zaś po każdym udanym odczycie; podobnie buforowanie danych wysyłanych umożliwia wysłanie dużej ich porcji w jednym akcie transmisji.

Z punktu widzenia aplikacji zapisanie porcji buforowanych danych nie oznacza więc jeszcze umieszczenia ich w medium docelowym, na przykład pliku dyskowym, lecz jedynie w buforze pośredniczącym; „wymiatanie” (ang. flushing) danych z buforu, jakkolwiek możliwe do jawnego inicjowania przez wątek żądający zapisu, zazwyczaj odbywa się poza jego kontrolą. Nie ma to większego znaczenia w przypadku bezbłędnej pracy systemu, staje się jednak istotną bolączką w przypadku nagłego załamania jego pracy, na przykład wskutek zaniku zasilania – dane, które nie zostały jeszcze „wymiecione” z buforów, giną wówczas bezpowrotnie.

Wynika stąd oczywisty wniosek, iż decyzje co do liczby i wielkości buforów stanowią przykład jednego ze wspomnianych wcześniej kompromisów pomiędzy efektywnością aplikacji a jej niezawodnością.

Użytkownicy C++Buildera i Delphi mogą wykorzystywać w charakterze buforów zmienne typu AnsiString. Zrealizowanie buforu w postaci kolejki FIFO jest tu niezwykle proste, sprowadza się bowiem do dołączania danych na końcu łańcucha i pobierania ich (z jednoczesnym usuwaniem) z jego początkowych pozycji. Ponadto pamięć na potrzeby długich łańcuchów przydzielana jest automatycznie, więc projektanci nie muszą troszczyć się o ten aspekt zagadnienia.

Standardowa biblioteka szablonów (STL) zawiera wiele użytecznych konstrukcji, realizujących koncepcje kolejek; umożliwiając tworzenie niezawodnych aplikacji, konstrukcje te pozostawiają jednak wiele do życzenia pod względem efektywności działania. W złożonych systemach, gdzie być może uwarunkowania czasowe są czynnikiem krytycznym, należy więc dokonać wyboru pomiędzy niezawodnymi szablonami STL a rozwiązaniami doraźnymi, bardziej efektywnymi, lecz bardziej kłopotliwymi w śledzeniu i testowaniu – to jeszcze jeden wybór pomiędzy niezawodnością a efektywnością.

 

Na płycie CD-ROM dołączonej do książki znajduje się projekt cd5Book.bpr, stanowiący przykład realizacji podstawowych operacji charakterystycznych dla protokołów transmisji szeregowej – otwierania i zamykania portu, odczytu i zapisu poprzez port szeregowy z wykorzystaniem nakładających się (ang. overlapped) operacji wejścia-wyjścia, uruchamiania wątków i komunikacji pomiędzy nimi oraz buforowania. Projekt ten stanowi fragment większego systemu i jako taki nie ma samodzielnego znaczenia praktycznego; ograniczone ramy tego rozdziału uniemożliwiają jego pełniejsze opisanie w tym miejscu.

Protokoły internetowe – SMTP, FTP, HTTP i POP3

Wszechobecność Internetu i ogrom tworzonych aplikacji typu klient-serwer stwarzają zapotrzebowanie na standaryzację powiązań pomiędzy sieciowymi aplikacjami tworzonymi za pośrednictwem narzędzi typu RAD i udostępnianiem ich poprzez Internet. Elementami spajającymi te dwa światy są protokoły internetowe, określające standardy wzajemnego komunikowania się aplikacji w warunkach przetwarzania rozproszonego. Jakkolwiek możliwe – i niekiedy pożądane – jest tworzenie ad hoc własnych protokołów, znakomita większość aplikacji wykorzystuje na swoje potrzeby jeden lub więcej protokołów predefiniowanych. W tym podrozdziale zajmiemy się kilkoma komponentami dostarczanymi przez C++Builder na potrzeby aplikacji internetowych.

Wycieczka po Palecie Komponentów

W wersjach 3. i 4. C++Buildera wszystkie komponenty związane z aplikacjami internetowymi zgrupowane były w pojedynczą stronę o nazwie Internet. W wersji 5. zostały one rozdzielone na dwie grupy. Pierwsza z nich, składająca się na obecny kształt strony Internet, obejmuje m.in. komponenty TClientSocket i TServerSocket, jak również kilka komponentów z grupy xxx_PageProducer przeznaczonych na potrzeby aplikacji – rozszerzeń serwera. W grupie drugiej, rezydującej na stronie o nazwie FastNet, znalazły się komponenty typu ActiveX przeznaczone na różnorodne potrzeby rozmaitych aplikacji sieciowych. Wykaz komponentów obydwu grup zawierają tabele 9.1 i 9.2.

 

Tabela 9.1. Komponenty ze strony Internet palety komponentów

Komponent

Przeznaczenie

TClientSocket

Reprezentuje mechanizmy Winsock na potrzeby aplikacji-klientów.

TServerSocket

Reprezentuje mechanizmy Winsock na potrzeby aplikacji-serwera.

TWebDispatcher

Umożliwia realizację aplikacji rozszerzającej serwera w postaci modułu danych (datamodule).

TPageProducer

Dokonuje konwersji szablonu HTML na dokument HTML.

TQueryTableProducer

Dokonuje konwersji komponentu TQuery na tabelę HTML.

TDataSetTableProducer

Dokonuje konwersji zbioru danych TDataSet na tabelę HTML.

TDataSetPageProducer

...
Zgłoś jeśli naruszono regulamin