DYPLOM, Instytut Informatyki

Pobierz dokument
dyplom.instytut.informatyki.doc
Rozmiar 1.5 MB

Instytut Informatyki

Wydziału Techniki Morskiej

Politechniki Szczecińskiej

PRACA DYPLOMOWA

Adam Szablewski

Temat pracy:

Pakiet programów narzędziowych do obsługi protokołów komunikacyjnych w systemie operacyjnym Windows 95

promotor:

mgr inż. Rafał Szmidt

Szczecin 1997


Spis treści

Wstęp 4

1. Cel pracy 5

2. Dostęp do sieci komputerowej w Windows 95 6

2.1. Składniki sieci 6

2.1.1. Klient sieci 6

2.1.2. Karta sieciowe 7

2.1.3. Protokół komunikacyjny 7

2.1.4. Usługa 7

2.2. Programy narzędziowe 9

2.2.1. Program Ping 9

2.2.2. Program Tracert 9

2.2.3. Program Telnet 10

2.3. Programy usługowe - dostęp do Internetu 11

3. Protokoły komunikacyjne 13

3.1. Oprogramowanie sieciowe NetBIOS 13

3.1.1. Informacje podstawowe 13

3.1.2. Rodzaje obsługi 14

3.1.3. Polecenia NetBIOS 14

3.2. Rodzina protokołów TCP/IP 17

3.2.1. Informacje podstawowe 17

3.2.2. Warstwy 17

3.2.3. Adresy Internetu 21

4. Programowanie sieciowe w Windows 95 23

4.1. Sieciowe funkcje API Win32 23

4.1.1. Dostęp do zasobów sieciowych przy użyciu protokołu NetBIOS 23

4.1.2. Wymiana danych przez łącza nazwane i skrzynki „mailslot” 23

4.2. Specyfikacja Windows Sockets 25

4.2.1. Inicjalizacja biblioteki 25

4.2.2. Gniazda 25

4.2.3. Dostęp do sieci 27

4.2.4. Dodatkowe funkcje usługowe 30

4.2.5. Przykładowe programy 32

5. Pakiet programów narzędziowych 36

5.1. Informacje podstawowe 36

5.2. Program Ping 37

5.2.1. Funkcja 37

5.2.2. Algorytm 37

5.2.3. Implementacja 39

5.2.4. Opis działania 41

5.3. Program Tracer 43

5.3.1. Funkcja 43

5.3.2. Algorytm 43

5.3.3. Implementacja 45

5.3.4. Opis działania 47

5.4. Program Namer 50

5.4.1. Funkcja 50

5.4.2. Algorytm 50

5.4.3. Implementacja 51

5.4.4. Opis działania 51

6. Wnioski 54

7. Bibliografia 55

Wstęp

Pojęcia takie jak Internet, poczta elektroniczna czy serwer WWW używane są obecnie przez zwykłych użytkowników komputerów, lecz jeszcze kilka lat temu występowały tylko w słowniku wąskiej grupy informatyków. Do globalnej sieci Internet podłączone są setki tysięcy komputerów, mimo że wojskowa sieć ARPANET, będąca zaczątkiem Internetu, opuściła okolice Departamentu Obrony USA zaledwie 10 lat temu! Nie należy się temu jednak dziwić - komputer podłączony do sieci udostępnia wiele przydatnych usług, takich jak przesyłanie listów zawierających tekst, grafikę, dźwięk i animację, możliwość przeglądania serwisów informacyjnych z najbardziej aktualnymi informacjami, zdalne zamawianie towarów w sklepach, a nawet wideotelefonię i wideokonferencję. Sieć WWW w ciągu krótkiego czasu stała się ogromną encyklopedią multimedialną, zawierającą informacje ze wszystkich dziedzin. Tak dynamiczny przyrost użytkowników wymusza potrzebę równie dynamicznego rozwoju sprzętu i oprogramowania, które służą do przesyłania informacji. Z powodu rosnących wymagań użytkowników, przesyłających coraz większe ilości danych multimedialnych, zwiększana jest przepustowość urządzeń transmisyjnych. Ostatnie osiągnięcia to technologia ATM, pozwalająca na przesyłanie danych z szybkością 2500 Mb/s oraz Gigabit Ethernet o przepustowości 1000 Mb/s. Równolegle z rozwojem sprzętu unowocześniane są protokoły komunikacyjne. Wprowadzana jest właśnie nowa wersja internetowej rodziny protokołów TCP/IP - IPv6. Jedną z najważniejszych zmian zaimplementowaną w nowym protokole jest poszerzenie przestrzeni adresowej z 32 do 128 bitów. Tym samym rozwiązano pojawiający się już problem braku wolnych adresów - teraz można będzie zaadresować w globalnej sieci, niewyobrażalną wprost liczbę, ponad 3*1038 komputerów. Producenci oprogramowania systemowego także dostrzegli zainteresowania sieciowe użytkowników mikrokomputerów. Potentat w dziedzinie oprogramowania, firma Microsoft, zaoferowała w 1995 roku system operacyjny Windows 95, który posiada wbudowane możliwości sieciowe, i to zarówno dla sieci lokalnych, jak i rozległych. Dodatkowa zaleta tego programu, jaką jest łatwość obsługi, spowodowała, że stał się on jednym z systemów operacyjnych najczęściej instalowanych w mikrokomputerach.

Jednakże, pomimo wzrostu jakości sprzętu i oprogramowania (a więc i bezpieczeństwa transmisji), nie sposób wyeliminować możliwości awarii w sieci, w której działa tak dużo elementów pośredniczących. Miejsce awarii w sieci komputerowej, a w szczególności w sieci rozległej, jest często bardzo trudne do wykrycia ze względu na duże odległości między kolejnymi węzłami sieci. Niebagatelne znaczenie mają więc wszelkie sieciowe analizatory sprzętowe i programy narzędziowe pozwalające wykryć awarię, a następnie precyzyjnie określić jej miejsce położenia w sieci.

1. Cel pracy

System operacyjny Windows 95, jako następca sieciowego systemu Windows for Workgroups, umożliwia wykorzystanie podłączenia komputera do sieci lokalnej i zawiera także podstawowe elementy pozwalające na dostęp do sieci rozległych.

Celem tej pracy jest zapoznanie z sieciowymi możliwościami systemu Windows 95, w szczególności z programowaniem sieciowym w środowisku 32-bitowym. W efekcie powstanie pakiet programów narzędziowych, przydatny w rozwiązywaniu problemów dostępu do węzłów sieci Internet. Program Ping umożliwi sprawdzenie, czy zdalna stacja o danym adresie jest dostępna dla użytkownika. Jednocześnie mierzony będzie czas przesyłu pakietu danych między stacją lokalną i zdalną. Do określenia jaka jest trasa pakietu danych w drodze do zdalnego celu posłuży program Tracer. Trzeci program, Namer, będzie pomocny przy określaniu nazw odległych komputerów.

Korzystanie z sieci komputerowych w systemie operacyjnym Windows 95 z poziomu użytkownika omówione zostanie w rozdziale 2. Przedstawione będą tam różne elementy sieci, takie jak: protokoły, usługi, standardowe sterowniki kart sieciowych. W dalszej części tego rozdziału opisane będą systemowe programy narzędziowe Ping, Tracert i Telnet oraz programy usługowe, pozwalające na dostęp do Internetu.

Rozdział 3 to opis dwu specyfikacji umożliwiających połączenie w sieci komputerowej. Przedstawiono w nim oprogramowanie sieciowe NetBIOS oraz rodzinę protokołów komunikacyjnych TCP/IP.

Rozdział 4 poświęcony będzie programowaniu sieciowemu. Omówione zostaną biblioteki funkcji Winsock oraz zestaw funkcji API Win32, służący do programowania w sieciach lokalnych.

Utworzone na podstawie wcześniejszych informacji programy narzędziowe, opisane zostaną w rozdziale 5 tej pracy. Znajdą się tam szczegółowe opisy działania programów, ich algorytmy i sposób wykorzystania.

Rozdział 6 zawierał będzie wnioski dotyczące wykorzystania opisanych bibliotek do sieciowego programowanie w Windows 95 i ewentualne możliwości rozbudowy przedstawionego pakietu programów narzędziowych.

2. Dostęp do sieci komputerowej w Windows 95

2.1. Składniki sieci

Twórcy systemu Windows 95 podzielili elementy udostępniające zasoby sieci na cztery składniki: klient sieci, karta sieciowa, protokół komunikacyjny oraz usługa (rys. 1). Oprogramowanie typu „Klient sieci” umożliwia korzystanie z plików i drukarek udostępnionych w innych komputerach sieciowych. Element „Karta sieciowa” pozwala ustalić sterownik programowy odpowiedni dla zamontowanej w komputerze karty sieciowej. Aby system operacyjny mógł skomunikować się z innym komputerem pracującym w sieci, musi znać język porozumiewania się, czyli protokół. Składnik sieciowy „Usługa” daje możliwość wybrania rodzaju usług jakie system operacyjny będzie świadczył na rzecz pozostałych komputerów sieci i jakie usługi będą dla niego dostępne.

Poszczególne elementy muszą być ze sobą powiązane. Jeżeli zainstalowano klienta sieci NetWare, to musi działać w komputerze karta sieciowa z tym klientem współpracująca, dołączyć należy także odpowiedni protokół (np. „Protokół zgodny z IPX/SPX”), warto też określić czy pliki na dysku lokalnym mają być udostępnione dla innych użytkowników sieci. Na szczęście powiązania te mogą być dokonywane automatycznie przez Windows 95.

0x01 graphic

rys. 1

2.1.1. Klient sieci

Standardowo zainstalowane są w systemie sterowniki obsługujące następujące typy sieci:

Najczęściej wykorzystywane są sieci Microsoft Networks i Novell NetWare. Pierwsza ze względu na pełną zgodność z systemem operacyjnym Windows 95 (przynajmniej deklarowaną), a druga z powodu powszechności sieciowego systemu Novell NetWare. Sieć Microsoft Networks tworzą połączone komputery, mające zainstalowane następujące systemy operacyjne firmy Microsoft: Windows 95, Windows NT oraz Windows for Workgroups. Do sieci Novell NetWare można dołączyć się na dwa sposoby - korzystając ze sterowników firmy Microsoft lub z oryginalnych sterowników firmy Novell. Należy pamiętać o dodaniu protokołów odpowiednich dla danego klienta sieci (jeśli nie zostały dołączone automatycznie).

Element FTP nie jest właściwie typem sieci, lecz protokołem wysokiego poziomu TCP/IP. Umożliwia on połączenie komputera z odległym, internetowym serwerem FTP, który udostępnia swoje zasoby plików.

2.1.2. Karta sieciowa

Windows 95 zawiera bardzo bogatą listę sterowników łączących system z kartą sieciową. Można odnaleźć standardowe sterowniki wszystkich znanych producentów kart sieciowych w licznych wersjach (m.in.: 3Com, Artisoft, Cabletron, Compaq, DEC, Hewlett Packard, IBM, Intel, Olicom, SMC). Ponieważ jednak powstają coraz nowsze typy transmisji, a więc i coraz nowsze karty sieciowe, dodano opcję pozwalającą na dodanie odpowiedniego sterownika z dyskietki dostarczonej przez producenta wraz z kartą sieciową.

2.1.3. Protokół komunikacyjny

W systemie zawarto kilkanaście różnych protokołów, a ich ilość może być zwiększona po zainstalowaniu protokołu z dyskietki. Najważniejsze standardowo zaimplementowane protokoły to:

Protokół NetBEUI, będący rozszerzeniem NetBIOS-u, wykorzystywany jest przez sieć typu Microsoft Networks. Sieć ta wykorzystuje też protokół zgodny z IPX/SPX, który potrzebny jest także dla prawidłowego funkcjonowania klienta sieci NetWare. Alternatywnie można wykorzystać protokół firmy Novell - Novell IPX ODI. Protokoły TCP/IP oraz PC-NFS służą do połączenia komputera z siecią rozległą. Protokołem podstawowym jest TCP/IP. Definiuje on sposób komunikacji między odległymi stacjami. Protokół PC-NFS (rozproszony system plików) pozwala na takie połączenie z siecią rozległą, że użytkownik może korzystać z zasobów tej sieci, jakby to były zasoby zapisane na dysku lokalnym. Aby korzystać z rozproszonego systemu plików na stacji roboczej musi być zainstalowany protokół TCP/IP do komunikacji ze stacjami zdalnymi.

2.1.4. Usługa

Ten składnik sieci systemu operacyjnego Windows 95 odpowiedzialny jest za to, czy zasoby komputera lokalnego udostępniane będą innym użytkownikom sieci. Najważniejsze usługi dotyczą sieci Microsoft Networks i Novell NetWare. Osobno ustawia się możliwość zewnętrznego dostępu do plików oraz lokalnych drukarek. Można określić też dwa rodzaje kontroli dostępu do tych zasobów:

2.2. Programy narzędziowe

Programy Ping, Tracert i Telnet są prostymi narzędziami do obsługi połączenia realizowanego za pomocą protokołu TCP/IP. Dostarczone są one wraz z systemem operacyjnym Windows 95. Niestety, Ping i Tracert są poleceniami wierszowymi, które należy uruchamiać w tekstowym środowisku MS-DOS 7.0, podając w linii komend listę opcji. Taki sposób obsługi znacząco zmniejsza funkcjonalność tych programów. W dalszej części pracy przedstawione będą nowe wersje programów Ping i Tracert, w pełni wykorzystujące możliwości okienkowego systemu Windows 95.

2.2.1. Program Ping

Program Ping służy do testowania poprawności połączenia z inną stacją roboczą lub bramą (ang. gateway), posiadającą własny adres IP. Działanie polega na wysłaniu komunikatu pod wskazany adres i oczekiwaniu na odpowiedź. Po odebraniu komunikatu zwrotnego porównywany jest czas nadania i odbioru pakietu, dzięki czemu określa się czas jego przesyłu między obiema stacjami. Program wywoływany jest z linii poleceń, a jego sposób wywołania i opcje są następujące:

ping [-t] [-a] [-n ilość] [-l wielkość] [-f] [-i TTL] [-w timeout] adres

opcje:

-t

pakiety wysyłane będą pod wskazany adres do momentu przerwania działania programu przez użytkownika za pomocą klawiszy Ctrl+C

-a

Ping określi nazwę stacji o podanym adresie

-n ilość

ilość pakietów do wysłania (domyślnie 4)

-l wielkość

wielkość wysyłanego pakietu (domyślnie 32)

-f

przed wysłaniem pakietu zostanie ustawiona flaga „Don't Fragment”, zabraniająca fragmentacji pakietów przy przechodzeniu przez kolejne podsieci

-i TTL

czas TTL, przy przechodzeniu przez kolejne bramy wartość ta zmniejszana jest o 1; gdy osiągnie 0, pakiet jest likwidowany - należy więc zwrócić uwagę, aby wartość ta nie była zbyt mała (domyślnie 128)

-w czas

maksymalny czas oczekiwania na powrót pakietu (podawany w milisekundach); jeśli przez ten czas nie będzie odpowiedzi, ping wyświetli komunikat: „Request timed out”

adres

adres (lub nazwa) przeznaczenia - stacji końcowej lub bramy

2.2.2. Program Tracert

Aby dowiedzieć się jak przebiega trasa pakietu od stacji źródłowej do docelowej należy skorzystać z programu Tracert. Podaje on adres i jeśli to możliwe, nazwę każdej bramy i stacji, przez którą transmitowany jest pakiet w drodze do celu. W przypadku awarii któregoś z węzłów sieci można więc określić jak daleko dostarczany jest pakiet i określić miejsce awarii. Tracert również należy wywołać z linii poleceń interpretera DOS. Składnia wywołania jest następująca:

tracert [-d] [-h ilość_prób] [-w czas] adres

opcje:

-d

opcja zabraniająca określanie nazw poszczególnych stacji pośrednich; wyświetlane będą jedynie adresy

-h ilość_prób

ilość prób wysłania pakietu do jednej stacji pośredniej

-w czas

maksymalny czas oczekiwania na odpowiedź od każdej stacji (podawany w milisekundach)

adres

adres (lub nazwa) przeznaczenia - stacji końcowej lub bramy

2.2.3. Program Telnet

Sieci rozległe, takie jak Internet, dają możliwość biernego przeglądania zawartości ogromnych zasobów odległych serwerów, ale również pozwalają na zdalną pracę.

rys. 2

Dzięki usłudze TCP/IP - Telnet i programowi o takiej nazwie, możliwe jest zalogowanie użytkownika w odległym systemie. Program Telnet pracujący w komputerze użytkownika odbiera jego polecenia i przesyła je do odległej stacji. Tam z kolei działa proces Telnet-serwer odbierający żądania, odpowiednio na nie reagując (np. uruchamia programy z przesłanymi danymi użytkownika, po czym odsyła wyniki).

System operacyjny Windows 95 wyposażony został w okienkową wersję Telneta. Po uruchomieniu programu (rys. 2) i pomyślnym połączeniu ze zdalnym komputerem, oferującym usługę Telnet, można zdalnie uruchamiać aplikacje.

2.3. Programy usługowe - dostęp do Inertnetu

Firma Microsoft dołączyła do systemu Windows 95 program Microsoft Exchange. Umożliwia on dostęp do wielu usług jakie oferuje Internet. Najważniejsze z nich to obsługa poczty elektronicznej (e-mail) oraz połączenie z inną sieciową usługą Microsoftu - The Microsoft Network. Dzięki Microsoft Exchange przychodzące do użytkownika przesyłki poczty elektronicznej kierowane są do foldera znajdującego się na pulpicie systemu. Ułatwione jest ich przeglądanie i segregowanie.

Po poprawnym zainstalowaniu modemu i protokołu TCP/IP możliwe jest połączenie z siecią The Microsoft Network. Użytkownik rejestruje się na jednym z wielu rozsianych po całym świecie serwerów (najbliższy znajduje się niestety aż w Berlinie) i może skorzystać z następujących usług:

Mimo, że powyższe usługi, oferowane przez programy dołączone do systemu operacyjnego, są bardzo przydatne, to brakuje wśród nich możliwości przeglądania stron WWW. Aby móc skorzystać z tej ogromnej składnicy informacji trzeba zainstalować odpowiedni program. Najlepsze, dostępne obecnie na rynku oprogramowanie tego typu to Netscape Communicator oraz Microsoft Internet Explorer (rys.3).

0x01 graphic

rys. 3

Oprócz wcześniej opisanych usług programy te oferują znacznie więcej. Są świetnymi przeglądarkami stron WWW, a Netscape zawiera również moduł do samodzielnego tworzenia doskonałych stron. Przeglądarki odczytują bardzo dużą liczbę formatów graficznych i dźwiękowych, dzięki czemu odwiedzane strony mogą być bardzo bogate. Obsługa języka VRML pozwala przeglądać strony trójwymiarowe, a RealAudio umożliwia rozmowę przez Internet tak, jak przez zwykły telefon. Poczta elektroniczna zawiera opcje ułatwiające segregowanie i archiwizację listów, a także ich szyfrowanie.

3. Protokoły komunikacyjne

Przedstawione w tym rozdziale protokoły są najważniejszymi protokołami wykorzystywanymi przez system operacyjny Windows 95. NetBIOS i jego rozszerzona wersja NetBEUI są podstawą sieci The Microsoft Networks, a rodzina protokołów TCP/IP umożliwia dostęp do sieci rozległych.

3.1. Oprogramowanie sieciowe NetBIOS

3.1.1. Informacje podstawowe

Prace nad protokołem Network Basic Input Output System (NetBIOS) rozpoczęła firma IBM w roku 1984, a nieco później dołączyła do niej firma Microsoft [8]. W wyniku współpracy powstał protokół NetBIOS/NetBEUI, którego model (w odniesieniu do modelu OSI) przedstawiono na rys.4. Zadaniem NetBIOS jest zapewnienie komunikacji w środowisku małej lub średniej sieci lokalnej. Wykorzystywany jest w sieciowych systemach operacyjnych firm Microsoft i IBM:

rys. 4

Poszczególne elementy środowiska protokołu NetBIOS wyjaśnione są poniżej:

3.1.2. Rodzaje obsługi

System NetBIOS pozwala korzystać z czterech podstawowych rodzajów obsługi:

Rys. 5 przedstawia schemat połączeń między poszczególnymi rodzajami obsługi.

rys. 5

Powyższy schemat nie uwzględnia interfejsu nazywanego blokiem komunikatów serwera (Server Message Block - rys.4). Interfejs ten, umieszczony w warstwie prezentacji, umożliwia programom korzystanie ze wspólnych plików i drukarek. Jego zadania to: tworzenie katalogów, otwieranie i czytanie plików, a także otwieranie, pisanie i zamykanie bufora drukowania.

3.1.3. Polecenia NetBIOS

Wszelkie zasoby systemu NetBIOS posiadają nazwy identyfikowane przez obsługę nazw. Nazwy mają swoje ograniczenia:

Są dwa rodzaje nazw: nazwy unikatowe i grupowe. W danej sieci lokalnej nie mogą istnieć dwa zasoby posiadające taką samą nazwę unikatową. Jeśli zaś zasoby posiadają taką samą nazwę grupową oznacza to, że przynależą do tej samej grupy.

Obsługa nazw wykorzystuje cztery polecenia wewnętrzne NetBIOS:

Gdy proces chce otrzymać nazwę unikatową musi najpierw sprawdzić, czy nazwa unikatowa nie jest już używana w sieci jako unikatowa lub grupowa. W tym celu wysyła polecenie ADD_NAME do wszystkich węzłów sieci i oczekuje odpowiedzi. Jeśli żaden węzeł nie zgłosi zastrzeżenia to dana nazwa jest rejestrowana w sieci. Nazwa grupowa może zostać odrzucona, jeśli jest już używana jako nazwa unikatowa. Każda nazwa otrzymuje identyfikujący ją numer nazwy lokalnej, który przekazywany jest wraz z innymi poleceniami NetBIOS.

Obsługa sesji zapewnia transmisję komunikatów w NetBIOS. Dane przesyłane są także jako komunikaty o długości do 131071 bajtów.

Na potrzeby obsługi sesji zdefiniowano polecenia:

NetBIOS korzysta z połączeniowego systemu klient-serwer. Najpierw serwer wydaje polecenie LISTEN, następnie klient łączy się z serwerem, wysyłając CALL. Wraz z poleceniem LISTEN serwer przekazuje ogólnie znany adres lokalny oraz adres zdalnego klienta. Jeśli jako adres zdalnego klienta serwer poda gwiazdkę, dowolny klient może połączyć się z serwerem. Po nawiązaniu połączenia procesy otrzymują numer sesji lokalnej, którym będą posługiwały się podczas przesyłania danych za pomocą poleceń SEND i RECEIVE. Po otrzymaniu polecenia SEND NetBIOS przesyła dane i czeka na potwierdzenie odbioru tych danych przez polecenie RECEIVE. Dopiero wtedy zwracane jest sterowanie do obu procesów. Można skorzystać z polecenia SEND_NO_ACK, które nie czeka na potwierdzenie odbioru danych. Jeśli proces nie chce określać od kogo ma otrzymać komunikat używa polecenia RECEIVE_ANY.

Datagramy są krótkimi komunikatami, przesyłanymi między procesami bez uprzednio nawiązanego połączenia. Maksymalna wielkość datagramu jest ograniczona i zależy od implementacji NetBIOS.

Istnieją cztery polecenia związane z obsługą datagramów:

Wraz z poleceniem SEND_DATAGRAM przekazywana jest nazwa odbiorcy datagramu. Może to być nazwa grupowa, wtedy wszyscy członkowie grupy otrzymają dany komunikat. Jeśli proces wyda polecenie SEND_BROADCAST_DATAGRAM, to wszystkie procesy, które wysłały polecenie RECEIVE_BROADCAST_DATAGRAM otrzymają wysłany datagram.

Dodatkowo zdefiniowano w NetBIOS kilka poleceń ogólnych:

Polecenie CANCEL ma sens tylko w tych implementacjach NetBIOS, które umożliwiają wykonywanie asynchroniczne poleceń sieciowych.

3.2. Rodzina protokołów TCP/IP

3.2.1. Informacje podstawowe

Oficjalna nazwa protokołów TCP/IP to „rodzina protokołów DARPA Internet”. Zostały one utworzone wraz z powstaniem sieci ARPANET w latach siedemdziesiątych. Sieć ARPANET rozrosła się do rozmiarów globalnej sieci Internet, ale TCP/IP obsługuje ją prawie w niezmienionej formie. Swój sukces rodzina TCP/IP zawdzięcza następującym właściwościom:

Powiązania między protokołami rodziny TCP/IP przedstawia rys.6. Aby nie zmniejszać czytelności rysunku nie uwzględniono na nim mniej ważnych protokołów. Protokoły TCP/IP opisane są dokładnie w dokumentach pt. Request for Comments (RFC).

rys. 6

3.2.2. Warstwy

Warstwa kanałowa Internetu, zawierająca warstwy OSI: fizyczną i łącza danych, wykorzystuje wielką liczbę różnych rodzajów połączeń, poczynając od standardu Ethernet, poprzez telefoniczne linie dzierżawione i zwykłe, łącza szeregowe RS-232, transmisję radiową, aż po łącza satelitarne.

Warstwę sieciową stanowi międzysieciowy protokół IP, obsługujący doręczanie pakietów dla protokołów TCP, UDP i ICMP. System ten bazuje na zawodnym i bezpołączeniowym przesyłaniu datagramów. Każdy datagram zawiera adres źródłowy i docelowy. Nie ma żadnej gwarancji, że datagram zostanie doręczony i nie zapewnia się kolejności odbierania równej kolejności nadawania datagramów. Jeśli okaże się, że datagram zawiera błąd (na podstawie obliczanej sumy kontrolnej), to jest on po prostu usuwany z sieci. Warstwa IP zakłada, że wyższa warstwa prześle pakiet ponownie. Każdy datagram traktowany jest w tym protokole niezależnie od pozostałych. Mogą one być składane i rozkładane na inne jednostki danych w wyższych warstwach. Tam też może być zaimplementowana procedura transmisji połączeniowej i poprawności przesyłu.

Warstwa IP zajmuje się wyborem trasy w Internecie oraz rozdrabnianiem zbyt dużych datagramów dla danej sieci. Datagramy takie są scalane na miejscu swojego przeznaczenia.

Nagłówek datagramu IP przedstawia rys.7.

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

wersja

IHL

typ usługi -TOS

całkowita wielkość

identyfikacja

flg

fragment

TTL

protokół

suma kontrolna

adres źródła

adres przeznaczenia

opcje

rys. 7

Poszczególne pola nagłówka IP określają następujące parametry:

Protokół IP opisany jest w dokumencie RFC 791 [5].

W warstwie sieciowej znajdują się także trzy inne protokoły: ICMP, ARP i RARP. Protokoły ARP i RARP służą do odwzorowywania adresów sprzętowych - np. w sieci ethernetowej odwzorowują adresy karty sieciowej na adresy internetowe i odwrotnie.

Protokół ICMP to protokół międzysieciowych komunikatów sterujących (Internet Control Message Protocol), obsługujący zawiadomienia o błędach i informacje sterujące przesyłane między bramami a stacjami. Opisuje go dokument RFC 792 [6]. Postać komunikatu ICMP ilustruje rys.8. Ponieważ komunikaty ICMP przesyłane są wewnątrz datagramów IP, w ramce znajduje się nagłówek IP. Postać nagłówka IP przedstawiona jest na rys.7. Pole protokół wypełnione jest wartością oznaczającą protokół ICMP: 1.

nagłówek IP

nagłówek ICMP

dane ICMP

rys. 8

Nagłówek ICMP ma długość 8 bajtów (rys. 9), a jego poszczególne pola oznaczają:

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

typ

kod

suma kontrolna

identyfikator

sekwencja

dane

rys. 9

Możliwe są następujące typy komunikatów ICMP:

nazwa komunikatu:

wartość pola typ nagłówka ICMP:

Echo Reply

0

Destination Unreachable

3

Source Quench

4

Redirect

5

Echo

8

Time Exceeded

11

Parameter Problem

12

Timestamp

13

Timestamp Reply

14

Information Request

15

Information Reply

16

Znaczenie poszczególnych typów:

znaczenie:

wartość:

sieć nieosiągalna

0

stacja nieosiągalna

1

protokół nieosiągalny

2

port nieosiągalny

3

niezbędna fragmentacja a bit DF nagłówka IP = 1

4

Source routing nie powiódł się

5

Dokumentacja dotycząca protokołów TCP/IP określa, że nie powinny być generowane komunikaty ICMP z powodu błędu w innym komunikacie ICMP.

Warstwa transportowa TCP/IP to protokoły UDP i TCP. Pośredniczą one w nawiązywaniu połączenia i wymianie danych między procesami użytkownika. Obydwa te protokoły korzystają z protokołu IP (rys. 6).

Jeśli proces wymaga strumieniowej transmisji połączeniowej, równoczesnej i niezawodnej powinien skorzystać z protokołu TCP. Moduł TCP zajmuje się ustanawianiem i kończeniem połączeń między procesami, ustalaniem właściwej kolejności pakietów, które mogą nadchodzić w przypadkowej kolejności, niezawodnością obsługi oraz bezpośrednim sterowaniem przepływem. Protokół UDP określa natomiast bezpołączeniową i zawodną transmisję datagramów.

Zarówno protokół TCP, jak i UDP używają do łączenia procesów tzw. numerów portów. Dzięki nim na jednej stacji może korzystać z sieci (i z jednego adresu IP) wiele procesów, co więcej jeden proces może nawiązać wiele połączeń na różnych portach. Numer portu umieszczany jest w nagłówku TCP lub UDP, a wraz z adresem IP i rodzajem protokołu stanowi swoistą, unikatową nazwę końcówki połączenia.

Protokół TCP opisany jest w RFC 793, a UDP w RFC 768.

3.2.3. Adresy Internetu

Adres w Internecie zajmuje 32 bity i składa się z części identyfikującej sieć i stację. Każda stacja posiada adres unikatowy, którego przydziałem zajmuje się Sieciowe Centrum Informacyjne (Network Information Center - NIC) [3]. Jednak pojedyncza stacja może mieć więcej niż jeden adres internetowy. Istnieją cztery klasy adresów IP: A, B, C, D (rys.10).

klasa A

7 bitów

24 bity

0

id. sieci

id. stacji

klasa B

14 bitów

16 bitów

1

0

id. sieci

id. stacji

klasa C

21 bitów

8 bitów

1

1

0

id. sieci

id. stacji

klasa D

28 bitów

1

1

1

0

adres grupowy

rys. 10

Adresy klasy A przydzielone są sieciom złożonym, w których występuje bardzo dużo komputerów na pojedyncze sieci. W przypadku sieci złożonych, składających się z wielu małych sieci bardziej odpowiednia będzie klasa C. Klasa D pozwala przesyłać dane jednocześnie dla wielu użytkowników, mających ten sam adres. Centrum NIC przydziela tylko klasę i identyfikator sieci adresu IP, identyfikatory stacji przydzielane są indywidualnie. Możliwe jest takie zorganizowanie sieci, że część przeznaczona na identyfikatory stacji zostanie podzielona na identyfikator podsieci i identyfikator stacji. Ułatwia to identyfikowanie stacji w poszczególnych sieciach lokalnych.

Adres IP przedstawiany jest najczęściej w postaci 4 liczb oddzielonych kropkami (np. 198.105.232.1). Dzięki internetowej usłudze DNS (Domain Name Service) liczbowe adresy mogą mieć swoje odpowiedniki tekstowe, dużo łatwiejsze do zapamiętania (np. ftp.microsoft.com).

4. Programowanie sieciowe w Windows 95

Dostępność wielu różnych bibliotek funkcji dla systemu Windows 95 daje możliwość tworzenia programów, które będą pracowały w wielu typach sieci komputerowych. Dla programowania w sieciach lokalnych przeznaczone są biblioteki: API Win32, biblioteka LAN Manager, system Network Dynamic Data Exchange. Specyfikacja Winsock wykorzystywana jest przy tworzeniu aplikacji sieciowych korzystających z protokołu TCP/IP.

4.1. Sieciowe funkcje API Win32

4.1.1. Dostęp do zasobów sieciowych przy użyciu protokołu NetBIOS

Biblioteka API Win32 [2] zawiera zestaw funkcji, które pozwalają dołączyć do lokalnego systemu zasoby znajdujące się w sieci za pomocą protokołu NetBIOS. Aby uzyskać połączenie z innym systemem plików lub drukarką należy użyć funkcji WNetAddConnection2(), której argumentami są struktura NETRESOURCE oraz hasło dostępu do danego zasobu, nazwa użytkownika starającego się o dostęp i opcje określające sposób dostępu. Struktura NETRESOURCE zawiera dane określające typ zasobu (pliki, drukarki, wszystko), nazwę własną zasobu i nazwę, którą zasób przyjmie w systemie lokalnym (np. F:). Po poprawnym połączeniu można już korzystać z sieciowych plików lub drukarek. Aby zakończyć połączenie używa się funkcji WNetCancelConnection2(). Zestaw funkcji wzbogacony jest o procedury zwracające kompletne informacje o rodzajach zasobów znajdujących się i dostępnych aktualnie w sieci. Są to funkcje: WNetOpenEnum() oraz WNetEnumResource(). Istnieje także dialogowa wersja funkcji do otwierania połączenia: WNetConnectionDialog(). Powoduje ona otwarcie okna dialogowego, dzięki któremu użytkownik programu może sam określić jakie zasoby i w jaki sposób dołączyć do systemu lokalnego. Powyższe funkcje nie pozwalają na bezpośrednią wymianę danych między programami pracującymi w różnych miejscach sieci, ale umożliwiają korzystanie ze zdalnych plików i drukarek.

4.1.2. Wymiana danych przez łącza nazwane i skrzynki „mailslot”

Przesłanie informacji między komputerami za pomocą plików często niepotrzebnie wydłużać będzie czas transmisji. Aby bezpośrednio do pamięci przekazywać dane można skorzystać z łącz nazwanych lub mailslotów.

Łącza nazwane to usługa systemowa polegająca na dwukierunkowym połączeniu jednego procesu nadrzędnego i jednego lub wielu procesów podrzędnych. Proces nadrzędny (serwer) tworzy łącze wywołując funkcję CreateNamedPipe(), podając jednocześnie nazwę łącza oraz ile łącze może mieć kanałów. Gdy proces podrzędny (klient) uruchomi funkcję CreateFile() lub CallNamedPipe() z nazwą łącza jako argumentem, ustanowione zostanie połączenie między serwerem i klientem, wykorzystujące jeden z kanałów łącza. Definiując większą liczbę kanałów można wykorzystać jedno łącze do komunikacji między jednym serwerem i wieloma klientami. Wymiana danych wykonywana jest za pomocą tych samych funkcji, które służą do operacji na plikach: do czytania z łącza ReadFile(), a do pisania WriteFile(). Oczywiście w przypadku łączy dane przekazywane są z pamięci do pamięci, z pominięciem systemu plików. Aby odczytać z łącza dane bez czyszczenia bufora łącza należy użyć funkcji PeekNamedPipes(). Po zakończeniu współdziałania łącze jest zamykane po wykonaniu funkcji: CloseHandle() po stronie klienta i DisconnectNamedPipe() po stronie serwera. Każdy proces może otrzymać informacje o łączu jeśli zna jego nazwę. Po uruchomieniu funkcji GetNamedPipeInfo() zwracana jest informacja o typie łącza, wielkościach buforów do nadawania i odbierania danych oraz liczba kanałów tego łącza.

Mailslot to bardzo podobna do łączy nazwanych usługa systemowa. Różnica polega na tym, że w tym przypadku transmisja danych jest jednokierunkowa. Aplikacja-serwer tworzy swój mailslot o pewnej nazwie (funkcja CreateMailslot()) i tylko ona może z niego czytać informacje (ReadFile()). Inne aplikacje (także te znajdujące się na innych komputerach sieci) mogą pisać do skrzynki serwera, pod warunkiem, że znają nazwę mailslota. Dostęp do niego uzyskują poprzez funkcję CreateFile(), a informacje zapisuje funkcja WriteFile. Kolejno przesyłane komunikaty są w skrzynce składowane. Właściciel skrzynki sprawdza, czy coś w niej się znajduje wywołując funkcję GetMailslotInfo(). Jeśli wiele procesów w obrębie jednej domeny sieci utworzyło mailsloty o takich samych nazwach, to przesyłany komunikat trafi do wszystkich tych skrzynek jednocześnie. W odróżnieniu od łącz nazwanych, komunikacja typu mailslot jest datagramowa - proces wysyłający informację nie otrzymuje więc potwierdzenia o poprawnym jej odebraniu.

4.2. Specyfikacja Windows Sockets

Dokumentacja Windows Sockets [1] definiuje interfejs programowania sieciowego, opierając się na pomyśle gniazd (ang. sockets) zastosowanym po raz pierwszy w UNIX-ie - BSD 4.3. Schemat korzystania z gniazd pozostał w systemach okienkowych taki sam, dodano natomiast funkcje wykorzystujące wielowątkowość Windows oraz sterowanie komunikatami. Ponieważ zestaw funkcji Windows Sockets jest interfejsem, to możliwe jest programowanie za jego pomocą w różnych rodzajach sieci (jeśli tylko dostępna jest odpowiednia biblioteka łącząca protokół sieci z gniazdami). Zarówno wersja UNIX-owa, jak i pierwsza specyfikacja Windows Sockets 1.1 [2] pozwalały wykorzystywać jedynie protokół TCP/IP. Co gorsza, wersja dla Windows umożliwiała użycie tylko dwóch typów gniazd (SOCK_STREAM i SOCK_DGRAM), co znacznie zmniejszało funkcjonalność biblioteki. Najnowsza dokumentacja Windows Sockets (wersja 2) nie ustanawia żadnych ograniczeń co do protokołu, dodano także do biblioteki obsługę kilku znanych z UNIX-a typów gniazd i protokołów. Autorzy specyfikacji (m.in.: Intel, Microsoft, FTP Software, Novell) zwracają uwagę, że jest ona opisem, który powinien posłużyć producentom programowania do tworzenia własnych, aktualniejszych wersji bibliotek.

4.2.1. Inicjalizacja biblioteki

Biblioteka funkcji Windows Sockets znajduje się w module dołączanym dynamicznie - winsock.dll (Dynamic Link Library). W czasie kompilacji programu niezbędna jest biblioteka umożliwiająca łatwe odwołania do funkcji DLL - ws2_32.lib oraz plik winsock2.h (starsza wersja: winsock.h), zawierający definicje. Aby uzyskać dostęp do biblioteki DLL należy ją zainicjalizować funkcją WSAStartup(). Jako argument przyjmuje ona numer wersji specyfikacji z jaką aplikacja chciałaby współpracować. WSAStartup() zwraca maksymalny numer wersji, który biblioteka potrafi obsługiwać. Zwracane są także takie informacje jak: maksymalna ilość wykorzystywanych jednocześnie gniazd i maksymalny rozmiar datagramowego komunikatu. Funkcja WSAStartup() powinna być pierwszą funkcją Windows Sockets wywoływaną w programie. Po zakończeniu współpracy z biblioteką należy ją zamknąć za pomocą funkcji WSACleanup().

4.2.2. Gniazda

Podstawą działania Windows Sockets, zapożyczoną z UNIX-a [9], są gniazda. Gniazdo jest końcówką połączenia między komunikującymi się stacjami. Każda transmisja danych użytkownika musi zostać poprzedzona utworzeniem gniazda, zarówno po stronie nadawczej, jak i odbiorczej (oczywiście, jeżeli obie strony korzystają z mechanizmów Windows Sockets). Gniazdo powstaje po wywołaniu funkcji socket(). Przyjmuje ona jako argumenty dane określające typ połączenia: format adresu, rodzaj gniazda oraz protokół, który będzie przez gniazdo używany. Najczęściej wykorzystywanym formatem adresu jest format ARPA Internet - AF_INET. Wersja Windows Sockets 1.1 umożliwiała przesyłanie danych tylko dwoma sposobami: gniazdem połączeniowym (SOCK_STREAM) i datagramowym (SOCK_DGRAM). Wersja 2.2 posiada kilka dodatkowych typów gniazd, między innymi SOCK_RAW, który umożliwia dostęp do całego odbieranego pakietu (z nagłówkami) i pozwala na większą swobodę w dostępie do nagłówków pakietu wysyłanego. Dzięki temu typowi gniazda możliwa jest na przykład obsługa kontrolnego protokołu ICMP. Do transmisji zwykłych danych wykorzystywane są jednak gniazda SOCK_STREAM i SOCK_DGRAM. SOCK_STREAM używa protokołu TCP [7], więc przesyłanie danych jest bezpieczniejsze, gdyż poprawne odebranie pakietu po stronie odbiorczej jest potwierdzane, pakiety przesyłane są sekwencyjnie i niemożliwe jest ich zduplikowanie. Gdy aplikacja wykorzystuje gniazdo typu SOCK_DGRAM i protokół UDP [4] transmisja jest szybsza, lecz z powodu braku potwierdzania - nie wiadomo, czy komunikat dotarł do adresata, a gdy dotarł - nie wiadomo, czy nie został podwojony lub wyprzedził w transmisji inne komunikaty. Program wywołujący socket() może sam określić rodzaj protokołu lub pozwolić funkcji wybrać domyślny dla danego typu gniazda protokół. W przypadku gniazd SOCK_STREAM i SOCK_DGRAM wybrane zostaną odpowiednie protokoły dla danego typu połączenia (IPPROTO_TCP i IPPROTO_UDP). W przypadku protokołu surowego (SOCK_RAW) należy samemu określić protokół. Można to zrobić podając funkcji odpowiednią stałą (np. IPPROTO_ICMP) lub wywołując funkcję getprotobyname(), która zwraca numer protokołu akceptowany przez socket(). Jako argument getprotobyname() przyjmuje nazwę protokołu w postaci ciągu znaków (może to być więc np. „icmp”).

Jeśli funkcja socket() zakończy z powodzeniem działanie zwraca aplikacji wywołującej deskryptor gniazda jako wartość typu SOCKET. Wartością tą należy posługiwać się podczas wszelkich odwołań do funkcji wykorzystujących zainicjalizowaną transmisję. Jeśli podane parametry są błędne, brakuje obsługi określonych protokołów lub przekroczono dopuszczalną liczbę gniazd, to socket() zwróci wartość INVALID_SOCKET oznaczającą błąd. Gniazda znajdujące się po obydwu stronach połączenia powinny używać tego samego typu gniazda.

Specyficzne dla Windows Sockets są gniazda blokowane i nieblokowane. Dzięki wykorzystaniu systemowego mechanizmu sterowania komunikatami można zlecić bibliotece Windows Sockets jakąś operację (np. odbieranie danych) i w czasie oczekiwania na napływanie tych danych z zewnątrz, wykonywać inne zadania. Gdy na wejściu pojawią się oczekiwane dane Windows Sockets prześle do systemu zdefiniowany wcześniej przez użytkownika komunikat. Jednakże wszystkie gniazda biorące udział w takiej operacji powinny być nieblokowane. Gdy gniazdo jest utworzone, działa w trybie blokowania, co można zmienić za pomocą funkcji ioctlsocket() lub WSAAsyncSelect(). Funkcja ioctlsocket() przyjmuje komendę FIONBIO ze wskaźnikiem na zmienną typu u_long różną od zera. Funkcja WSAAsyncSelect() odblokowuje gniazdo i pozwala określić jaki komunikat zostanie przesłany do systemu, gdy wystąpi jakaś akcja związana z gniazdem (np.: zewnętrzne gniazdo spróbuje się skontaktować, pojawią się dane do odczytu, bufor do wysyłania został opróżniony - można wysyłać dane dalej). Gniazdo w czasie swojego istnienia może wielokrotnie zmieniać stan z blokowanego na nieblokowane i odwrotnie. Aby ustawić stan gniazda na nieblokowane trzeba znów użyć funkcji ioctlsocket() z komendą FIONBIO i zmienną u_long równą zero. Gdy gniazdo pracuje w trybie blokowanym, aplikacja może sprawdzić jaki jest jego stan i dopiero wtedy wykonać odpowiednią operację (np. czytanie z gniazda). Do określania stanu blokowanego gniazda służy funkcja select(). Pozwala ona sprawdzić czy w zestawie gniazd znajdują się takie, z których można czytać, do których można pisać i takie, które nie działają prawidłowo.

Opcje dotyczące gniazda mogą być zmieniane przy użyciu funkcji setsockopt(). Dzięki temu można rozsyłać komunikaty w sieci (SO_BROADCAST), zmienić wielkość buforów: nadającego i odbierającego (SO_SNDBUF i SO_RCVBUF) i, co bardzo ważne, ustawić przynajmniej niektóre składniki nagłówka IP wysyłanego pakietu.

4.2.3. Dostęp do sieci

W czasie, gdy gniazdo jest tworzone przez funkcję socket(), należy określić jego pełną nazwę. Nazwa, w przypadku gniazd typu SOCK_STREAM i SOCK_DGRAM, składa się z trzech elementów: adresu IP stacji, protokołu oraz numeru portu. Dowiązanie adresu i portu do gniazda wykonuje funkcja bind(), przyjmująca jako argumenty deskryptor gniazda oraz strukturę typu sockaddr, która ma następującą postać:

struct sockaddr

{

u_short sa_family;

char sa_data[14];

};

Zmienna sa_family przyjmuje wartość formatu adresu (najczęściej AF_INET), natomiast sa_data to 14 bajtów zawierających adres i numer portu dla gniazda. Wypełnienie tej tablicy ułatwia struktura sockaddr_in, którą należy następnie zrzutować (przekonwersować) na sockaddr. Struktura sockaddr_in wygląda następująco:

struct sockaddr_in

{

short sin_family;

unsigned short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

Zmienne sin_port i sin_addr określają numer portu i adres IP. Tablica sin_zero dodana jest, aby zgadzały się długości struktury sockaddr_in i sockaddr. Struktura sin_addr zdefiniowana jest jako unia:

struct in_addr

{

union

{

struct

{

unsigned char s_b1,s_b2,s_b3,s_b4;

} S_un_b;

struct

{

unsigned short s_w1,s_w2;

} S_un_w;

unsigned long s_addr;

} S_un;

};

Dzięki temu można uzyskać dostęp do adresu IP na trzy sposoby.

Aby zamienić łańcuch znaków określający adres IP, dany w postaci „kropkowej”, na wartość typu unsigned long, należy użyć funkcji inet_addr(), np.:

unsigned long adres=inet_addr(”192.100.100.1”);

Zmienną adres z powyższego przykładu można podstawić pod zmienną s_addr struktury sockaddr_in. Do własnych celów aplikacje powinny łączyć się, wykorzystując porty o numerach większych od 1024. Numery niższe mogą być zarezerwowane dla tzw. dobrze znanych usług (np. FTP zawsze wykorzystuje port numer 21). Istnieje pewien problem związany z tym, że inna może być kolejność bajtów w danych komputera lokalnego i w sieci. Aby uniknąć błędów należy stosować funkcję htons(), zamieniającą, w razie potrzeby (np. na komputerach typu PC), liczbę u_short na odwrotną kolejność używaną w sieci:

u_short port=htons(10000);

Po poprawnym wypełnieniu struktury sockaddr_in można już użyć funkcji bind():

bind(sock,(struct sockaddr*)&sockaddr_in_DATA,sizeof(sockaddr_in));

Jeśli po stronie nadawczej i odbiorczej istnieją już gniazda z przydzielonymi adresami można przystąpić do połączenia. W przypadku gniazd typu SOCK_STREAM ustanawiane jest połączenie typu klient-serwer. Gniazdo typu serwer wykonuje funkcję listen(), klient natomiast connect(). Gdy listen() zwróci poprawną wartość serwer może zaakceptować połączenie funkcją accept(). Po takiej konwersacji dane mogą być przesyłane za pomocą funkcji send() i odbierane przez recv() w obydwu kierunkach.

Schemat połączenia jest następujący:

serwer

klient

gniazdo1S=socket(AF_INET,SOCK_STREAM,0)

gniazdo1K=socket(AF_INET,SOCK_STREAM,0)

bind(gniazdo1S,&addrS,...)

bind(gniazdo1K,&addrK,...)

listen(gniazdo1S,1)

connect(gniazdo1K,&addrS,...)

gniazdo2S=accept(gniazdo1S,&addrK,...)

Argumentami funkcji listen() są: deskryptor niepołączonego gniazda oraz wartość backlog, określająca ile żądań połączenia musi nadejść do funkcji, aby zwróciła ona sterowanie aplikacji. Funkcja listen() zwraca zero, gdy pojawiły się żądania dostępu do gniazda lub SOCKET_ERROR w przypadku wystąpienia błędu.

Jeśli jakakolwiek aplikacja w sieci zna nazwę gniazda SOCK_STREAM (protokół, adres IP i port) utworzonego na innym komputerze, może spróbować się z nim skontaktować za pomocą funkcji connect(). Jako argumenty funkcja ta przyjmuje deskryptor lokalnego gniazda oraz strukturę sockaddr z adresem odległej stacji. Jeśli pod wskazanym adresem istnieje gniazdo oczekujące połączenia (funkcja listen()), to connect() zwróci 0, jeśli wystąpi błąd, to wartość powrotna będzie miała wartość SOCKET_ERROR.

Aby zatwierdzić połączenie stacja-serwer wywołuje funkcję accept(), zaraz po powrocie z funkcji listen(). Procedura accept() wymaga podania gniazda, które oczekiwało na połączenie i ewentualnie wskaźnika na strukturę sockaddr, w której funkcja zwróci parametry gniazda żądającego połączenia. Jeśli zamiast wskaźnika na sockaddr podany zostanie NULL, gniazdo akceptujące nie otrzyma informacji o gnieździe kontaktującym się. accept() zwraca wartość, będącą deskryptorem nowego gniazda - gotowego do wymiany danych z gniazdem odległym. Jeśli działanie funkcji zakończy się niepowodzeniem, zwraca ona INVALID_SOCKET.

Dzięki funkcji WSAAsyncSelect() i gniazdom nieblokującym można zdefiniować zdarzenia, które zostaną uruchomione w momencie, gdy aplikacja może uzyskać połączenie z innym komputerem. Jest to dość ważne, ponieważ w czasie oczekiwania na połączenie aplikacja może wykonywać inne zadania. Po zgłoszeniu się odpowiednich zdarzeń, program wykonuje funkcję accept() lub connect().

Do przesyłania danych służą funkcje send() oraz recv(). Funkcja send() wysyłająca dane potrzebuje następujących argumentów: deskryptora połączonego gniazda, wskaźnika typu char* na obszar pamięci, zawierający dane do przesłania oraz ilość tych danych. Aplikacja odbierająca dane wykonuje recv() i podaje podobne argumenty: swoje gniazdo, wskaźnik na miejsce w pamięci, gdzie zostaną umieszczone dane i wielkość tego bufora. Jeśli ilość danych przekracza wielkość bufora, recv() wypełni obszar i w dalszym ciągu można będzie czytać dane z tego gniazda. Funkcja zwraca ilość odczytanych bajtów. Także w przypadku funkcji send() i recv() możliwe jest zdefiniowanie zdarzeń reagujących na nadchodzące z zewnątrz dane i możliwość wysłania danych (w przypadku zastosowania gniazda nieblokującego).

Powyższy schemat połączenia i przesyłania danych obowiązuje dla gniazd typu SOCK_STREAM. Gniazda typu SOCK_DGRAM nie wymagają używania funkcji: listen(), connect() i accept(). Po utworzeniu gniazd i przydzieleniu im konkretnych adresów (za pomocą bind()) można transmitować komunikaty przy użyciu funkcji sendto() oraz recvfrom(). Funkcje te mają takie same argumenty wywołania jak funkcje send() i recv(), dodatkowo wymagają podania struktury sockaddr. Trzeba umieścić w niej adres gniazda, do którego przesyłane są dane (w przypadku sendto()) lub można w tej strukturze uzyskać adres gniazda nadającego (w przypadku recv()). Komunikaty datagramowe mają ograniczoną wielkość, którą można uzyskać ze struktury WSAData, zwróconej przez WSAStartup(). Składnik tej struktury: iMaxUdpDg, zawiera maksymalną wielkość pakietu IP. Jeśli wielkość ta zostanie przekroczona, sendto() zwróci błąd WSAEMSGSIZE. Należy pamiętać, że datagramowy mechanizm przesyłania komunikatów nie gwarantuje poprawnego odebrania danych, mimo że sendto() nie zasygnalizuje błędu. Funkcje sendto() oraz recvfrom() mogą być również używane przez gniazda typu połączeniowego. Argumenty wywołania dotyczące adresów są wtedy ignorowane. Jeżeli natomiast gniazdo datagramowe otrzyma przypisany adres gniazda zdalnego (przy pomocy funkcji connect()), to będzie mogło korzystać z funkcji send() oraz recv(), gdyż znany jest już adres docelowy.

Po zakończeniu sesji transmisyjnej gniazda powinny zostać zamknięte. Zwolnienia deskryptorów gniazd dokonuje funkcja closesocket(), przyjmująca jako parametr deskryptor zamykanego gniazda.

4.2.4. Dodatkowe funkcje usługowe

Biblioteka Windows Sockets zawiera funkcje umożliwiające łatwe zdobycie wiadomości o odległych stacjach, dostępnych protokołach i usługach.

Aby uzyskać lokalną nazwę komputera należy wywołać funkcję gethostname(). Jako argumenty przekazuje się do funkcji wskaźnik typu char* do bufora, w którym znajdzie się nazwa oraz wielkość bufora. Zwrócona nazwa jest łańcuchem zakończonym znakiem NULL i może (lecz nie musi) zawierać pełną nazwę z częścią oznaczającą domenę. Nazwa ta może być użyta jako argument wywołania funkcji gethostbyname(), w celu określenia innych parametrów stacji.

Funkcja gethostbyname() przyjmuje jako argument nazwę stacji (nawet odległej) a zwraca strukturę hostent, opisaną dalej:

struct hostent

{

char FAR * h_name;

char FAR * FAR * h_aliases;

short h_addrtype;

short h_length;

char FAR * FAR * h_addr_list;

};

Poszczególne elementy struktury zawierają:

Aplikacja nie powinna nigdy modyfikować tej struktury, gdyż jest ona alokowana tylko raz dla jednego wątku procesu. Najbezpieczniej jest przekopiować jej zawartość przed uruchomieniem innych funkcji Windows Sockets. Jeśli nie można odnaleźć w sieci stacji o podanej nazwie, gethostbyname() zwróci NULL.

Jeżeli aplikacja zna adres stacji, a nie jej nazwę może wywołać funkcję gethostbyaddr() w celu określenia dodatkowych informacji. Ta funkcja także zwraca opisaną wcześniej strukturę hostent. Adres przekazywany jest do niej w postaci ciągu bajtów. Aby stworzyć taki ciąg można skorzystać z funkcji inet_addr(), przyjmującej „kropkowy” adres internetowy, a zwracającej - adres w postaci wartości typu unsigned long. Podobnie jak poprzednio, w przypadku podania błędnego adresu, gethostbyaddr() zwraca NULL.

Jeśli aplikacja chce skorzystać z jakiegoś protokołu powinna znać jego numer. Aby uzyskać numer protokołu, znając jego nazwę wywołuje się funkcję getprotobyname(). Przyjmuje ona ciąg znaków, np.: „udp”, „tcp”,”icmp”, a zwraca strukturę protoent:

struct protoent

{

char FAR * p_name;

char FAR * FAR * p_aliases;

short p_proto;

};

Elementy struktury oznaczają:

Istnieje także funkcja getprotobynumber() pozwalająca uzyskać powyższe informacje, jeśli aplikacja zna numer protokołu (a nie zna jego nazwy).

Obie funkcje zwrócą NULL jeśli argumenty wywołania są niepoprawne lub Windows Sockets nie obsługuje danego protokołu.

Możliwe jest także uzyskanie informacji o nazwach i portach usług dostępnych w sieci. Jeśli znana jest nazwa usługi (np.: „ftp”, „telnet”) należy skorzystać z funkcji getservbyname(), jeśli zaś znany jest jej port - getservbyport(). Funkcje te zwracają strukturę servent:

struct servent

{

char FAR * s_name;

char FAR * FAR * s_aliases;

short s_port;

char FAR * s_proto;

};

Struktura zawiera następujące informacje:

Bardzo ważną funkcją jest WSAGetLastError(). W przypadku jeśli jakakolwiek inna funkcja (oprócz WSAStartup()) zwróci informację o błędzie, dzięki WSAGetLastError() można uzyskać numer tego błędu. Ponieważ WSAStartup() służy do zainicjalizowania biblioteki Windows Sockets, której częścią jest WSAGetLastError(), błędy powstałe przy WSAStartup() zwracane są bezpośrednio przez nią.

4.2.4. Przykładowe programy

Aby zilustrować działanie Windows Sockets przedstawione zostaną kompletne aplikacje służące do przesyłania danych. Pierwsza z nich - serwer.cpp - służy do wysłania krótkiego łańcucha znaków, a druga - klient.cpp - do jego odebrania. Chociaż w takim przypadku łatwiej byłoby wykorzystać połączenie datagramowe, programy przekażą dane za pomocą gniazd typu SOCK_STREAM. Takie podejście pozwoli zaprezentować działanie funkcji połączeniowych.

Program SERWER przedstawiony jest na wydruku 1. Zdefiniowano w nim numer lokalnego portu, na którym będą odbierane dane (PORT_LOCAL). Adres IP komputera aplikacji zapamiętany jest w definicji ADDRESS_LOCAL_TEXT.

W przypadku wystąpienia jakichkolwiek błędów wykonana zostanie procedura printError(), która przyjmuje łańcuch znaków i wyświetla go na ekranie w postaci okienka MessageBox. Błędy pochodzące od serwera mają w nagłówku tego okna napis: „SERWER - Błąd!”.

Z uwagi na niewielki rozmiar programu prawie cała jego zawartość znajduje się w głównej funkcji WinMain().

Wykorzystywane są następujące zmienne lokalne. Tablica znakowa text o wielkości 100 bajtów posłuży do zapamiętania odebranej wiadomości; zmienna typu int: fromLen - wielkość struktury sockaddr_in podawana funkcji accept(); struktura WSAData, wypełniana przez funkcję WSAStartup(); dwa gniazda sock i sock2: jedno służące do odbierania połączenia (funkcja listen()), a drugie do odbierania danych (accept() i recv()); dwie struktury sockaddr_in potrzebne przy adresowaniu: jedna do dowiązania adresu lokalnego dla gniazda sock, a druga wypełniana przez accept() adresem gniazda nadającego.

Program zaczyna się od inicjalizacji Windows Sockets funkcją WSAStartup(). Jako argument przekazywany jest żądany numer wersji 2.0 (jako wyrażenie: (0<<8)+2 ). następnie tworzone jest gniazdo sock, z którym wiązany jest lokalny adres (funkcja bind() i struktura to). Po wywołaniu funkcji listen() funkcja oczekuje na żądanie połączenia. Jeśli jakaś aplikacja, gdziekolwiek w sieci, zażąda połączenia z gniazdem sock funkcja listen() zwróci do aplikacji wartość zero. Serwer akceptuje to połączenie uruchamiając funkcję accept(), która zwraca deskryptor nowego gniazda. Zapamiętany jest on w zmiennej sock2. Przy jej pomocy odbierana jest wiadomość od KLIENTA po wywołaniu funkcji recv(). Wiadomość ta wyświetlana jest w okienku przez funkcję MessageBox(). Przed zakończeniem aplikacji zwalniane są oba deskryptory gniazd (closesocket()) oraz zamykana jest sama biblioteka Windows Sockets (WSACleanup()).

//SERWER

#include<winsock2.h>

#include<windows.h>

#include<string.h>

/***************************************************************************************/

#define PORT_LOCAL 10001

#define TEXT_OUT "Tekst klienta"

#define ADDRESS_LOCAL_TEXT "192.100.100.1"

/***************************************************************************************/

void printError(char *text) { MessageBox(NULL,text,"SERWER - Błąd!",MB_ICONSTOP); }

/***************************************************************************************/

int PASCAL WinMain(HANDLE inst, HANDLE prev, LPSTR argv, int state)

{

char text[100];

int fromLen;

WSADATA wsaData;

SOCKET sock,sock2;

struct sockaddr_in FAR from,to;

if (WSAStartup((0<<8)+2,&wsaData) != 0)

{

printError("Brak w systemie zainstalowanej biblioteki WINSOCK 2.0"); return 0;

}

if ((sock=socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET)

{

printError("Nie można otworzyć gniazda"); return 0;

}

to.sin_family=AF_INET;

to.sin_port=htons(PORT_LOCAL);

to.sin_addr.s_addr=inet_addr(ADDRESS_LOCAL_TEXT);

if (bind(sock,(struct sockaddr *)&to,sizeof(to)) == SOCKET_ERROR)

{

printError("Nie można powiązać adresu z gniazdem"); return 0;

}

if (listen(sock,1) == SOCKET_ERROR)

{

printError("Nie można nawiązać połączenia"); return 0;

}

fromLen=sizeof(from);

if ((sock2=accept(sock,(struct sockaddr *)&from,&fromLen)) == INVALID_SOCKET)

{

printError("Nie można nawiązać połączenia"); return 0;

}

if (recv(sock2,text,sizeof(text),0) == SOCKET_ERROR)

{

printError("Odebranie danych nie powiodło się"); return 0;

}

MessageBox(NULL,text,"SERWER - Wiadomość od klienta:",MB_ICONSTOP);

closesocket(sock2);

closesocket(sock);

WSACleanup();

return 1;

}

/***************************************************************************************/

wydruk 1

Na wydruku 2 znajduje się program KLIENT wysyłający dane do SERWERA.

Oprócz lokalnego portu i adresu zdefiniowano w nim także port i adres gniazda zdalnego SERWERA (PORT_REMOTE i ADDRESS_REMOTE_TEXT) oraz napis „Tekst klienta” (TEXT_OUT). Tu także występuje funkcja printError() do informowania użytkownika o sytuacjach nieprawidłowych.

Zmienne mają podobne znaczenie do tych występujących w programie SERWER. Nowa zmienna typu char * text, wskazuje na łańcuch znaków do wysłania.

Początek działania aplikacji także przypomina poprzedni program: inicjalizowana jest biblioteka Windows Sockets i tworzone gniazdo sock, do którego dowiązywany jest lokalny adres. Następna funkcja, connect(), próbuje połączyć się z SERWEREM. Jeśli na komputerze o adresie danym w strukturze to uruchomiono aplikację SERWER i zdążyła już ona wywołać funkcję listen(), to connect() zwróci wartość różną od SOCKET_ERROR, oznaczającą błąd. Połączenie gotowe jest do transmisji, którą wykonuje funkcja send(). Pobiera ona z pamięci określonej przez wskaźnik text tyle bajtów ile liczy łańcuch TEXT_OUT (wartość zwrócona przez strlen() - ilość znaków + bajt o wartości 0, oznaczający koniec łańcucha). Na koniec zwalniany jest deskryptor gniazda sock i zamykana jest biblioteka.

Jeśli którakolwiek z funkcji zakończy się niepowodzeniem, to zwróci wartość SOCKET_ERROR (w przypadku funkcji zwracających typ int) lub INVALID_SOCKET (gdy funkcja powinna zwrócić deskryptor gniazda). Aby określić jaki dokładnie rodzaj błędu wystąpił, należałoby wtedy wywołać funkcję WSAGetLastError().

Chociaż w podanym przykładzie dane transmitowane były w kierunku od KLIENTA do SERWERA, to nic nie stoi na przeszkodzie, aby także SERWER używał funkcji send(), a KLIENT - recv().

Numery portów stosowane w tego rodzaju aplikacjach powinny być większe od 1024. Pozostałe porty mogą być zarezerwowane dla różnych usług.

Programy wykonywalne SERWER i KLIENT, wraz z ich kodami źródłowymi, znajdują się na dyskietce dołączonej do pracy.

//KLIENT

#include<winsock2.h>

#include<windows.h>

#include<string.h>

/***************************************************************************************/

#define PORT_LOCAL 10000

#define PORT_REMOTE 10001

#define TEXT_OUT "Tekst klienta"

#define ADDRESS_LOCAL_TEXT "192.100.100.1"

#define ADDRESS_REMOTE_TEXT "192.100.100.1"

/***************************************************************************************/

void printError(char *text) { MessageBox(NULL,text,"KLIENT - Błąd!",MB_ICONSTOP); }

/***************************************************************************************/

int PASCAL WinMain(HANDLE inst, HANDLE prev, LPSTR argv, int state)

{

char *text=TEXT_OUT;

int err;

WSADATA wsaData;

SOCKET sock;

struct sockaddr_in FAR from, to;

if ((err=WSAStartup((0<<8)+2,&wsaData)) != 0)

{

printError("Brak w systemie zainstalowanej biblioteki WINSOCK 2.0"); return 0;

}

if ((sock=socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET)

{

printError("Nie można otworzyć gniazda"); return 0;

}

from.sin_family=AF_INET;

from.sin_port=htons(PORT_LOCAL);

from.sin_addr.s_addr=inet_addr(ADDRESS_LOCAL_TEXT);

if (bind(sock,(struct sockaddr *)&from,sizeof(from)) == SOCKET_ERROR)

{

printError("Nie można powiązać adresu z gniazdem"); return 0;

}

to.sin_family=AF_INET;

to.sin_port=htons(PORT_REMOTE);

to.sin_addr.s_addr=inet_addr(ADDRESS_REMOTE_TEXT);

if (connect(sock,(struct sockaddr *)&to,sizeof(to)) == SOCKET_ERROR)

{

printError("Nie można nawiązać połączenia"); return 0;

}

if (send(sock,text,strlen(text)+1,0) == SOCKET_ERROR)

{

printError("Wysłanie danych nie powiodło się"); return 0;

}

closesocket(sock);

WSACleanup();

return 1;

}

/***************************************************************************************/

wydruk 2

5. Pakiet programów narzędziowych

5.1. Informacje podstawowe

Przedstawione w tym rozdziale programy narzędziowe pomagają określić stan połączenia między stacjami w sieci. Mogą być uruchamiane w sieci wykorzystującej protokół TCP/IP, a więc także w Internecie. Aplikacje te odwołują się do biblioteki Windows Sockets 2.0, aby więc mogły działać biblioteka taka musi być zainstalowana w systemie.

Programy zostały napisane w języku C, a skompilowane za pomocą kompilatora Microsoft Visual C++ wersja 4.0. Z uwagi na wielkość kodów źródłowych nie są one dołączone do pracy w postaci listingów, lecz znajdują się na dyskietce. Wraz z plikami cpp znaleźć tam można także pliki wykonywalne.

Programy Ping i Tracer wykorzystują w swoim działaniu protokół ICMP (Internet Control Message Protocol - internetowy protokół komunikatów sterujących) i wielokrotnie zmieniają poszczególne składniki nagłówków IP i ICMP [9].

5.2. Program Ping

5.2.1. Funkcja

Nazwa Ping odnosi się do programów realizujących podobne cele w wielu różnych rodzajach sieci. Jest skrótem od określenia: Packet InterNet Groper (internetowy lokalizator sieciowy).

Ping jest najprostszym sposobem ustalenia, czy jest możliwe połączenie z daną stacją. Pozwala określić stan przeładowania połączenia sieciowego. Program ten określa czas przebiegu pakietu od stacji źródłowej do docelowej i z powrotem. Dzięki możliwości określenia wielkości przesyłanego pakietu pozwala ustalić jak wielkość ramki wpływa na transmisję danych.

5.2.2. Algorytm

Istnieje wiele różnych mutacji programu Ping, lecz najczęściej wykorzystywany jest w jego algorytmie protokół ICMP. Pozwala on wysłać pakiet danych pod określony adres IP, tak by stacja docelowa odebrała go i odesłała odpowiednią wiadomość. Algorytm zastosowany w opisanym dalej programie Ping przedstawiono na rys 11. Program Ping wysyła komunikat ICMP: Echo, żądający przekazanie echa, a następnie oczekuje na nadejście odpowiedzi. Stacja, która otrzyma Echo powinna niezwłocznie wysłać do stacji żądającej echa komunikat Echo Replay. Po otrzymaniu echa stacja może już stwierdzić, że istnieje połączenie z danym adresem. Aby zmierzyć czas przesyłu komunikatu można zapamiętać czas startu pakietu i porównać go z czasem powrotu. Jednak w przypadku dużej liczby wysyłanych komunikatów tego typu staje się to kłopotliwe. Ponieważ wraz z nagłówkiem ICMP przesyłana jest także pewna porcja danych, można tam umieścić czas nadania komunikatu, a po jego powrocie sprawdzić czas nadejścia i oba czasy porównać. Możliwe to jest dlatego, że komunikat Echo Replay zawiera dane z komunikatu Echo. Dokładniej: większość pól nagłówka ICMP komunikatu Echo oraz dane pozostają takie same w komunikacie Echo Replay. Warunkiem możliwości zastosowania takiego mechanizmu jest wielkość danych równa 8 bajtów, gdyż tyle potrzeba na zapamiętanie struktury timeval zawierającej czas. Zaletą obecności w powrotnym komunikacie danych pierwotnie wysłanych jest możliwość sprawdzenia ich identyczności. Jest to pewną miarą poprawności transmisji.

rys. 11

Ponieważ wygodnie jest ustalić liczbę komunikatów Echo i wysyłać je raz za razem, to ważne jest, by kolejne komunikaty były numerowane. Jednak takie sesje mogą być powtarzane, co mogłoby prowadzić do pomylenia spóźnionych komunikatów powrotnych z poprzedniej sesji z komunikatami dopiero co wysłanymi. Warto więc zawrzeć w komunikacie numer kolejny uruchomienia sesji Ping. Dodatkowo na jednej stacji może być uruchomionych kilka instancji tej samej aplikacji Ping - tak więc dobrze jest identyfikować komunikaty wartością jednoznacznie określającą instancję.

5.2.3. Implementacja

Implementacja powyższego algorytmu wykorzystuje możliwości systemu Windows 95. Program posiada wygodne okna dialogowe (rys.12) oraz okno pomocy, dzięki wykorzystaniu zdarzeń sterujących program można w każdej chwili zatrzymać.

0x01 graphic

rys. 12

Podczas inicjalizacji głównego okna uruchamiana jest funkcja initPing(), która rozpoczyna współpracę z Windows Sockets, odczytuje informacje o stacji lokalnej (getHostInfo()), inicjalizuje część zmiennych globalnych i tworzy gniazdo w protokole ICMP za pomocą funkcji socket(). Funkcją sterującą jest funkcja głównego okna dialogowego: mainProc(). Odbiera ona wszystkie komunikaty przesyłane do tego okna. Gdy użytkownik naciśnie klawisz START wywoływana jest funkcja startPing(). Odczytuje ona najpierw wartości okienek dialogowych zawierających dane dla aktualnej sesji Ping:

Dzięki wykorzystaniu funkcji gethostbyname() adres stacji zdalnej podany może być w postaci nazwy DNS. Funkcja startPing() sprawdza poprawność wszystkich danych, a w razie błędu informuje o tym użytkownika za pomocą funkcji printError(). W przypadku pozytywnego odczytania wszystkich danych inicjalizowane są zmienne potrzebne w danej sesji Ping, uruchamiany jest zegar nr 1 z czasem odstępu określonym przez użytkownika (zmienna timeDelay w funkcji SetTimer()), ustawiana jest pułapka na nadchodzące do gniazda komunikaty (funkcja WSAAsyncSelect() z komendą FD_READ i zdarzeniem WM_SOCK_READ).

Zegar nr 1 co odstęp czasu równy timeDelay wysyła do systemu komunikat WM_TIMER. Uruchamiana jest wtedy funkcja sendPing() wysyłająca kolejny komunikat ICMP Echo. Równolegle, w razie nadejścia komunikatu ICMP do gniazda sock, system otrzymuje zdefiniowane wcześniej zdarzenie WM_SOCK_READ i wywołuje funkcję recvPing(), która odczytuje pakiet.

Funkcja sendPing() wypełnia poszczególne pola nagłówka ICMP:

Wypełniony pakiet jest wysyłany za pomocą funkcji sendto(). Po poprawnym wysłaniu pakietu inkrementowana jest wartość transmPacketNum oznaczająca ilość wysłanych pakietów. Jeśli wysłano już wszystkie pakiety ustawiany jest zegar nr 2. Przy prawidłowym, wystarczająco krótkim czasie powrotu komunikatów zegar ten powinien być wyłączony przed upływem określonego dla niego czasu. Wyłączyć go powinna funkcja recvPing(), gdy odbierze ostatni pakiet. Jeśli jednak pakiet ten nie nadejdzie zegar nr 2 umożliwi dalsze działanie programu (aplikacja nie będzie w nieskończoność czekała na ostatni, zaginiony pakiet).

Funkcja recvPing() wywoływana jest zawsze, gdy gniazdo sock może odebrać jakiś komunikat ICMP. Ponieważ gniazdo sock jest typu SOCK_RAW (gniazdo surowe), funkcja recvfrom() odbiera cały datagram IP. Po tym sprawdzane jest, czy wielkość datagramu jest odpowiednia oraz czy odebrany komunikat ICMP jest typu Echo Replay. Jeśli tak, należy porównać wartości oznaczające identyfikator procesu (processID) i numer sesji Ping tego procesu (pingNum) z identycznymi danymi odczytanymi z komunikatu ICMP. Jeśli się nie zgadzają, komunikat nie został wysłany przez ten proces lub został wysłany w poprzedniej sesji. W obu przypadkach nie ma sensu przetwarzać tego komunikatu. Dzięki temu, że wszystkie procesy oczekujące komunikatów ICMP pod danym adresem otrzymują swoją kopię tego samego komunikatu, to nie ma obawy o odczytanie komunikatu tylko przez jeden proces. W dalszej części funkcji recvping() sprawdzane jest, czy pakiet już wcześniej nie przybył (dublowanie pakietów) oraz czy nie zawiera błędów. Po zmierzeniu czasu powrotu formowany i drukowany jest odpowiedni tekst, informujący o powrocie komunikatu ICMP.

Jeśli odczytano już wszystkie komunikaty, na które Ping czekał w tej sesji wyłączany jest zegar nr 2 i zdarzenie WM_SOCK_READ. Wykonywana jest teraz funkcja drukująca ostateczne informacje statystyczne recvPingFinish(). Drukuje ona informacje o ilości pakietów wysłanych, odebranych, zgubionych i zdublowanych oraz czasy przesyłu pakietów: minimalny, średni i maksymalny.

5.2.4. Opis działania

Wygląd głównego okna aplikacji przedstawia rys.12, a ikona programu ukazana jest na rys.13.

0x01 graphic

rys. 13

Okno to zawiera następujące pola:

Na rys.12 przedstawiono sytuację, gdy Ping wysłał pięć komunikatów ICMP o wielkości danych 100 bajtów, pod adres 198.105.232.1, z odstępem 100 msek. Wszystkie pakiety dotarły pod wskazany adres i powróciły komunikaty Echo Replay (0% zgubionych). Rys.14 przedstawia natomiast sytuację, kiedy użytkownik nie chciał, aby były wyświetlane informacje o poszczególnych komunikatach (0x01 graphic
). Podczas tej sesji jeden komunikat zaginął - powodem tego mogło być zmniejszenie odstępu do 10 msek.

0x01 graphic

rys. 14

5.3. Program Tracer

5.3.1. Funkcja

Funkcją programu Tracer jest określenie trasy przebiegu pakietów wysyłanych do określonej stacji docelowej. Dzięki temu można określić, w którym miejscu nastąpiła awaria, jeśli pakiety nie docierają do adresata.

Internet zbudowany jest z wielu sieci, które połączone są ze sobą za pomocą urządzeń nazywanych bramami (ang. gateway). Komputer wysyłający wiadomość, na podstawie adresu (identyfikator sieci), określa, czy stacja docelowa dołączona jest do tej samej sieci. Jeśli tak, to połączenie dokonywane jest w ramach sieci lokalnej. W przeciwnym razie pakiet IP kierowany jest do lokalnej bramy, która zawiera tablicę marszrutowania z adresami innych sieci Internetu. Zwykle tablica ta zawiera wiele kierunków połączeń, lecz jeśli nie ma w niej szukanej sieci, to wykorzystywana jest brama domyślna. W ten sposób następuje próba określenia położenia celu pakietu, który może znajdować się „gdzieś na świecie”. Aby tablice marszrutowania nie były zbyt wielkie, sieci (np. jednego państwa) zorganizowane są w domeny, które z kolei mają możliwości połączenia z innymi domenami.

5.3.2. Algorytm

Program Tracer, podobnie jak Ping, wykorzystuje w swoim działaniu kontrolny protokół Internetu ICMP. Wykorzystano tutaj właściwość przesyłania datagramów IP, polegającą na usuwaniu z sieci tych pakietów, których czas życia się skończył. Czas życia TTL określany jest dla datagramu w momencie startu w stacji źródłowej i zwykle ma on wartość 128. Po przejściu przez kolejną bramę, oddzielającą sieć od sieci, wartość ta zmniejszana jest o 1. Jeśli któraś brama stwierdzi, że TTL przetwarzanego pakietu ma wartość zero, zobowiązana jest go usunąć z obiegu. Jednocześnie brama ta powinna wysłać komunikat ICMP Time Exceed do stacji źródłowej pakietu. W komunikacie tym zawarty jest adres danej bramy. Dzięki temu mechanizmowi, ustawiając TTL startującego pakietu na kolejne wartości (począwszy od 1), można określić adresy wszystkich pośrednich bram na drodze pakietu.

Wysyłany datagram ma nagłówek UDP z pewnym, określonym numerem portu. Algorytm zakłada, że port ten nie jest obsługiwany przez protokół UDP na stacji docelowej. W przeciwnym razie pakiet, który dotrze do tej stacji, zostanie przez nią odebrany bez powiadomienia o osiągnięciu celu stacji źródłowej. Jeśli port nie będzie obsługiwany, to warstwa IP powinna wysłać komunikat ICMP Destination Unreachable z kodem Port Unreachable. Numer portu przeznaczenia może być swobodnie zmieniany w granicach od 1024 do 65535, małe jest więc prawdopodobieństwo obsługiwania danego portu przez stację docelową. Zazwyczaj wykorzystywane są numery portów poniżej 1024, są to tzw. ogólnie znane porty.

Algorytm programu Tracer przedstawia rys.15.

rys. 15

5.3.3. Implementacja

Program Tracer wykorzystuje dwa protokoły TCP/IP: datagramowy UDP oraz kontrolny ICMP. Protokół datagramowy służy do wysyłania pakietów pod wskazany przez użytkownika adres ze zmiennym czasem życia pakietu TTL. Obsługa komunikatów ICMP polega na odbieraniu wiadomości kontrolnych od bram pośrednich i stacji końcowej. Funkcja initTracer(), wywoływana na początku działania programu wykonuje następujące czynności:

Główną funkcją okna, wywoływaną przy okazji każdego zdarzenia z nim związanego, jest mainWinProc(). Gdy użytkownik naciśnie klawisz START, wykonywana jest funkcja startTracer(), a później sendProbe(). Funkcja startTracer() pobiera dane z pól edycyjnych i inicjalizuje zmienne potrzebne w bieżącej sesji Tracera: ttl=0, probe=0. Wykonuje też dwie ważne operacje na gniazdach

Funkcja sendProbe() sprawdza, czy wysłano już wszystkie komunikaty przeznaczone dla jednej bramy pośredniej (probeNum) i jeśli tak zeruje zmienną probe, a zmienna ttl jest inkrementowana. Czas TTL pakietu UDP ustalany jest na ttl za pomocą funkcji setsockopt(). Aktualna biblioteka Windows Sockets nie umożliwia niestety swobodnego dostępu do wszystkich pól nagłówka IP wysyłanego pakietu. Wykorzystanie funkcji setsockopt() wydaje się być jedyną możliwością dokonania zmiany czasu życia pakietu. Pakiet wysyłany jest za pomocą funkcji sendto(). Następnie ustawiany jest zegar na czas określony przez użytkownika: „waitTime” sekund. Po tym czasie przyjmuje się, że nie ma odpowiedzi od danej bramy lub stacji. Aby móc odczytać komunikat ICMP ustawiana jest pułapka na zdarzenie FD_READ gniazda socketIn: WM_SOCK_READ.

Po zakończeniu funkcji sendProbe() następuje oczekiwanie na przybycie komunikatu ICMP lub zadziałanie zegara. Gdy przybędzie komunikat ICMP, aplikacja otrzymuje zdarzenie WM_SOCK_READ i wywołuje funkcję recvProbe(). Funkcja ta wywołuje funkcję recvReplay(), określającą, czy komunikat ICMP przyszedł od bramy pośredniej, dla której skończył się czas życia TTL pakietu (typ=Time Exceed, kod=In Transmission), czy też od stacji końcowej (typ=Destination Unreachable). Wszystkie inne komunikaty ICMP są ignorowane. Gdy odebrano oczekiwany komunikat zatrzymywany jest zegar i drukowana jest odpowiednia informacja dla użytkownika: jeśli jest to pierwsza pozytywna próba, to odczytywany jest adres IP z nagłówka komunikatu ICMP bramy pośredniej; przedstawiany jest także czas transmisji. Adres IP przekształcany jest na nazwę DNS i, jeśli taka istnieje, jest wyświetlana.

Po odebraniu spodziewanego komunikatu ICMP lub upływie czasu oczekiwania na niego, wysyłany jest kolejny datagram UDP. Wysyłanie datagramów kończy się w czterech przypadkach:

Po odebraniu ostatniego komunikatu ICMP, wyświetlana jest informacja końcowa (rys.16) i Tracer gotowy jest do następnej sesji.

0x01 graphic

rys. 16

5.3.4. Opis działania

Okno dialogowe programu Tracer ilustruje rys.16. Na rys.17 przedstawiono ikonę programu.

0x01 graphic

rys. 17

Poszczególne pola okna dialogowego oznaczają:

0x01 graphic

rys. 18

Okno wyjściowe programu Tracer posiada wygodny suwak, którym należy się posługiwać, gdy informacje nie będą się w nim już mieściły (rys.18 i 19).

0x01 graphic

rys. 19

Rys.18 i rys.19 przedstawiają wyniki działania programu Tracer, który określał trasę do stacji: ftp.microsoft.com. Tracer określił, że po drodze do tej stacji jest 18 bram, część z nich ma swoje nazwy tekstowe, które zostały wyświetlone. Dla każdej bramy i stacji zażądano trzech prób i zazwyczaj tyle czasów transmisji widnieje przy każdym z adresów. Czasami jednak zdarzało się, że komunikat ICMP nie dotarł do Tracera - oznaczone jest to gwiazdką (bramy: 8, 10, 12, 14, 15,17, 18). Może to wynikać, ze znacznego obciążenia sieci.

Na rys.17 przedstawiono sytuację, kiedy Tracer z powodzeniem osiągnął stację info.nask.pl. Maksymalna dopuszczalna wartość TTL ustawiona była na 30, ale wystarczyło tylko 9 - 8 bram pośrednich i stacja docelowa. Zachowanie Tracera w przypadku zbyt małego maksymalnego czasu TTL ilustruje rys.20. ponieważ Max TTL = 5, po osiągnięciu piątej bramy Tracer zgłosił komunikat: „TRACER nie mógł osiągnąć stacji docelowej”.

0x01 graphic

rys. 20

5.4. Program Namer

5.4.1. Funkcja

Namer jest dodatkowym programem narzędziowym pozwalającym ustalić adres IP, jeśli jest znana nazwa DNS stacji lub odwrotnie: nazwę, gdy znany jest adres.

5.4.2. Algorytm

rys. 21

Algorytm działania programu Namer przedstawia rys.21. W zależności od operacji jakiej zażąda użytkownik, wykonywana jest funkcja gethostbyaddr() lub gethostbyname().

0x01 graphic

rys. 22

5.4.3. Implementacja

Funkcją sterującą jest mainWinProc(). Gdy użytkownik naciśnie klawisz 0x01 graphic
lub 0x01 graphic
(rys.22) funkcja ta otrzyma zdarzenie ID_IP_NAME lub ID_NAME_IP. Pierwsze zdarzenie oznacza chęć zamiany adresu na nazwę, a drugie operację odwrotną. W tym momencie wywoływana jest funkcja startNamer() z argumentem określającym rodzaj operacji. Jeśli zamieniany jest adres na nazwę, to wywoływana jest funkcja gethostbyaddr(). W przeciwnym razie: gethostbyname(). Obie funkcje zwracają wskaźnik do struktury hostent, zawierającej zarówno adres IP, jak i nazwę stacji. Jeśli odnalezienie adresu lub nazwy nie jest możliwe zwracana jest wartość NULL, a Namer wyświetla odpowiedni komunikat (rys.24 i rys.25).

5.4.4. Opis działania

Wygląd ekranu z uruchomionym programem Namer widnieje na rys.22, a jego ikona na rys.23.

0x01 graphic

rys. 23

Okno dialogowe programu zawiera następujące składniki:

0x01 graphic

rys. 24

W przypadku wpisania błędnego adresu IP lub adresu, który nie posiada nazwy DNS, wypisany zostanie komunikat jak na rys.24. Gdy natomiast użytkownik poda nazwę, z którą nie jest związany żaden adres IP, to program zareaguje tak, jak na rys.25.

0x01 graphic

rys. 25

Ponieważ oczekiwanie na określenie nazwy lub adresu (funkcje gethostbyaddr() i gethostbyname()) może trwać nawet kilka sekund, w tym czasie wyświetlany jest komunikat: 0x01 graphic
, a klawisze służące do zamiany są nieaktywne.

6. Wnioski

System operacyjny Windows 95 dość dobrze spełnia rolę systemu dla niezaawansowanej stacji roboczej. Zaimplementowano w nim obsługę wielu protokołów i zaoferowano użytkownikowi liczne usługi. Jednak ze względu na zawodność tego systemu nie powinien być on raczej stosowany w profesjonalnych zastosowaniach. Wydaje się, że w takich przypadkach dużo bezpieczniej jest zastosować system Windows NT.

Istniejące biblioteki oprogramowania dla systemu Windows 95, zarówno dla sieci lokalnych jak i rozległych bardzo dobrze spełniają swoją rolę podczas przesyłania wiadomości. Przedstawione w rozdziale 4 przykładowe programy, bazujące na funkcjach biblioteki Windows Sockets, są podstawą do budowy dużych programów wymagających komunikacji TCP/IP. Tego rodzaju programowanie nie powinno nastręczać żadnych trudności.

Jednakże biblioteki te bardzo dobrze oddzielają programistę od dostępu do warstwy niskiego poziomu. Może to być przydatne, aby zapobiec przypadkowej zmianie niektórych parametrów, co mogłoby spowodować nawet załamanie pracy systemu. Jednakże czasami dostęp niskiego poziomu jest niezbędny. Biblioteka Windows Sockets nie umożliwia ustawienia wszystkich pól w nagłówkach poszczególnych pakietów. Niedostępnych jest też wiele opcji znanych z wersji UNIX-owej gniazd. Niedoskonałości te nie są jednak zbyt znaczące w zwykłym programowaniu aplikacji transmisyjnych. Należy też mieć nadzieję, że zgodnie ze specyfikacją Windows Sockets, kolejne wersje bibliotek będą miały zaimplementowaną większą liczbę opcji TCP/IP.

Programy Ping, Tracer i Namer spełniają swoje założenia i są przydatnym dodatkiem do systemu sieciowego. Programy te mogą zostać rozwinięte w jedną dużą aplikację narzędziową. Można by nawet pokusić się o napisanie swoistego edytora IP - programu, który pozwalałby edytować pola datagramu IP, wysyłałby pakiet, a następnie oczekiwał odpowiedzi, zgodnie z zamierzeniami operatora. Pozwalałoby to na obsługę dowolnych protokołów i umożliwiało obserwację wielu różnych zjawisk w sieci.

7. Bibliografia

1. Hall M.:Windows Sockets 2 Application Programming Interface, 1996,

2. Help for Microsoft Developer Studio, SDK - Network Services, 1994-95,

3. Hunt C.: TCP/IP Administracja sieci, Oficyna Wydawnicza READ ME, Warszawa 1996,

4. Postel J.: User Datagram Protocol, RFC 768, 1980

5. Postel J.: Internet Protocol, RFC 791, 1981

6. Postel J.: Internet Control Message Protocol, RFC 792, 1981

7. Postel J.: Transmission Control Protocol, RFC 793, 1981

8. Sheldon T.: Wielka Encyklopedia Sieci Komputerowych, Wydawnictwo ROBOMATIC, Wrocław

9. Stevens W. R.: Programowanie zastosowań sieciowych w systemie UNIX, Wydawnictwa Naukowo-Techniczne, Warszawa 1995,

____________________________________________________________________________________

strona 55


Pobierz dokument
dyplom.instytut.informatyki.doc
Rozmiar 1.5 MB

Wyszukiwarka