10. týždeň

Networking

Ciele

  1. Pripraviť si prostredie pre prácu na cvičení
  2. Porozumieť soketom
  3. Porozumieť službe bind
  4. Porozumieť službe listen
  5. Porozumieť službe accept
  6. Porozumieť službe connect
  7. Naučiť sa zasielať, odosielať správy a zrušiť deskriptor súboru
  8. Pochopiť aplikáciu poznatkov v praxi

Postup

Krok 1: Úvod

Príprava

Pre prácu na cvičení si stiahnite súbor k cvičeniu.

Obsah súboru:

.
└── 10
    ├──u1_1.c
    ├──u1_2.c
    └──Makefile

Poznámka

Programy je možné kompilovať pomocou make nazov_suboru.c Napríklad: make u1_1.c

Krok 2: Sokety

Model Klient - Server

Komunikačný mechanizmu socket je obojsmernou komunikačnou technológiou. Umožňuje komunikáciu medzi procesmi. Možno sním pracovať ako so súborom, má pridelený vlastný jedinečný deskriptor.

Jedným zo základných modelov pre komunikáciu medzi procesmi prostredníctvom socketov je model klient–server.Tento model je založený na existencii dvoch typov procesov:procesu-servera a procesu-klienta. Proces-servervykonáva pasívnu úlohu - čaká na požiadavky od klientských procesov, ktorýmposkytuje nejakú „službu“. Klienti, ktorí spolupracujú s jedným typom servera, môžu byť rôzneho typu a môžu sa navzájom líšiť používateľským prostredím.

Komunikačný systém
IP adresa sieťová adresa stroja, pomocou nej vedia komunikovať sieťové zariadenia
Port celočíselný identifikátor komunikujúceho procesu, na ktorom sú vybavované požiadavky procesov

Protokol TCP/IP pozostáva zo skupiny protokolov, z ktorých pre prácu so socketmi na štvrtej vrstve TCP/IP modelu sa využívajú protokoly uvedené v nasledujúcej tabuľke.

TCP a UDP

Rozdiel medzi TCP a UDP
Obr. 1: Rozdiel medzi TCP a UDP

Na strane procesu server (protokol TCP) musíme na rozdiel od procesu klient priradiť socketu adresu (službou bind()). Potom musíme vytvoriť front do ktorého sa budú ukladať požiadavky na spojenie (službou listen()). Požiadavky na spojenie musíme z frontu vyberať postupne (služba accept()). Ak vo fronte nie je žiadna požiadavka na spojenie, proces server počká (bude uspatý), kým nejaká požiadavka nedôjde. Služba accept() nám vráti nový socket, pomocou ktorého budeme komunikovať s procesom-klientom, ktorý sa pripája na proces server systémovým volaním connect().

Úloha 2.1

Pre podrobnejšie vysvetlenie príznakov modelu Klient - Server navštívte: wiki klient-server

Uveďte názov protokolu v rámci sieťového protokolu IPv4 pre potvrdzované doručovanie paketov.

Správna odpoveď: TCP
Nesprávna odpoveď

Krok 3: Vytvorenie záverečného bodu komunikácie

Služba jadra socket()

Služba socket() vytvára záverečné body komunikácie.

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

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

Parametre služby socket:

  1. Parameter domain určuje spôsob adresácie komunikačných uzlov. Môžme to prirovnať k výberu sôsobu komunikácie medzi ľudmi (telefón, list, e-mail, skype).
Domény Popis
PF_UNIX Lokálna komunikácia
PF_INET IPv4 internetové protokoly
PF_INET6 IPv6 internetové protokoly
PF_IPX IPX-Nowell protokoly
PF_APPLETALK Appletalk DDP
  1. Parameter type určuje typ socketu. Vieme ho prirovnať k službám mobilného operátora (SMS, hovory, internet, MMS).
Možné hodnoty
SOCK_STREAM spojená transportná slúžba, využíva sa vtedy ake chceme najprv vytvoriť spojenie medzi socketmi
SOCK_DGRAM nespojovaná slúžba, nie je žiadna záruka že správa bude doručená
  1. Parameter protocol. Pre naše potreby bude tento parameter nastavený na nulu, čo znamená použitie defaultného protokolu. Ak budeme chcieť vytvoriť socket pre spojovo orientovanú komunikáciu, použijeme službu socket() s týmito parametrami:
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
Návratová hodnota
v prípade úspešného vykonania vráti socket
v prípade neúspešného vykonania vráti -1

Aplikácia služby v programe:

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

int main(){ 
    int socket = socket(PF_INET, SOCK_STREAM,0);

    //vytvorenie socketu
    if(socket == -1){
        perror("socket"); 
    }

    //kontrola sluzby socket()
    else{
        printf("Socket s id %d bol vytvoreny.\n",socket);
    }

    return 0;
}
Opis kódu:

Predchadzajúci fragment kódu vytvorí socket pre spojovanú komunikáciu s defaultne nastaveným protokolom.

Výpis z programu má vyzerať následovne:

Socket s id .. bol vytvorený.

Úloha 3.1

Čo ste mali namiesto ..?

Úloha 3.2

Pre podrobnejšie vysvetlenie služby socket navštívte: man socket

Aký parameter sa využíva vtedy ak chceme najprv vytvoriť spojenie medzi socketmi?

Správna odpoveď: SOCK_STREAM
Nesprávna odpoveď

Uveďte hodnotu prvého parametra služby pre vytvorenie koncového komunikačného bodu pre komunikáciu protokolom IPv4 (symbolická konštanta z manualovej stránky)

Správna odpoveď: PF_INET
Nesprávna odpoveď

Krok 4: Priradenie IP adresy

Služba jadra bind()

Služba bind() zviaže resp. priradí socketu IP adresu a port. Po zviazaní môžeme socket využívať na komunikáciu medzi procesmi vrámci počítačovej siete.

#include <sys/socket.h>

int bind (int socket, struct sockaddr *address, int address_len);
Parametre služby
socket špecifikuje socket(pomocou deskriptora), ktorý má byť zviazaný s adresou
address ukazuje na sockaddr štruktúru, formát ktorý je požadovaným správaním socketu
address_len určuje dľžku štruktúry sockaddr, určenú parametrom address

Poznámka k parametrom: deskriptor sme získali pomocou služby socket() štruktúra sockaddr sa skladá zo štruktúr sockaddr a sockaddr_um

Návratová hodnota
v prípade úspešného vykonania vráti 0
v prípade neúspešného vykonania vráti -1

Opis štruktúry sockaddr:

Proces potrebuje na nadviazanie spojenia socket, IP adresu stroja a portu ktorý je na nej určený pre pripájanie. Adresa je súčasťou štruktúry:

struct sockaddr {        
    unsigned short sa_family;   
    char sa_data[14];    
}; 
Premenné štruktúry Vysvetlenie
unsigned short sa_family typ komunikačného socketu AF nie PF
char sa_data[14] 14 bytov protokolovej adresy

Položka štruktúry sa_data[14] obsahuje cieľovú adresu a číslo portu pre daný socket. Pre IPv4 budeme používať odvodenú štruktúru sockaddr_in, ktorá je definovaná v hlavičkovom súbore v tvare:

struct sockaddr_in {        
    short int sin_family;   
    unsigned short int sin_port;   
    struct in_addr sin_addr;           
    unsigned char sin_zero[8];   
}; 
Premenné štruktúry Vysvetlenie
short int sin_family typ komunikačného socketu AF nie PF
unsigned short int sin_port 2 byty cislo portu
struct in_addr sin_addr 4 byty actual IP address
unsigned char sin_zero[8] zvysnych 8 bytov nulujeme

Príklad použitia

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MYPORT 3490    //port, na ktory sa budu uzivatelia pripajat

int main() {
    int s;  
    struct sockaddr_in my_addr;    /* struktura obasahujuca informacie o mojej adrese*/
    int sin_size;

    if ((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");          //vytvorenie socketu
        exit(1);
        } 
    my_addr.sin_family = AF_INET; //naplnenie struktury sockaddr_in         
    my_addr.sin_port = htons(MYPORT);     
    my_addr.sin_addr.s_addr = INADDR_ANY; 
    bzero(&(my_addr.sin_zero), 8);        

    //zviazanie socketu sluzbou bind()
   if(bind(s,(struct sockaddr *)&my_addr, sizeof(struct sockaddr))==-1){
        perror("bind");
        exit(1);
    }
}

Poznámka

Pomocou služby bind() je možné zviazať socket s IP adresou a portom

Opis kódu

Najprv bol vytvorený socket pre spojovanú komunikáciu s defaultne nastaveným protokolom pomocou služby socket()

Aby sa mohla zviazať IP adresa a port s službou bind(), musí sa najprv naplniť štruktúra sockaddr_in

Všetky príkazy začínajúce my_addr slúžia na napľňanie štruktúry sockaddr_in.

Funkcia htons() zabezpečí transformáciu endianity počítača na endianitu siete. Funkcia bzero() nám doplní reťazec my_addr.sin_zero o 8 núl.

Rozlíšenie adresy

V prípade, že máme meno uzla (host), ktorý sa má podieľať na komunikácii, jeho IP adresu zistíme pomocou funkcie gethostbyname(char hostname), ktorá vracia smerník na štruktúru hostent, ktorej definícia je:

struct hostent {
       char*   h_name;      //oficialne meno hostu    
       char**  h_aliases;   //smernik na zoznam aliasov (iných mien)    
       int     h_addrtype;  //typ adresy    
       int     h_length;    //dlzka adresy    
       char**  h_addr_list; //smernik na zoznam adries ak ich ma viac    
  };
Postupnosť bajtov

Ak chceme získať IP adresu v čitateľnom tvare, použijeme funkciu inet_ntoa()

-htons() - krátke celé čísla z hostovej postupnosti do sieťovej (pre porty)
-ntohs() – krátke celé čísla zo sieťovej do hostovej postupnosti (pre porty)
-htonl() – dlhé celé čísla z hostovej do sieťovej postupnosti (pre IP adresy)
-ntohl() – dlhé celé čísla zo sieťovej do hostovej postupnosti (pre IP adresy)

Poznámka

Formovanie adresy pre internetové protokoly sa uskutočnuje pomocou štruktúry sockaddr_in.

Úloha 4.1

Pre podrobnejšie vysvetlenie navštívte man 2 bind

Otázky:

Uveďte názov služby jadra, ktorou priradíme adresu koncovému komunikačnému bodu v rámci zvoleného protokolu.

Správna odpoveď: bind

Uveďte názov zložky/prvku zodpovedajúceho záznamu (štruktúry) pre nastavenie portu koncového komunikačného bodu v rámci protokolu IP (viď 'man 7 ip'; len názov zložky/prvku, bez názvu záznamu)

Správna odpoveď: sin_port

Krok 5: Vytvorenie vyrovnávajúcej pamäti pre pripojenie

Služba jadra listen()

Služba listen() vytvára vyrovnávajúcu pamäť pre uchovávanie požiadaviek o pripojenie. Ak je front plný a nejaký klient sa pokúsi pripojiť, bude spojenie odmietnuté.

#include <sys/socket.h>

int listen (int socket, int backlog);
Parametre
socket určuje jedinečný identifikátor socketu vytvorený službou socket() s adresou priradenou službou bind()
backlog určuje maximálne množstvo simultárnych požiadaviek na spojenie

Poznámka k parametru backlog: Horný limit je špecifikovaný konštantou SOMAXCONN v hlavičkovom súbore <sys/socket.h>. Hodnota parametra backlog je nastavená štandardne na hodnotu 5.

Návratová hodnota
v prípade úspešného vykonania vracia 0
v prípade neúspešného vykonania vráti -1

Úloha 5.1

Pre podrobnejšie vysvetlenie navštívte man 2 listen

Otázky:

Uveďte názov parametra zodpovedajúcej služby jadra, ktorý špecifikuje počet požiadaviek, ktoré budú zaznamenané (archivované) v štruktúrach jadra OS počas obsluhy/spracovania aktuálnej požiadavky (podľa manuálovej stránky).

Správna odpoveď: backlog
Nesprávna odpoveď

Uveďte názov služby jadra, ktorou 'spasívnime' koncový komunikačný bod aby čakal na prichádzajúce spojenia.

Správna odpoveď: listen
Nesprávna odpoveď

Krok 6: Výber požiadaviek

Služba jadra accept()

Služba jadra accept() využíva v procese server pre výber požiadaviek z fronty čakajúcich na spojenie a potvrdí ju. Pre každé prijaté spojenie sa vytvorí nový socket. Potom cez tento nový socket prebieha komunikácia. Ak nie sú ďalšie požiadavky na spojenie (front je prázdny), tak služba accept() uspí proces pokiaľ nie je prítomná ďalšia požiadavka na spojenie.

#include <sys/socket.h>

int accept(int socket, struct sockaddr *restrict addr, socklen_t *restrict len);
Parametre
socket určuje socket, ktorý bol vytvorený službou socket(), bol zviazaný s adresou a má vytvorený backlog službou listen()
address ukazuje na sockaddr štruktúru ktorá obsahuje IP adresu a port klientskeho procesu. Jej formát je určený doménou alebo požadovaným správaním socketu
address_len určuje dľžku štruktúry sockaddr, určenú parametrom address

Poznámka

Ak je address nastavený na NULL tak je potom celý parameter ignrovaný.

Návratová hodnota
v prípade úspešného vykonania vracia nezáporný descriptor(socket)
v prípade neúspešného vykonania vráti -1

Úloha 6.1

Pre podrobnejšie vysvetlenie navštívte man 2 listen

Otázky:

Z manuálu ste zistili, akú hodnotu má mať parameter flags k službe accept4() aby bola ekvivalentná k službe accept():

0
Správna odpoveď: 0
Nesprávna odpoveď

Ktorou službou získame hodnotu prvého parametra služby accept().

Správna odpoveď: socket
Nesprávna odpoveď

Krok 7: Vytvorenie spojenia

Služba jadra connect()

Služba jadra connect() vytvára spojenie medzi dvoma procesmi. Táto služba vykonáva rôzne činnosti pre každý z nasledujúcich typov socketov:

-ak je socket SOCK_DGRAM, služba `connect()` vytvorí peer adresu. Peer adresa identifikuje socket ktorému sú zaslané všetky dáta následnou službou `send()`. Taktiež identifikuje socket, z ktorého majú byť dáta príjmané. Ale nie je žiadna záruka, že dáta budú doručené. Jedná sa o prenos bez vytvorenia spojenia medzi socketmi(nespojová služba).
-ak je socket SOCK_STREAM, služba `connect()` sa pokúša nadviazať spojenie so socketom špecifikovaným parametrom _serv_addr_ (spojovaná služba).
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
Parametre
sockfd špecifikuje deskriptor socketu
address ukazuje na sockaddr štruktúru, ktorá obsahuje IP adresu a port procesu na ktorý sa chceme pripojiť
address_les určuje dľžku štruktúry sockaddr, určenú parametrom address
Návratová hodnota
v prípade úspešného vykonania vracia 0
v prípade neúspešného vykonania vráti -1

......................... rozpísanie služieb send a recv samostatne ako služby jadra +++ niečo ku close

Odosielanie a príjem dát

Na odosielanie dát slúži služba send().

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

Na príjem dát slúži služba recv().

int recv(int s, void *buf, size_t len, int flags);

Krok 8: Zaslanie a prijímanie správ

Služba jadra send()

Využíva sa na zaslanie správ do socketu. Služba môže byť využítá len vtedy, ak skocket je v pripojenom stave.

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

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Služba jadra recv()

Využíva sa príjmanie správ z socketu.

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

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

Krok 9: Zatvorenie deskriptora súboru

SLužba jadra close()

Služba close() spôsobí, že deskriptor súboru už ďalej nebude ukazovať na daný súbor. To znamená, že tento deskriptor už nie je možné viac používať.

#include <unistd.h>

int close(int fd);

Úloha 9.1

Pre podrobnejšie vysvetlenie navštívte man 2 send man 2 recv man 2 close

Krok 10: Praktická aplikácia naučených poznatkov

Otázky

Akú hodnotu musí mať druhý parameter služby pre vytvorenie koncového komunikačného bodu pre komunikáciu (transportným) protokolom bez potvrdzovania (symbolická konštanta z manuálovej stránky)

Správna odpoveď: SOCK_DGRAM
Nesprávna odpoveď

Ktorou službou jadra odošleme dáta 'protiľahlému' koncovému komunikačnému bodu pri použití doručovania s potvrdzovaním?

Správna odpoveď: send
Nesprávna odpoveď

Aplikácia služieb v programe

Príklad pooužitia pre server

#include <unistd.h> 
#include <stdio.h> 
#include <sys/socket.h> 
#include <stdlib.h> 
#include <netinet/in.h> 
#include <string.h> 

#define PORT 8080 

int main(int argc, char const *argv[]) { 
    int server_fd, new_socket, valread; 
    struct sockaddr_in address; 
    int opt = 1; 
    int addrlen = sizeof(address); 
    char buffer[1024] = {0}; 
    char *hello = "*** Sprava odoslana z programu server.c ***"; 

    //Vytvorenie deskriptoru pre socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0){ 
        perror("socket failed"); 
        exit(EXIT_FAILURE); 
    } 

    //Napojenie socketu na port 8080
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))){ 
        perror("setsockopt"); 
        exit(EXIT_FAILURE); 
    } 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = INADDR_ANY; 
    address.sin_port = htons(PORT); 

    //Priradenie IP adresy socketu
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0){ 
        perror("bind failed"); 
        exit(EXIT_FAILURE); 
    } 
    if (listen(server_fd, 3) < 0){ 
        perror("listen"); 
        exit(EXIT_FAILURE); 
    } 
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0){ 
        perror("accept"); 
        exit(EXIT_FAILURE); 
    } 
    valread = read(new_socket , buffer, 1024); 
    printf("Pocet precitanych bajtov: %d.\nSprava: %s.\n",valread, buffer); 
    send(new_socket , hello , strlen(hello) , 0); 
    printf("Sprava so znenim:\n*** Sprava odoslana z programu server.c ***\nbola odoslana klientovi.\n"); 
    return 0; 
} 

Príklad použitia pre klienta

#include <stdio.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <string.h> 

#define PORT 8080 

int main(int argc, char const *argv[]){ 
    int sock = 0, valread; 
    struct sockaddr_in serv_addr; 
    char *hello = "*** Sprava odoslana z programu client.c ***"; 
    char buffer[1024] = {0}; 
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){ 
        perror("socket failed"); 
        return -1; 
    } 

    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_port = htons(PORT); 

    // Convert IPv4 and IPv6 addresses from text to binary form 
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0){
        perror("Nespravna IP adresa alebo tato adresa nie je podporovana."); 
        return -1; 
    } 

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
        perror("Spojenie zlyhalo.");
        return -1; 
    } 
    send(sock , hello , strlen(hello) , 0 );
    printf("Sprava so znenim:\n*** Sprava odoslana z programu client.c ***\nbola odoslana serveru.\n"); 
    valread = read( sock , buffer, 1024);
    printf("Pocet precitanych bajtov: %d.\nSprava: %s.\n",valread, buffer);
    return 0; 
} 

Ako by ste zmenili nasledujúci riadok kódu, ak by ste chceli pracovať s IPv6 internetovými protokolmi. [Prepíšte v rovnakom tvare s rovnako umiestnenými medzerami].

Správna odpoveď: server_fd = socket(AF_INET6, SOCK_STREAM, 0)
Nesprávna odpoveď

Ako by ste zmenili nasledujúci riadok kódu, ak by ste nepotrebovali záruku, že bude správa doručená? [Prepíšte v rovnakom tvare s rovnako umiestnenými medzerami].

Správna odpoveď: server_fd = socket(AF_INET, SOCK_DGRAM, 0)
Nesprávna odpoveď

Vďaka ktorej službe v uvedenom príklade použitia (program server.c) je určené maximálne množstvo požiadaviek na spojenie? [Uveďte v tvare nazovSluzby()].

Správna odpoveď: listen()
Nesprávna odpoveď

Akou hodnotou je v uvedenom príklade použitia (program server.c) stanovené maximálne množstvo týchto požiadaviek? [Uveďte číselnú hodnotu].

Správna odpoveď: 3
Nesprávna odpoveď

Akú hodnotu vám v uvedenom príklade použitia (program server.c) vráti služba send()? [Pre riešenie je potrebné upraviť kód. Uveďte číselnú hodnotu].

Správna odpoveď: 43
Nesprávna odpoveď

Vytvára služba connect() v programe client.c peer adresu?

Správna odpoveď: Nie
Nesprávna odpoveď

Ktorý z programov [client.c alebo server.c] na začiatku odošle správu?

Odoslať odpoveď
Správna odpoveď: client.c
Nesprávna odpoveď

........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................


Nadpis: Networking Týždeň: 10 Týždeň-zverejnenia: 1


Krok 11: Úvod

Príprava

Pre prácu na cvičení si stiahnite súbor k cvičeniu.

Obsah súboru:

.
└── 10
    ├──u1_1.c
    ├──u1_2.c
    └──Makefile

Poznámka

Programy je možné kompilovať pomocou make nazov_suboru.c Napríklad: make u1_1.c

Krok 12: Sokety

Model Klient - Server

Komunikačný mechanizmus socket je obojsmernou komunikačnou technológiou. Umožňuje komunikáciu medzi procesmi. Možno s ním pracovať ako so súborom, má pridelený vlastný jedinečný deskriptor.

Jedným zo základných modelov pre komunikáciu medzi procesmi prostredníctvom socketov je model klient–server. Tento model je založený na existencii dvoch typov procesov: procesu-servera a procesu-klienta. Proces-server vykonáva pasívnu úlohu - čaká na požiadavky od klientských procesov, ktorým poskytuje nejakú „službu“. Klienti, ktorí spolupracujú s jedným typom servera, môžu byť rôzneho typu a môžu sa navzájom líšiť používateľským prostredím.

Komunikačný systém
IP adresa sieťová adresa stroja, pomocou nej vedia komunikovať sieťové zariadenia
Port celočíselný identifikátor komunikujúceho procesu, na ktorom sú vybavované požiadavky procesov

Protokol TCP/IP pozostáva zo skupiny protokolov, z ktorých pre prácu so socketmi na štvrtej vrstve TCP/IP modelu sa využívajú protokoly uvedené v nasledujúcej tabuľke.

TCP a UDP

Rozdiel medzi TCP a UDP
Obr. 2: Rozdiel medzi TCP a UDP

Na rozdiel od procesu klient je potrebné na strane procesu server priradiť socketu adresu pomocou služby bind(). Následne je potrebné vytvoriť pomocou služby listen() front, ktorý bude slúžiť na ukladanie požiadaviek na spojenie. Službou accept() sa postupne vyberajú požiadavky na spojenie z frontu. Ak vo fronte nie je žiadna požiadavka na spojenie, proces server čaká - je uspatý. V takomto stave je až do momentu, pokiaľ nejaká požiadavka nedôjde. Služba accept() vracia nový socket, pomocou ktorého je možné komunikovať medzi procesom a klientom, ktorý sa pripája na proces pomocou služby connect().

Úloha 12.1

Pre podrobnejšie vysvetlenie príznakov modelu Klient - Server navštívte: wiki klient-server

Uveďte názov protokolu v rámci sieťového protokolu IPv4 pre potvrdzované doručovanie paketov.

Správna odpoveď: TCP
Nesprávna odpoveď

Krok 13: Vytvorenie záverečného bodu komunikácie

Služba jadra socket()

Služba socket() vytvára záverečné body komunikácie.

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

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

Parametre služby socket:

  1. Parameter domain určuje spôsob adresácie komunikačných uzlov. Môžeme to prirovnať k výberu spôsobu komunikácie medzi ľudmi (telefón, list, e-mail, skype).
Domény Popis
PF_UNIX Lokálna komunikácia
PF_INET IPv4 internetové protokoly
PF_INET6 IPv6 internetové protokoly
PF_IPX IPX-Nowell protokoly
PF_APPLETALK Appletalk DDP
  1. Parameter type určuje typ socketu. Vieme ho prirovnať k službám mobilného operátora (SMS, hovory, internet, MMS).
Možné hodnoty
SOCK_STREAM spojená transportná slúžba, využíva sa vtedy ake chceme najprv vytvoriť spojenie medzi socketmi
SOCK_DGRAM nespojovaná slúžba, nie je žiadna záruka že správa bude doručená
  1. Parameter protocol. Pre naše potreby bude tento parameter nastavený na nulu, čo znamená použitie defaultného protokolu. Ak budeme chcieť vytvoriť socket pre spojovo orientovanú komunikáciu, použijeme službu socket() s týmito parametrami:
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
Návratová hodnota
V prípade úspešného vykonania vráti socket.
V prípade neúspešného vykonania vráti -1.

Aplikácia služby v programe:

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

int main(){
    //Vytvorenie socketu:
    int socket = socket(PF_INET, SOCK_STREAM,0);

    //Kontrola správnosti vykonania služby socket():
    if(socket == -1){
        perror("socket"); 
    }
    else{
        printf("Socket s id %d bol vytvoreny.\n",socket);
    }
    return 0;
}
Opis kódu:

Predchadzajúci fragment kódu vytvorí socket pre spojovanú komunikáciu s defaultne nastaveným protokolom.

Výpis z programu má vyzerať následovne:

Socket s id .. bol vytvorený.

Úloha 13.1

Čo ste mali namiesto ..?

Úloha 13.2

Pre podrobnejšie vysvetlenie služby socket navštívte: man socket

Aký parameter sa využíva vtedy ak chceme najprv vytvoriť spojenie medzi socketmi?

Správna odpoveď: SOCK_STREAM
Nesprávna odpoveď

Uveďte hodnotu prvého parametra služby pre vytvorenie koncového komunikačného bodu pre komunikáciu protokolom IPv4 (symbolická konštanta z manualovej stránky)

Správna odpoveď: PF_INET
Nesprávna odpoveď

Krok 14: Priradenie IP adresy

Služba jadra bind()

Služba bind() zviaže resp. priradí socketu IP adresu a port. Po zviazaní môžeme socket využívať na komunikáciu medzi procesmi v rámci počítačovej siete.

#include <sys/socket.h>

int bind (int socket, struct sockaddr *address, int address_len);
Parametre služby
socket špecifikuje socket(pomocou deskriptora), ktorý má byť zviazaný s adresou
address ukazuje na sockaddr štruktúru, formát ktorý je požadovaným správaním socketu
address_len určuje dĺžku štruktúry sockaddr, určenú parametrom address

Poznámka k parametrom: Deskriptor sme získali pomocou služby socket(). Štruktúra sockaddr sa skladá zo štruktúr sockaddr a sockaddr_um.

Návratová hodnota
V prípade úspešného vykonania vráti 0.
V prípade neúspešného vykonania vráti -1.

Opis štruktúry sockaddr:

Proces potrebuje na nadviazanie spojenia: socket IP adresu stroja a portu, ktorý je na nej určený pre pripájanie.

Adresa je súčasťou štruktúry:

struct sockaddr {        
    unsigned short sa_family;   
    char sa_data[14];    
}; 
Premenné štruktúry Vysvetlenie
unsigned short sa_family typ komunikačného socketu AF nie PF
char sa_data[14] 14 bytov protokolovej adresy

Položka štruktúry sa_data[14] obsahuje cieľovú adresu a číslo portu pre daný socket. Pre IPv4 budeme používať odvodenú štruktúru sockaddr_in, ktorá je definovaná v hlavičkovom súbore <netinet/in.h> v tvare:

struct sockaddr_in {        
    short int sin_family;   
    unsigned short int sin_port;   
    struct in_addr sin_addr;           
    unsigned char sin_zero[8];   
}; 
Premenné štruktúry Vysvetlenie
short int sin_family typ komunikačného socketu AF nie PF
unsigned short int sin_port 2 byty - číslo portu
struct in_addr sin_addr 4 byty - actual IP address
unsigned char sin_zero[8] zvyšných 8 bytov nulujeme

Príklad použitia

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MYPORT 3490 //Port, na ktorý sa budú používateľlia pripájať.

int main() {
    int s;  
    struct sockaddr_in my_addr; //Štruktúra obsahujúca informácia o mojej adrese.
    int sin_size;

    //Vytvorenie socketu:
    if ((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    my_addr.sin_family = AF_INET; //naplnenie struktury sockaddr_in         
    my_addr.sin_port = htons(MYPORT);     
    my_addr.sin_addr.s_addr = INADDR_ANY;

    //Nulovanie zvyšných bytov:
    bzero(&(my_addr.sin_zero), 8);        

    //Zviazanie socketu službou bind():
    if(bind(s,(struct sockaddr *)&my_addr, sizeof(struct sockaddr))==-1){
        perror("bind");
        exit(1);
    }
}

Poznámka

Pomocou služby bind() je možné zviazať socket s IP adresou a portom

Opis kódu

Najprv bol vytvorený socket pre spojovanú komunikáciu s defaultne nastaveným protokolom pomocou služby socket()

Aby sa mohla zviazať IP adresa a port so službou bind(), musí sa najprv naplniť štruktúra sockaddr_in

Všetky služby začínajúce my_addr slúžia na napĺňanie štruktúry sockaddr_in.

Funkcia htons() zabezpečí transformáciu endianity počítača na endianitu siete. Funkcia bzero() nám doplní reťazec my_addr.sin_zero o 8 núl.

Rozlíšenie adresy

V prípade, že máme meno uzla (host), ktorý sa má podieľať na komunikácii, jeho IP adresu zistíme pomocou funkcie gethostbyname(char hostname), ktorá vracia smerník na štruktúru hostent, ktorej definícia je nasledovná:

struct hostent {
       char*   h_name;      //Oficiálne meno hostu    
       char**  h_aliases;   //Smerník na zoznam aliasov (iných mien)    
       int     h_addrtype;  //Typ adresy    
       int     h_length;    //Dĺžka adresy    
       char**  h_addr_list; //Smerník na zoznam adries ak ich má viac    
};
Postupnosť bajtov

Ak chceme získať IP adresu v čitateľnom tvare, použijeme funkciu inet_ntoa()

-htons() - krátke celé čísla z hostovej postupnosti do sieťovej (pre porty)
-ntohs() – krátke celé čísla zo sieťovej do hostovej postupnosti (pre porty)
-htonl() – dlhé celé čísla z hostovej do sieťovej postupnosti (pre IP adresy)
-ntohl() – dlhé celé čísla zo sieťovej do hostovej postupnosti (pre IP adresy)

Poznámka

Formovanie adresy pre internetové protokoly sa uskutočnuje pomocou štruktúry sockaddr_in.

Úloha 14.1

Pre podrobnejšie vysvetlenie navštívte man 2 bind

Uveďte názov služby jadra, ktorou priradíme adresu koncovému komunikačnému bodu v rámci zvoleného protokolu.

Správna odpoveď: bind
Nesprávna odpoveď

Uveďte názov zložky/prvku zodpovedajúceho záznamu (štruktúry) pre nastavenie portu koncového komunikačného bodu v rámci protokolu IP (viď 'man 7 ip'; len názov zložky/prvku, bez názvu záznamu).

Správna odpoveď: sin_port
Nesprávna odpoveď

Krok 15: Vytvorenie vyrovnávajúcej pamäti pre pripojenie

Služba jadra listen()

Služba listen() vytvára vyrovnávajúcu pamäť pre uchovávanie požiadaviek o pripojenie. Ak je front plný a nejaký klient sa pokúsi pripojiť, bude spojenie odmietnuté.

#include <sys/socket.h>

int listen (int socket, int backlog);
Parametre
socket určuje jedinečný identifikátor socketu vytvorený službou socket() s adresou priradenou službou bind()
backlog určuje maximálne množstvo simultárnych požiadaviek na spojenie

Poznámka

Horný limit parametru backlog je špecifikovaný konštantou SOMAXCONN v hlavičkovom súbore <sys/socket.h>. Hodnota parametra backlog je nastavená štandardne na hodnotu 5.

Návratová hodnota
V prípade úspešného vykonania vracia 0.
V prípade neúspešného vykonania vráti -1.

Úloha 15.1

Pre podrobnejšie vysvetlenie navštívte man 2 listen.

Uveďte názov parametra zodpovedajúcej služby jadra, ktorý špecifikuje počet požiadaviek, ktoré budú zaznamenané (archivované) v štruktúrach jadra OS počas obsluhy/spracovania aktuálnej požiadavky (podľa manuálovej stránky).

Správna odpoveď: backlog
Nesprávna odpoveď

Uveďte názov služby jadra, ktorou 'spasívnime' koncový komunikačný bod aby čakal na prichádzajúce spojenia.

Správna odpoveď: listen
Nesprávna odpoveď

Krok 16: Výber požiadaviek

Služba jadra accept()

Služba jadra accept() sa využíva v procese server pre výber požiadaviek z fronty čakajúcich na spojenie a potvrdzuje ich. Pre každé prijaté spojenie sa vytvorí nový socket. Potom cez tento nový socket prebieha komunikácia. Ak nie sú ďalšie požiadavky na spojenie (front je prázdny), tak služba accept() uspí proces pokiaľ nie je prítomná ďalšia požiadavka na spojenie.

#include <sys/socket.h>

int accept(int socket, struct sockaddr *restrict addr, socklen_t *restrict len);
Parametre
socket určuje socket, ktorý bol vytvorený službou socket(), bol zviazaný s adresou a má vytvorený backlog službou listen()
address ukazuje na sockaddr štruktúru ktorá obsahuje IP adresu a port klientskeho procesu. Jej formát je určený doménou alebo požadovaným správaním socketu
address_len určuje dĺžku štruktúry sockaddr, určenú parametrom address

Poznámka

Ak je address nastavený na NULL tak je potom celý parameter ignrovaný.

Návratová hodnota
V prípade úspešného vykonania vracia nezáporný descriptor(socket).
V prípade neúspešného vykonania vráti -1.

Úloha 16.1

Pre podrobnejšie vysvetlenie navštívte man 2 listen.

Z manuálu ste zistili, akú hodnotu má mať parameter flags k službe accept4() aby bola ekvivalentná k službe accept():

Správna odpoveď: 0
Nesprávna odpoveď

Ktorou službou získame hodnotu prvého parametra služby accept().

Správna odpoveď: socket
Nesprávna odpoveď

Krok 17: Vytvorenie spojenia

Služba jadra connect()

Služba jadra connect() vytvára spojenie medzi dvoma procesmi. Táto služba vykonáva rôzne činnosti pre každý z nasledujúcich typov socketov:

-ak je socket SOCK_DGRAM, služba connect() vytvorí peer adresu. Peer adresa identifikuje socket, ktorému sú zaslané všetky dáta službou send(). Taktiež identifikuje socket, z ktorého majú byť dáta prijímané. Ale nie je žiadna záruka, že dáta budú doručené. Jedná sa o prenos bez vytvorenia spojenia medzi socketmi(nespojovaná služba), -ak je socket SOCK_STREAM, služba connect() sa pokúša nadviazať spojenie so socketom špecifikovaným parametrom serv_addr (spojovaná služba).

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
Parametre
sockfd špecifikuje deskriptor socketu
address ukazuje na sockaddr štruktúru, ktorá obsahuje IP adresu a port procesu na ktorý sa chceme pripojiť
address_les určuje dľžku štruktúry sockaddr, určenú parametrom address
Návratová hodnota
V prípade úspešného vykonania vracia 0.
V prípade neúspešného vykonania vráti -1.

Krok 18: Zasielanie, prijímanie správ a zrušenie deskriptora súboru

Služba jadra send()

Využíva sa na zaslanie správ do socketu. Služba môže byť využítá len vtedy, ak skocket je v pripojenom stave.

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

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Služba jadra recv()

Využíva sa prijímanie správ zo socketu.

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

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

Služba jadra close()

Služba close() spôsobí, že deskriptor súboru už ďalej nebude ukazovať na daný súbor. To znamená, že tento deskriptor už nie je možné viac používať.

#include <unistd.h>

int close(int fd);

Úloha 18.1

Pre podrobnejšie vysvetlenie navštívte man 2 send man 2 recv man 2 close.

Akú hodnotu musí mať druhý parameter služby pre vytvorenie koncového komunikačného bodu pre komunikáciu (transportným) protokolom bez potvrdzovania (symbolická konštanta z manuálovej stránky)

Správna odpoveď: SOCK_DGRAM
Nesprávna odpoveď

Ktorou službou jadra odošleme dáta 'protiľahlému' koncovému komunikačnému bodu pri použití doručovania s potvrdzovaním?

Správna odpoveď: send
Nesprávna odpoveď

Príklad pooužitia pre server

#include <unistd.h> 
#include <stdio.h> 
#include <sys/socket.h> 
#include <stdlib.h> 
#include <netinet/in.h> 
#include <string.h> 

#define PORT 8080 

int main(int argc, char const *argv[]) { 
    int server_fd, new_socket, valread; 
    struct sockaddr_in address; 
    int opt = 1; 
    int addrlen = sizeof(address); 
    char buffer[1024] = {0}; 
    char *hello = "*** Sprava odoslana z programu server.c ***"; 

    //Vytvorenie deskriptoru pre socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0){ 
        perror("socket failed"); 
        exit(EXIT_FAILURE); 
    } 

    //Napojenie socketu na port 8080
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))){ 
        perror("setsockopt"); 
        exit(EXIT_FAILURE); 
    } 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = INADDR_ANY; 
    address.sin_port = htons(PORT); 

    //Priradenie IP adresy socketu
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0){ 
        perror("bind failed"); 
        exit(EXIT_FAILURE); 
    } 
    if (listen(server_fd, 3) < 0){ 
        perror("listen"); 
        exit(EXIT_FAILURE); 
    } 
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0){ 
        perror("accept"); 
        exit(EXIT_FAILURE); 
    } 
    valread = read(new_socket , buffer, 1024); 
    printf("Pocet precitanych bajtov: %d.\nSprava: %s.\n",valread, buffer); 
    send(new_socket , hello , strlen(hello) , 0); 
    printf("Sprava so znenim:\n*** Sprava odoslana z programu server.c ***\nbola odoslana klientovi.\n"); 
    return 0; 
} 

Príklad použitia pre klienta

#include <stdio.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <string.h> 

#define PORT 8080 

int main(int argc, char const *argv[]){ 
    int sock = 0, valread; 
    struct sockaddr_in serv_addr; 
    char *hello = "*** Sprava odoslana z programu client.c ***"; 
    char buffer[1024] = {0}; 
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){ 
        perror("socket failed"); 
        return -1; 
    } 

    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_port = htons(PORT); 

    // Convert IPv4 and IPv6 addresses from text to binary form 
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0){
        perror("Nespravna IP adresa alebo tato adresa nie je podporovana."); 
        return -1; 
    } 

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
        perror("Spojenie zlyhalo.");
        return -1; 
    } 
    send(sock , hello , strlen(hello) , 0 );
    printf("Sprava so znenim:\n*** Sprava odoslana z programu client.c ***\nbola odoslana serveru.\n"); 
    valread = read( sock , buffer, 1024);
    printf("Pocet precitanych bajtov: %d.\nSprava: %s.\n",valread, buffer);
    return 0; 
} 

Ako by ste zmenili nasledujúci riadok kódu, ak by ste chceli pracovať s IPv6 internetovými protokolmi. [Prepíšte v rovnakom tvare s rovnako umiestnenými medzerami].

Správna odpoveď: server_fd = socket(AF_INET6, SOCK_STREAM, 0)
Nesprávna odpoveď

Ako by ste zmenili nasledujúci riadok kódu, ak by ste nepotrebovali záruku, že bude správa doručená? [Prepíšte v rovnakom tvare s rovnako umiestnenými medzerami].

Správna odpoveď: server_fd = socket(AF_INET, SOCK_DGRAM, 0)
Nesprávna odpoveď

Vďaka ktorej službe v uvedenom príklade použitia (program server.c) je určené maximálne množstvo požiadaviek na spojenie? [Uveďte v tvare nazovSluzby()].

Správna odpoveď: listen()
Nesprávna odpoveď

Akou hodnotou je v uvedenom príklade použitia (program server.c) stanovené maximálne množstvo týchto požiadaviek? [Uveďte číselnú hodnotu].

Správna odpoveď: 3
Nesprávna odpoveď

Akú hodnotu vám v uvedenom príklade použitia (program server.c) vráti služba send()? [Pre riešenie je potrebné upraviť kód. Uveďte číselnú hodnotu].

Správna odpoveď: 43
Nesprávna odpoveď

Vytvára služba connect() v programe client.c peer adresu?

Správna odpoveď: Nie
Nesprávna odpoveď

Ktorý z programov [client.c alebo server.c] na začiatku odošle správu?

Odoslať odpoveď
Správna odpoveď: client.c
Nesprávna odpoveď

Ciele

  1. Pripraviť si prostredie pre prácu na cvičení
  2. Porozumieť soketom
  3. Porozumieť službe bind
  4. Porozumieť službe listen
  5. Porozumieť službe accept
  6. Porozumieť službe connect
  7. Naučiť sa zasielať a odosielať správy
  8. Naučiť sa zatvoriť deskriptor súboru
  9. Pochopiť aplikáciu poznatkov v praxi