====== Programowanie połączeń sieciowych ======
===== DO PRZYGOTOWANIA =====
* Proszę przypomnieć sobie użycie ''[[http://home.agh.edu.pl/~gjn/dydaktyka/UGLX/node11.html#SECTION000117000000000000000|fork()]]''
* Proszę poczytać opis podstaw gniazd sieciowych (socketów) z [[https://web.archive.org/web/20170405054449/http://www.linuxpl.org/LPG/node1.html|The Linux Programmer's Guide]]. Konkretnie sekcję [[https://web.archive.org/web/20170305021856/http://www.linuxpl.org:80/LPG/node81.html|Gniazda sieciowe - podstawy]] oraz jej podsekcje: "Podstawowe funkcje", "TCP: SOCK_STREAM", "SOCK_DGRAM (UDP)", "PF_UNIX", "SOCK_RAW i PF_PACKET"
===== 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: {{ sockets-comm.png}}
* ''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:
#!/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
#!/bin/bash
###
# Pobierz aktualny czas z serwera NTP
###
cat
#!/bin/bash
###
# Skaner portów (sprawdza które porty są otwarte).
# Jako argument wywołania podaj adres serwera, który chcesz przeskanować,
# np. ./port-scanner.sh localhost
###
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ł:
[[http://beej.us/guide/bgnet/html/single/bgnet.html|Beej's Guide to Network Programming]]
Skompilować i przetestować omówione w nim programy, w tym:
{{:pl:dydaktyka:unix:client.c}} + {{:pl:dydaktyka:unix:server.c}} (uwaga na nr portu!)
{{:pl:dydaktyka:unix:listener.c}} + {{:pl:dydaktyka:unix:talker.c}} (uwaga na nr portu!)
Kompilacja:
gcc -Wall -o server server.c
==== gethostbyname ====
- Proszę przeanalizować, skompilować i uruchomić program:
#include
#include
#include /* 4 gethostbyname, hostent structure */
#include /* 4 exit */
#include /* 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;jh_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.yahoo.com'' oraz innych wybranych adresów symbolicznych.
- 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.
==== Serwer ====
Poniżej przedstawiony jest kod programu //server.c//
/*
** server.c -- a stream socket server demo
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
}
sin_size = sizeof their_addr;
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
perror("accept");
}
printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
perror("send");
sleep(5); // just for observing easily that the server cannot serve a few clients concurrently
close(new_fd);
return 0;
}
=== - Komunikator ===
* 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).
* Korzystając z programu telnet można połączyć się z serwerem wpisując: $ telnet remotehostname XXX
gdzie ''remotehostname'' jest nazwą komputera, na którym uruchomiono serwer (''localhost'', jeżeli to ta sama maszyna), a ''XXX'' numerem przypisanego do niego portu.
* Podpowiedź: po akceptacji połączenia program powinien utworzyć dwa procesy potomne, jeden do czytania portu, drugi do pisania.
=== - Eternal vigilance ==
* Proszę zmodyfikować serwer tak, aby po obsłużeniu klienta nie kończył działania, ale powracał do oczekiwania na kolejne połączenie.
=== - Obsługa wielu klientów ===
* 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.
===== Dla poszerzenia wiedzy =====
* [[http://tools.ietf.org/|IETF]]
* [[http://www.kohala.com/start/|W. Richard Stevens' Home Page]]
* BSD sockets
* http://www.frostbytes.com/~jimf/papers/sockets/sockets.html
* http://www.lowtek.com/sockets/
* http://beej.us/guide/bgnet/
* http://www.uwo.ca/its/doc/courses/notes/socket/
* http://gaia.cs.umass.edu/ntu_socket/
* http://www.devdaily.com/Dir/Unix/Socket_Programming/
* http://www.unl.csi.cuny.edu/faqs/sock-faq/html/unix-socket-faq.html
* http://www.ibm.com/developerworks/linux/library/l-sockpit/
* OpenSSL
* http://www.ibm.com/developerworks/linux/library/l-openssl.html
* http://www.ibm.com/developerworks/linux/library/l-openssl2.html
* http://www.ibm.com/developerworks/linux/library/l-openssl3.html
* http://www.ibm.com/developerworks/linux/library/l-hisock.html
* TCP/IP
* http://userpages.umbc.edu/~jeehye/cmsc491b/lectures/tcpstate/sld001.htm
* http://www.tcpipguide.com/free/
* http://www.ipprimer.com/overview.cfm
* http://www.linux-tutorial.info/modules.php?name=MContent&obj=page&pageid=142
* Użycie Select
* [[http://www.lowtek.com/sockets/select.html|The World of select()]]