Słyszysz cały czas gadkę o "gniazdach", i zapewne zastanawiasz się co one tak właściwie znaczą. Więc, są one sposobem rozmowy z innymi programami używając Unixowych deskryptorów plików.
Co?
Dobra, mogłeś słyszeć kilku hackerów Unixa mówiących "Woooooow, wszystko w Uniksie jest plikiem!". To o czym mówiła ta osoba jest fakt, że kiedy Uniksowe programy wykonują operacje I/O, wykonują je poprzez czytanie lub zapisywanie do dekryptora pliku. Deksryptor pliku jest po prostu liczbą (integer) przypisaną do otwartego pliku. Ale (i tu jest haczyk), ten plik może być połączeniem sieciowym, FIFO, pipem, terminalem, lub prawdziwym plikiem na dysku, lub wszystkim innym. W Uniksie wszystko jest plikiem! Więc gdy chcesz komunikować się z innym programem poprzez Internet zrobisz to za pomocą deskryptora pliku, lepiej uwierz w to.
"Skąd wziąć ten deskryptor pliku dla komunikacji sieciowej, Panie Mądralo?" jest pewnie ostatnim pytaniem chodzącym Ci teraz po głowie, ale zamierzam na nie odpowiedzieć właśnie teraz: Wywołujesz funckje socket(). Zwraca ona deskryptor gniazda, i komunikujesz się używając specjalnej funkcji send() oraz recv() (
man send,
man recv).
"Ale skoro jest to deksryptor pliku, to dlaczego nie mogę używać po prostu read() i write() do komunikacji przez gniazdo?" Krótką odpowiedzią jest "Możesz!". Dłuższą jest "Możesz, ale send() i recv() dają Ci większą kontrolę nad transmisją danych."
Co dalej? Może to: jest kilka rodzajów gniazd. Są: Internetowe adresy DARPA (gniazda internetowe), ścieżki na lokalnym systemie (gniazda Unixowe), adresy CCITT X.25 (gniazda X.25, które możesz normalnie spokojnie zignorować), i prawdopodobnie inne... Ten dokument "bawi się" tylko tymi pierwszymi: gniazdami Internetowymi.
Dwa typy gniazd internetowych
Co to jest? Są dwa typy gniazd internetowych? Tak. No, nie. Kłamię. Jest ich więcej, ale nie chcę was przestraszyć. Zamierzam tylko powiedzieć, że "surowe gniazda" są również potężne i powinieneś się nimi zainteresować.
No dobra, co to za dwa typy gniazd internetowych? Jeden to "gniazdo strumieniowe"; drugi "gniazdo datagramowe", których nazwami są odpowiednio "SOCK_STREAM" i "SOCK_DGRAM". Gniazda datagramowe są czasami nazywane "gniazdami bezpołączeniowymi". (Mimo, że mogą one być połączone za pomocą connect() jeśli naprawdę tego chcesz. Spójrz na
connect() poniżej.)
Gniazda strumieniowe są godnym zaufania dwukierunkowym połączeniem strumieniowym. Jeśli wyślesz dwa znaki do gniazda w kolejności "1, 2" dotrą one w kolejności "1, 2" na drugim końcu kabla. Będą również bezbłędne. Wszelkie błędy, których doświadczysz są Twoimi wyimaginowanymi błędami i nie będą tutaj poruszane.
Przez co są używane gniazda strumieniowe? Mogłeś słyszeć o programie telnet... On używa gniazd strumieniowych. Wszystkie znaki, które wpisujesz muszą dotrzeć w tej samej kolejności w jakiej je wpisywałeś, prawda? Przeglądarki internetowe używają protokołu HTTP, który to korzysta z gniazd strumieniowych aby pobrać strony. Możesz to sprawdzić używając telneta podając jako adres adres strony i port 80 i wpisując "GET /" - zwróci to HTMLa strony głównej.
Jak gniazda strumieniowe osiągają tak wysoki poziom jakości transmisji danych? Używają protokołu zwanego "The Transmission Control Protocol" (protokół kontroli transmisji), zwanego również w skrócie "TCP" (patrz
RFC-793 po bardzo szczegółowy opis TCP). TCP martwi się, żeby dane docierały sekwencyjnie i bezblędne. Prawdopodobnie słyszałes "TCP" wcześniej jako lepszą połowę "TCP/IP", gdzie "IP" to "Internet Protocol" (protokół Internetowy) (patrz
RFC-791). IP zajmuje się głównie routingiem i nie jest generalnie odpowiedzialne za integralność danych.
Fajnie. A co z gniazdami datagramowymi? Dlaczego są nazywane bezpołączeniowymi? Dlaczego nie są godne zaufania? Masz tu kilka faktów: jeśli wysyłasz datagram, może on dotrzeć. Może też dotrzeć za późno (jeśli w ogóle dotrze). Jeśli dotrze, dane zawarte w pakiecie będą bezbłędne.
Gniazda datagramowe również korzystają z IP dla routingu, ale nie wykorzystują TCP; wykorzystują "User Datagram Protocol" (datagramowy protokół użytkownika?) - w skrócie "UDP" (patrz
RFC-768).
Dlaczego są bezpołączeniowe? Dlatego, że nie musisz utrzymywać otwartego połączenia tak jak to robisz w przypadku gniazd strumieniowych. Po prostu budujesz pakiet, doklejasz nagłówek IP z informacją o celu i wysyłasz. Połączenie nie jest potrzebne. Zazwyczaj są wykorzystywane do transmisji informacji pakiet-za-pakietem. Prosty przykład: tftp, bootp,itp.
"Wystarczy!" możesz krzyknąć. "Jak w takim razie te programy mogą działać, skoro datagramy mogą się zgubić?!" No cóż, mój ludzki kumplu, każdy z nich ma swój protokół nad UDP. Na przykład protokół tftp mówi, że dla każdego wysłanego pakietu, odbiorca musi odesłać pakiet mówiący "mam go!" (pakiet "ACK"). Jeśli nadawca nie otrzymuje odpowiedzi w ciągu, powiedzmy pięciu sekund, ponownie wysyła pakiet aź w końcu otrzyma ACK. Taka procedura informowania jest bardzo ważna w przypadku implementowania programów korzystających z SOCK_DGRAM.
Niskopoziomowy nonsense a teoria sieci
Po wspomnieniu wartsw protokołów, przyszedł czas na powiedzenie jak sieci naprawdę działają, i jak pakiety SOCK_DGRAM są zbudowane. Praktycznie możesz ominąć tą sekcję. Jednakże jest to dobra podstawa.
Rysunek 1. Enkapsulacja danych.
TODO: wstawić rysunek, jeżeli się gdzieś go znajdzie |
Hej dzieciaki, przyszedł czas na naukę o enkapsulacji danych! Jest to bardzo ważne. Jest to tak ważne, żeby możesz się tego uczyć jeśli będziesz uczęszczał na kurs sieciowy na Chico State ;-). A więc: pakiet się rodzi, pakiet jest owijany (przetwarzany) w nagłównku (czasami w stopce) przez pierwszy protokół (powiedzmy TFTP), następnie całość (nagłówek TFTP) jest przetwarzana przez następny protokół (powiedzmy UDP), potem znowu to samo tyle, że przez następny protokół (IP), i znowu przez ostatni protokół na warstwie sieciowej (fizycznej) (powiedzmy Ethernet).
Gdy inny komputer odbiera pakiet, sprzęt wycina nagłówek Ethernet, jądro wycina nagłówki IP i UDP, program TFTP wycina nagłówek TFTP, i w końcu ma dane.
Mogę w końcu powiedzieć o niesławnym "Layered Network Model" (wasrtwowy model sieci). Ten model opisuje system funkcjonowania sieci, który ma wiele zalet w stosunku do innych modeli. Na przydkład, możesz pisać program wykorzystujące gniazda, które są zawsze takie same (programy) bez zamartwiania się jak dane są fizycznie przesyłane (serial, Ethernet, AUI, cokolwiek), ponieważ programy na niższych poziomach zajmują się tym za Ciebie. Prawdziwe urządzenia sieciowe jak i topologia sieci są niewidoczne dla programisty gniazd.
Bez żadnego dalszego ględzenia zaprezentuję warstwy tego modelu sieci:
Warstwa fizyczna jest sprzętem (serial, Ethernet...). Warstwa aplikacji jest tak daleka od warstwy fizycznej jak tylko to sobie możesz wyobrazić--jest to miejsce, gdzie użytkownicy działają przez sieć.
Ten model jest tak uogólniony, że prawdopodobnie mógłbyś go użyć do naprawy swojego samochodu gdybyś tylko naprawdę tego chciał. Warstwowy model bardziej związany z Uniksem mógłby wyglądać taj:
W tym momencie możesz zobaczyć jak te warstwy współpracują ze sobą aby przetworzyć dane.
Widzisz ile jest roboty przy budowaniu prostego pakietu? Jeejku! I ty to wszystko musisz wpisać do nagłówka pakietu używając "cat"! To był żart. Wszystko co musisz zrobić przy gniazdach strumieniowych to wysłać (send()) dane. Wszystko co musisz zrobić przy gniazdach datagramowych to enkapsulować pakiet wybraną metodą i wysłać go (sendto()). Jądro zbuduję warstwę transportową i internetową za ciebie, a sprzęt doda warstwę dostępu do sieci. Ach, dzisiejsza technologia.
I tak się kończy nasze krótkie wprowadzenie w teorię sieci. Aha, zapomniałem Ci powiedzieć wszystkiego co chciałem powiedzieć o rutingu: nic! Dokładnie, nie zamierzam o tym mówić. Ruter wycina pakiet z nagłówka IP, sprawdza tabelę rutingu, bla bla bla. Sprawdź
IP RFC jeśli Ci naprawdę na tym zależy. Nawet jeśli się tego nie nauczysz, przeżyjesz.