Sieci komputerowe — ćwiczenia 2


Temat zajęć: Programowanie gniazd BSD c.d.

Literatura:

 Protokół UDP

W przypadku korzystania z protokołu UDP miejsce funkcji send i recv zajmują funkcje

Przykład 1

Przykład realizuje te same funkcje, co Przykład 5 z Ćwiczeń 1 (przesłanie wiersza tekstu), jednak korzysta z protokołu UDP.

Plik c2p1a.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>


char buf[512];

int main(int argc, char **argv)
{
struct sockaddr_in myaddr, endpoint;
int sdsocket, r;
socklen_t addrlen;
unsigned int port;

printf("Na ktorym porcie mam sluchac? : ");
scanf("%u", &port);

if ((sdsocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
printf("socket() nie powiodl sie\n");
return 1;
}

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(port);
myaddr.sin_addr.s_addr = INADDR_ANY;

if (
bind(sdsocket,
(struct sockaddr*) &myaddr,
sizeof(struct sockaddr_in)) < 0) {
printf("bind() nie powiodl sie\n");
return 1;
}

addrlen = sizeof(struct sockaddr_in);
while (1) { /* nieskonczona petla */
memset(buf, 0, 512);
r = recvfrom(sdsocket,
buf,
512,
0,
(struct sockaddr*) &endpoint,
&addrlen);
printf("Wiadomosc od %s: %s\n",
inet_ntoa(endpoint.sin_addr),
buf);
}
return 0;
}

Plik c2p1b.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>


char buf[512];

int main(int argc, char **argv)
{
struct sockaddr_in adr;
int gniazdo, r;
unsigned int port;
char abcd[512];

printf("Podaj adres IP odbiorcy: ");
scanf("%s", abcd);
printf("Podaj numer portu odbiorcy: ");
scanf("%u", &port);
gniazdo = socket(AF_INET, SOCK_DGRAM, 0);
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.s_addr = inet_addr(abcd);
printf("Podaj wiadomosc: ");
fflush(stdout); fgetc(stdin);
fgets(buf, 512, stdin);

r = sendto(gniazdo,
buf,
512,
0,
(struct sockaddr*) &adr,
sizeof(adr));
if (r != 512) printf("sendto() nie powiodl sie\n");
else printf("Wiadomosc wyslana.\n");
close(gniazdo);
return 0;
}

 Zadanie 1

Podobnie jak w przypadku Przykładu 5 z Ćwiczeń 1, proszę zmodyfikować kod przykładu tak, aby po pierwsze użytkownik mógł podawać nazwy domenowe zamiast adresów IP, a po drugie aby możliwe było przesłanie dowolnej liczby komunikatów w jednej sesji (jednym uruchomieniu programu klienckiego).


 Przykład 2

Poniższy przykład mierzy średnią wydajność protokołu TCP przy przesyłaniu bloków o długości 10000 bajtów w obie strony. Przykład działa w ten sposób, że do każdego przesłania bloku nawiązywane jest osobne połączenie. Mierzymy więc czas, jaki zabiera socket, connect, send 10000 bajtów, recv 10000 bajtów i close. Do mierzenia czasu wykorzystana została funkcja int gettimeofday(struct timeval *t, struct timezone *z) (za z podstawiamy NULL, bo strefa czasowa nas akurat nie obchodzi). Funkcja ta wypełnia strukturę wskazywaną przez t danymi o bieżącym czasie z dokładnością do mikrosekundy. Struktura timeval (zarówno struktura, jak i funkcja gettimeofday zdefiniowane są w sys/time.h) zawiera dwa pola typu long: tv_sec i tv_usec. Określają one czas (w sekundach i mikrosekundach), jaki minął od północy dnia 1 stycznia 1970r. Wystarczy więc wywołać gettimeofday przed i po badanym bloku instrukcji, a następnie odjąć odp. wielkości (pamiętając o skali) aby otrzymać przedział czasowy. W naszym przykładzie powtarzamy proces przesyłania 100 razy (średni czas pojedynczej transmisji klient-serwer-klient uzyskamy dzieląc otrzymany wynik przez 100).

UWAGA: Tym razem program nie pyta o adres i numer portu w sposób interaktywny. W przypadku serwera (c2p2a.c) należy jako parametr w wierszu poleceń podać numer portu, a w przypadku klienta należy podać jako parametry kolejno nazwe hosta serwera i jego numer portu.

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

/* rozmiar bufora */
#define BUFFER_SIZE 10000

/* liczba powtorzen */
#define ATTEMPTS 100

char buf[BUFFER_SIZE];

/*
argv[1] - numer portu
*/
int main(int argc, char **argv)
{
struct sockaddr_in myaddr, endpoint;
int sdsocket, sdconnection, addrlen, received;

if (argc < 2) {
printf("podaj numer portu jako parametr\n");
return 1;
}

sdsocket = socket(AF_INET, SOCK_STREAM, 0);
addrlen = sizeof(struct sockaddr_in);

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[1]));
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(sdsocket,(struct sockaddr*) &myaddr,addrlen) < 0) {
printf("bind() nie powiodl sie\n");
return 1;
}

if (listen(sdsocket, 10) < 0) {
printf("listen() nie powiodl sie\n");
return 1;
}

while ((sdconnection =
accept(sdsocket,
(struct sockaddr*) &endpoint,
&addrlen)) >= 0)
{
received = 0;
/* odbior moze odbywac sie w mniejszych
segmentach */
while (received < BUFFER_SIZE)
{
received += recv(sdconnection,
buf+received,
BUFFER_SIZE-received,
0);
}
send(sdconnection, buf, BUFFER_SIZE, 0);
close(sdconnection);
}

close(sdsocket);
return 0;
}

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

#define BUFFER_SIZE 10000
#define ATTEMPTS 100

char buf[BUFFER_SIZE];

/*
argv[1] - nazwa hosta
argv[2] - numer portu
*/
int main(int argc, char **argv)
{
struct sockaddr_in endpoint;
int sdsocket, addrlen, i, received;
struct hostent *he;
struct timeval time_b, time_e;

if (argc<3) {
printf("podaj nazwe hosta i numer portu jako parametry\n");
return 1;
}

he = gethostbyname(argv[1]);
if (he == NULL) {
printf("Nieznany host: %s\n",argv[1]);
return 0;
}

endpoint.sin_family = AF_INET;
endpoint.sin_port = htons(atoi(argv[2]));
endpoint.sin_addr = *(struct in_addr*) he->h_addr;
addrlen = sizeof(struct sockaddr_in);

gettimeofday(&time_b, NULL);
for (i=0; i<ATTEMPTS; i++) {

if ((sdsocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("socket() %d nie powiodl sie\n", i);
return 1;
}

if (connect(sdsocket,(struct sockaddr*) &endpoint, addrlen) < 0) {
printf("connect() %d nie powiodl sie\n", i);
return 0;
}
send(sdsocket, buf, BUFFER_SIZE, 0);
received = 0;
while (received < BUFFER_SIZE)
{
received += recv(sdsocket,
buf+received,
BUFFER_SIZE-received,
0);
}
close(sdsocket);
}
gettimeofday(&time_e, NULL);

printf("czas: %.6f s\n",
(((double) (time_e.tv_sec - time_b.tv_sec) * 1000000) +
((double) (time_e.tv_usec - time_b.tv_usec)))
/ (1000000.0 * ATTEMPTS));
return 0;
}

Zadanie 2

Wysłać, korzystając z protokołu UDP (IPv4) na adres 150.254.77.101, port (zostanie podany na tablicy) z lokalnego komputera pakiet zawierający numer indeksu przesłany jako ciąg znaków ASCII bez spacji zakończony znakiem przejścia do nowej linii i ten sam numer, zapisany jako czterobajtowa liczba bez znaku, zapisana w kolejności sieciowej. Ewentualną odpowiedź należy zapisać jako potwierdzenie. Przykład dla numeru 123456, podane wartości szesnastkowe kolejnych bajtów (podpowiedź: do zapisu w postaci kodów ASCII można wykorzystać funcję sprintf()):
31 32 33 34 35 36 0A 00 01 E2 40

Zadanie 3 (domowe)

W oparciu o kod z Przykładu 2 zaimplementować następujące przypadki: Przeimplementować należy zarówno stronę klienta, jak i stronę serwera.
Program proszę uruchamiać na dwóch komputerach w laboratoriach wydziału aby uzyskane wyniki faktycznie uwzględniały przesyłanie danych przez sieć.
Wyniki wszystkich trzech (włączając w to Przykład 2) pomiarów proszę przesłać e-mailem na adres marcing[at]wmi[dot]amu[dot]edu[dot]pl, umieszczając w tytule listu ciąg DSIKLI0 1CX, gdzie X to numer grupy (ułatwi mi to sortowanie poczty).
Na wyniki czekam do niedzieli, 16 marca 2014r.


Problem do przemyślenia

Załóżmy, że pewna usługa sieciowa ma być dostępna zarówno przez TCP, jak i UDP. Powinna więc nasłuchiwać na dwóch gniazdach (oznaczmy je przez gnTCP i gnUDP) jednocześnie, ponieważ nie wiadomo, którą drogą przyjdzie żądanie wykonania usługi od potencjalnego klienta.
Z jednej strony więc nasz program powinien wykonać sekwencję

gnTCP = socket(...)
bind(gnTCP,...)
listen(gnTCP,...)
gnTCP_klient = accept(gnTCP,...) BLOKADA
send / recv na gnTCP_klient
close(gnTCP_klient)
ew. powrót do accept

a z drugiej, w tym samym czasie, sekwencję

gnUDP = socket(...)
bind(gnUDP,...)
recvfrom(gnUDP,...) BLOKADA
sendto / recvfrom na gnUDP
close(gnUDP)
ew. powrót do pierwszego recvfrom

Problem polega na tym, że jeśli wykonamy accept na gnieździe TCP, program traci sterowanie do momentu nawiązania połączenia przez klienta TCP, zatem nie mamy szansy wykonać recvfrom na gnieździe UDP, aby zapewnić nasłuch również na tym protokole. Z drugiej strony, jeśli najpierw wykonamy recvfrom na gnieździe UDP, program również straci sterowanie do momentu odebrania pierwszego datagramu i nie będzie możliwe wykonanie accept na gnieździe TCP.

Możliwe podejścia do rozwiązania problemu: Proszę przeczytać informacje na temat funkcji select znajdujące się w man pages systemu (sekcja 2).
Przykłady stosowania funkcji select przeanalizujemy na następnych zajęciach.


Problem do przemyślenia 2

Wszystkie dotychczasowe przykłady wykorzystują protokół IPv4. Jak zmienić je, by stosowały IPv6?