Gniazdo Unix - Szybki przewodnik

Gniazda umożliwiają komunikację między dwoma różnymi procesami na tej samej lub różnych maszynach. Mówiąc dokładniej, jest to sposób na rozmowę z innymi komputerami przy użyciu standardowych deskryptorów plików Uniksa. W systemie Unix każda akcja we / wy jest wykonywana poprzez zapisanie lub odczytanie deskryptora pliku. Deskryptor pliku to po prostu liczba całkowita powiązana z otwartym plikiem i może to być połączenie sieciowe, plik tekstowy, terminal lub coś innego.

Dla programisty gniazdo wygląda i zachowuje się podobnie jak deskryptor pliku niskiego poziomu. Dzieje się tak, ponieważ polecenia takie jak read () i write () działają z gniazdami w taki sam sposób, jak z plikami i potokami.

Gniazda zostały po raz pierwszy wprowadzone w 2.1BSD, a następnie poprawione do ich obecnej postaci w 4.2BSD. Funkcja gniazd jest teraz dostępna w większości aktualnych wersji systemu UNIX.

Gdzie jest używane gniazdo?

Gniazdo Unix jest używane w strukturze aplikacji klient-serwer. Serwer to proces, który wykonuje pewne funkcje na żądanie klienta. Większość protokołów na poziomie aplikacji, takich jak FTP, SMTP i POP3, wykorzystuje gniazda do ustanawiania połączenia między klientem a serwerem, a następnie do wymiany danych.

Rodzaje gniazd

Użytkownicy mają do dyspozycji cztery rodzaje gniazd. Pierwsze dwa są najczęściej używane, a ostatnie dwa rzadko.

Zakłada się, że procesy komunikują się tylko między gniazdami tego samego typu, ale nie ma ograniczeń uniemożliwiających komunikację między gniazdami różnych typów.

  • Stream Sockets- Gwarantowana dostawa w środowisku sieciowym. Jeśli wyślesz przez gniazdo strumieniowe trzy elementy „A, B, C”, dotrą one w tej samej kolejności - „A, B, C”. Te gniazda używają protokołu TCP (Transmission Control Protocol) do transmisji danych. Jeśli doręczenie nie jest możliwe, nadawca otrzymuje informację o błędzie. Rekordy danych nie mają żadnych granic.

  • Datagram Sockets- Dostawa w środowisku sieciowym nie jest gwarantowana. Są bezpołączeniowe, ponieważ nie musisz mieć otwartego połączenia, jak w przypadku Stream Sockets - tworzysz pakiet z informacjami o miejscu docelowym i wysyłasz go. Używają UDP (User Datagram Protocol).

  • Raw Sockets- Zapewniają one użytkownikom dostęp do podstawowych protokołów komunikacyjnych, które obsługują abstrakcje gniazd. Gniazda te są zwykle zorientowane na datagram, chociaż ich dokładna charakterystyka zależy od interfejsu zapewnianego przez protokół. Surowe gniazda nie są przeznaczone dla zwykłego użytkownika; zostały one przeznaczone głównie dla osób zainteresowanych opracowaniem nowych protokołów komunikacyjnych lub uzyskaniem dostępu do niektórych z bardziej tajemniczych funkcji istniejącego protokołu.

  • Sequenced Packet Sockets- Są podobne do gniazda strumieniowego, z wyjątkiem tego, że granice rekordów są zachowywane. Ten interfejs jest dostarczany tylko jako część abstrakcji gniazd Network Systems (NS) i jest bardzo ważny w większości poważnych aplikacji NS. Gniazda pakietów sekwencyjnych pozwalają użytkownikowi manipulować nagłówkami Sequence Packet Protocol (SPP) lub Internet Datagram Protocol (IDP) w pakiecie lub grupie pakietów, albo przez napisanie nagłówka prototypu wraz z danymi, które mają być wysłane, lub przez określenie domyślnego nagłówka, który ma być używany ze wszystkimi wychodzącymi danymi i umożliwia użytkownikowi odbieranie nagłówków w przychodzących pakietach.

Co jest następne?

Następnych kilka rozdziałów ma na celu wzmocnienie twoich podstaw i przygotowanie podstaw przed pisaniem programów serwera i klienta przy użyciu gniazda . Jeśli chcesz bezpośrednio przejść, aby zobaczyć, jak napisać program klienta i serwera, możesz to zrobić, ale nie jest to zalecane. Zdecydowanie zaleca się, aby przejść krok po kroku i ukończyć te kilka początkowych rozdziałów, aby stworzyć bazę, zanim przejdziesz do programowania.

Zanim przejdziemy do faktycznych rzeczy, omówmy trochę o adresach sieciowych - adresie IP.

Adres hosta IP lub częściej po prostu adres IP służy do identyfikacji hostów podłączonych do Internetu. IP oznacza protokół internetowy i odnosi się do warstwy internetowej ogólnej architektury sieciowej Internetu.

Adres IP to 32-bitowa liczba interpretowana jako cztery 8-bitowe liczby lub oktety. Każdy adres IP jednoznacznie identyfikuje uczestniczącą sieć użytkownika, hosta w sieci i klasę sieci użytkownika.

Adres IP jest zwykle zapisywany w notacji dziesiętnej z kropkami w postaci N1.N2.N3.N4, gdzie każdy Ni jest liczbą dziesiętną z przedziału od 0 do 255 (szesnastkowo od 00 do FF).

Klasy adresowe

Adresami IP zarządza i tworzy Internet Assigned Numbers Authority (IANA). Istnieje pięć różnych klas adresów. Możesz określić, do której klasy należy adres IP, sprawdzając pierwsze cztery bity adresu IP.

  • Class A adresy zaczynają się od 0xxxlub 1 to 126 dziesiętny.

  • Class B adresy zaczynają się od 10xxlub 128 to 191 dziesiętny.

  • Class C adresy zaczynają się od 110xlub 192 to 223 dziesiętny.

  • Class D adresy zaczynają się od 1110lub 224 to 239 dziesiętny.

  • Class E adresy zaczynają się od 1111lub 240 to 254 dziesiętny.

Adresy zaczynające się od 01111111lub 127 dziesiętne, są zarezerwowane dla sprzężenia zwrotnego i wewnętrznego testowania na komputerze lokalnym [Możesz to sprawdzić: zawsze powinieneś być w stanie pingować 127.0.0.1, co wskazuje na ciebie]; Adresy klasy D są zarezerwowane dla multiemisji; Adresy klasy E są zarezerwowane do użytku w przyszłości. Nie powinny być używane jako adresy hostów.

Przykład

Class Leftmost bits Start address Finish address
ZA 0xxx 0.0.0.0 127.255.255.255
b 10xx 128.0.0.0 191.255.255.255
do 110x 192.0.0.0 223.255.255.255
re 1110 224.0.0.0 239.255.255.255
mi 1111 240.0.0.0 255.255.255.255

Podsieci

Podsieci lub podsieci zasadniczo oznaczają rozgałęzienie sieci. Można to zrobić z różnych powodów, takich jak sieć w organizacji, użycie różnych fizycznych nośników (takich jak Ethernet, FDDI, WAN itp.), Zachowanie przestrzeni adresowej i bezpieczeństwo. Najczęstszym powodem jest kontrolowanie ruchu sieciowego.

Podstawową ideą tworzenia podsieci jest podzielenie części adresu IP zawierającej identyfikator hosta na dwie części -

  • Adres podsieci w samym adresie sieciowym; i
  • Adres hosta w podsieci.

Na przykład powszechnym formatem adresu klasy B jest N1.N2.SH, gdzie N1.N2 oznacza sieć klasy B, 8-bitowe pole S określa podsieć, a 8-bitowe pole H identyfikuje hosta w podsieci.

Nazwy hostów w postaci liczb są trudne do zapamiętania, dlatego nazywane są zwykłymi nazwami, takimi jak Takshila lub Nalanda. Piszemy aplikacje, aby znaleźć kropkowany adres IP odpowiadający danej nazwie.

Proces znajdowania kropkowanego adresu IP na podstawie podanej alfanumerycznej nazwy hosta jest znany jako hostname resolution.

Rozpoznawanie nazwy hosta odbywa się za pomocą specjalnego oprogramowania znajdującego się w systemach o dużej pojemności. Systemy te nazywane są systemami nazw domen (DNS), które przechowują mapowanie adresów IP i odpowiadające im zwykłe nazwy.

Plik / etc / hosts

Korespondencja między nazwami hostów i adresami IP jest przechowywana w pliku o nazwie hosts . W większości systemów ten plik znajduje się w/etc informator.

Wpisy w tym pliku wyglądają następująco -

# This represents a comments in /etc/hosts file.
127.0.0.1       localhost
192.217.44.207  nalanda metro
153.110.31.18   netserve
153.110.31.19   mainserver centeral
153.110.31.20   samsonite
64.202.167.10   ns3.secureserver.net
64.202.167.97   ns4.secureserver.net
66.249.89.104   www.google.com
68.178.157.132  services.amrood.com

Należy pamiętać, że z danym adresem IP może być skojarzona więcej niż jedna nazwa. Ten plik jest używany podczas konwersji adresu IP na nazwę hosta i odwrotnie.

Nie miałbyś dostępu do edycji tego pliku, więc jeśli chcesz umieścić dowolną nazwę hosta wraz z adresem IP, musisz mieć uprawnienia roota.

Większość aplikacji sieciowych wykorzystuje architekturę klient-serwer, która odnosi się do dwóch procesów lub dwóch aplikacji, które komunikują się ze sobą w celu wymiany informacji. Jeden z dwóch procesów działa jako proces klienta, a inny proces działa jako serwer.

Proces klienta

Jest to proces, który zazwyczaj polega na żądaniu informacji. Po otrzymaniu odpowiedzi proces ten może się zakończyć lub wykonać inne przetwarzanie.

Example, Przeglądarka internetowa działa jako aplikacja kliencka, która wysyła żądanie do serwera sieci Web w celu uzyskania jednej strony internetowej w formacie HTML.

Proces serwera

Jest to proces, który przyjmuje żądanie od klientów. Po otrzymaniu żądania od klienta proces ten wykona wymagane przetwarzanie, zgromadzi żądane informacje i wyśle ​​je do klienta żądającego. Po zakończeniu staje się gotowy do obsługi innego klienta. Procesy serwera są zawsze czujne i gotowe do obsługi przychodzących żądań.

Example - Serwer sieciowy czeka na żądania z przeglądarek internetowych i gdy tylko otrzyma żądanie z przeglądarki, odbiera żądaną stronę HTML i wysyła ją z powrotem do tej przeglądarki.

Należy pamiętać, że klient musi znać adres serwera, ale serwer nie musi znać adresu ani nawet istnienia klienta przed ustanowieniem połączenia. Po ustanowieniu połączenia obie strony mogą wysyłać i odbierać informacje.

Architektury dwu- i trójwarstwowe

Istnieją dwa typy architektur klient-serwer -

  • 2-tier architecture- W tej architekturze klient współpracuje bezpośrednio z serwerem. Ten typ architektury może mieć pewne luki w zabezpieczeniach i problemy z wydajnością. Internet Explorer i Web Server działają w architekturze dwuwarstwowej. Tutaj problemy z bezpieczeństwem są rozwiązywane przy użyciu protokołu Secure Socket Layer (SSL).

  • 3-tier architectures- W tej architekturze jeszcze jedno oprogramowanie znajduje się pomiędzy klientem a serwerem. To oprogramowanie pośrednie nazywa się „oprogramowaniem pośrednim”. Oprogramowanie pośredniczące służy do wykonywania wszystkich kontroli bezpieczeństwa i równoważenia obciążenia w przypadku dużego obciążenia. Oprogramowanie pośredniczące pobiera wszystkie żądania od klienta i po wykonaniu wymaganego uwierzytelnienia przekazuje je do serwera. Następnie serwer wykonuje wymagane przetwarzanie i wysyła odpowiedź z powrotem do oprogramowania pośredniego, a na końcu oprogramowanie pośredniczące przekazuje tę odpowiedź z powrotem do klienta. Jeśli chcesz zaimplementować architekturę trójwarstwową, możesz zachować wszelkie oprogramowanie pośrednie, takie jak oprogramowanie Web Logic lub WebSphere, pomiędzy serwerem WWW a przeglądarką WWW.

Rodzaje serwerów

Istnieją dwa typy serwerów, które możesz mieć -

  • Iterative Server- Jest to najprostsza forma serwera, w której proces serwera obsługuje jednego klienta i po wykonaniu pierwszego żądania przyjmuje żądanie od innego klienta. Tymczasem kolejny klient czeka.

  • Concurrent Servers- Ten typ serwera uruchamia wiele współbieżnych procesów, aby obsłużyć wiele żądań jednocześnie, ponieważ jeden proces może trwać dłużej, a inny klient nie może czekać tak długo. Najprostszym sposobem napisania współbieżnego serwera pod Uniksem jest rozwidlenie procesu potomnego do obsługi każdego klienta osobno.

Jak zrobić klienta

System wywołań ustanowienia połączenia jest nieco inny dla klienta i serwera, ale oba obejmują podstawową konstrukcję gniazda. Oba procesy tworzą własne gniazda.

Kroki związane z ustanowieniem gniazda po stronie klienta są następujące -

  • Utwórz gniazdo z socket() wywołanie systemowe.

  • Podłącz gniazdo do adresu serwera za pomocą rozszerzenia connect() wywołanie systemowe.

  • Wysyłaj i odbieraj dane. Można to zrobić na wiele sposobów, ale najprostszym jest użycie rozszerzeniaread() i write() wywołania systemowe.

Jak stworzyć serwer

Kroki związane z ustanowieniem gniazda po stronie serwera są następujące -

  • Utwórz gniazdo z socket() wywołanie systemowe.

  • Powiąż gniazdo z adresem przy użyciu rozszerzenia bind()wywołanie systemowe. W przypadku gniazda serwera w Internecie adres składa się z numeru portu na komputerze hosta.

  • Nasłuchuj połączeń z listen() wywołanie systemowe.

  • Zaakceptuj połączenie z accept()wywołanie systemowe. To wywołanie zazwyczaj blokuje połączenie, dopóki klient nie połączy się z serwerem.

  • Wysyłaj i odbieraj dane za pomocą read() i write() wywołania systemowe.

Interakcja klienta i serwera

Poniżej znajduje się diagram przedstawiający pełną interakcję klienta i serwera -

Różne struktury są używane w programowaniu gniazd Unix do przechowywania informacji o adresie i porcie oraz innych informacji. Większość funkcji gniazd wymaga jako argumentu wskaźnika do struktury adresu gniazda. Struktury zdefiniowane w tym rozdziale odnoszą się do rodziny protokołów internetowych.

sockaddr

Pierwsza struktura to sockaddr, która przechowuje informacje o gnieździe -

struct sockaddr {
   unsigned short   sa_family;
   char             sa_data[14];
};

Jest to ogólna struktura adresu gniazda, która będzie przekazywana w większości wywołań funkcji gniazda. Poniższa tabela zawiera opis pól członków -

Atrybut Wartości Opis
sa_family

AF_INET

AF_UNIX

AF_NS

AF_IMPLINK

Reprezentuje rodzinę adresów. W większości aplikacji internetowych używamy AF_INET.
sa_data Adres zależny od protokołu Zawartość 14 bajtów adresu specyficznego dla protokołu jest interpretowana zgodnie z typem adresu. W przypadku rodziny internetowej użyjemy adresu IP numeru portu, który jest reprezentowany przez strukturę sockaddr_in zdefiniowaną poniżej.

sockaddr in

Druga struktura, która pomaga w odwołaniu się do elementów gniazda, jest następująca -

struct sockaddr_in {
   short int            sin_family;
   unsigned short int   sin_port;
   struct in_addr       sin_addr;
   unsigned char        sin_zero[8];
};

Oto opis pól członkowskich -

Atrybut Wartości Opis
sa_family

AF_INET

AF_UNIX

AF_NS

AF_IMPLINK

Reprezentuje rodzinę adresów. W większości aplikacji internetowych używamy AF_INET.
sin_port Port serwisowy 16-bitowy numer portu w sieciowej kolejności bajtów.
sin_addr Adres IP 32-bitowy adres IP w sieciowej kolejności bajtów.
sin_zero Nieużywany Po prostu ustaw tę wartość na NULL, ponieważ nie jest ona używana.

in addr

Ta struktura jest używana tylko w powyższej strukturze jako pole struktury i zawiera 32-bitowy identyfikator netid / hostid.

struct in_addr {
   unsigned long s_addr;
};

Oto opis pól członkowskich -

Atrybut Wartości Opis
s_addr port serwisowy 32-bitowy adres IP w sieciowej kolejności bajtów.

hostent

Ta struktura służy do przechowywania informacji związanych z hostem.

struct hostent {
   char *h_name; 
   char **h_aliases; 
   int h_addrtype;  
   int h_length;    
   char **h_addr_list
	
#define h_addr  h_addr_list[0]
};

Oto opis pól członkowskich -

Atrybut Wartości Opis
h_name ti.com itp. Jest to oficjalna nazwa gospodarza. Na przykład tutorialspoint.com, google.com itp.
h_aliases TI Zawiera listę aliasów nazw hostów.
h_addrtype AF_INET Zawiera rodzinę adresów, aw przypadku aplikacji internetowych zawsze będzie to AF_INET.
h_length 4 Przechowuje długość adresu IP, która wynosi 4 dla adresu internetowego.
h_addr_list in_addr W przypadku adresów internetowych tablica wskaźników h_addr_list [0], h_addr_list [1] itd. Wskazuje na strukturę in_addr.

NOTE - h_addr jest zdefiniowany jako h_addr_list [0], aby zachować wsteczną kompatybilność.

servent

Ta szczególna struktura służy do przechowywania informacji związanych z usługą i powiązanymi portami.

struct servent {
   char  *s_name; 
   char  **s_aliases; 
   int   s_port;  
   char  *s_proto;
};

Oto opis pól członkowskich -

Atrybut Wartości Opis
s_name http To jest oficjalna nazwa serwisu. Na przykład SMTP, FTP POP3 itp.
s_aliases ALIAS Zawiera listę aliasów usług. W większości przypadków będzie to NULL.
sport 80 Będzie miał powiązany numer portu. Na przykład dla HTTP będzie to 80.
s_proto

TCP

UDP

Jest ustawiony na używany protokół. Usługi internetowe są świadczone przy użyciu protokołu TCP lub UDP.

Wskazówki dotyczące struktur gniazd

Struktury adresów gniazd są integralną częścią każdego programu sieciowego. Przydzielamy je, wypełniamy i przekazujemy do nich wskaźniki do różnych funkcji gniazd. Czasami przekazujemy wskaźnik do jednej z tych struktur do funkcji gniazda i wypełnia ona zawartość.

Zawsze przekazujemy te struktury przez odniesienie (tj. Przekazujemy wskaźnik do struktury, a nie samą strukturę) i zawsze przekazujemy rozmiar struktury jako kolejny argument.

Gdy funkcja gniazda wypełnia strukturę, długość jest również przekazywana przez odwołanie, dzięki czemu jej wartość może być aktualizowana przez funkcję. Nazywamy te argumenty wartość-wynik.

Zawsze ustawiaj zmienne struktury na NULL (tj. '\ 0') za pomocą memset () dla funkcji bzero (), w przeciwnym razie może dojść do nieoczekiwanych wartości śmieci w Twojej strukturze.

Gdy proces klienta chce połączyć się z serwerem, klient musi mieć możliwość zidentyfikowania serwera, z którym chce się połączyć. Jeśli klient zna 32-bitowy adres internetowy hosta, na którym znajduje się serwer, może skontaktować się z tym hostem. Ale w jaki sposób klient identyfikuje konkretny proces serwera działający na tym hoście?

Aby rozwiązać problem z identyfikacją konkretnego procesu serwera działającego na hoście, w protokołach TCP i UDP zdefiniowano grupę dobrze znanych portów.

W naszym celu port zostanie zdefiniowany jako liczba całkowita z zakresu od 1024 do 65535. Dzieje się tak, ponieważ wszystkie numery portów mniejsze niż 1024 są uważane za dobrze znane - na przykład telnet używa portu 23, http używa 80, ftp używa 21, i tak dalej.

Przypisania portów do usług sieciowych można znaleźć w pliku / etc / services. Jeśli piszesz na własnym serwerze, musisz zadbać o przypisanie portu do swojego serwera. Powinieneś upewnić się, że ten port nie powinien być przypisany do żadnego innego serwera.

Zwykle praktyką jest przypisywanie dowolnego portu o numerze większym niż 5000. Ale jest wiele organizacji, które napisały serwery o numerach portów powyżej 5000. Na przykład Yahoo Messenger działa na 5050, SIP Server na 5060 itd.

Przykładowe porty i usługi

Oto mała lista usług i powiązanych portów. Najbardziej aktualną listę portów internetowych i powiązanych usług można znaleźć na stronie IANA - TCP / IP Port Assignments .

Service Port Number Service Description
Echo 7 UDP / TCP odsyła to, co otrzymuje.
odrzucać 9 UDP / TCP odrzuca dane wejściowe.
dzień 13 UDP / TCP zwraca czas ASCII.
ładować 19 UDP / TCP zwraca znaki.
ftp 21 Transfer plików TCP.
telnet 23 Zdalne logowanie TCP.
smtp 25 E-mail TCP.
dzień 37 UDP / TCP zwraca czas binarny.
tftp 69 Prosty transfer plików UDP.
palec 79 Informacje TCP o użytkownikach.
http 80 Sieć TCP w sieci WWW.
Zaloguj sie 513 Zdalne logowanie TCP.
WHO 513 UDP różne informacje o użytkownikach.
Xserver 6000 Okna TCP X (NB> 1023).

Funkcje portów i usług

Unix udostępnia następujące funkcje do pobierania nazwy usługi z pliku / etc / services.

  • struct servent *getservbyname(char *name, char *proto) - To wywołanie przyjmuje nazwę usługi i nazwę protokołu oraz zwraca odpowiedni numer portu dla tej usługi.

  • struct servent *getservbyport(int port, char *proto) - To wywołanie przyjmuje numer portu i nazwę protokołu oraz zwraca odpowiednią nazwę usługi.

Wartość zwracana dla każdej funkcji jest wskaźnikiem do struktury o następującej postaci -

struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
};

Oto opis pól członkowskich -

Atrybut Wartości Opis
s_name http To jest oficjalna nazwa serwisu. Na przykład SMTP, FTP POP3 itp.
s_aliases ALIAS Zawiera listę aliasów usług. W większości przypadków będzie ustawiony na NULL.
sport 80 Będzie miał powiązany numer portu. Na przykład dla HTTP będzie to 80.
s_proto

TCP

UDP

Jest ustawiony na używany protokół. Usługi internetowe są świadczone przy użyciu protokołu TCP lub UDP.

Niestety nie wszystkie komputery przechowują bajty zawierające wartość wielobajtową w tej samej kolejności. Rozważmy 16-bitowy internet, który składa się z 2 bajtów. Istnieją dwa sposoby przechowywania tej wartości.

  • Little Endian - W tym schemacie młodszy bajt jest przechowywany pod adresem początkowym (A), a najstarszy bajt jest przechowywany pod następnym adresem (A + 1).

  • Big Endian - W tym schemacie najstarszy bajt jest przechowywany pod adresem początkowym (A), a najmniejszy bajt jest przechowywany pod następnym adresem (A + 1).

Aby umożliwić komunikację między komputerami o różnych konwencjach kolejności bajtów, protokoły internetowe określają kanoniczną konwencję kolejności bajtów dla danych przesyłanych w sieci. Jest to znane jako kolejność bajtów sieci.

Podczas ustanawiania połączenia internetowego przez gniazdo należy upewnić się, że dane w elementach sin_port i sin_addr struktury sockaddr_in są reprezentowane w sieciowej kolejności bajtów.

Funkcje porządkowania bajtów

Procedury konwersji danych między wewnętrzną reprezentacją hosta a kolejnością bajtów sieci są następujące -

Funkcjonować Opis
htons () Host do sieci Short
htonl () Host do sieci długo
ntohl () Sieć do hostowania długo
ntohs () Sieć do hostowania Short

Poniżej znajduje się więcej szczegółów na temat tych funkcji -

  • unsigned short htons(unsigned short hostshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.

  • unsigned long htonl(unsigned long hostlong) - Ta funkcja konwertuje wielkości 32-bitowe (4-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.

  • unsigned short ntohs(unsigned short netshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z sieciowej kolejności bajtów na kolejność bajtów hosta.

  • unsigned long ntohl(unsigned long netlong) - Ta funkcja konwertuje ilości 32-bitowe z kolejności bajtów sieci na kolejność bajtów hosta.

Te funkcje są makrami i powodują wstawienie kodu źródłowego konwersji do programu wywołującego. Na maszynach little-endian kod zmieni wartości na sieciową kolejność bajtów. Na maszynach typu big-endian żaden kod nie jest wstawiany, ponieważ nie jest potrzebny; funkcje są zdefiniowane jako null.

Program do określania kolejności bajtów hosta

Zachowaj następujący kod w pliku byteorder.c, a następnie skompiluj go i uruchom na swoim komputerze.

W tym przykładzie przechowujemy dwubajtową wartość 0x0102 w krótkiej liczbie całkowitej, a następnie patrzymy na dwa kolejne bajty, c [0] (adres A) ic [1] (adres A + 1), aby określić bajt zamówienie.

#include <stdio.h>

int main(int argc, char **argv) {

   union {
      short s;
      char c[sizeof(short)];
   }un;
	
   un.s = 0x0102;
   
   if (sizeof(short) == 2) {
      if (un.c[0] == 1 && un.c[1] == 2)
         printf("big-endian\n");
      
      else if (un.c[0] == 2 && un.c[1] == 1)
         printf("little-endian\n");
      
      else
         printf("unknown\n");
   }
   else {
      printf("sizeof(short) = %d\n", sizeof(short));
   }
	
   exit(0);
}

Dane wyjściowe generowane przez ten program na komputerze z procesorem Pentium są następujące -

$> gcc byteorder.c $> ./a.out
little-endian
$>

Unix udostępnia różne wywołania funkcji, które pomagają w manipulowaniu adresami IP. Funkcje te konwertują adresy internetowe między łańcuchami ASCII (preferowanymi przez ludzi) a wartościami binarnymi uporządkowanymi według bajtów sieci (wartościami przechowywanymi w strukturach adresów gniazda).

Następujące trzy wywołania funkcji są używane do adresowania IPv4 -

  • int inet_aton (const char * strptr, struct in_addr * addrptr)
  • in_addr_t inet_addr (const char * strptr)
  • char * inet_ntoa (struct in_addr inaddr)

int inet_aton (const char * strptr, struct in_addr * addrptr)

To wywołanie funkcji konwertuje określony ciąg w standardowej notacji kropkowej w Internecie na adres sieciowy i przechowuje adres w podanej strukturze. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 1, jeśli ciąg był prawidłowy i 0 w przypadku błędu.

Poniżej znajduje się przykład użycia -

#include <arpa/inet.h>

(...)

   int retval;
   struct in_addr addrptr
   
   memset(&addrptr, '\0', sizeof(addrptr));
   retval = inet_aton("68.178.157.132", &addrptr);

(...)

in_addr_t inet_addr (const char * strptr)

To wywołanie funkcji konwertuje określony ciąg w standardowej notacji z kropkami w Internecie na liczbę całkowitą odpowiednią do użycia jako adres internetowy. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 32-bitowy binarny adres sieciowy uporządkowany w bajtach IPv4 i INADDR_NONE w przypadku błędu.

Poniżej znajduje się przykład użycia -

#include <arpa/inet.h>

(...)

   struct sockaddr_in dest;

   memset(&dest, '\0', sizeof(dest));
   dest.sin_addr.s_addr = inet_addr("68.178.157.132");
   
(...)

char * inet_ntoa (struct in_addr inaddr)

To wywołanie funkcji konwertuje określony adres hosta internetowego na ciąg znaków w standardowej notacji kropkowej.

Poniżej znajduje się przykład użycia -

#include <arpa/inet.h>

(...)

   char *ip;
   
   ip = inet_ntoa(dest.sin_addr);
   
   printf("IP Address is: %s\n",ip);
   
(...)

W tym rozdziale opisano podstawowe funkcje gniazda wymagane do napisania kompletnego klienta i serwera TCP.

Poniższy diagram przedstawia pełną interakcję klienta i serwera -

Funkcja gniazda

Aby wykonać operacje we / wy sieci, pierwszą rzeczą, którą musi wykonać proces, jest wywołanie funkcji gniazda, określenie typu żądanego protokołu komunikacyjnego i rodziny protokołów itp.

#include <sys/types.h>
#include <sys/socket.h>

int socket (int family, int type, int protocol);

To wywołanie zwraca deskryptor gniazda, którego można użyć w późniejszych wywołaniach systemowych lub -1 w przypadku błędu.

Parametry

family - Określa rodzinę protokołów i jest jedną ze stałych pokazanych poniżej -

Rodzina Opis
AF_INET Protokoły IPv4
AF_INET6 Protokoły IPv6
AF_LOCAL Protokoły domeny Unix
AF_ROUTE Gniazda routingu
AF_KEY Gniazdo Ket

W tym rozdziale nie omówiono innych protokołów z wyjątkiem IPv4.

type- Określa rodzaj gniazda, które chcesz. Może przyjąć jedną z następujących wartości -

Rodzaj Opis
SOCK_STREAM Gniazdo strumieniowe
SOCK_DGRAM Gniazdo datagramowe
SOCK_SEQPACKET Sekwencyjne gniazdo pakietów
SOCK_RAW Gniazdo surowe

protocol - Argument powinien być ustawiony na konkretny typ protokołu podany poniżej lub 0, aby wybrać domyślny system dla danej kombinacji rodziny i typu -

Protokół Opis
IPPROTO_TCP Protokół transportowy TCP
IPPROTO_UDP Protokół transportowy UDP
IPPROTO_SCTP Protokół transportowy SCTP

Connect Function

Funkcja connect jest używana przez klienta TCP do nawiązywania połączenia z serwerem TCP.

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

To wywołanie zwraca 0, jeśli pomyślnie łączy się z serwerem, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • serv_addr - Jest to wskaźnik do struct sockaddr, który zawiera docelowy adres IP i port.

  • addrlen - Ustaw na sizeof (struct sockaddr).

Wiążą Function

Funkcja bind przypisuje adres protokołu lokalnego do gniazda. W przypadku protokołów internetowych adres protokołu to kombinacja 32-bitowego adresu IPv4 lub 128-bitowego adresu IPv6 wraz z 16-bitowym numerem portu TCP lub UDP. Ta funkcja jest wywoływana tylko przez serwer TCP.

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr,int addrlen);

To wywołanie zwraca 0, jeśli pomyślnie połączy się z adresem, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • my_addr - Jest to wskaźnik do struct sockaddr, który zawiera lokalny adres IP i port.

  • addrlen - Ustaw na sizeof (struct sockaddr).

Możesz automatycznie umieścić swój adres IP i port

Wartość 0 dla numeru portu oznacza, że ​​system wybierze losowy port, a wartość INADDR_ANY adresu IP oznacza, że ​​adres IP serwera zostanie przypisany automatycznie.

server.sin_port = 0;  		     
server.sin_addr.s_addr = INADDR_ANY;

NOTE- Wszystkie porty poniżej 1024 są zarezerwowane. Możesz ustawić port powyżej 1024 i poniżej 65535, chyba że są one używane przez inne programy.

Słuchać funkcji

Funkcja nasłuchiwania jest wywoływana tylko przez serwer TCP i wykonuje dwie czynności -

  • Funkcja Listen przekształca niepołączone gniazdo w gniazdo pasywne, wskazując, że jądro powinno akceptować przychodzące żądania połączeń kierowane do tego gniazda.

  • Drugi argument tej funkcji określa maksymalną liczbę połączeń, jakie jądro powinno kolejkować dla tego gniazda.

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd,int backlog);

To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • backlog - Jest to liczba dozwolonych połączeń.

Przyjąć funkcji

Funkcja accept jest wywoływana przez serwer TCP w celu zwrócenia następnego ukończonego połączenia z początku kolejki zakończonych połączeń. Podpis wezwania jest następujący -

#include <sys/types.h>
#include <sys/socket.h>

int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

To wywołanie zwraca nieujemny deskryptor w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu. Przyjmuje się, że zwrócony deskryptor jest deskryptorem gniazda klienta i wszystkie operacje odczytu i zapisu będą wykonywane na tym deskryptorze w celu komunikacji z klientem.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • cliaddr - Jest to wskaźnik do struct sockaddr, który zawiera adres IP i port klienta.

  • addrlen - Ustaw na sizeof (struct sockaddr).

Wyślij Function

Funkcja wysyłania służy do wysyłania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. Jeśli chcesz wysyłać dane przez NIEPODŁĄCZONE gniazda datagramowe, musisz użyć funkcji sendto ().

Możesz użyć wywołania systemowego write () do wysyłania danych. Jego podpis jest następujący -

int send(int sockfd, const void *msg, int len, int flags);

To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwróci -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • msg - Jest to wskaźnik do danych, które chcesz wysłać.

  • len - Jest to długość danych, które chcesz wysłać (w bajtach).

  • flags - Jest ustawiony na 0.

Recv Function

Funkcja recv służy do odbierania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. Jeśli chcesz otrzymywać dane przez NIEPODŁĄCZONE gniazda datagramowe, musisz użyć funkcji recvfrom ().

Do odczytania danych można użyć wywołania systemowego read () . To wywołanie jest wyjaśnione w rozdziale o funkcjach pomocniczych.

int recv(int sockfd, void *buf, int len, unsigned int flags);

To wywołanie zwraca liczbę bajtów odczytanych do bufora, w przeciwnym razie zwróci -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • buf - Jest to bufor do wczytywania informacji.

  • len - Jest to maksymalna długość bufora.

  • flags - Jest ustawiony na 0.

Sendto Function

Funkcja sendto służy do wysyłania danych przez NIEPODŁĄCZONE gniazda datagramowe. Jego podpis jest następujący -

int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);

To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • msg - Jest to wskaźnik do danych, które chcesz wysłać.

  • len - Jest to długość danych, które chcesz wysłać (w bajtach).

  • flags - Jest ustawiony na 0.

  • to - Jest to wskaźnik struct sockaddr dla hosta, do którego mają zostać wysłane dane.

  • tolen - Jest ustawiony na sizeof (struct sockaddr).

Recvfrom Function

Funkcja recvfrom służy do odbierania danych z gniazd datagramowych UNCONNECTED.

int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);

To wywołanie zwraca liczbę bajtów wczytanych do bufora, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • buf - Jest to bufor do wczytywania informacji.

  • len - Jest to maksymalna długość bufora.

  • flags - Jest ustawiony na 0.

  • from - Jest to wskaźnik struct sockaddr dla hosta, z którego mają zostać odczytane dane.

  • fromlen - Jest ustawiony na sizeof (struct sockaddr).

Blisko Function

Funkcja close służy do zamykania komunikacji między klientem a serwerem. Jego składnia jest następująca -

int close( int sockfd );

To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

Wyłączanie funkcji

Funkcja zamykania służy do bezpiecznego zamykania komunikacji między klientem a serwerem. Ta funkcja zapewnia większą kontrolę w porównaniu z funkcją zamykania . Poniżej podano składnię shutdown -

int shutdown(int sockfd, int how);

To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • how - Wpisz jedną z liczb -

    • 0 - wskazuje, że odbieranie jest niedozwolone,

    • 1 - wskazuje, że wysyłanie jest niedozwolone i

    • 2- wskazuje, że zarówno wysyłanie, jak i odbieranie są niedozwolone. Gdy jak jest ustawione na 2, jest to to samo, co close ().

Select Function

Funkcja select wskazuje, który z określonych deskryptorów plików jest gotowy do odczytu, gotowy do zapisu lub ma oczekujący stan błędu.

Gdy aplikacja wywołuje recv lub recvfrom , jest blokowana do momentu nadejścia danych do tego gniazda. Aplikacja może wykonywać inne przydatne operacje, gdy strumień danych przychodzących jest pusty. Inna sytuacja ma miejsce, gdy aplikacja otrzymuje dane z wielu gniazd.

Wywołanie recv lub recvfrom w gnieździe, które nie ma danych w swojej kolejce wejściowej, zapobiega natychmiastowemu otrzymywaniu danych z innych gniazd. Wywołanie funkcji select rozwiązuje ten problem, umożliwiając programowi odpytywanie wszystkich uchwytów gniazd w celu sprawdzenia, czy są one dostępne dla nieblokujących operacji odczytu i zapisu.

Poniżej podano składnię select -

int select(int  nfds, fd_set  *readfds, fd_set  *writefds, fd_set *errorfds, struct timeval *timeout);

To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.

Parametry

  • nfds- Określa zakres deskryptorów plików do przetestowania. Funkcja select () testuje deskryptory plików w zakresie od 0 do nfds-1

  • readfds- Wskazuje na obiekt typu fd_set, który na wejściu określa deskryptory plików, które mają być sprawdzane pod kątem gotowości do odczytu, a na wyjściu wskazuje, które deskryptory plików są gotowe do odczytu. Może mieć wartość NULL, aby wskazać pusty zestaw.

  • writefds- Wskazuje na obiekt typu fd_set, który na wejściu określa deskryptory plików do sprawdzenia pod kątem gotowości do zapisu, a na wyjściu wskazuje, które deskryptory plików są gotowe do zapisu. Może mieć wartość NULL, aby wskazać pusty zestaw.

  • exceptfds- Wskazuje na obiekt typu fd_set, który na wejściu określa deskryptory plików, które mają być sprawdzane pod kątem oczekujących warunków błędów, a na wyjściu wskazuje, które deskryptory plików mają oczekujące warunki błędu. Może mieć wartość NULL, aby wskazać pusty zestaw.

  • timeout- Wskazuje na strukturę timeval, która określa, jak długo wywołanie select powinno odpytywać deskryptory pod kątem dostępnej operacji we / wy. Jeśli wartość limitu czasu wynosi 0, funkcja select natychmiast zwróci. Jeśli argument timeout ma wartość NULL, select będzie blokować, dopóki co najmniej jeden uchwyt pliku / gniazda nie będzie gotowy na dostępną operację we / wy. W przeciwnym razie opcja select zwróci wartość po upływie określonego czasu LUB gdy co najmniej jeden deskryptor pliku / gniazda jest gotowy do operacji we / wy.

Wartość zwracana z funkcji select to liczba uchwytów określonych w zestawach deskryptorów plików, które są gotowe do operacji we / wy. Jeśli zostanie osiągnięty limit czasu określony przez pole limitu czasu, wybierz opcję powrotu 0. Istnieją następujące makra służące do manipulowania zestawem deskryptorów plików -

  • FD_CLR(fd, &fdset)- Czyści bit dla deskryptora pliku fd w zestawie deskryptorów pliku fdset.

  • FD_ISSET(fd, &fdset)- Zwraca niezerową wartość, jeśli bit deskryptora pliku fd jest ustawiony w deskryptorze pliku wskazywanym przez fdset , a 0 w przeciwnym razie.

  • FD_SET(fd, &fdset) - Ustawia bit dla deskryptora pliku fd w zestawie deskryptorów pliku fdset.

  • FD_ZERO(&fdset) - Inicjuje zestaw deskryptorów plików fdset tak, aby miał zero bitów dla wszystkich deskryptorów plików.

Zachowanie tych makr jest niezdefiniowane, jeśli argument fd jest mniejszy niż 0 lub większy lub równy FD_SETSIZE.

Przykład

fd_set fds;

struct timeval tv;

/* do socket initialization etc.
tv.tv_sec = 1;
tv.tv_usec = 500000;

/* tv now represents 1.5 seconds */
FD_ZERO(&fds);

/* adds sock to the file descriptor set */
FD_SET(sock, &fds); 

/* wait 1.5 seconds for any data to be read from any single socket */
select(sock+1, &fds, NULL, NULL, &tv);

if (FD_ISSET(sock, &fds)) {
   recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
   /* do something */
}
else {
   /* do something else */
}

Ten rozdział opisuje wszystkie funkcje pomocnicze, które są używane podczas programowania gniazd. Inne funkcje pomocnicze opisano w rozdziałach -Ports and Servicesi sieć Byte Orders.

Zapisu Funkcja

Funkcja write próbuje zapisać n-bajtów bajtów z bufora wskazanego przez buf do pliku skojarzonego z deskryptorem otwartego pliku, fildes .

Możesz także użyć funkcji send (), aby wysłać dane do innego procesu.

#include <unistd.h>

int write(int fildes, const void *buf, int nbyte);

Po pomyślnym zakończeniu write () zwraca liczbę bajtów faktycznie zapisanych w pliku powiązanym z fildami. Ta liczba nigdy nie jest większa niż nbajtów. W przeciwnym razie zwracane jest -1.

Parametry

  • fildes - Jest to deskryptor gniazda zwracany przez funkcję gniazda.

  • buf - Jest to wskaźnik do danych, które chcesz wysłać.

  • nbyte- Jest to liczba bajtów do zapisania. Jeśli nbyte jest równe 0, write () zwróci 0 i nie da żadnych innych wyników, jeśli plik jest zwykłym plikiem; w przeciwnym razie wyniki są nieokreślone.

Odczytu Funkcja

The read function attempts to read nbyte bytes from the file associated with the buffer, fildes, into the buffer pointed to by buf.

You can also use recv() function to read data to another process.

#include <unistd.h>

int read(int fildes, const void *buf, int nbyte);

Upon successful completion, write() returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.

Parameters

  • fildes − It is a socket descriptor returned by the socket function.

  • buf − It is the buffer to read the information into.

  • nbyte − It is the number of bytes to read.

The fork Function

The fork function creates a new process. The new process called the child process will be an exact copy of the calling process (parent process). The child process inherits many attributes from the parent process.

#include <sys/types.h>
#include <unistd.h>

int fork(void);

Upon successful completion, fork() returns 0 to the child process and the process ID of the child process to the parent process. Otherwise -1 is returned to the parent process, no child process is created and errno is set to indicate the error.

Parameters

  • void − It means no parameter is required.

The bzero Function

The bzero function places nbyte null bytes in the string s. This function is used to set all the socket structures with null values.

void bzero(void *s, int nbyte);

This function does not return anything.

Parameters

  • s − It specifies the string which has to be filled with null bytes. This will be a point to socket structure variable.

  • nbyte − It specifies the number of bytes to be filled with null values. This will be the size of the socket structure.

The bcmp Function

The bcmp function compares byte string s1 against byte string s2. Both strings are assumed to be nbyte bytes long.

int bcmp(const void *s1, const void *s2, int nbyte);

This function returns 0 if both strings are identical, 1 otherwise. The bcmp() function always returns 0 when nbyte is 0.

Parameters

  • s1 − It specifies the first string to be compared.

  • s2 − It specifies the second string to be compared.

  • nbyte − It specifies the number of bytes to be compared.

The bcopy Function

The bcopy function copies nbyte bytes from string s1 to the string s2. Overlapping strings are handled correctly.

void bcopy(const void *s1, void *s2, int nbyte);

Ta funkcja nic nie zwraca.

Parametry

  • s1 - Określa ciąg źródłowy.

  • s2v - Określa ciąg docelowy.

  • nbyte - Określa liczbę bajtów do skopiowania.

Memset Function

Funkcja memset służy również do ustawiania zmiennych strukturalnych w taki sam sposób, jakbzero. Spójrz na jego składnię, podaną poniżej.

void *memset(void *s, int c, int nbyte);

Ta funkcja zwraca wskaźnik do void; w rzeczywistości jest to wskaźnik do pamięci zestawu i musisz go odpowiednio skasować.

Parametry

  • s - Określa źródło do ustawienia.

  • c - Określa znak do ustawienia w miejscach n-bajtowych.

  • nbyte - Określa liczbę bajtów do ustawienia.

Aby proces stał się serwerem TCP, musisz wykonać kroki podane poniżej -

  • Utwórz gniazdo za pomocą funkcji systemowej socket () .

  • Powiąż gniazdo z adresem za pomocą wywołania systemowego bind () . W przypadku gniazda serwera w Internecie adres składa się z numeru portu na komputerze hosta.

  • Nasłuchuj połączeń przy użyciu wywołania systemowego Listen () .

  • Zaakceptuj połączenie za pomocą wywołania systemowego accept () . To wywołanie jest zwykle blokowane, dopóki klient nie połączy się z serwerem.

  • Wysyłaj i odbieraj dane za pomocą wywołań systemowych read () i write () .

Teraz umieśćmy te kroki w postaci kodu źródłowego. Umieść ten kod w pliku server.c i skompiluj go za pomocą kompilatora gcc .

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int  n;
   
   /* First call to socket() function */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
   
   /* Initialize socket structure */
   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = 5001;
   
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY;
   serv_addr.sin_port = htons(portno);
   
   /* Now bind the host address using bind() call.*/
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR on binding");
      exit(1);
   }
      
   /* Now start listening for the clients, here process will
      * go in sleep mode and will wait for the incoming connection
   */
   
   listen(sockfd,5);
   clilen = sizeof(cli_addr);
   
   /* Accept actual connection from the client */
   newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
	
   if (newsockfd < 0) {
      perror("ERROR on accept");
      exit(1);
   }
   
   /* If connection is established then start communicating */
   bzero(buffer,256);
   n = read( newsockfd,buffer,255 );
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
   
   printf("Here is the message: %s\n",buffer);
   
   /* Write a response to the client */
   n = write(newsockfd,"I got your message",18);
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
      
   return 0;
}

Obsługa wielu połączeń

Aby umożliwić serwerowi obsługę wielu jednoczesnych połączeń, wprowadzamy następujące zmiany w powyższym kodzie -

  • Umieść instrukcję accept i następujący kod w nieskończonej pętli.

  • Po nawiązaniu połączenia wywołaj fork (), aby utworzyć nowy proces.

  • Proces potomny zamknie sockfd i wywoła funkcję doprocessingu , przekazując jako argument nowy deskryptor pliku gniazda. Gdy dwa procesy zakończą konwersację, na co wskazuje zwracanie funkcji doprocessing () , proces ten po prostu kończy się.

  • Proces nadrzędny zamyka newsockfd . Ponieważ cały ten kod jest w nieskończonej pętli, powróci do instrukcji accept, aby czekać na następne połączenie.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

void doprocessing (int sock);

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int n, pid;
   
   /* First call to socket() function */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
   
   /* Initialize socket structure */
   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = 5001;
   
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY;
   serv_addr.sin_port = htons(portno);
   
   /* Now bind the host address using bind() call.*/
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR on binding");
      exit(1);
   }
   
   /* Now start listening for the clients, here
      * process will go in sleep mode and will wait
      * for the incoming connection
   */
   
   listen(sockfd,5);
   clilen = sizeof(cli_addr);
   
   while (1) {
      newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
		
      if (newsockfd < 0) {
         perror("ERROR on accept");
         exit(1);
      }
      
      /* Create child process */
      pid = fork();
		
      if (pid < 0) {
         perror("ERROR on fork");
         exit(1);
      }
      
      if (pid == 0) {
         /* This is the client process */
         close(sockfd);
         doprocessing(newsockfd);
         exit(0);
      }
      else {
         close(newsockfd);
      }
		
   } /* end of while */
}

Poniższy fragment kodu przedstawia prostą implementację funkcji doprocesowania .

void doprocessing (int sock) {
   int n;
   char buffer[256];
   bzero(buffer,256);
   n = read(sock,buffer,255);
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
   
   printf("Here is the message: %s\n",buffer);
   n = write(sock,"I got your message",18);
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
	
}

Aby uczynić proces klientem TCP, musisz wykonać kroki podane poniżej & minus;

  • Utwórz gniazdo za pomocą funkcji systemowej socket () .

  • Połącz gniazdo z adresem serwera za pomocą wywołania systemowego connect () .

  • Wysyłaj i odbieraj dane. Można to zrobić na wiele sposobów, ale najprostszym sposobem jest użycie wywołań systemowych read () i write () .

Teraz umieśćmy te kroki w postaci kodu źródłowego. Umieść ten kod w plikuclient.c i skompiluj go z gcc kompilator.

Uruchom ten program i podaj nazwę hosta i numer portu serwera, aby połączyć się z serwerem, który musisz już uruchomić w innym oknie Uniksa.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

int main(int argc, char *argv[]) {
   int sockfd, portno, n;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   
   char buffer[256];
   
   if (argc < 3) {
      fprintf(stderr,"usage %s hostname port\n", argv[0]);
      exit(0);
   }
	
   portno = atoi(argv[2]);
   
   /* Create a socket point */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
	
   server = gethostbyname(argv[1]);
   
   if (server == NULL) {
      fprintf(stderr,"ERROR, no such host\n");
      exit(0);
   }
   
   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
   serv_addr.sin_port = htons(portno);
   
   /* Now connect to the server */
   if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR connecting");
      exit(1);
   }
   
   /* Now ask for a message from the user, this message
      * will be read by server
   */
	
   printf("Please enter the message: ");
   bzero(buffer,256);
   fgets(buffer,255,stdin);
   
   /* Send message to the server */
   n = write(sockfd, buffer, strlen(buffer));
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
   
   /* Now read server response */
   bzero(buffer,256);
   n = read(sockfd, buffer, 255);
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
	
   printf("%s\n",buffer);
   return 0;
}

Oto lista wszystkich funkcji związanych z programowaniem gniazd.

Funkcje portów i usług

Unix udostępnia następujące funkcje do pobierania nazwy usługi z pliku / etc / services.

  • struct servent *getservbyname(char *name, char *proto) - To wywołanie przyjmuje nazwę usługi i nazwę protokołu oraz zwraca odpowiedni numer portu dla tej usługi.

  • struct servent *getservbyport(int port, char *proto) - To wywołanie pobiera numer portu i nazwę protokołu oraz zwraca odpowiednią nazwę usługi.

Funkcje porządkowania bajtów

  • unsigned short htons (unsigned short hostshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.

  • unsigned long htonl (unsigned long hostlong) - Ta funkcja konwertuje wielkości 32-bitowe (4-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.

  • unsigned short ntohs (unsigned short netshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z sieciowej kolejności bajtów na kolejność bajtów hosta.

  • unsigned long ntohl (unsigned long netlong) - Ta funkcja konwertuje ilości 32-bitowe z kolejności bajtów sieci na kolejność bajtów hosta.

Funkcje adresu IP

  • int inet_aton (const char *strptr, struct in_addr *addrptr)- To wywołanie funkcji konwertuje określony ciąg w standardowej notacji internetowej z kropką na adres sieciowy i zapisuje adres w podanej strukturze. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 1, jeśli ciąg jest prawidłowy i 0 w przypadku błędu.

  • in_addr_t inet_addr (const char *strptr)- To wywołanie funkcji konwertuje określony ciąg w standardowej notacji z kropkami w Internecie na wartość całkowitą, która może być używana jako adres internetowy. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 32-bitowy binarny adres sieciowy uporządkowany w bajtach IPv4 i INADDR_NONE w przypadku błędu.

  • char *inet_ntoa (struct in_addr inaddr) - To wywołanie funkcji konwertuje określony adres hosta internetowego na ciąg znaków w standardowej notacji kropkowej.

Funkcje rdzenia gniazda

  • int socket (int family, int type, int protocol) - To wywołanie zwraca deskryptor gniazda, którego możesz użyć w późniejszych wywołaniach systemowych lub daje -1 w przypadku błędu.

  • int connect (int sockfd, struct sockaddr *serv_addr, int addrlen)- Funkcja connect jest używana przez klienta TCP do nawiązywania połączenia z serwerem TCP. To wywołanie zwraca 0, jeśli pomyślnie łączy się z serwerem, w przeciwnym razie zwraca -1.

  • int bind(int sockfd, struct sockaddr *my_addr,int addrlen)- Funkcja bind przypisuje adres protokołu lokalnego do gniazda. To wywołanie zwraca 0, jeśli pomyślnie połączy się z adresem, w przeciwnym razie zwraca -1.

  • int listen(int sockfd, int backlog)- Funkcja nasłuchiwania jest wywoływana tylko przez serwer TCP w celu nasłuchiwania żądania klienta. To wywołanie zwraca 0 po pomyślnym zakończeniu, w przeciwnym razie zwraca -1.

  • int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)- Funkcja akceptacji jest wywoływana przez serwer TCP w celu akceptowania żądań klientów i ustanowienia rzeczywistego połączenia. To wywołanie po pomyślnym zakończeniu zwraca nieujemny deskryptor, w przeciwnym razie zwraca -1.

  • int send(int sockfd, const void *msg, int len, int flags)- Funkcja wysyłania służy do wysyłania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwraca -1.

  • int recv (int sockfd, void *buf, int len, unsigned int flags)- Funkcja recv służy do odbierania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. To wywołanie zwraca liczbę bajtów wczytanych do bufora, w przeciwnym razie zwraca -1 w przypadku błędu.

  • int sendto (int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen)- Funkcja sendto służy do wysyłania danych przez NIEPODŁĄCZONE gniazda datagramowe. To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwraca -1 w przypadku błędu.

  • int recvfrom (int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen)- Funkcja recvfrom służy do odbierania danych z gniazd datagramowych UNCONNECTED. To wywołanie zwraca liczbę bajtów wczytanych do bufora, w przeciwnym razie zwraca -1 w przypadku błędu.

  • int close (int sockfd)- Funkcja close służy do zamykania komunikacji między klientem a serwerem. To wywołanie zwraca 0 po pomyślnym zakończeniu, w przeciwnym razie zwraca -1.

  • int shutdown (int sockfd, int how)- Funkcja zamykania służy do bezpiecznego zamykania komunikacji między klientem a serwerem. Ta funkcja zapewnia większą kontrolę w porównaniu z funkcją zamykania. Po pomyślnym zakończeniu zwraca 0, w przeciwnym razie -1.

  • int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) - Ta funkcja służy do odczytu lub zapisu wielu gniazd.

Funkcje pomocnicze gniazda

  • int write (int fildes, const void *buf, int nbyte)- Funkcja zapisu próbuje zapisać n-bajtów bajtów z bufora wskazywanego przez buf do pliku skojarzonego z deskryptorem otwartego pliku, fildes. Po pomyślnym zakończeniu write () zwraca liczbę bajtów faktycznie zapisanych w pliku powiązanym z fildami. Ta liczba nigdy nie jest większa niż nbajtów. W przeciwnym razie zwracane jest -1.

  • int read (int fildes, const void *buf, int nbyte)- Funkcja odczytu próbuje odczytać n-bajtowych bajtów z pliku skojarzonego z deskryptorem otwartego pliku, fildes, do bufora wskazywanego przez buf. Po pomyślnym zakończeniu write () zwraca liczbę bajtów faktycznie zapisanych w pliku powiązanym z fildami. Ta liczba nigdy nie jest większa niż nbajtów. W przeciwnym razie zwracane jest -1.

  • int fork (void)- Funkcja widelca tworzy nowy proces. Nowy proces, zwany procesem potomnym, będzie dokładną kopią procesu wywołującego (proces nadrzędny).

  • void bzero (void *s, int nbyte)- Funkcja bzero umieszcza nbyte bajtów null w łańcuchu s. Ta funkcja zostanie użyta do ustawienia wszystkich struktur gniazd na wartości null.

  • int bcmp (const void *s1, const void *s2, int nbyte)- Funkcja bcmp porównuje łańcuch bajtów s1 z łańcuchem bajtów s2. Zakłada się, że oba łańcuchy mają długość n-bajtów.

  • void bcopy (const void *s1, void *s2, int nbyte)- Funkcja bcopy kopiuje n-bajtów bajtów z łańcucha s1 do łańcucha s2. Nakładające się ciągi są obsługiwane poprawnie.

  • void *memset(void *s, int c, int nbyte) - Funkcja memset jest również używana do ustawiania zmiennych strukturalnych w taki sam sposób jak bzero.