Programowanie połączeń sieciowych

DO PRZYGOTOWANIA

  • należy sobie przypomnieć użycie fork()

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 na wyższej, czwartej warstwie sieciowego modelu OSI/ISO.
  • Otwieranie ganiazd dokonuje się za pomocą funkcji socket(int domain, int type, int protocol).
    • Proszę przeczytać manual funkcji (man socket(3))
    • Adres IP identyfikuje hosta w danej sieci (podsieci), co identyfikuje numer portu
    • Czym różni się deskryptor gniazda od deskryptora pliku?
  • Odczytanie numeru portu na podstawie deskryptora dokonuje się za pomocą funkcji bind(int sockfd, struct sockaddr *my_addr, int addrlen).
    • Przeczytać manual dla funkcji bind(3) zwrócić uwagę na parametry jakie przyjmuje i wartości jakie zwraca.

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(3) 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 funckje specjalizowane send(3), recv(3) 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(3)
  • Dla zainteresowanych: porównać funkcję close(3) z funkcją shutdown(3).

ĆWICZENIA

Telnet i usługi sieciowe

SMTP

POP3

HTTP

 cd ; mkdir public_html ; chmod a+rx public_html ; chmod o+x . ; echo "Jestem $USER" > public_html/index.html
  • oglądnąć własną stronę przez: telnet student/borg.ia.agh.edu.pl/~user 80
  • oglądnąć stronę AGH przez: telnet www.agh.edu.pl 80

Wprowadzenie do gniazd

Przeczytać artykuł BSD Sockets: A Quick And Dirty Primer uruchamiając podane w nim fragmenty kodu.

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

Last modified: 2009/05/27 10:11