(artykuł)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
dział serwisuArtykuły
kategoriaInne artykuły
artykuł[C++] Programowanie sieciowe pod Linuksem
Autor: 'chondipo'

[C++] Programowanie sieciowe pod Linuksem

[artykuł]

Wstęp

Artykuł ten ma na celu przekazanie wstępnych wiadomości z programowania sieciowego. W artykule zostaną przedstawione najprostsze programy wykorzystujące socket-y. Tak, by po przeczytaniu artykułu można było przejść do pisania czegoś własnego. Przede wszystkim zawsze najważniejszy jest start, dlatego postaram się przedstawić wszystko możliwie w, jak najprostszy sposób, tak by wszystko było czytelne i jasne. Teoria zostanie przedstawiona w taki sposób, by zrozumieć podstawowe pojęcia typu socket, protokół TCP/IP, UDP. Na początek przedstawię różnicę pomiędzy protokołem UDP i TCP/IP,  ponieważ jest to jedno najważniejszych pojęć, jakie warto poznać.

Wymagania

Język C jest podstawą do tego, by zrozumieć programy przedstawione w tym artykule. Są one bardzo proste, ponieważ przedstawiają podstawową implementacje np. protokołu TCP/IP. Warto posiadać, także podstawowe informacje teoretyczne z sieci, ponieważ w tym artykule zostaną przedstawione tylko niektóre w ogólnej postaci.

Platforma

Przedstawione programy zawarte w artykule napisałem pracując na linux-ie. Jeśli chodzi o pisanie na platformę Windows to funkcje, sposób ich wywołania minimalnie różni się od linuks-owych.

Start – teoria

UDP (User datagram protocol)

Jest to protokół w warstwie transportowej, który charakteryzuję się szybkością, jest zdecydowanie szybszy od TCP/IP. Przede wszystkim dlatego, że nie otrzymujemy potwierdzenia ze strony serwera po wysłaniu pakietu. UDP nie nawiązuje połączenia z serwerem, tylko od razu wysyła pakiety. Wadą UDP jest to, że nie ma pewności, że pakiet dotrze do serwera. Tutaj pakiety wysyłam praktycznie non-stop. UDP stosujemy np. w grach komputerowych np. typu FPS. W tym momencie ważne jest to, żeby akcja działa się możliwie jak najszybciej, pakiety muszą być przesyłane z klienta do serwera tak szybko jak tylko to możliwe. Staramy się zawsze przesłać możliwie jak najmniejszą paczkę, po to by niepotrzebnie nie zajmować łącza. Jeżeli jakiś pakiet nie dojdzie to nie czekamy na niego.

Gniazda w protokole UDP są nieblokujące.

TCP/IP (Transmission Control Protocol)

W TCP zaczynamy od połączenia z serwerem. Musimy z nim jawnie nawiązać połączenie. Tak więc wysyłamy do serwera żądanie połączenia i czekamy, aż serwer odpowie, wysyłając potwierdzenie (ACK) o połączeniu. W TCP mamy pewność, że pakiet dotrze do serwera, niekoniecznie szybko, ale dotrze. TCP/IP stosujemy w sytuacjach, gdy np. chcemy napisać komunikator. Tutaj nie jest ważna prędkość, pakiet dotrze i tak szybko, ale ważne jest to, żeby pakiet z wiadomością na pewno dotarł do adresata.

Gniazdo (Socket)

Trudno jest zdefiniować, co to tak naprawdę jest gniazdo. Programując sieciowo będziemy programować na socket-ach. Wszystko stanie się jasne, gdy przejdziemy do pierwszego programu i zdefiniujemy pierwszy socket.
Krótka definicja wygląda następująco.
Gniazdo pojęcie abstrakcyjne reprezentujące dwukierunkowy punkt końcowy połączenia. Dwukierunkowość oznacza możliwość wysyłania i przyjmowania danych. Wykorzystywane jest przez aplikacje do komunikowania się przez sieć.

Struktura sockaddr_in

Jednym z podstawowych elementów jest struktura sockaddr_in, która wygląda następująco:

1.1)

C/C++
struct sockaddr_in
{
    short sin_family; // AF_INET (IPv4), AF_INET6 (IPv6)
    unsigned short sin_port; // port – htons()
    struct in_addr sin_addr; // adres - inet_pton()
    char sin_zero[ 8 ];
    sockaddr
};

2.1)

C/C++
struct sin_addr
{
    unsigned long s_addr;
}

Zaczynamy od wypełnienia struktury sockaddr_in (1.1) :
ArgumentOpis
sin_familyadres IP (w wersji 4 lub 6)
sin_portport
sin_addradres IP
sin_zeronie będziemy wypełniać

Jak widać adres IP przypisujemy do zagnieżdżonej struktury in_addr (2.1). Potrzebne nagłówki do użycia sockkaddr_in to
#include <netinet/in.h>

Przykład użycia


C/C++
struct sockaddr_in servaddr;

// wyzerowanie przydzielonej pamięci
bzero( & servaddr, sizeof( servaddr ) );

// IP w wersji 4
servaddr.sin_family = AF_INET;

/*
AF_INET – makro, które mówi o tym, że będziemy korzystali z adresów IP
w wersji 4.
AF_INET6 – adresy IP w wersji 6.
*/

// przypisanie numeru portu na 1001

servaddr.sin_port = htons( 1001 );

// 1001 – to przykładowa wartość. Należy pamiętać o tym, aby wybrać wolny port, który nie jest zajęty.

// dwa sposoby przypisania adresu IP :
// a) dowolnego adresu wybranego przez system operacyjny:
( INADDR_ANY ) servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
// b) konkretnego adresu
inet_pton( AF_INET, "127.0.0.1", & servaddr.sin_addr );

|> Wyjaśnienie użytych powyżej funkcji znajduje się w dalszej części artykułu.

Struktura sockaddr

Druga struktura, równie ważna. Do niej rzutujemy strukturę sockaddr_in np. w funkcji bind(), o której trochę więcej poniżej w artykule.

2.2)

C/C++
struct sockaddr
{
    unsigned short sa_family; // AF_INET (IPv4), AF_INET6 (IPv6)
    char sa_data[ 14 ]; // adres sieciowy
};

Funkcje I


  • C/C++
    uint16_t htons( uint16_t hostshort );
    Pozwala na przypisanie podanego portu do sin_port. Inaczej - przekształca wartość short integer hostshort z lokalnego na sieciowy porządek bajtów.

    Argumenty:
    ArgumentOpis
    uint16_t hostshortnumer portu, który chcemy przekonwertować

    Przykład użycia:
    C/C++
    struct sockaddr_in serwer;
    serwer.sin_port = htons( SERWER_PORT );



  • C/C++
    int inet_pton( int af, const char * src, void * dst );
    Pozwala na przypisanie adresu IP do sin_addr. Inaczej - konwersja tablicy znaków zwierająca adres IP do reprezentacji sieciowej.

    Argumenty:
    ArgumentOpis
    int afrodzina adresów AF_INET (IPv4), AF_INET6 (IPv6)
    const char *srcadres IP zródła
    void *dstmiejsce przeznaczenia adresu IP

    Przykład użycia:
    C/C++
    struct sockaddr_in serwer;
    inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr );



  • C/C++
    const char * inet_ntop( int af, const void * src, char * dst, socklen_t size );
    Konwersja adresu IP z reprezentacji sieciowej do tablicy znaków

    Argumenty:
    ArgumentOpis
    int afrodzina adresów AF_INET (IPv4), AF_INET6 (IPv6)
    const void *srcadres IP zródła
    char *dstmiejsce przeznaczenia adresu IP
    socklen_t sizerozmiar socketa

    Przykład użycia:
    C/C++
    struct sockaddr_in from;
    char bufor_ip[ 128 ];
    inet_ntop( AF_INET, & from.sin_addr, bufor_ip, sizeof( bufor_ip ) );



  • C/C++
    uint32_t htonl( uint32_t hostlong );
    Pozwala na przypisanie adresu IP do sin_addr.s_addr

  • C/C++
    uint16_t ntohs( uint16_t netshort );
    Konwersja portu z reprezentacji sieciowej

  • C/C++
    void bzero( void * s, size_t n );
    Zeruje pierwszych n bajtów obszaru zaczynającego się pod adresem s

    Przykład użycia:
    C/C++
    struct sockaddr_in serwer;
    bzero( & serwer, sizeof( serwer ) );



  • C/C++
    int bind( int sockfd, struct sockaddr * my_addr, int addrlen );
    Powiązanie gniazda z adresem IP oraz portem zdefiniowanym w strukturze sockaddr_in.

    Argumenty:
    ArgumentOpis
    int sockfddeskryptor gniazda, który chcemy powiązać ze strukturą
    struct sockaddr *my_addrstruktura zawierająca adres IP oraz port
    int addrlenwielkość struktury

    Wartość zwracana:
    • w przypadku powodzenia: 0
    • w przypadku błędu: -1

    Przykład użycia:
    C/C++
    struct sockaddr_in serwer;

    socklen_t len = sizeof( serwer );
    bind( gniazdo,( struct sockaddr * ) & serwer, len );
    lub
    C/C++
    bind( gniazdo,( struct sockaddr * ) & serwer, sizeof( struct sockaddr ) );



  • C/C++
    int socket( int domain, int type, int protocol );
    Tworzy gniazdo do komunikacji sieciowej

    Przykład użycia:
    C/C++
    int gniazdo = 0;
    gniazdo = socket( AF_INET, SOCK_STREAM, 0 ) );



  • C/C++
    int connect( int sockfd, const struct sockaddr * serv_addr, socklen_t addrlen );
    Nawiązanie połączenia przez gniazdo (klienta) w przypadku protokołu TCP. W protokole UDP pomijamy ten krok.

    Argumenty:
    ArgumentOpis
    int sockfddeskryptor gniazda przez które chcemy się połączyć
    const struct sockaddr *serv_addrstruktura zawierająca docelowy adres IP oraz port
    socklen_t addrlenwielkość struktury

    Wartość zwracana:
    • w przypadku powodzenia: 0
    • w przypadku błędu: -1

    Przykład użycia:
    C/C++
    struct sockaddr_in serwer;
    int gniazdo;

    socklen_t len = sizeof( serwer );
    connect( gniazdo,( struct sockaddr * ) & serwer, len );



  • C/C++
    int listen( int sockfd, int backlog );
    Przełączenie gniazda w tryb nasłuchiwania na połączenia w przypadku protokołu TCP. W protokole UDP ten krok pomijamy.

    Argumenty:
    ArgumentOpis
    int sockfddeskryptor gniazda, na którym oczekujemy połączeń
    int backlogmaksymalna ilość równoczesnych połączeń oczekujących w kolejce do zaakceptowania

    Wartość zwracana:
    • w przypadku powodzenia: 0
    • w przypadku błędu: -1

    Przykład użycia:
    C/C++
    #define MAX_CONNECTION 10
    int gniazdo;
    listen( gniazdo, MAX_CONNECTION );



  • C/C++
    int accept( int s, struct sockaddr * addr, socklen_t * addrlen );
    Odbieranie połączeń z gniazda podobnie jak wyżej tylko w protokole TCP.

    Argumenty:
    ArgumentOpis
    int sdeskryptor gniazda, na którym oczekujemy połączeń
    struct sockaddr *addrwskaźnik do struktury sockaddr_in, w której zostaną zapisane informacje o odebranym połączeniu (adres IP i port)
    socklen_t *addrlenrozmiar struktury sockaddr_in

    Wartość zwracana:
    • w przypadku powodzenia: deskryptor gniazda odebranego połączenia
    • w przypadku błędu: -1

    Przykład użycia:
    C/C++
    struct sockaddr_in serwer;
    socklen_t len = sizeof( serwer );
    struct sockaddr_in from;
    accept( gniazdo,( struct sockaddr * ) & from, & len );



  • C/C++
    ssize_t recv( int sockfd, void * buf, size_t len, int flags );
    Odbieranie danych z gniazda – protokół TCP.

    Argumenty:
    ArgumentOpis
    int sockfddeskryptor gniazda, z którego chcemy odebrać dane
    void *bufbufor, do którego zostaną zapisane odczytane dane
    size_t lenwielkość bufora
    int flagsokreśla szczegółowe zachowanie. Ustawiamy na 0.

    Wartość zwracana:
    • w przypadku powodzenia: ilość odebranych bajtów
    • gdy druga strona zamknęła połączenie: 0
    • w przypadku błędu -1

    Przykład użycia:
    C/C++
    int gniazdo_clienta = 0;
    char bufor[ MAX_MSG_LEN ];
    recv( gniazdo_clienta, bufor, sizeof( bufor ), 0 );



  • C/C++
    ssize_t send( int sockfd, const void * buf, size_t len, int flags );
    Wysyłanie danych przez gniazdo – protokół TCP.

    Argumenty:
    ArgumentOpis
    int sockfddeskryptor gniazda, do którego chcemy wysłać
    const void *bufbufor z danymi, które zostaną wysłane
    size_t lenwielkość bufora
    int flagsokreśla szczegółowe zachowanie. Ustawiamy na 0.

    Wartość zwracana:
    • w przypadku powodzenia: ilość wysłanych bajtów
    • gdy druga strona zamknęła połączenie: 0
    • w przypadku błędu: -1

    Przykład użycia:
    C/C++
    int gniazdo_clienta = 0;
    char bufor[ MAX_MSG_LEN ];
    send( gniazdo_clienta, bufor, strlen( bufor ), 0 );



  • C/C++
    int shutdown( int sockfd, int flag )
    Zamknięcie połączenia na danym gnieździe.

    Argumenty:
    ArgumentOpis
    int sockfddeskryptor gniazda połączenia, które chcemy zamknąć
    int flagsposób zamknięcia gniazda:
    • SHUT_RD - 0 (dalszy odbiór danych jest zabroniony)
    • SHUT_WR - 1 (dalszy zapis danych jest zabroniony)
    • SHUT_RDRW - 2 (dalszy odbiór i zapis danych jest zabroniony)

    Wartość zwracana:
    • w przypadku powodzenia: 0
    • w przypadku błędu: -1

    Przykład użycia:
    C/C++
    int gniazdo = 0;
    shutdown( gniazdo, SHUT_RDWR );

Pozostały jeszcze metody recvfrom(), sendto(). Omówimy je jak przejdziemy do implementowania protokołu UDP.
W przykładowych programach, będzie przedstawiony sposób użycia większości z powyższych funkcji.
Podstawy z części teoretycznej mamy już za sobą. Czas na jakąś praktykę. Jeszcze mała uwaga: korzystając z Linuksa zachęcam do korzystania z manuala, z którego można wyciągnąć dużo informacji.

$ man sendto

Zaczniemy od napisania dwóch programów klienta i serwera, które będą nawzajem do siebie wysyłały jakieś pakiety. Na początek proponuję napisać programy wykorzystujące protokół TCP/IP. Potem można spróbować to samo napisać wykorzystując UDP. Na koniec spróbować połączyć dwa protokoły i przykładowo wybór protokołu pozostawić osobie włączającej klienta i serwera z wiersza poleceń np. za pomocą argc i argv. Programy będziemy pisali w języku C.

Programy I


Protokół TCP


a) Podstawowy client – server


// client.c:

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 8888
#define SERWER_IP "127.0.0.1"

int main()
{
    struct sockaddr_in serwer;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( bufor, sizeof( bufor ) );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( SERWER_PORT );
    if( inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    if( connect( gniazdo,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "connect() ERROR" );
        exit( - 1 );
    }
   
    strcpy( bufor, "Wyslane z clienta" );
    if(( send( gniazdo, bufor, strlen( bufor ), 0 ) ) <= 0 )
    {
        perror( "send() ERROR" );
        exit( - 1 );
    }
   
    bzero( bufor, sizeof( bufor ) );
    if(( recv( gniazdo, bufor, sizeof( bufor ), 0 ) ) <= 0 )
    {
        perror( "recv() ERROR" );
        exit( - 1 );
    }
    printf( "|Wiadomosc z serwera|: %s \n", bufor );
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc client.c -g -Wall -o client && ./client

// server.c :

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 8888
#define SERWER_IP "127.0.0.1"
#define MAX_CONNECTION 10

int main()
{
    struct sockaddr_in serwer;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( bufor, sizeof( bufor ) );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( SERWER_PORT );
    if( inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    if( bind( gniazdo,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "bind() ERROR" );
        exit( - 1 );
    }
   
    if( listen( gniazdo, MAX_CONNECTION ) < 0 )
    {
        perror( "listen() ERROR" );
        exit( - 1 );
    }
   
    while( 1 )
    {
        struct sockaddr_in from;
        int gniazdo_clienta = 0;
        bzero( & from, sizeof( from ) );
        printf( "Waiting for connection...\n" );
        if(( gniazdo_clienta = accept( gniazdo,( struct sockaddr * ) & from, & len ) ) < 0 )
        {
            perror( "accept() ERROR" );
            continue;
        }
       
        bzero( bufor, sizeof( bufor ) );
        if(( recv( gniazdo_clienta, bufor, sizeof( bufor ), 0 ) ) <= 0 )
        {
            perror( "recv() ERROR" );
            exit( - 1 );
        }
        printf( "|Wiadomosc od clienta|: %s \n", bufor );
        char bufor_ip[ 128 ];
        bzero( bufor_ip, sizeof( bufor_ip ) );
        printf( "|Client ip: %s port: %d|\n", inet_ntop( AF_INET, & from.sin_addr, bufor_ip, sizeof( bufor_ip ) ), ntohs( from.sin_port ) );
        printf( "  New connection from: %s:%d\n", inet_ntoa( from.sin_addr ), ntohs( from.sin_port ) );
       
        bzero( bufor, sizeof( bufor ) );
        strcpy( bufor, "Wyslane z serwera" );
        if(( send( gniazdo_clienta, bufor, strlen( bufor ), 0 ) ) <= 0 )
        {
            perror( "send() ERROR" );
            exit( - 1 );
        }
       
        shutdown( gniazdo_clienta, SHUT_RDWR );
    }
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc server.c -g -Wall -o server && ./server

B) Client z funkcją bind()


// client.c

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 8888
#define SERWER_IP "127.0.0.1"

int main()
{
    struct sockaddr_in serwer, client;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( & client, sizeof( client ) );
    bzero( bufor, sizeof( bufor ) );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( SERWER_PORT );
    if( inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    client.sin_family = AF_INET;
    client.sin_port = htons( 44444 ); // client.sin_port=htons(SERWER_PORT);
    if( inet_pton( AF_INET, "127.0.0.1", & client.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    if( bind( gniazdo,( struct sockaddr * ) & client, len ) < 0 )
    {
        perror( "bind() ERROR" );
        exit( - 1 );
    }
   
    if( connect( gniazdo,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "connect() ERROR" );
        exit( - 1 );
    }
   
    strcpy( bufor, "Wyslane z clienta" );
    if(( send( gniazdo, bufor, strlen( bufor ), 0 ) ) <= 0 )
    {
        perror( "send() ERROR" );
        exit( - 1 );
    }
   
    bzero( bufor, sizeof( bufor ) );
    if(( recv( gniazdo, bufor, sizeof( bufor ), 0 ) ) <= 0 )
    {
        perror( "recv() ERROR" );
        exit( - 1 );
    }
    printf( "|Wiadomosc z serwera|: %s \n", bufor );
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc client.c -g -Wall -o client && ./client

C ) Gniazda nieblokujące


Do tej pory metody accept() oraz recv() blokowały program do czasu, aż nie otrzymały danych z gniazda. Istnieje jednak możliwość, aby gniazda połączeniowe (TCP) pracowały w trybie nieblokującym. Funkcje accept() oraz recv() zwrócą wartość -1 w przypadku braku danych.
Aby utworzyć gniazda nieblokujące w protokole komunikacyjnym TCP/IP będziemy potrzebowali minimalnie zmodyfikować program client-a oraz server-a. A mianowicie :

- client.c

Musimy dodać odpowiednią flagę w funkcji send(). Tę modyfikacje będziemy również potrzebowali zmienić w programie serwera.

C/C++
if( send( gniazdo, bufor, strlen( bufor ), MSG_DONTWAIT ) <= 0 )
{
    perror( "send() ERROR" );
    exit( - 1 );
}

- server.c

Poza tym, co wprowadziliśmy w programie klienta, co również trzeba zmienić w programie serwera, należy dodać wywołanie funkcji fcntl():

C/C++
int fcntl( int fd, int cmd, struct flock * lock );

- funckja fcntl() dokonuje jednej z wielu różnych operacji na fd. Wykonywana operacja zdeterminowana jest przez cmd. Dla naszych potrzeb funkcja ta będzie nam potrzebna do przełączenia gniazda w tryb nieblokujący.

Przykład użycia


C/C++
if( fcntl( gniazdo, F_SETFL, O_NONBLOCK ) < 0 )
{
    perror( "fcntl() ERROR" );
    exit( - 1 );
}

Również zawartość pętli while() ulegnie minimalnej zmianie:

C/C++
while( 1 )
{
    if(( gniazdo_clienta = accept( gniazdo,( struct sockaddr * ) & from, & len ) ) < 0 )
    {
        //perror("accept() ERROR");
        continue;
    }
    shutdown( gniazdo_clienta, SHUT_RDWR );
}

Trzeba również pamiętać o dodaniu nowego nagłówka :

#include <sys/fcntl.h>

Poniżej programy z wprowadzonymi modyfikacjami na nieblokujące gniazda. Należy zwrócić uwagę, iż tym razem adres IP oraz numer portu podawane są z linii komend w momencie wywoływania programu :

// client.c :

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 2048
#define SERWER_IP "127.0.0.1"

int main( int argc, char ** argv )
{
    struct sockaddr_in serwer;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( bufor, sizeof( bufor ) );
   
    const char * ip = argv[ 1 ];
    uint16_t port = atoi( argv[ 2 ] );
    printf( "%s   %s   %s\n", argv[ 0 ], argv[ 1 ], argv[ 2 ] );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( port );
    if( inet_pton( AF_INET, ip, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    if( connect( gniazdo,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "connect() ERROR" );
        exit( - 1 );
    }
   
    strcpy( bufor, "Wyslane z clienta" );
    if(( send( gniazdo, bufor, strlen( bufor ), MSG_DONTWAIT ) ) <= 0 ) // MSG_DONTWAIT
    {
        perror( "send() ERROR" );
        exit( - 1 );
    }
   
    bzero( bufor, sizeof( bufor ) );
    if(( recv( gniazdo, bufor, sizeof( bufor ), 0 ) ) <= 0 )
    {
        perror( "recv() ERROR" );
        exit( - 1 );
    }
    printf( "|Wiadomosc z serwera|: %s \n", bufor );
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc client.c -g -Wall -o client && ./client 127.0.0.1 2048

// serwer.c :

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>


#include <sys/fcntl.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 2048
#define SERWER_IP "127.0.0.1"
#define MAX_CONNECTION 10

int main()
{
    struct sockaddr_in serwer;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( bufor, sizeof( bufor ) );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( SERWER_PORT );
    if( inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    if( fcntl( gniazdo, F_SETFL, O_NONBLOCK ) < 0 ) // fcntl()
    {
        perror( "fcntl() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    if( bind( gniazdo,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "bind() ERROR" );
        exit( - 1 );
    }
   
    if( listen( gniazdo, MAX_CONNECTION ) < 0 )
    {
        perror( "listen() ERROR" );
        exit( - 1 );
    }
   
    while( 1 )
    {
        struct sockaddr_in from;
        int gniazdo_clienta = 0;
        bzero( & from, sizeof( from ) );
        //printf("Waiting for connection...\n");
        if(( gniazdo_clienta = accept( gniazdo,( struct sockaddr * ) & from, & len ) ) < 0 )
        {
            //perror("accept() ERROR");
            continue;
        }
       
        bzero( bufor, sizeof( bufor ) );
        if(( recv( gniazdo_clienta, bufor, sizeof( bufor ), 0 ) ) <= 0 )
        {
            perror( "recv() ERROR" );
            exit( - 1 );
        }
        printf( "|Wiadomosc od clienta|: %s \n", bufor );
        char bufor_ip[ 128 ];
        bzero( bufor_ip, sizeof( bufor_ip ) );
        printf( "|Client ip: %s port: %d|\n", inet_ntop( AF_INET, & from.sin_addr, bufor_ip, sizeof( bufor_ip ) ), ntohs( from.sin_port ) );
        printf( "  New connection from: %s:%d\n", inet_ntoa( from.sin_addr ), ntohs( from.sin_port ) );
       
        bzero( bufor, sizeof( bufor ) );
        strcpy( bufor, "Wyslane z serwera" );
        if(( send( gniazdo_clienta, bufor, strlen( bufor ), MSG_DONTWAIT ) ) <= 0 )
        {
            perror( "send() ERROR" );
            exit( - 1 );
        }
       
        shutdown( gniazdo_clienta, SHUT_RDWR );
    }
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc server.c -g -Wall -o server && ./server

Myślę, ze nadszedł czas na przejście do protokołu UDP. Żeby wysłać bądź odebrać dane w UDP używamy metod sendto() oraz recvfrom(). Są to jedne
z podstawowych funkcji. Poniżej zamieszczam opis podanych funkcji.

Funkcje II :


1)

C/C++
ssize_t sendto( int sockfd, const void * buf, size_t len, int flags, const struct sockaddr * dest_addr, socklen_t addrlen );

- wysyłanie wiadomości do gniazda

Argumenty funkcji :
    int sockfd - deskryptor gniazda, do którego chcemy wysłać                 
    const void * buf – bufor z danymi, które będą wysłane
    size_t  len – rozmiar bufora
    int flags – specjalne zachowanie, my będziemy ustawiać na 0
    const struct sockaddr * dest_addr - wskaźnik do struktury, w której znajdują się informacje o przeznaczeniu    pakietu (adres IP oraz port)
    socklen_t addrlen - wielkość struktury sockaddr addrlen

Wartość zwracana :

a)w przypadku powodzenia
    - ilość wysłanych bajtów
b)w przypadku błędu
    - -1

Przykład użycia :


    int gniazdo;
    char bufor[MAX_MSG_LEN];
    struct sockaddr_in serwer;
    socklen_t len = sizeof(serwer);
    sendto(gniazdo, bufor, strlen(bufor), 0, (struct sockaddr*)&serwer, len);

2)

C/C++
ssize_t recvfrom( int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, socklen_t * addrlen );

-odebranie wiadomości z gniazda

Argumenty:
    int sockfd - deskryptor gniazda, z którego chcemy odebrać dane (adres, port)
    void *buf – bufor na odebrane dane
    size_t len – rozmiat bufora
    int flags – specjalne zachowanie, my będziemy ustawiac na 0
    struct sockaddr *src_addr - wskaźnik do struktury, w której zostaną zapisane informacje o odebranym połączeniu           (adres, port)
    socklen_t *addrlen – rozmiar struktury sockaddr

Przykład użycia :


    int gniazdo;
    char bufor[MAX_MSG_LEN];
    struct sockaddr_in from;
    struct sockaddr_in serwer;
    socklen_t len = sizeof(serwer);
    recvfrom(gniazdo, bufor, sizeof(bufor), 0, (struct sockaddr*)&from, &len);


Teraz chciałbym przedstawić gotowe programy client-a oraz server-a, gdzie będziemy wykorzystywać przedstawione wyżej funkcje.

Programy II :


Protokół UDP :


// client.c :

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 8888
#define SERWER_IP "127.0.0.1"

int main()
{
    struct sockaddr_in serwer;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( bufor, sizeof( bufor ) );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( SERWER_PORT );
    if( inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    strcpy( bufor, "Wyslane z clienta" );
    if( sendto( gniazdo, bufor, strlen( bufor ), 0,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "sendto() ERROR" );
        exit( - 1 );
    }
   
    struct sockaddr_in from;
    bzero( & from, sizeof( from ) );
    bzero( bufor, sizeof( bufor ) );
    if( recvfrom( gniazdo, bufor, sizeof( bufor ), 0,( struct sockaddr * ) & from, & len ) < 0 )
    {
        perror( "recvfrom() ERROR" );
        exit( - 1 );
    }
    printf( "|Wiadomosc z serwera|: %s \n", bufor );
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc client.c -g -Wall -o client && ./client

// server.c :

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_MSG_LEN 4096
#define SERWER_PORT 8888
#define SERWER_IP "127.0.0.1"


int main()
{
    struct sockaddr_in serwer;
    int gniazdo;
    char bufor[ MAX_MSG_LEN ];
   
    bzero( & serwer, sizeof( serwer ) );
    bzero( bufor, sizeof( bufor ) );
   
    serwer.sin_family = AF_INET;
    serwer.sin_port = htons( SERWER_PORT );
    if( inet_pton( AF_INET, SERWER_IP, & serwer.sin_addr ) <= 0 )
    {
        perror( "inet_pton() ERROR" );
        exit( - 1 );
    }
   
    if(( gniazdo = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
    {
        perror( "socket() ERROR" );
        exit( - 1 );
    }
   
    socklen_t len = sizeof( serwer );
    if( bind( gniazdo,( struct sockaddr * ) & serwer, len ) < 0 )
    {
        perror( "bind() ERROR" );
        exit( - 1 );
    }
   
    while( 1 )
    {
        struct sockaddr_in from;
        bzero( & from, sizeof( from ) );
        bzero( bufor, sizeof( bufor ) );
       
        printf( "Waiting for connection...\n" );
        if( recvfrom( gniazdo, bufor, sizeof( bufor ), 0,( struct sockaddr * ) & from, & len ) < 0 )
        {
            perror( "recvfrom() ERROR" );
            exit( - 1 );
        }
        printf( "|Wiadomosc od clienta|: %s \n", bufor );
        char bufor_ip[ 128 ];
        bzero( bufor_ip, sizeof( bufor_ip ) );
        printf( "|Client ip: %s port: %d|\n", inet_ntop( AF_INET, & from.sin_addr, bufor_ip, sizeof( bufor_ip ) ), ntohs( from.sin_port ) );
        //printf("  New connection from: %s:%d\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
       
        bzero( bufor, sizeof( bufor ) );
        strcpy( bufor, "Wyslane z serwera" );
        if( sendto( gniazdo, bufor, strlen( bufor ), 0,( struct sockaddr * ) & from, len ) < 0 )
        {
            perror( "sendto() ERROR" );
            exit( - 1 );
        }
    }
   
    shutdown( gniazdo, SHUT_RDWR );
   
    return 0;
}

// gcc server.c -g -Wall -o server && ./server

Funkcje III :


1)

C/C++
struct hostent * gethostbyname( const char * nazwa );

- konwersja nazwy hosta do adresu IP (tylko IPv4)

Argumenty:
const char *nazwa - nazwa hosta do konwersji na adres IP

Wartość zwracana:
    a) w przypadku powodzenia
        - wskaźnik do struktury zawierającej dane hosta
    b) w przypadku błędu
        - wartość NULL

Przykład użycia :


    gethostbyname(„www.onet.pl”);

2)

C/C++
struct hostent * gethostbyaddr( const char * adres, int dlug, int typ );

Argumenty:
    const char *adres - adres IP w reprezentacji sieciowej (inet_pton)
    int dlug - wielkość adresu
    int typ - typ adresu (AF_INET - IPv4, AF_INET6 - IPv6)

Wartość zwracana:
    a) w przypadku powodzenia
        - wskaźnik do struktury zawierającej dane hosta
    b) w przypadku błędu
- wartość NULL

Przykład użycia :


    struct hostent *he;
    struct in_addr ipv4addr;
    char buf[100];
    inet_pton(AF_INET, buf, &ipv4addr);
    gethostbyaddr(&ipv4addr, sizeof ipv4addr, AF_INET);


Należy jednak pamiętać, że funkcje gethostbyaddr() oraz gethostbyname() są funkcjami przestarzałymi, ponieważ nie wspierają one rodziny adresów ipv6. Nowe funkcje jakie należy stosować to:

    - getaddrinfo()
    - getnameinfo()

Jednak warto wiedzieć, jak zaimplementować takie programy.

Programy III :


// 1.c – gethostbyname()

C/C++
#include <sys/socket.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#include <sys/time.h>


int main()
{
    int i;
    struct hostent * he;
    struct in_addr ** addr_list;
   
    char buf[ 100 ];
    scanf( "%99s", buf );
   
    if(( he = gethostbyname( buf ) ) == NULL )
    {
        herror( "gethostbyname" );
        return - 1;
    }
   
    // print information about this host:
    printf( "Official name is: %s\n", he->h_name );
   
    printf( "    IP addresses: " );
    addr_list =( struct in_addr ** ) he->h_addr_list;
    for( i = 0; addr_list[ i ] != NULL; i++ )
    {
        printf( "%s ", inet_ntoa( * addr_list[ i ] ) );
    }
    printf( "\n" );
   
    return 0;
}

// gcc 1.c -g -Wall -o 1 && ./1
// www.onet.pl

// 2.c – gethostbyaddr()

C/C++
#include <sys/socket.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#include <sys/time.h>


int main()
{
    struct hostent * he;
    struct in_addr ipv4addr;
   
    char buf[ 100 ];
    scanf( "%99s", buf );
   
    if( inet_pton( AF_INET, buf, & ipv4addr ) < 1 )
    {
        perror( "Wrong address:" );
        return - 1;
    }
   
    if(( he = gethostbyaddr( & ipv4addr, sizeof ipv4addr, AF_INET ) ) == NULL )
    {
        perror( "gethostbyaddr ERROR:" );
        return - 1;
    }
   
    printf( "Host name: %s\n", he->h_name );
    printf( "\n" );
   
    return 0;
}

// gcc 2.c -g -Wall -o 2 && ./2
// 213.180.141.140


Podsumowanie :


Powyższe implementacje są tylko przykładowe. Wszystkie programy można napisać na wiele różnych sposób. Zachęcam do tego, by eksperymentować.
Artykuł miał na celu przedstawienie tylko podstawowych i najprostszych implementacji. Warto przyjrzeć się jeszcze np. funkcji select(). Mam nadzieję, że zdobyta tutaj wiedza przyda się w przyszłości.


Bibliografia :


- kurs „Programowanie sieciowe” UJ, WFAIS.IF-C123
- http://pl.wikipedia.org/wiki​/Gniazdo_(telekomunikacja)
- manual linux