Sieci komputerowe — ćwiczenia 1 (część 2)


Temat zajęć: Programowanie gniazd BSD.

Literatura:

Uwaga:
Opisy większości funkcji, z których będziemy korzystać na zajęciach znajdują się w man pages systemu. Zwykle obejmuje je rozdział 2 dokumentacji, zatem aby uzyskać np. opis funkcji socket, należy wpisać w terminalu
man 2 socket

Kompilacja kodu źródłowego z przykładów:
Zakładając, że kod źródłowy znajduje się w pliku program.c, a wynikowy kod ma znaleźć się w pliku program, należy użyć polecenia
gcc -o program program.c

Uruchomienie skompilowanego programu:
./program [parametry]

 Funkcje konwertujące liczby i adresy.


Funkcje konwertujące liczby całkowite z formatu lokalnego hosta na format sieciowy: Funkcje konwertujące liczby całkowite z formatu sieciowego na format lokalnego hosta: Prototypy wyżej wymienionych funkcji znajdują się w pliku nagłówkowym netinet/in.h.

Funkcja konwertująca adres IP z postaci a.b.c.d na format sieciowy:
Funkcja konwertująca adres IP z formatu sieciowego na postać a.b.c.d:
Struktura in_addr zdefiniowana jest w netinet/in.h jako
struct in_addr {
unsigned long int s_addr;
};


Funkcje zamieniające nazwy domenowe na adresy IP i na odwrót (prototypy w netdb.h): Struktura hostent zdefiniowana jest następująco:
struct hostent {
char* h_name;
char** h_aliases;
int h_addrtype;
int h_length;
char** h_addr_list;
};

Dodatkowo dla wygody w netdb.h zdefiniowane jest makro

#define h_addr h_addr_list[0]

zatem wyrażenie zmienna->h_addr jest tożsame z zmienna->h_addr_list[0].

 Przykład 1

Przykład obrazuje różnice w reprezentacji liczb całkowitych w lokalnym formacie hosta i w formacie sieciowym.

Plik c1p1.c pobierz
#include <stdio.h>
#include <netinet/in.h>

int main(void) {
in_addr_t h, n;
unsigned char *jako_bajty;
printf("Podaj liczbe calkowita: ");
scanf("%d",&h);
printf("Liczba w formacie lokalnym jako hex: %X\n",h);
jako_bajty = (unsigned char *) &h;
printf("Liczba w formacie lokalnym jako bajty hex: %X %X %X %X\n",
jako_bajty[0],
jako_bajty[1],
jako_bajty[2],
jako_bajty[3]);
n = htonl(h);
jako_bajty = (unsigned char *) &n;
printf("Liczba w formacie sieciowym jako bajty: %X %X %X %X\n",
jako_bajty[0],
jako_bajty[1],
jako_bajty[2],
jako_bajty[3]);
printf("Liczba w formacie sieciowym jako hex: %X\n", n);
printf("co daje dziesietnie %u\n", n);
return 0;
}

 Przykład 2

Przykład pokazuje sposób wykonywania konwersji z adresu w formacie sieciowym na adres postaci a.b.c.d i na odwrót.

Plik c1p2a.c pobierz
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>

int main(void) {
char abcd[512];
in_addr_t adrsiec;
unsigned char *jako_bajty =
(unsigned char*) &adrsiec;

printf("Podaj adres IP w formacie a.b.c.d: ");
scanf("%s",abcd);
adrsiec = inet_addr(abcd);
if (adrsiec == 0xffffffff) {
printf("To nie jest prawidlowy adres IP!\n");
return 1;
}
printf("Adres w formacie sieci to %u, hex=%X\n",
adrsiec, adrsiec);
printf("Adres bajt po bajcie (hex): %X %X %X %X\n",
jako_bajty[0], jako_bajty[1],
jako_bajty[2], jako_bajty[3]);
printf("Adres bajt po bajcie (dec): %u %u %u %u\n",
jako_bajty[0], jako_bajty[1],
jako_bajty[2], jako_bajty[3]);
return 0;
}

Plik c1p2b.c pobierz
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(void) {
in_addr_t adr;
unsigned char *jako_bajty =
(unsigned char *) &adr;
char *abcd;
struct in_addr tmp;

printf("Podaj adres IP jako liczbe dziesietna w formacie sieci: ");
scanf("%u",&adr);
printf("Adres (hex): %X\n", adr);
printf("Adres jako bajty (hex): %X %X %X %X\n",
jako_bajty[0], jako_bajty[1],
jako_bajty[2], jako_bajty[3]);
printf("Adres jako bajty (dec): %u %u %u %u\n",
jako_bajty[0], jako_bajty[1],
jako_bajty[2], jako_bajty[3]);
tmp.s_addr = adr;
abcd = inet_ntoa(tmp);
printf("Adres w formacie a.b.c.d: %s\n", abcd);

return 0;
}

 Przykład 3

Przykład pokazuje sposób, w jaki można zamienić nazwę domenową na adres IP i odwrotnie. Faktyczny sposób zamiany zależy od konfiguracji systemu (zwykle najpierw sprawdzany jest plik /etc/hosts, a później odpytywany jest serwer DNS lub NIS, jednak można zmienić tą kolejność).

Plik c1p3a.c pobierz
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

int main(void) {
char nazwa[512];
struct hostent *he;
struct in_addr adr;
int i;

printf("Podaj nazwe hosta: ");
scanf("%s", nazwa);
he = gethostbyname((const char*) nazwa);
if (he == NULL) {
printf("Nie ma hosta o takiej nazwie\n");
printf("lub blad serwera DNS.\n");
return 1;
}
i = 0;
while (he->h_addr_list[i]) {
adr.s_addr =
*((unsigned long*) he->h_addr_list[i]);
printf("Adres hosta: %s\n",
inet_ntoa(adr));
printf("W formacie sieci (hex): %X, (dec): %u\n",
adr.s_addr, adr.s_addr);
i++;
}
return 0;
}

Plik c1p3b.c pobierz
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

int main(int argc, char **argv)
{
struct hostent *he;
struct in_addr addr;
char abcd[512];

printf("Podaj adres IP: ");
scanf("%s", abcd);
addr.s_addr = inet_addr(abcd);
he = gethostbyaddr((char*) &addr, sizeof(struct in_addr), AF_INET);
if (he == NULL) {
printf("Brak danych o %s\n", abcd);
return 1;
}
printf("Nazwa: %s\n",
he->h_name);

return 0;
}

 Przykład 4

Przykład pokazuje komunikację między dwoma programami za pośrednictwem kolejki FIFO zrealizowanej w systemie plików. Program A przykładu odczytuje z kolejki liczbę 32-bitową, która jest do kolejki zapisywana przez program B. Dodatkowo wykonywana jest konwersja między formatem lokalnym i formatem sieciowym. Kolejka tworzona jest przez program A, który powinien być uruchamiany jako pierwszy. Przesyłaną liczbą jest 999999.
Wykorzystane funkcje:
UWAGA:
Programy z przykładu działają poprawnie tylko w systemach plików, które umożliwiają utworzenie pliku specjalnego typu kolejka FIFO (czyli nie będą działać np. dla systemu FAT).

Plik c1p4a.c pobierz
#include <stdio.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include <netinet/in.h>

char *endpoint = "./p2.fifo";

int main(int argc, char **argv)
{
int fdFifo;
long lNetworkFormat, lHostFormat;

mkfifo(endpoint, 0666);
fdFifo = open(endpoint, O_RDONLY);
read(fdFifo, &lNetworkFormat, sizeof(long));
lHostFormat = ntohl(lNetworkFormat);
printf("odczytano: %ld\n", lHostFormat);
close(fdFifo);
return 0;
}


Plik c1p4b.c pobierz
#include <stdio.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <unistd.h>

char *endpoint = "./p2.fifo";

int main(int argc, char **argv)
{
int fdFifo;
long lNetworkFormat, lHostFormat;

lHostFormat = 999999;
lNetworkFormat = htonl(lHostFormat);
fdFifo = open(endpoint, O_WRONLY);
write(fdFifo, &lNetworkFormat, sizeof(long));
printf("zapisano: %ld\n", lHostFormat);
close(fdFifo);
return 0;
}


 Operacje na gniazdach

Struktury danych wykorzystywane w komunikacji sieciowej realizowanej na gniazdach BSD (definicje w netinet/in.h):

Funkcje wykorzystywane w operacjach na gniazdach:

Schemat transmisji

gniazdabsd_tcp.png

 Przykład 5

Bardzo prosta aplikacja klient-serwer oparta na protokole TCP. Program A nasłuchuje na konkretnym porcie (jego numer podaje użytkownik), a program B łączy się na podany adres IP i port, po czym przesyła wiadomość tekstową, która jest wyświetlana na terminalu, na którym działa program A.

Plik c1p5a.c pobierz
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(void) {
unsigned int port;
char bufor[1024];
int gniazdo, gniazdo2;
struct sockaddr_in adr, nadawca;
socklen_t dl = sizeof(struct sockaddr_in);

printf("Na ktorym porcie mam sluchac? : ");
scanf("%u", &port);
gniazdo = socket(PF_INET, SOCK_STREAM, 0);
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.s_addr = INADDR_ANY;
if (bind(gniazdo, (struct sockaddr*) &adr,
sizeof(adr)) < 0) {
printf("Bind nie powiodl sie.\n");
return 1;
}
if (listen(gniazdo, 10) < 0) {
printf("Listen nie powiodl sie.\n");
return 1;
}
printf("Czekam na polaczenie ...\n");
while ((gniazdo2 = accept(gniazdo,
(struct sockaddr*) &nadawca,
&dl)) > 0
)
{
memset(bufor, 0, 1024);
recv(gniazdo2, bufor, 1024, 0);
printf("Wiadomosc od %s: %s\n",
inet_ntoa(nadawca.sin_addr),
bufor);
close(gniazdo2);
}
close(gniazdo);
return 0;
}

Plik c1p5b.c pobierz
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <string.h> #include <arpa/inet.h> #include <unistd.h>
int main(void) {
char abcd[512];
char wiadomosc[1024];
struct sockaddr_in adr;
unsigned int port;
int gniazdo;

printf("Podaj adres IP odbiorcy: ");
scanf("%s", abcd);
printf("Podaj numer portu odbiorcy: ");
scanf("%u", &port);
gniazdo = socket(PF_INET, SOCK_STREAM, 0);
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.s_addr = inet_addr(abcd);
if (connect(gniazdo, (struct sockaddr*) &adr,
sizeof(adr)) < 0)
{
printf("Nawiazanie polaczenia nie powiodlo sie.\n");
return 1;
}
printf("Polaczenie nawiazane.\nPodaj wiadomosc: ");
fflush(stdout); fgetc(stdin);
fgets(wiadomosc, 1024, stdin);
send(gniazdo, wiadomosc, strlen(wiadomosc), 0);
printf("Wiadomosc wyslana.\n");
close(gniazdo);
return 0;
}

Zadanie 1

Zmodyfikować kod programu B z przykładu 5 tak, aby użytkownik mógł podać nazwę domenową komputera odbiorcy zamiast jego adresu IP (oczywiście nadal musi podawać numer portu).
(modify example „Przykład 5” so that user can provide a domain name instead of an IP address; of course they still have to provide a port number)

Zadanie 2

Zmodyfikować kod programów A i B z przykładu 5 tak, aby możliwe było wysłanie więcej niż jednej wiadomości podczas jednego połączenia. Dokładniej, program A powinien czekać na nawiązanie połączenia, po czym bez końca (w nieskończonej pętli) odbierać i wyświetlać wiadomości. Program B powinien nawiązać połączenie, po czym w nieskończoność prosić użytkownika o wprowadzenie wiadomości i wysyłać ją do programu A (Modify „Przykład 5” so that it can send more than one message during a single connection; program A should wait for a connection, then in infinite loop, receive and display incoming messages; program B shoud connect to program A, and, in infinite loop, prompt a user for messages to send).