To jest stara wersja strony!


Programowanie połączeń sieciowych

DO PRZYGOTOWANIA

Instrukcja poniżej jest w trakcie restrukturyzacji. Natomiast materiały do przygotowania, zamieszczone powyżej, nie zmienią się. Można więc już powoli się przygotowywać :-)

WPROWADZENIE

Network Byte Order

  • Zapoznać się z następującymi pojęciami:
    • Network Byte Order
    • Big-Endian
    • Little-Endian
  • Zapoznać się z funkcjami: htonl, htons, ntohl, ntohs

getaddrinfo

  • Przeczytać manual do funkcji getaddrinfo
    • Jakie parametry przyjmuje funkcja i jakie wartości zwraca.
    • Który parametr funkcji może jako wartość przyjąć http lub ftp lub telnet lub smtp?
  • Zwrócić uwagę na opis struktury: struct addrinfo
    • Co oznaczają poszczególne pola struktury i jakie wartości mogą przyjmować.
    • Jaki jest sens wprowadzenia pola ai_next w strukturze?

Obsługa adresów IPv4 oraz IPv6

  • Zapoznać się z następującymi strukturami (man socket, /usr/include):
    • struct sockaddr
    • struct sockaddr_in
    • struct in_addr
    • struct sockaddr_in6
    • struct in6_addr
    • Które z nich dotyczą protokołu IPv4 a które IPv6?
    • Jakie są zależności pomiędzy tymi strukturami?
  • Funkcje konwersji:
    • inet_pton - konwertuje zapis „192.168.1.1” na odpowiednią strukturę - czyli inaczej konwertuje string do reprezentacji binarnej.
    • inet_ntop - konwertuje strukturę (reprezentację binarną) na string.

Gniazda

  • Gniazdo z ang. socket.
  • Są używane w czwartej warstwie sieciowego modelu OSI/ISO.
  • Otwieranie gniazd (i uzyskanie deskryptora do komunikacji sieciowej) dokonuje się za pomocą funkcji
    socket(int domain, int type, int protocol)
    • Adres IP identyfikuje hosta w danej sieci (podsieci), co identyfikuje numer portu?
    • Czym różni się deskryptor gniazda od deskryptora pliku?
  • Istnieje kilka rodzajów socketów w tym:
    • Stream Socket - służą do komunikacji połączeniowej (użycie TCP)
    • datagram Socket - służą do komunikacji bezpołączeniowej (użycie UDP)
  • Powiązanie numeru portu z deskryptorem gniazda dokonuje się za pomocą funkcji
    bind(int sockfd, struct sockaddr *my_addr, int addrlen)
  • Proszę przeczytać manuale dla funkcji socket(2) i bind(2), zwrócić uwagę na parametry jakie przyjmują i wartości jakie zwracają.

Podstawowe funkcje systemowe:

  • socket(2)
  • bind(2)
  • listen(2)
  • accept(2)
  • connect(2)

Połączenie

  • Do nawiązywania połączeń wykorzystujemy funkcję
    connect(int sockfd, struct sockaddr *serv_addr, int addrlen)
    • sockfd - deskryptor gniazda
    • serv_addr - adres hosta docelowego, który możemy otrzymać przy pomocy funckji getaddrinfo
    • addrlen - długość adresu, najczęściej podaje się wartość addrinfo::ai_addrlen
  • Dopiero po pomyślnym nawiązaniu połączenia możemy używać sockfd do komunikowania się z serwerem.

Nasłuchiwanie

  • Rozpoczęcie nasłuchiwania nie wymaga użycia funkcji connect ponieważ to zdalny klient będzie jej używał do połączenia się z naszym serwerem.
  • Nasłuchiwanie można rozpocząć przy pomocy funkcji
    listen(int sockfd, int backlog)
    • sockfd - deskryptor gniazda. Nasłuchiwanie będzie się odbywać zgodnie z parametrami opisywanymi przez deskryptor.
    • backlog - maksymalna liczba połączeń oczekujących na akceptaję.
  • Ostatnim krokiem rozpoczęcia komunikacji z klientem jest akceptacja jego próby połączenia. Dokonuje się tego za pomocą funkcji:
    accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
  • Funkcja accept(2) jako wartość zwraca nowy deskryptor gniazda który służy do komunikacji z akceptowanym połączeniem.

Wysyłanie/odbieranie danych

  • Wszystko w systemach GNU/Linux/Unix jest reprezentowane za pomocą plików - tak więc gniazda również.
  • Wysyłanie/odbieranie danych przez/z gniazd jest bardzo podobne do zapisu/odczytu danych do/z pliku.
  • Jest tak podobne, że do tego celu można użyć funkcji write(2), read(2) :!:
  • Jednak system oferuje funkcje specjalizowane send(2), recv(2) które oferują dodatkową konfigurację.
  • Proszę przeczytać manual dla powyższych funkcji zwracając uwagę na:
    • przyjmowane parametry
    • zwracane wartości

Zamknięcie połączenia

  • Po zakończeniu wysyłania/odbierania danych należy zamknąć połączenie.
  • Zamknięcie połączenia reprezentowanego przez dany deskryptor można dokonać przy pomocy funkcji close(2)
  • Dla zainteresowanych: porównać funkcję close(3) z funkcją shutdown(3).

ĆWICZENIA

Sockety w Bashu

  • Jest możliwe otworzenie Socketa w Bashu za pomocą następującej składni:
    exec {deskryptor-pliku}<>/dev/tcp/{host}/{port}
  • Np. aby otworzyć dwukierunkowego socketa dla strony Google z portem HTTP i deskryptorem nr 3 (dlaczego akurat taki?) należy napisać:
    exec 3<>/dev/tcp/www.google.pl/80
  • Uruchom i przeanalizuj poniższe przykłady:
webpage.sh
#!/bin/bash
 
###
# Połącz się ze stroną internetową i pobierz zawartość strony głównej
### 
 
exec 3<>/dev/tcp/www.google.pl/80
echo -e "GET / HTTP/1.1\nHost: www.google.pl\nConnection: close\n\n" >&3
cat <&3
timeserver.sh
#!/bin/bash
 
###
# Pobierz aktualny czas z serwera NTP
### 
 
cat </dev/tcp/time.nist.gov/13
netscanner.sh
#!/bin/bash
 
###
# Skaner portów (sprawdza które porty są otwarte). Jako argument wywołania podaj adres serwera, który chcesz przeskanować
### 
 
host=$1
port_first=1
port_last=65535
for ((port=$port_first; port<=$port_last; port++))
do
  echo "Skanowanie portu $port..."
  timeout 1 bash -c "(echo >/dev/tcp/$host/$port) >/dev/null 2>&1" && echo "$port otwarty!"
done

Programowanie gniazd

Przeglądnąć artykuł: Beej's Guide to Network Programming

Skompilować i przetestować omówione w nim programy, w tym:

client.c + server.c (uwaga na nr portu!)

listener.c + talker.c (uwaga na nr portu!)

Kompilacja:

gcc -Wall -o server server.c

gethostbyname

Przeanalizować, skompilować i uruchomić program:

#include <stdio.h>
#include <errno.h>
#include <netdb.h>      /* 4 gethostbyname, hostent structure */
#include <unistd.h>     /* 4 exit */
#include <netinet/in.h> /* 4 ntohn */
 
int main(int argc, char *argv[])
{
     int i,j;
     struct hostent *he;
 
     if (argc != 2) 
     {
          fprintf(stderr,"usage: %s hostname\n", argv[0]);
          return 1;
     }
 
     if ((he = gethostbyname(argv[1])) == NULL) 
     {  
          fprintf(stderr, "gethostbyname error\n");
          return 1;
     }
 
     /* host info: */
     printf("\nHost name: %s", he->h_name);
     printf("\nAliases:");
     for(i=0;he->h_aliases[i] != NULL;++i)
          printf("\n%d. %s", i+1, he->h_aliases[i]);
     if(he->h_addrtype == AF_INET)
          printf("\nAddres type: IPv4");
     if(he->h_addrtype == AF_INET6)
          printf("\nAddres type: IPv6");
     printf("\nAddress length: %d bytes", he->h_length);
     printf("\nAddresses:");
     for(j=0;j<he->h_length;++j)
     {
          printf("%d", (uint8_t)he->h_addr[j]);
          if(j < (he->h_length-1))
               printf(".");
     }
     printf("\n");
 
     return 0;
}
  • Sprawdzić działanie programu dla www.google.pl oraz innych wybranych adresów symbolicznych.
  • Do powyższego kodu dopisać instrukcje które szczegółowo sprawdzają typ błędu funkcji gethostbyname i w zależności od tego wyświetlają odpowiedni komunikat.
  • Zmodyfikować tak program aby wyświetlał wszystkie adresy IP odnoszące się do podanego adresu.

Komunikator

Poniżej przedstawiony jest kod programu server.c

/*
** server.c -- a stream socket server demo
*/
 
#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 <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
 
#define MYPORT 3490	// the port users will be connecting to
 
#define BACKLOG 10	 // how many pending connections queue will hold
 
void sigchld_handler(int s)
{
	while(waitpid(-1, NULL, WNOHANG) > 0);
}
 
int main(void)
{
	int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
	struct sockaddr_in my_addr;	// my address information
	struct sockaddr_in their_addr; // connector's address information
	socklen_t sin_size;
	struct sigaction sa;
	int yes=1;
 
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}
 
	if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
		perror("setsockopt");
		exit(1);
	}
 
	my_addr.sin_family = AF_INET;		 // host byte order
	my_addr.sin_port = htons(MYPORT);	 // short, network byte order
	my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
	memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
 
	if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr) == -1) {
		perror("bind");
		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);
	}
 
	while(1) {  // main accept() loop
		sin_size = sizeof their_addr;
		if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
			perror("accept");
			continue;
		}
		printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
		if (!fork()) { // this is the child process
			close(sockfd); // child doesn't need the listener
			if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
				perror("send");
			close(new_fd);
			exit(0);
		}
		close(new_fd);  // parent doesn't need this
	}
	return 0;
}
  • Należy przerobić powyższy program tak aby działał jako server. Łącząc się za pomocą np. programu telnet program powinien umożliwiać prowadzenie dialogu jak popularne komunikatory internetowe (np. gg, tlen, itp).
  • Podpowiedź po akceptacji połączenia program powinien utworzyć dwa procesy potomne, jeden do czytania portu, drugi do pisania.

Transfer plików

  • Powyższy komunikator może być z łatwością zmodyfikowany aby zamiast tekstu wysyłał plik.
  • Zmodyfikować komunikator tak aby po nawiązaniu połączenia przez klienta rozpoczął wysyłanie pliku.
  • Zmodyfikować program client.c tak aby był zdolny odebrać i zapisać przesyłany plik.

Użycie Select

Dla poszerzenia wiedzy

pl/dydaktyka/unix/lab_prog_siec.1481820925.txt.gz · ostatnio zmienione: 2019/06/27 15:55 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0