r 12 00 UQPR7FWFQHZUZ65QU5XRP7IFJLNYIH6WW3UJ4OI


Programowanie zagadnień
telekomunikacyjnych
Żaden człowiek nie jest samotną wyspą, nie jest nią również typowy 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.
Niniejszy rozdział 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ł znalezć 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 zasadniczo 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ądz
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 zró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:






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).
Wyrazne 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 w ogólności. 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 4
kilometrowy). 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 swej
funkcjonalnoś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ą synchronizacje danych
zapewnić tu 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 czasu 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 doraznymi , bardziej efektywnymi,
lecz bardziej kłopotliwymi w śledzeniu i testowaniu  to jeszcze jeden wybór pomiędzy
niezawodnością a efektywnością.
Na CD ROMie dołączonym do niniejszej 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
niniejszego 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 niniejszej
sekcji 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 12.1 i 12.2.
Tabela 12.1 Komponenty ze strony Internet Palety
Komponentów
Komponent Przeznaczenie
TClientSocket
Reprezentuje mechanizmy Winsock a na potrzeby aplikacji
kientów
TServerSocket
Reprezentuje mechanizmy Winsock a 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
Umożliwia wykorzystanie zawartości zbioru danych TDataSet
w dokumencie HTML
TCppWebBrowser
Reprezentuje przeglÄ…darkÄ™ Internet Explorer w aplikacji
klienckiej.
Tabela 12.2 Komponenty ze strony FastNet Palety
Komponentów
Komponent Przeznaczenie
TNMDayTime
Odczyt daty i czasu z wzorcowego serwera internetowego
TNMMsg
Wysyłanie prostego komunikatu ASCII poprzez Internet lub intranet z
użyciem protokołu TCP/IP
TNMMsgServ
Tworzenie serwera przetwarzającego komunikaty wysyłane przez
komponent TNMMsg
TNMEcho
Wysyłanie komunikatu typu  echo do serwera i odbiór odpowiedzi
TNMFTP
Tworzenie aplikacji klienta FTP
TNMHTTP
Zarządzanie transferem dokumentów HTTP poprzez Internet
TNMNNTP
Odczytywanie i wysyłanie wiadomości z (do) internetowych (lub
intranetowych) serwerów grup dyskusyjnych
TNMStrm
Wysyłanie strumieni do serwera strumieniowego
TNMStrmServ
Tworzenie serwera strumieniowego
TNMPOP3
Tworzenie aplikacji klienta odczytujÄ…cej pocztÄ™ elektronicznÄ… za
pomocą protokołu POP3
TNMSMTP
Tworzenie aplikacji klienta wysyłającej pocztę elektroniczną za pomocą
protokołu SMTP
TNMTime
Odczyt czasu z wzorcowego serwera internetowego
TNMUDP
Implementacja protokołu UDP (User Datagram Protocol) w celu
przesyłania datagramów poprzez Internet lub intranet
TNMURL
Kodowanie (dekodowanie) informacji pomiędzy formatem URL a
łańcuchem czytelnym dla użytkownika
TNMUUProcessor
Kodowanie (dekodowanie) typu MIME lub UU
TPowerSock
Klasa bazowa dla komponentów gniazd (socket components)
TNMGeneralServer
Klasa bazowa dla wielowątkowych serwerów internetowych
TNMFinger
Uzyskiwanie informacji o użytkowniku za pomocą protokołu Finger.
Serwer pogawędki
Pierwszy z przykładowych projektów ilustrujących zastosowanie komponentów wymienionych w
powyższych tabelach umożliwia prostą pogawędkę internetową (ang. chat); pozwala on
połączonemu użytkownikowi na przesyłanie komunikatów innym połączonym aktualnie
użytkownikom. Kod zródłowy projektu serwera znajduje się na załączonym CD ROMie pod
nazwÄ… ChatServer.bpr.
Tworzenie formularza
W celu skonstruowania aplikacji serwera należy wykonać kolejno następujące kroki:
1. Zainicjować nowy projekt, wybierając opcję File|New Application z menu głównego
IDE.
2. Umieścić na formularzu komponent TMemo i nadać mu nazwę LogMemo.
3. Umieścić na formularzu komponent TPanel, wyczyścić jego tytuł (Caption) i ustawić
wyrównanie (Align) na alTop oraz wysokość (Height) na 90 pikseli
4. Ustawić wyrównanie (Align) komponentu LogMemo na alClient.
5. Umieścić na panelu (Panel1) dwa przyciski, nadając im nazwy StartButton i
StopButton oraz tytuły (odpowiednio) Start i Stop.
6. Umieścić na panelu komponent TEdit nadając mu nazwę PortEdit i poprzedzając go
etykietą (TLabel) o treści (Caption)  Port .
7. Umieścić na panelu komponent TServerSocket (ze strony Internet) i nadać mu nazwę
MyServer.
8. Założyć katalog o nazwie Server i zapisać w nim projekt pod nazwą ChatServer (lub
pod innymi nazwami, wg własnych upodobań).
W wyniku powyższego scenariusza formularz projektu powinien przyjąć postać podobną do tej z
rysunku 12.1
Tu proszę wkleić rysunek z pliku ORIG-12-1.BMP
Rysunek 12.1 Formularz główny projektu serwera
Tworzenie listy użytkowników
Kolejny krok to wyposażenie projektu w obiekt przechowujący nazwy aktualnie połączonych
użytkowników  obiektem tym będzie lista TStringList wskazywana przez prywatne pole
ConnectedList formularza. W związku z tym należy uzupełnić deklarację klasy formularza (w
pliku Unit1.h) o następujący wpis w jego sekcji prywatnej:
TStringList *ConnectedList;
oraz zapewnić automatyczne tworzenie egzemplarza wspomnianej listy w momencie tworzenia
formularza, modyfikując następująco funkcję obsługi zdarzenia OnCreate:
void __fastcall TForm1::FormCreate(TObject *Sender)
{
ConnectedList = new TStringList();
}
Uruchamianie i zatrzymywanie serwera
Jak łatwo się domyślić, do uruchamiania i zatrzymywania serwera służą przyciski StartButton
i StopButton. Należy w związku z tym oprogramować ich zdarzenia OnClick:
void __fastcall TForm1::StartButtonClick(TObject *Sender)
{
MyServer->Port = PortEdit->Text.ToIntDef(1971);
Caption = MyServer->Port;
MyServer->Active = true;
StopButton->Enabled = true;
StartButton->Enabled = false;
PortEdit->Enabled = false;
}
void __fastcall TForm1::StopButtonClick(TObject *Sender)
{
MyServer->Active = false;
StartButton->Enabled = true;
StopButton->Enabled = false;
PortEdit->Enabled = true;
}
Pierwsza z powyższych funkcji rozpoczyna swą pracę od przypisania serwerowi portu o numerze
podanym przez użytkownika w polu PortEdit. Jeżeli zawartość tego pola nie jest poprawną
liczbą całkowitą, przyjmuje się domyślną wartość 1971, co zapewnia metoda ToIntDef() klasy
AnsiString.
Zarządzanie połączeniami
W momencie logowania się użytkownika do serwera generowane jest w jego (serwera) kontekście
zdarzenie OnClientConnect. Stosowna funkcja jego obsługi wypisuje w oknie LogMemo
informację o adresie przyłączanego użytkownika i dodaje do listy użytkowników informację o
obiekcie gniazda związanego z tym połączeniem (identyfikatorem pozycji w liście jest adres
użytkownika):
void __fastcall TForm1::MyServerClientConnect(TObject *Sender,
TCustomWinSocket *Socket)
{
LogMemo->Lines->Add("Połączono z adresem " + Socket->RemoteAddress);
ConnectedList->AddObject(Socket->RemoteAddress, Socket);
}
Obsługa zdarzenia towarzyszącemu odłączaniu się użytkownika jest nieco bardziej
skomplikowana, oprócz bowiem oczywistego wpisania do okna LogMemo informacji o
odłączeniu należy także przesłać te informację każdemu z połączonych aktualnie użytkowników,
usuwając ostatecznie z listy pozycję reprezentującą użytkownika odłączonego:
void __fastcall TForm1::MyServerClientDisconnect(TObject *Sender,
TCustomWinSocket *Socket)
{
// wypisz informacjÄ™ w oknie serwera
LogMemo->Lines->Add(
"Użytkownik " + Socket->RemoteAddress + " odłączył się");
// wyślij komunikat do połączonych użytkowników
SendMessage(Format(
"%s odłączył się.",
OPENARRAY(TVarRec,
(ConnectedList->Strings[
ConnectedList->IndexOfObject(Socket)
]))),"Server");
// usuń z listy odłączonego użytkownika
ConnectedList->Delete(ConnectedList->IndexOfObject(Socket));
}
Zarządzanie nazwami użytkowników i wysyłanie komunikatów
Ostatnią funkcją naszego serwera wymagającą oprogramowania jest identyfikacja połączonych
użytkowników i rozsyłanie do nich wprowadzanych wiadomości. Rozpoczniemy od dodania do
naszego formularza metody SendMessage()  do pliku nagłówkowego Unit1.h należy
wpisać następującą jej deklarację (w sekcji public deklaracji formularza):
void __fastcall SendMessage(AnsiString aMessage, AnsiString aFrom);
zaś w pliku Unit1.cpp należy umieścić jej kompletną treść:
void __fastcall TForm1::SendMessage(AnsiString aMessage, AnsiString aFrom)
{
for(int i=0;iSocket->ActiveConnections;i++)
{
if(aFrom == "Server")
MyServer->Socket->
Connections[i]->SendText(Format("%s",OPENARRAY(TVarRec,(aMessage))));
else
MyServer->Socket->
Connections[i]->SendText(Format(
"%s napisał: %s",OPENARRAY(TVarRec,(aFrom, aMessage))));
}
}
Jak nietrudno stwierdzić, powyższa funkcja iteruje po wszystkich aktywnych w danej chwili
połączeniach przesyłając komunikat o zadanej treści wszystkim połączonym użytkownikom. Jeżeli
komunikat pochodzi od użytkownika, nie od serwera (aFrom ma zawartość inną niż "Server")
komunikat poprzedzany jest przedrostkiem identyfikującym użytkownika wysyłającego.
Pozostaje jeszcze kwestia odbioru komunikatów nadsyłanych przez użytkowników.
Przetworzenie komunikatu nadesłanego przez użytkownika odbywa się w ramach zdarzenia
OnClientRead, przy czym należy odróżnić komunikat skierowany do pozostałych
użytkowników od komunikatu niosącego nazwę użytkownika  w tym drugim przypadku
komunikat rozpoczyna siÄ™ frazÄ… "UserName=".
Wydruk 12.1 Obsługa zdarzenia OnClientRead
void __fastcall TForm1::MyServerClientRead(TObject *Sender,
TCustomWinSocket *Socket)
{
AnsiString TextIn, CurrentName;
int iIndex;
TextIn = Socket->ReceiveText();
iIndex = ConnectedList->IndexOfObject(Socket);
if(iIndex == -1)
return;
TStringList *UserName = new TStringList();
if(TextIn.Pos("UserName=") == 1)
{
// ustaw nazwę użytkownika
UserName->Text = TextIn;
ConnectedList->Strings[iIndex] = UserName->Values["UserName"];
SendMessage(Format("Zalogował się użytkownik %s.",
OPENARRAY(TVarRec,(UserName->Values["UserName"]))),"Server");
}
else
{
// roześlij komunikat
CurrentName = ConnectedList->Strings[iIndex];
SendMessage(TextIn, CurrentName);
}
delete UserName;
}
Funkcja zdarzeniowa rozpoczyna swÄ… pracÄ™ od sprawdzenia, czy gniazdo reprezentujÄ…ce
użytkownika znajduje się jako obiekt w liście użytkowników  w normalnych warunkach
powinno się tam znajdować, ale ostrożność nie zawadzi.
Zadaniem tajemniczego obiektu UserName nie jest  jak można by domniemywać po jego typie
 przechowywanie jakiejś listy, a jedynie wydzielenie nazwy użytkownika z sekwencji
UserName=nazwa
co wygodnie jest zrealizować podstawiając tę sekwencję pod właściwość Text, a następnie
odczytując wartość pozycji reprezentowanej przez klucz UserName. Wyodrębniona nazwa
użytkownika jest następnie wpisywana na tę pozycję listy ConnectedList, na której znajduje
się (jako obiekt) gniazdo użytkownika.
W tym miejscu przerwiemy pracę nad naszym serwerem, by zająć się aplikacją klienta; w dalszym
ciągu rozdziału wyposażymy nasz serwer z możliwości uruchamiania za pośrednictwem
przeglÄ…darki internetowej, na razie natomiast pozostaje zapisanie projektu (za pomocÄ… opcji
File|Save All menu głównego).
Klient pogawędki
Prosta aplikacja kliencka, którą teraz opiszemy, umożliwia użytkownikowi specyfikację
identyfikującej go nazwy oraz wybór serwera i numeru portu, a po uzyskaniu połączenia 
wysyłanie i odbiór komunikatów. Kod zródłowy projektu (o nazwie Client.bpr) znajduje się
na załączonym CD ROMie  zaawansowani użytkownicy z pewnością zechcą go wzbogacić o
kilka użytecznych możliwości, jak np. uzyskiwanie na żądanie listy przyłączonych klientów,
formatowanie tekstu itd.
Tworzenie formularza
Należy oczywiście rozpocząć od zainicjowania nowego projektu (File|New Application) i
dalej postępować wg. poniższego scenariusza:
1. Umieścić na formularzu komponent TMemo i nazwać go MemoIn.
2. Umieścić na formularzu panel (TPanel) i wyczyścić jego tytuł (Caption)
3. Umieścić na formularzu komponent TMemo i nazwać go MemoOut.
4. Ustawić wyrównanie (Align) komponentów: MemoOut na alBottom, Panel1 na alTop
i MemoIn na alClient.
5. Ustawić na true właściwość ReadOnly komponentu MemoIn  jego zadaniem jest
wyświetlanie komunikatów serwera, które nie powinny być modyfikowane.
6. Bezpośrednio po uruchomieniu programu użytkownik nie będzie przyłączony do serwera,
należy więc ukryć komponent MemoOut, ustawiając na false jego właściwość Enabled.
Uniemożliwi to wpisywanie komunikatów nie przyłączonemu jeszcze użytkownikowi.
7. Umieścić na panelu przycisk TButton, nadając mu nazwę ConnectButton i tytułując
 Połącz
8. Umieścić na panelu przycisk TButton, nadając mu nazwę DisconnectButton i
tytułując  Rozłącz
9. Umieścić na panelu trzy kontrolki TEdit, nazywając je kolejno UserNameEdit,
PortEdit i ServerEdit i usuwając ich zawartość początkową.
10. Poprzedzić kontrolki edycyjne etykietami (TLabel) o treści (kolejno)  Użytkownik ,  Port
i  Serwer .
Dodanie komponentu gniazda i oprogramowanie przycisków
Komponentem odpowiedzialnym za połączenie klienckie z serwerem jest TClientSocket,
skrywający w sobie mechanizmy Winsock a. Umieśćmy jego egzemplarz na panelu naszego
formularza i nadajmy mu nazwę MySocket. Zestaw komponentów naszego formularza stanie się
tym samym kompletny  jego wyglÄ…d przedstawia rysunek 12.1
Tu proszę umieścić rysunek z pliku ORIG-12-2.BMP
Rysunek 12.2 Formularz główny projektu klienta
Pora teraz na skojarzenie z obydwoma przyciskami czynności, o których mówią ich tytuły, czyli
oprogramowaniu ich zdarzenia OnClick. Rozpoczniemy od przycisku  Połącz :
void __fastcall TForm1::ConnectButtonClick(TObject *Sender)
{
MySocket->Address = ServerEdit->Text;
MySocket->Port = PortEdit->Text.ToIntDef(1971);
MySocket->Active = true;
ConnectButton->Enabled = false;
DisconnectButton->Enabled = true;
MemoOut->Enabled = true;
}
Najpierw odpowiednim właściwościom komponentu MySocket przypisywane są: adres serwera i
numer portu, pobrane z pól edycyjnych (w przypadku nieprawidłowej specyfikacji portu
domyślnie przyjmuje się port 1971). Komponent ten zostaje następnie uaktywniony, po czym
blokowany jest przycisk  Połącz , natomiast odblokowywane są: przycisk  Rozłącz i ukryty
dotÄ…d komponent MemoOut.
Rozłączenie z serwerem sprowadza się do dezaktywacji komponentu MySocket i zmiany statusu
zablokowania (odblokowania) poszczególnych przycisków i MemoOut:
void __fastcall TForm1::DisconnectButtonClick(TObject *Sender)
{
MySocket->Active = false;
ConnectButton->Enabled = true;
DisconnectButton->Enabled = false;
MemoOut->Enabled = false;
}
Przesyłanie do serwera nazwy użytkownika i innych komunikatów
W kodzie na wydruku 12.1 specjalnemu traktowaniu podlega komunikat rozpoczynajÄ…cy siÄ™ od
frazy  UserName= . Komunikat taki wysyłany jest do serwera w momencie nawiązania z nim
łączności przez obiekt TClientSocket, czemu towarzyszy wystąpienie w jego kontekście
zdarzenia OnConnect:
void __fastcall TForm1::MySocketConnect(TObject *Sender,
TCustomWinSocket *Socket)
{
MemoIn->SetFocus();
MemoIn->Lines->Add("Połączono ...");
MemoOut->SetFocus();
MySocket->Socket->SendText(
Format("UserName=%s",OPENARRAY(TVarRec,(UserNameEdit->Text))));
}
Po nawiązaniu połączenia kontrolka MemoIn otrzymuje na chwilę skupienie, by można było
wpisać do niej stosowny komunikat z ewentualnym przewinięciem zawartości w górę
 dla kontrolki nieskupionej przewinięcie takie nie jest gwarantowane i nowo dopisana linia
może nie być widoczna. Skupienie przenoszone jest następnie na kontrolkę MemoOut, do której
użytkownik wpisywać może własne komunikaty. Wpisywany komunikat uznaje się za zakończony
i gotowy do wysłania w chwili, gdy użytkownik naciśnie klawisz ENTER; by tę sytuację wykryć,
należy kontrolować każde naciśnięcie klawisza dokonane w kontekście kontrolki MemoOut:
void __fastcall TForm1::MemoOutKeyPress(TObject *Sender, char &Key)
{
if(Key == VK_RETURN)
{
MySocket->Socket->SendText(MemoOut->Text);
MemoOut->Lines->Clear();
Key = 0;
}
}
Zwróć uwagę, iż parametr zawierający kod naciśniętego klawisza ENTER jest zerowany co
oznacza, iż naciśnięcie to zostało obsłużone.
Obsługa komunikatów nadchodzących z serwera
Komponent TClientSocket reaguje na komunikat z serwera wygenerowaniem zdarzenia
OnRead. W naszym prostym projekcie obsługa takiego komunikatu sprowadza się do wypisania
go w oknie kontrolki MemoIn:
void __fastcall TForm1::MySocketRead(TObject *Sender,
TCustomWinSocket *Socket)
{
MemoIn->SetFocus();
MemoIn->Lines->Add(Socket->ReceiveText());
MemoOut->SetFocus();
}
Przed wpisaniem zawartości komunikatu do pamięci kontrolki jest ona przełączana w stan
skupienia z powodów przed chwilą opisanych.
Na tym zakończyliśmy budowanie aplikacji klienta, pozostaje więc jej zapisanie (sugerujemy
podkatalog Client i nazwę ChatClient.bpr)  i oczywiście przetestowanie jej współpracy
ze stworzonym niedawno serwerem. W tym celu wykonaj kolejno następujące czynności:
1. Uruchom aplikacjÄ™ serwer
2. Ustaw domyślny numer portu (1971)
3. Kliknij w przycisk Start (na formularzu serwera)
4. Uruchom aplikacjÄ™ klienta (na tym samym komputerze, co serwer)
5. Ustaw numer portu identyczny jak w pkt. 2
6. Wpisz adres serwera jako 127.0.0.1  to adres tzw. hosta lokalnego (localhost)
7. Wpisz swą nazwę użytkownika i kliknij w przycisk  Połącz .
Przyjemnej zabawy!
Klient poczty elektronicznej
Protokoły POP (Post Office Protocol) i SMTP (Simple Mail Transfer Protocol) należą do
najpopularniejszych predefiniowanych protokołów używanych do tworzenia aplikacji
internetowych. POP używany jest do odczytywania, zaś SMTP  do wysyłania poczty za
pośrednictwem serwerów realizujących te protokoły. W niniejszej sekcji zilustrujemy to za
pomocą przykładowego projektu Mail.bpr, którego kod zródłowy znajduje się na dołączonym
do książki CD ROMie.
Tworzenie formularza
1. Aby zbudować przykładową aplikację e mailową, należy w nowo zainicjowanym projekcie
wykonać kolejno następujące czynności:
2. Umieścić na formularzu komponent TListView ze strony Win32 Palety Komponentów,
pozostawiając jego domyślną nazwę ListView1 i ustawiając wyrównanie jako alBottom.
3. Umieścić na formularzu dwa przyciski o nazwach ChekckMail i NewButton i tytułach
(odpowiednio)  Sprawdz i  Nowa .
4. Umieścić na formularzu trzy komponenty TEdit o nazwach UserEdit, PasswordEdit i
HostEdit. Aby znaki wpisywanego hasła nie były widoczne, należy ustawić właściwość
PasswordChar komponentu PasswordEdit na wartość różną od #0.
5. Poprzedzić komponenty TEdit etykietami (TLabel) o treści (odpowiednio)  Użytkownik ,
 Hasło i  Serwer .
6. Uruchomić edytor kolumnowy, klikając dwukrotnie w komponent ListView1 i zdefiniować
dwie kolumny o nazwach (odpowiednio)  Od i  Temat .
7. Ustawić właściwość ViewStyle komponentu ListView1 na vsReport.
8. Umieścić na formularzu komponent TNMPOP3 ze strony FastNet Palety Komponentów.
Po wykonaniu powyższego scenariusza formularz powinien wyglądać tak, jak na rysunku 12.3.
Tu proszę wkleić rysunek z pliku ORIG-12-3.BMP
Rysunek 12.3 Formularz główny aplikacji e mailowej
Komponenty protokołu POP i sprawdzanie poczty
Kolejną czynnością przy budowie naszej aplikacji będzie dodanie kilku publicznych pól
formularza  w pliku nagłówkowym Unit1.h należy mianowicie dodać w sekcji public
następujące pola:
bool bConnected, bSummary;
int MyId;
a w pliku Unit1.cpp oprogramować następująco zdarzenie OnClick przycisku  Sprawdz :
Wydruk 12.2 Sprawdzanie poczty oczekujÄ…cej
void __fastcall TForm1::CheckMailClick(TObject *Sender)
{
bConnected = false;
NMPOP31->UserID = UserEdit->Text;
NMPOP31->Password = PasswordEdit->Text;
NMPOP31->Host = HostEdit->Text;
NMPOP31->Connect();
if(NMPOP31->Connected)
{
if(NMPOP31->MailCount > 0)
{
bSummary = true;
for(int i = 0; i < NMPOP31->MailCount; i++)
{
myId = i + 1;
NMPOP31->GetSummary(myId);
}
}
else
ShowMessage("Brak oczekujących wiadomości ");
NMPOP31->Disconnect();
}
}
Sprawdzanie poczty rozpoczyna się od zainicjowania poszczególnych właściwości komponentu
TNMPOP3 reprezentujących nazwę użytkownika, hasło i adres serwera. Po udanej próbie
połączenia następuje sprawdzenie liczby oczekujących wiadomości (właściwość MailCount) i
załadowanie ich streszczeń do listy ListView1 (jeżeli brak jest oczekujących wiadomości, fakt
ten kwitowany jest stosownym komunikatem). Cały scenariusz kończy się rozłączeniem.
Streszczenie wiadomości pobierane jest za pomocą metody GetSummary() komponentu
TNMPOP3; parametrem wywołania tej metody jest numer kolejny wiadomości (pierwsza
wiadomość ma numer 1), zapisany aktualnie w zmiennej myId.
Po zakończeniu pobierania podsumowania generowane jest zdarzenie OnRetrieveEnd (w
kontekście komponentu TNMPOP3). Zdarzenie to generowane jest także po zakończeniu
pobierania treści wiadomości  i te dwa przypadki należy wyraznie odróżnić; temu właśnie
celowi służy zmienna publiczna bSummary zapewniająca, iż po pobraniu podsumowania
(bSummary=true) do listy ListView1 wpisywana jest linia zawierająca żądane informacje,
zaś po pobraniu treści wiadomości (bSummary=false) w ramach zdarzenia
OnRetrieveEnd nie są wykonywane żadne czynności.
void __fastcall TForm1::NMPOP31RetrieveEnd(TObject *Sender)
{
if(bSummary)
{
TListItem *Temp = ListView1->Items->Add();
Temp->Caption = NMPOP31->Summary->From;
Temp->SubItems->Add(NMPOP31->Summary->Subject);
Temp->SubItems->Add(myId);
}
}
Pobieranie i przeglądanie wiadomości pocztowych
Dodamy teraz do naszego projektu nowy formularz umożliwiający przeglądanie pobieranych
wiadomości. Wybierając opcję File|New Form z menu głównego IDE spowodujemy
utworzenie nowego formularza o nazwie Form2. Formularz ten należy uzupełnić według
następującego scenariusza:
1. Zmienić jego tytuł na  Wiadomość pocztowa
2. Umieścić na nim panel TPanel o z wyrównaniem określonym jako alTop i pustym
tytułem.
3. Umieścić na panelu cztery etykiety o nazwach Label1, Label2, FromLabel i
SubjectLabel; ustawić treść (Caption) etykiet Label1 i Label2 na (odpowiednio)
 Od i  Temat .
4. Umieścić na panelu przycisk (TButton) o nazwie CloseButtoni tytule  Zamknij
5. Umieścić na formularzu komponent TMemo, nazwać go MailMemo i ustawić jego
wyrównanie jako alClient.
6. Spowodować, by kliknięcie w przycisk  Zamknij spowodowało zamknięcie formularza:
void __fastcall TForm2::CloseButtonClick(TObject *Sender)
{
Close();
}
Po wykonaniu tych operacji formularz Form2 powinien wyglądać jak na rysunku 12.4.
Tu proszę wkleić rysunek z pliku ORIG-12-4.BMP
Rysunek 12.4 Formularz przeglądania wiadomości pocztowej
Aby formularz Form2 mógł w ogóle współpracować z formularzem głównym Form1, moduł
zródłowy tego ostatniego (Unit1.cpp) musi mieć dostęp do deklaracji klasy TForm2 w pliku
nagłówkowym Unit2.h  inaczej jakiekolwiek odwołania do elementów formularza Form2 w
module Unit1.cpp zostaną przez kompilator zakwestionowane. Należy więc w pliku Unit1.cpp
umieścić dyrektywę
#include "Unit2.h"
co łatwo można wykonać  przechodząc w Edytorze Kodu do pliku Unit1.cpp, naciskając
kombinację Alt+F11 i wybierając z menu pozycję Unit2 (zamiast Alt+F11 można również
użyć opcji File|Include Unit Hdr z menu głównego IDE).
Jako że elementami listy ListView1 (na formularzu Form1) podlegającymi wyborowi są całe
wiersze, nie tylko ich początkowe elementy, należy ustawić na true właściwość RowSelect tej
listy. Konkretna wiadomość, reprezentowana przez konkretny wiersz, wybierana jest do
przeglądania w wyniku dwukrotnego kliknięcia w ów wiersz, co wynika z poniższej funkcji
zdarzeniowej:
void __fastcall TForm1::ListView1DblClick(TObject *Sender)
{
NMPOP31->Connect();
if(ListView1->SelCount > 0)
{
bSummary = false;
NMPOP31->GetMailMessage(
ListView1->Selected->SubItems->Strings[1].ToIntDef(0)
);
}
NMPOP31->Disconnect();
}
Dwukrotne kliknięcie odnoszone jest do całej listy, nie zaś konkretnego jej elementu 
skojarzenie go z wybranym elementem listy jest już sprawą funkcji zdarzeniowej. Funkcja ta
sprawdza więc, czy w liście istnieje wybrany element (SelCount >0) i jeżeli tak, to przekazuje
do metody GetMailMessage() identyfikator reprezentowanej przez niego wiadomości;
identyfikator ten umieszczony został w podliście SubItems przez ostatnią z instrukcji funkcji
obsługującej zdarzenie OnRetrieveEnd generowane przy pobieraniu streszczenia.
Po zakończeniu pobierania wiadomości zdarzenie OnRetrieveEnd generowane jest ponownie.
Jak pamiętamy, jego funkcja zdarzeniowa w swej obecnej postaci nie wykonuje żadnych
czynności, gdy zmienna bSummary ma wartość false, tymczasem właśnie w tym miejscu
przeprowadzony ma być cały proces przeglądania wybranej wiadomości; dokonajmy więc
niezbędnych uzupełnień:
Wydruk 12.3 Obsługa zdarzenia OnRetrieveEnd
void __fastcall TForm1::NMPOP31RetrieveEnd(TObject *Sender)
{
if(bSummary)
{
// streszczenie
TListItem *Temp = ListView1->Items->Add();
Temp->Caption = NMPOP31->Summary->From;
Temp->SubItems->Add(NMPOP31->Summary->Subject);
Temp->SubItems->Add(myId);
}
else
{
// szczegółowa postać wiadomości
TForm2 *Temp = new TForm2(NULL);
Temp->MailMemo->Lines->Assign(NMPOP31->MailMessage->Body);
Temp->FromLabel->Caption = NMPOP31->MailMessage->From;
Temp->SubjectLabel->Caption = NMPOP31->MailMessage->Subject;
Temp->Show();
}
}
Po utworzeniu egzemplarza formularza Form2 następuje skopiowanie  ciała wiadomości
(będącego obiektem TStringList) do listy zawartej w MailMemo oraz nadanie właściwych
treści etykietom wyświetlającym nadawcę i temat wiadomości. Formularz wyświetlany jest
następnie w trybie niemodalnym (Show()) co pozwala na podgląd kilku wiadomości
jednocześnie.
Redagowanie i wysyłanie wiadomości pocztowych
Trzeci z formularzy  Form3  służący do redagowania i wysyłania wiadomości, kompletowany
jest w następujący sposób:
1. Należy umieścić na nim panel (TPanel) z wyrównaniem do górnej krawędzi (alTop) i
wyczyszczonym tytułem (Caption).
2. Rolę edytora wiadomości pełnić będzie komponent TMemo, któremu po umieszczeniu na
formularzu należy nadać nazwę MessageMemo i ustawić wyrównanie na cały obszar klienta
(alClient).
3. Należy następnie umieścić na panelu pięć komponentów TEdit, nadając im kolejno nazwy
ToEdit, FromEdit, SubjectEdit, CCEdit i BCCEdit. Komponenty te należy
poprzedzić etykietami (TLabel) wskazującymi ich przeznaczenie, odpowiednio  Do ,  Od ,
 Temat ,  CC i  BCC 1.
4. Komponentem udostępniającym funkcjonalność protokołu SMTP jest TNMSMTP ze strony
FastNet Palety Komponentów. Po umieszczeniu go na formularzu należy pozostawić jego
domyślną nazwę NMSMTP1.
5. Dwa ostatnie komponenty formularza to przyciski odpowiedzialne za wysłanie wiadomości
oraz jej anulowanie; należy im nadać nazwy (odpowiednio) SendButton i
CancelButton oraz opatrzyć tytułami  Wyślij i  Anuluj .
Wygląd formularza Form3 po wykonaniu wymienionych czynności przedstawia rysunek 12.5.
Tu proszę wkleić rysunek z pliku ORIG-12-5.BMP.
Rysunek 12.5 Formularz wysyłania nowej wiadomości
Po kliknięciu w przycisk  Wyślij powinien rozegrać się cały scenariusz wysłania wiadomości,
oczywiście pod nadzorem komponentu NMSMTP1:
Wydruk 12.4 Wysyłanie poczty za pomocą
komponentu TNMSMTP
void __fastcall TForm3::SendButtonClick(TObject *Sender)
{
NMSMTP1->PostMessage->ToAddress->Clear();
NMSMTP1->PostMessage->ToBlindCarbonCopy->Clear();
NMSMTP1->PostMessage->ToCarbonCopy->Clear();
NMSMTP1->PostMessage->ToAddress->CommaText = ToEdit->Text;
NMSMTP1->PostMessage->FromAddress = FromEdit->Text;
NMSMTP1->PostMessage->ReplyTo = FromEdit->Text;
NMSMTP1->PostMessage->ToBlindCarbonCopy->CommaText = BCCEdit->Text;
NMSMTP1->PostMessage->ToCarbonCopy->CommaText = CCEdit->Text;
1
Aby nie komplikować układu formularza, pozostawiłem w oryginalnej postaci skróty CC i BCC (przyp.
tłum.)
NMSMTP1->PostMessage->Body->Assign(MessageMemo->Lines);
NMSMTP1->PostMessage->Subject = SubjectEdit->Text;
NMSMTP1->PostMessage->LocalProgram = "My Emailer";
if(NMSMTP1->Connected)
NMSMTP1->Disconnect();
NMSMTP1->UserID = Form1->UserEdit->Text;
NMSMTP1->Host = Form1->HostEdit->Text;
NMSMTP1->Connect();
NMSMTP1->SendMail();
}
 Sercem komponentu TNMSMTP jest obiekt wskazywany przez właściwość PostMessage.
Cała procedura rozpoczyna się od wyczyszczenia jego właściwości reprezentujących adresatów
poczty. Właściwości te są następnie inicjowane zawartością kontrolek edycyjnych. W
przeciwieństwie do pól CC (ang. Carbon Copy) i BCC (ang. Blind Carbon Copy) w polu  Do
może znalezć się kilka adresów, oddzielonych przecinkami lub spacjami  ich separację ułatwia
właściwość CommaText listy TStringList. Właściwość Body reprezentująca treść
wiadomości  wypełniana jest zawartością kontrolki MessageMemo. Właściwość
LocalProgram zawiera nazwę programu użytego do wysłania wiadomości; w naszym
przypadku informacja ta jest bez znaczenia i nazwa wspomnianego programu może być dowolna.
Jeżeli komponent NMSMTP1 połączony jest aktualnie z jakimś serwerem, następuje rozłączenie i
ponowne nawiązanie połączenia, tym razem z serwerem określonym na formularzu głównym
Form1. Fizyczne przesłanie poczty wykonywane jest przez metodę SendMail().
Zależnie od ostatecznego rezultatu  bezbłędnego przesłania albo niepowodzenia  generowane
jest jedno ze zdarzeń OnSuccess albo OnFailure; ich obsługa w naszym przypadku ogranicza
siÄ™ do wypisania stosownego komunikatu:
void __fastcall TForm3::NMSMTP1Success(TObject *Sender)
{
ShowMessage("Wiadomość została wysłana");
Close();
}
void __fastcall TForm3::NMSMTP1Failure(TObject *Sender)
{
ShowMessage("Błąd - wiadomośc nie została wysłana");
}
Najprostszą czynnością jest niewątpliwie oprogramowanie kliknięcia w przycisk  Anuluj 
należy wówczas po prostu zamknąć formularz:
void __fastcall TForm3::CancelButtonClick(TObject *Sender)
{
Close();
}
Podobnie jak w przypadku formularza Form2, także deklaracja formularza Form3, zawarta w
pliku nagłówkowym Unit3.h, musi zostać dołączona do pliku Unit1.cpp  w sposób
uprzednio opisany. Po kliknięciu w przycisk  Nowa formularza głównego następuje wyświetlenie
formularza Form3 w trybie modalnym:
void __fastcall TForm1::NewButtonClick(TObject *Sender)
{
TForm3 *OutMail = new TForm3(NULL);
OutMail->ShowModal();
}
Wiadomości pocztowe odczytywane przez naszą aplikację z serwera nadal na nim pozostają;
znakomita większość aplikacji komercyjnych oferuje opcję usuwania z serwera przeczytanych
wiadomości. Zainteresowani Czytelnicy mogą wzbogacić o tę opcję opisywany tu projekt 
usuwania wiadomości z serwera dokonuje metoda TNMPOP3::DeleteMailMessage() z
numerem wiadomości jako pojedynczym parametrem wywołania; zależnie od wyniku operacji
generowane jest jedno ze zdarzeń OnSuccess albo OnFailure (w kontekście komponentu
TNMPOP3).
Serwer HTTP
Najpopularniejszym chyba protokołem internetowych jest HTTP (ang. Hypertext Transfer
Protocol)  trudno byłoby dziś spotkać użytkownika komputera nie posługującego się jakąś
odmianÄ… przeglÄ…darki internetowej. Z punktu widzenia architektury klient serwer przeglÄ…darka
taka pełni rolę klienta,  serwerem jest natomiast cała sieć. Aby zademonstrować możliwości
protokołu HTTP powrócimy do naszego projektu ChatServer.bpr i wprzęgniemy weń kilka
elementów funkcjonalności charakterystycznych dla tego protokołu, w szczególności:
" zdalne sprawdzanie stanu serwera
" zdalne uruchamianie serwera
" uzyskiwanie listy aktualnie przyłączonych użytkowników
Dodawanie komponentu gniazda
Po otwarciu projektu ChatServer.bpr dodamy do formularza jeszcze jeden komponent
TServerSocket, nadając mu nazwę HttpServer. Port dostępu określony przez jego
właściwość Port można ustawić dowolnie  standardowym numerem portu dla serwerów
HTML jest 80; w naszym przypadku arbitralnie przyjmiemy wartość 8000. Konieczne jest także
ustalenie adresu IP serwera, który łatwo można uzyskać uruchamiając z linii poleceń program
ipconfig. Standardowo bieżący komputer (localhost) identyfikowany jest przez adres
127.0.0.1, lecz w przypadku użycia firewalla (lub innego rodzaju zabezpieczeń) adres ten
może być inny.
Po dokonaniu niezbędnych ustawień należy przypisać wartość true właściwości Active
komponentu HttpServer.
Obsługa żądań klientów
Chociaż pojęcie  serwera HTTP kojarzy się z wieloma skomplikowanymi usługami związanymi
m.in. ze zwrotnym przesyłaniem plików klientowi, nasz przykładowy serwer realizować będzie
jedynie trzy funkcje wymienione na wstępie, mianowicie:
" uzyskiwanie informacji o stanie zdalnego serwera  żądanie to ma postać
http://127.0.0.1:8000/Status.htm (pod warunkiem, iż adres 127.0.0.1
identyfikuje lokalny komputer localhost);
" zdalne uruchamianie serwera  http://127.0.0.1:8000/Start.htm;
" uzyskiwanie listy połączonych bieżąco użytkowników 
http://127.0.0.1:8000/Users.htm.
Obsługa żądania klienta odbywa się w ramach zdarzenia OnClientConnect komponentu
HttpServer:
Wydruk 12.5 Obsługa żądania klienta przez serwer
HttpServer
void __fastcall TForm1::HttpServerClientConnect(TObject *Sender,
TCustomWinSocket *Socket)
{
AnsiString aRequest, aResponse;
aRequest = Socket->ReceiveText();
LogMemo->Text = aRequest;
if(aRequest.Pos("Status.htm") > 0)
{
aResponse = "StatusStatus :";
aResponse += (MyServer->Active) ? "Uruchomiony" : "Zatrzymany";
if(MyServer->Active)
{
aResponse += Format(
"
Port: %d
%d Przyłączonych użytkowników",
OPENARRAY(TVarRec,(MyServer->Port,MyServer->Socket->ActiveConnections))
);
}
aResponse += "";
}
else
{
if(aRequest.Pos("Start.htm") > 0)
{
if(!MyServer->Active)
StartButton->Click();
aResponse =
"StartStarted";
}
else
{
if(aRequest.Pos("Users.htm") > 0)
{
AnsiString aHead;
aResponse = "";
aHead = ""; <br> for(int i=0; i< ConnectedList->Count; i++) <br> { <br> aResponse += ConnectedList->Strings[i] + "<BR>"; <br> } <br> aHead += ConnectedList->CommaText; <br> aHead += "";
aResponse = "" + aHead + "" +
aResponse + "";
}
}
}
Socket->SendText(aResponse);
Socket->Close();
}
Treść żądania sczytywana jest najpierw do pomocniczej zmiennej aRequest, po czym następuje
rozpoznanie rodzaju żądania  łańcuch przechowywany w zmiennej aRequest sprawdzany jest
na występowanie jednego z trzech predefiniowanych wzorców. W przypadku rozpoznania wzorca
tworzony jest odpowiedni dla niego tekst odpowiedzi, w postaci wymaganej przez protokół HTTP;
tekst ten przechowywany jest (jako łańcuch AnsiString) w zmiennej aResponse.
I tak żądanie raportu o stanie serwera powoduje sprawdzenie, czy serwer jest uruchomiony, czy
też zatrzymany, i wypisanie tej informacji w treści odpowiedzi  jeżeli serwer jest uruchomiony,
raportowana jest dodatkowo liczba przyłączonych użytkowników.
W przypadku zażądania zdalnego uruchomienia serwera, o ile nie jest on jeszcze uruchomiony,
symulowane jest kliknięcie w przycisk  Start na formularzu głównym.
W odpowiedzi na trzecie żądanie tworzony jest (jako odpowiedz) wykaz aktywnych
użytkowników na podstawie zawartości listy ConnectedList.
Skompletowany tekst odpowiedzi przesyłany jest następnie za pomocą metody SendText(), po
czym następuje zamknięcie gniazda.
Zaawansowanym użytkownikom polecamy jako ćwiczenie rozbudowanie naszego serwera o
dodatkowe funkcje, na przykład zwracanie jako odpowiedz tekstu wskazanych stron HTML,
zdalne administrowanie serwerem czy nawet uruchamianie aplikacji CGI.
Klient FTP
Protokół zdalnego transferu plików (ang. FTP  File Transfer Protocol) pozostaje zdecydowanie
w cieniu HTTP z oczywistego powodu: większość przeglądarek internetowych implementuje jego
podstawowe funkcje, przez co zapotrzebowanie na typowe aplikacje klientów FTP nie jest już tak
duże. Mimo to sam protokół FTP okazuje się niezastąpiony w wielu zastosowaniach, wśród
których wymienić należy przede wszystkim automatyczną aktualizację aplikacji czy pobieranie z
odległych komputerów danych zapisywanych następnie w bazie centralnej.
W charakterze ostatniego z przykładów demonstrujących możliwości internetowych
komponentów C++Buildera zademonstrujemy wykorzystanie komponentu TNMFTP ze strony
FastNet Palety Komponentów. Cytowany projekt znajduje się na dołączonym do książki CD
ROMie pod nazwÄ… Ftp.bpr.
Tworzenie formularza
Jak zwykle należy rozpocząć od zainicjowania nowego projektu, a następnie skompletować jego
formularz zgodnie z poniższym scenariuszem:
1. Umieścić na formularzu panel z wyrównaniem do górnej krawędzi (alTop) i
wyczyszczonym tytułem.
2. Umieścić na formularzu komponent TTreeView (ze strony Win32), nadać mu nazwę
MyTree i określić jego rozciągnięcie w wolnym obszarze klienta (Align = alClient).
3. Umieścić na panelu trzy komponenty TEdit o nazwach UserEdit, PasswordEdit i
ServerEdit. Można je opcjonalnie wypełnić domyślną zawartością (właściwość Text) na
przykład wpisując w pierwszą z nich  anonymous , w drugą  jakikolwiek adres e mailowy,
zaÅ› w trzeciÄ… nazwÄ™ serwera wydawnictwa HELION  ftp.helion.pl. Aby
poszczególne znaki hasła w komponencie PasswordEdit nie były widoczne, należy
ustawić jego właściwość PasswordChar na wartość różną od #0.
4. Poprzedzić komponenty edycyjne z poprzedniego punktu etykietami określającymi ich
przeznaczenie   Użytkownik ,  Hasło i  Serwer .
5. Umieścić na panelu trzy przyciski (TButton) o nazwach StartButton, StopButton i
UploadButton, zatytułowane (odpowiednio)  Start ,  Stop i  Prześlij .
TUTAJ W ORYGINALE URYWA SI SCENARIUSZ; UZUPEANIAM GO ZGODNIE Z
POSTACI PROJEKTU
6. Umieścić na formularzu komponent TNMFTP, nazwać go MyFtp i ustawić jego właściwości
(kolejno) UserId, Password i Host zgodnie z domyślną właściwością komponentów
(odpowiednio) UserEdit, PasswordEdit i ServerEdit.
7. Umieścić na formularzu komponent TOpenDialog, pozostawiając domyślną nazwę
OpenDialog1 i tytułując (właściwość Title) jako  Wybierz plik do przesłania
8. Umieścić na formularzu komponent TSaveDialog, zmienić jego nazwę na SaveDialog i
zatytułować  Zapisz plik jako... .
9. Umieścić na formularzu komponent TImageList, pozostawiając domyślną nazwę
ImageList1. Klikając w niego dwukrotnie załadować dwa obrazki o nazwach
fldropen.bmp i filenew.bmp umieszczone w katalogu Program Files\Common
Files\Borland Shared\Images\Buttons. Ponieważ obrazki te mają rozmiary
16×32 piksele, zaÅ› wÅ‚aÅ›ciwoÅ›ci Height i Width listy okreÅ›lajÄ… obrazki jako kwadraty o
boku 16 pikseli, edytor zaproponuje podział obrazków na dwie części  należy propozycję tę
zaakceptować, po czym usunąć obrazki  wyszarzone . Ostatecznie lista powinna zawierać
dwa obrazki o indeksach 0 i 1, jak to pokazuje rysunek 12.6.
Tu proszę wkleić rysunek z pliku AG-12-A.BMP
Rysunek 12.6 Ikony załadowane do komponentu ImageList1
Ostatecznie skompletowany formularz projektu przedstawiony jest na rysunku 12.7.
Tu proszę wkleić rysunek z pliku ORIG-12-6.BMP
Rysunek 12.7 Formularz aplikacji klienta FTP
Połączenie z serwerem FTP
Połączenie z serwerem FTP nawiązywane jest w wyniku kliknięcia w przycisk  Start :
void __fastcall TForm1::StartButtonClick(TObject *Sender)
{
MyFtp->Host = Edit3->Text;
MyFtp->UserID = Edit1->Text;
MyFtp->Password = Edit2->Text;
MyFtp->Connect();
StartButton->Enabled = false;
StopButton->Enabled = true;
MyTree->Items->Clear();
DoList();
}
Po przepisaniu zawartości pól edycyjnych do odpowiednich właściwości komponentu MyFtp
wywoływana jest jego metoda Connect() inicjująca połączenie, po czym blokowany jest
przycisk  Start , udostępniany przycisk  Stop i czyszczona jest lista przeznaczona na
przechowanie nazw plików i katalogów w bieżącej lokalizacji serwera FTP. Za skompletowanie
zawartości listy odpowiedzialna jest metoda DoList(), wywoływana na samym końcu.
Pobieranie nazw plików i katalogów serwera
Aby komponent MyFtp w ogóle pobierał informację o zawartości katalogu serwera, należy
wpierw ustawić na true jego właściwość ParseList. W wyniku tego nazwy plików i
podkatalogów serwera przechowywane będą w liście typu TFTPDirectoryList, wskazywanej
przez właściwość FTPDirectoryList komponentu.
Następnie należy uzupełnić klasę formularza o rzeczoną metodę DoList(), uzupełniając jego
część publiczną (w pliku Unit1.h) o deklarację
void ___fastcall DoList();
i umieszczajÄ…c w pliku Unit1.cpp implementacjÄ™ metody, zgodnie z wydrukiem 12.6.
Wydruk 12.6 Tworzenie listy plików i podkatalogów
serwera
void __fastcall TForm1::DoList()
{
TTreeNode *Temp, *Root;
int i;
TCursor Save_Cursor = Screen->Cursor;
Screen->Cursor = crHourGlass; // zmień wygląd kursora (zajętość)
Root = MyTree->Selected;
MyFtp->List();
MyTree->Items->BeginUpdate();
for(i=0;iFTPDirectoryList->Attribute->Count;i++)
{
Temp = MyTree->Items->AddChild(
Root,MyFtp->FTPDirectoryList->name->Strings[i]);
if((MyFtp->FTPDirectoryList->Attribute->Strings[i])[1] == 'd')
{
//podkatalog
Temp->ImageIndex = 0;
Temp->SelectedIndex = 0;
}
else
{
//plik
Temp->ImageIndex = 1;
Temp->SelectedIndex = 1;
}
}
MyTree->AlphaSort();
MyTree->Items->EndUpdate();
if(Root)
Root->Expand(true);
Screen->Cursor = Save_Cursor; // przywróc normalny kursor
}
Tworzenie listy rozpoczyna się od określenia wybranego  węzła listy MyTree  węzły
reprezentujące pozycje bieżącego katalogu serwera dołączone zostaną jako potomne do owego
wybranego węzła. Jeżeli wybrany węzeł nie istnieje (bo np. lista jest pusta), wspomniane węzły
zostaną umieszczone na najwyższym poziomie listy.
Zawartość bieżącego katalogu serwera pobierana jest przez metodę List() komponentu MyFtp,
a następnie używana jest ona do zaktualizowania listy MyTree. Aby uniknąć kłopotliwego i
nieestetycznego migotania ekranu w związku z ciągłym odzwierciedlaniem zmian zachodzących
w liście, przed rozpoczęciem aktualizacji wywoływana jest jej metoda BeginUpdate()
blokująca odświeżanie jej wyglądu. Odświeżenie to nastąpi dopiero w momencie wywołania
metody EndUpdate(), po zakończeniu aktualizacji.
Kolejne pozycje przenoszone z wewnętrznej listy komponentu MyFtp do listy MyTree
sprawdzane sÄ… co do swego charakteru (plik albo podkatalog) w celu przypisania im odpowiednich
ikon wyświetlanych na ekranie obok ich nazw. Ostatnim etapem aktualizacji jest posortowanie
pozycji w liście MyTree.
Zwróć uwagę iż przez cały czas realizacji metody DoList() kursor myszki ma kształt klepsydry,
sygnalizując w ten sposób zajętość aplikacji.
Sortowanie pozycji w liście, zmiana bieżącego katalogu i pobieranie
pliku
Zatrzymajmy się przez chwilę nad sortowaniem pozycji w liście MyTree. Kolejność jej
elementów wynikać musi nie tylko z ich nazw, lecz przede wszystkim z ich charakteru  katalogi
muszą pojawić się jako pierwsze. Każdorazowo, gdy procedura sortująca wymaga ustalenia relacji
porządku pomiędzy dwoma węzłami, generowane jest zdarzenie OnCompare:
void __fastcall TForm1::MyTreeCompare(TObject *Sender, TTreeNode *Node1,
TTreeNode *Node2, int Data, int &Compare)
{
if(Node1->ImageIndex > Node2->ImageIndex)
Compare = 1;
else
if(Node1->ImageIndex == Node2->ImageIndex)
Compare = CompareStr(Node1->Text, Node2->Text);
else
Compare = -1;
}
Do funkcji zdarzeniowej przekazywane są wskazniki pierwszego i drugiego węzła, zaś pod
parametr Compare funkcja zapisać ma wynik porównania. Wynik ten powinien być liczbą
dodatnią gdy pierwszeństwo ma węzeł Node1, ujemną gdy pierwszeństwo ma węzeł Node2 i
zerem, gdy obydwa węzły są równoważne. W naszym przykładzie katalogi odróżniane są od
plików na podstawie indeksu ImageIndex, przy czym tak się szczęśliwie składa, iż katalogi,
mające pierwszeństwo przed plikami mają jednocześnie większą wartość tego indeksu. Zatem
obsługa zdarzenia OnCompare rozpoczyna się od porównania wspomnianego indeksu u obydwu
węzłów i dopiero gdy porównanie to nie jest rozstrzygające, porównywane są nazwy obydwu
węzłów.
Kolejnym zagadnieniem jest oprogramowanie dwukrotnego kliknięcia w którąś z wyświetlanych
pozycji listy MyTree. Możliwe są trzy następujące przypadki:
" jeżeli pozycja reprezentuje plik, należy rozpocząć jego ściąganie;
" jeżeli pozycja reprezentuje katalog, którego zawartość nie znajduje się aktualnie w
pamięci listy, należy zawartość tę pobrać;
" jeżeli pozycja reprezentuje  załadowany katalog, należy  rozwinąć (expand) jego
zawartość.
Standardową reakcją listy na dwukrotne kliknięcie jest rozwijanie wskazanej pozycji do listy jej
węzłów potomnych, zatem jedynie trzeci z wymienionych przypadków nie wymagałby
oprogramowania; by uwzględnić wszystkie trzy przypadki, należy więc oprogramować zdarzenie
OnDblClick w sposób pokazany na wydruku 12.7.
Wydruk 12.7 Obsługa dwukrotnego kliknięcia w
pozycjÄ™ listy MyTree
void __fastcall TForm1::MyTreeDblClick(TObject *Sender)
{
if(MyTree->Selected->ImageIndex == 0)
{
if(MyTree->Selected->Count == 0)
{
MyFtp->ChangeDir(GetPath());
DoList();
}
}
else
{
AnsiString RemoteFile;
RemoteFile = GetPath();
SaveDialog->FileName = MyTree->Selected->Text;
if(SaveDialog->Execute())
MyFtp->Download(RemoteFile, SaveDialog->FileName);
}
}
Pierwsza z instrukcji if sprawdza, czy mamy do czynienia z katalogiem  jeżeli tak, to
wykonywany jest kolejny test: sprawdza się mianowicie liczbę węzłów potomnych wybranej
pozycji i jeżeli liczba ta równa jest zero, być może istnieją niezaładowane jeszcze jej pozycje
potomne, należy więc pobrać zawartość reprezentowanego przez nią katalogu.
Jeżeli wybrana pozycja reprezentuje plik, inicjuje się jego ściąganie, po uprzednim ustaleniu
docelowej nazwy, pod którą ma zostać zapisany.
Ściągnięcie pliku  lub pobranie zawartości katalogu  reprezentowanego przez wybraną
pozycję listy nie byłoby niczym niezwykłym, gdyby nie fakt, iż pozycje listy są
wyodrębnionymi nazwami plików (katalogów), tymczasem metoda DownLoad() wymaga
określenia pełnej ścieżki dostępu (tak przynajmniej jest bezpieczniej, bowiem nazwy  relatywne
odnoszone są do bieżącego katalogu). Utworzenie kompletnej ścieżki dla danej pozycji listy
realizowane jest przez metodę GetPath() formularza o treści prezentowanej na wydruku 12.8.
Deklarację tej metody (w postaci AnsiString __fastcall GetPath();) należy dopisać
do publicznej części deklaracji formularza w pliku Unit1.h.
Wydruk 12.8 Tworzenie kompletnej ścieżki dla
wybranej pozycji listy
AnsiString __fastcall TForm1::GetPath()
{
TTreeNode *Base, *Temp;
TStringList *TempList = new TStringList();
int i;
AnsiString ToReturn;
Base = MyTree->Selected;
TempList->Add(Base->Text);
Temp = Base->Parent;
while(Temp)
{
TempList->Add(Temp->Text);
Temp = Temp->Parent;
}
for(i=TempList->Count-1;i>-1;i--)
{
ToReturn += "/" + TempList->Strings[i];
}
return ToReturn;
}
Poczynając od wybranej pozycji, podąża się tu  w górę hierarchii węzłów  nazwa każdego
napotkanego węzła stanowi kolejny człon ścieżki, oddzielany znakiem  / .
Zamykanie sesji oraz przesyłanie plików
Zakończenie połączenia z serwerem FTP następuje w wyniku kliknięcia w przycisk  Stop :
void __fastcall TForm1::StopButtonClick(TObject *Sender)
{
MyFtp->Disconnect();
StartButton->Enabled = true;
StopButton->Enabled = false;
}
Po zamknięciu połączenia za pomocą metody Disconnect() następuje zablokowanie przycisku
 Stop i odblokowanie przycisku  Start .
Przesyłanie pliku (upload) jest czynnością zgoła nieskomplikowaną i następuje w wyniku
kliknięcia w przycisk  Prześlij :
void __fastcall TForm1::UploadButtonClick(TObject *Sender)
{
if(OpenDialog1->Execute())
{
MyFtp->Upload(
OpenDialog1->FileName,ExtractFileName(OpenDialog1->FileName)
);
}
}
Plik do przesłania wybierany jest tutaj za pomocą standardowego dialogu otwarcia pliku. Jego
specyfikacja znajduje się pod właściwością FileName tegoż dialogu. Zwróć uwagę, iż plik
zapisywany jest na serwerze pod swoją oryginalną nazwą w bieżącym katalogu  funkcja
ExtractFileName() usuwa ze specyfikacji pliku ewentualną ścieżkę dostępu.
Podsumowanie
Niniejszy rozdział stanowi kolejne świadectwo niezwykłej użyteczności narzędzia typu RAD
jakim jest C++Builder; skomplikowane poniekąd technologie internetowe dostępne są dla
programisty niemal na wyciągnięcie ręki, a to za sprawą komponentów udostępniających
funkcjonalność podstawowych protokołów komunikacyjnych. Prezentowane tu projekty ze
zrozumiałych względów okrojone są do wersji minimalnych, mogą jednak być bez przeszkód
rozbudowywane i być może używane jako składniki aplikacji bardziej skomplikowanych.


Wyszukiwarka

Podobne podstrony:
12 00 23 cgsyqs4opipvhwih67donr7sh6iqeg5e5tdl2ty
12 00
12 00 Roboty branzowe
00 Program nauki Cukiernik 741 01id 12
TI 00 12 07 T pl(2)
TI 00 12 19 T pl(1)
TI 00 04 12 T pl
CNC 07 30 206 00 kolo zebate 12 5M 20

więcej podobnych podstron