Table of Contents

Elementy programowania sieciowego

Przygotuj się do laboratorium

Wiedza

1. Network Byte Order

2. getaddrinfo(3)

3. Obsługa adresów IPv4 oraz IPv6

4. Gniazda

Ćwiczenia

I. Sockety w Bashu

II. getaddrinfo

  1. Proszę przeanalizować, skompilować i uruchomić program (źródło):
    showip.c
    /*
    ** showip.c -- show IP addresses for a host given on the command line
    */
     
    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
     
    int main(int argc, char *argv[])
    {
    	struct addrinfo hints, *res, *p;
    	int status;
    	char ipstr[INET6_ADDRSTRLEN];
     
    	if (argc != 2) {
    	    fprintf(stderr,"usage: showip hostname\n");
    	    return 1;
    	}
     
    	memset(&hints, 0, sizeof hints);
    	hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
    	hints.ai_socktype = SOCK_STREAM;
     
    	if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
    		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
    		return 2;
    	}
     
    	printf("IP addresses for %s:\n\n", argv[1]);
     
    	for(p = res;p != NULL; p = p->ai_next) {
    		void *addr;
    		char *ipver;
     
    		// get the pointer to the address itself,
    		// different fields in IPv4 and IPv6:
    		if (p->ai_family == AF_INET) { // IPv4
    			struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
    			addr = &(ipv4->sin_addr);
    			ipver = "IPv4";
    		} else { // IPv6
    			struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
    			addr = &(ipv6->sin6_addr);
    			ipver = "IPv6";
    		}
     
    		// convert the IP to a string and print it:
    		inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
    		printf("  %s: %s\n", ipver, ipstr);
    	}
     
    	freeaddrinfo(res); // free the linked list
     
    	return 0;
    }
  2. Sprawdzić działanie programu dla www.yahoo.com, uj.edu.pl oraz innych wybranych adresów symbolicznych.
  3. Jakie parametry przyjmuje funkcja getaddrinfo(3) i jakie zwraca?
  4. W jaki sposób można wyświetlić komunikaty o błędach funkcji getaddrinfo(3)?
  5. Zmodyfikuj program w taki sposób, aby przyjmował drugi argument oznaczający usługę (np. http, ftp, telnet, smtp). Przekaż ten parametr w odpowiedni sposób do funkcji getaddrinfo(3)

III. Programowanie gniazd - protokół TCP

  1. Proszę przeanalizować, skompilować i uruchomić program:
    simple-server.c
    /*
    ** simple-server.c -- a stream socket server demo
    ** Modified version of server.c from https://beej.us/guide/bgnet/html/
    */
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <sys/wait.h>
    #include <signal.h>
     
    #define PORT "3490"  // the port users will be connecting to
     
    #define BACKLOG 10	 // how many pending connections queue will hold
     
    void sigchld_handler(int s)
    {
    	(void)s; // quiet unused variable warning
     
    	// waitpid() might overwrite errno, so we save and restore it:
    	int saved_errno = errno;
     
    	while(waitpid(-1, NULL, WNOHANG) > 0);
     
    	errno = saved_errno;
    }
     
     
    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
    	if (sa->sa_family == AF_INET) {
    		return &(((struct sockaddr_in*)sa)->sin_addr);
    	}
     
    	return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
     
    int main(void)
    {
    	int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
    	struct addrinfo hints, *servinfo, *p;
    	struct sockaddr_storage their_addr; // connector's address information
    	socklen_t sin_size;
    	struct sigaction sa;
    	int yes=1;
    	char s[INET6_ADDRSTRLEN];
    	int rv;
     
    	memset(&hints, 0, sizeof hints);
    	hints.ai_family = AF_UNSPEC;
    	hints.ai_socktype = SOCK_STREAM;
    	hints.ai_flags = AI_PASSIVE; // use my IP
     
    	if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
    		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    		return 1;
    	}
     
    	// loop through all the results and bind to the first we can
    	for(p = servinfo; p != NULL; p = p->ai_next) {
    		if ((sockfd = socket(p->ai_family, p->ai_socktype,
    				p->ai_protocol)) == -1) {
    			perror("server: socket");
    			continue;
    		}
     
    		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
    				sizeof(int)) == -1) {
    			perror("setsockopt");
    			exit(1);
    		}
     
    		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
    			close(sockfd);
    			perror("server: bind");
    			continue;
    		}
     
    		break;
    	}
     
    	freeaddrinfo(servinfo); // all done with this structure
     
    	if (p == NULL)  {
    		fprintf(stderr, "server: failed to bind\n");
    		exit(1);
    	}
     
    	if (listen(sockfd, BACKLOG) == -1) {
    		perror("listen");
    		exit(1);
    	}
     
    	sa.sa_handler = sigchld_handler; // reap all dead processes
    	sigemptyset(&sa.sa_mask);
    	sa.sa_flags = SA_RESTART;
    	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
    		perror("sigaction");
    		exit(1);
    	}
     
    	printf("server: waiting for connections...\n");
     
    	sin_size = sizeof their_addr;
    	new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
    	if (new_fd == -1) {
    		perror("accept");
    	}
     
    	inet_ntop(their_addr.ss_family,
    		get_in_addr((struct sockaddr *)&their_addr),
    		s, sizeof s);
    	printf("server: got connection from %s\n", s);
     
    	if (send(new_fd, "Hello, world!", 13, 0) == -1)
    		perror("send");
    	sleep(5);  // just for observing easily that the server cannot serve a few clients concurrently
    	close(new_fd);
     
    	return 0;
    }
    • Uwaga: do danego portu może być przypisany tylko jeden program (za pomocą bind) – dlatego jeżeli pracujesz na współdzielonym serwerze (np. SPK), zmień wartość PORT na jakąś własną (w #define PORT “3490”)
    • Jest to prosty serwer, który czeka na połączenie od klienta, wysyła mu wiadomość Hello, world!, utrzymuje połączenie przez 5 sekund (sleep), a następnie zamyka połączenie i kończy swoje działanie
    • Podłączenie do serwera jako klient można zrealizować na dwa sposoby:
      • Korzystając z programu telnet wpisując:
        $ telnet host port

        podając odpowiednią nazwę hosta i port (np. telnet localhost 3490, jeżeli łączymy się z tej samej maszyny i korzystamy z domyślnego portu)

      • Korzystając z przykładowego programu-klienta: client.c (źródło) – uwaga: zmodyfikuj zmienną PORT, aby była zgodna z wartością w programie serwera!
  2. Kilka pytań na rozgrzewkę:
    • Adres IP identyfikuje hosta w danej sieci (podsieci), co identyfikuje numer portu?
    • Czym różni się deskryptor gniazda od deskryptora pliku?
  3. Proszę zmodyfikować serwer tak, aby po obsłużeniu klienta nie kończył działania, ale powracał do oczekiwania na kolejne połączenie
  4. Proszę zmodyfikować serwer tak, aby mógł obsługiwać jednocześnie więcej niż jednego klienta
    • Podpowiedź: Można użyć funkcji fork() do tworzenia procesów potomnych - każdy proces potomny będzie obsługiwał jednego klienta.
  5. Proszę zmodyfikować serwer tak, aby umożliwiał prowadzenie dialogu a'la komunikator internetowy:
    • Jedną stroną jest podłączony klient, a drugą stroną może być serwer (w wersji minimum), albo inny klient (wtedy serwer powinien parować klientów w odpowiedni sposób, np. pierwszy klient rozmawia z drugim, trzeci z czwartym, itd)
    • Powinna być umożliwiona komunikacja asynchroniczna (tzn. jedna strona może napisać kilka wiadomości pod rząd). Podpowiedź: po akceptacji połączenia program powinien utworzyć dwa procesy potomne, jeden do czytania portu, drugi do pisania

IV. Programowanie gniazd - protokół UDP

  1. Jak zmienia się komunikacja w protokole UDP? Proszę przeanalizować, skompilować i uruchomić programy (źródło):
    • listener.c - program oczekujący na przychodzącą wiadomość
    • talker.c - program umożliwiający wysyłanie wiadomości (wcześniej należy uruchomić program listener, aby wiadomość została odebrana)
  2. W jakich zastosowaniach przydaje się protokół UDP, a w jakich TCP? Jakie są ich zalety i wady?