další předchozí obsah
Další: Závěr Předchozí: Meziprocesní komunikace

  Síťování





Úvod

V této kapitole si popíšeme základy komunikace v počítačových sítích, kde mohou být zapojeny i jiné systémy, než jen systémy typu unix. Dozvíme se také, jak využívat tyto služby v unixu, pomocí služby, která se nazývá sokety.



ISO OSI

Nejdříve trocha teorie:

Aby mohly různé systémy spolu komunikovat po sítích, vypracoval mezinárodní normalizační úřad ISO takzvaný referenční model OSI (Reference Model for Open System Interconection). Jeho základem je rozdělení všech činností při výměně dat do sedmi částí -- vrstev, počínaje fyzickou definicí spoje a konče aplikačními programy -- viz. obr. 19. Každá vrstva definuje vlastnosti svých dvou rozhraní, tj. služby, poskytované vyšší vrstvě a služby, poskytované nižší vrstvě. Vrstvy jsou definovány i tvarem přenášených dat.

Zde má být moc hezký obrázek 'isoosi', škoda, že ho nevidíte

Obrázek 19: Vrstvová architektura modelu OSI

Jednotlivé vrstvy si ve stručnosti popíšeme:

Fyzická vrstva
(Physical Layer) se zabývá elektrickými a mechanickými vlastnostmi přenosového média. Definuje druh kabelu, tvary konektorů, atd. Jejím úkolem je přenos jednotlivých bitů po vedení.

Linková vrstva
(Data Link Layer) poskytuje vyšším vrstvám služby, které jsou již nezávislé na typu média. K jejím úkolům patří převod slov nebo znaků počítače na sériovou posloupnost bitů a naopak. Doplňuje data o synchronizační posloupnost a cílovou a zdrojovou adresu. Vytváří tzv. rámce.

Síťová vrstva
(Network Layer) zajišťuje přepravu paketů mezi jednotlivými uzly sítě. Také se v této vrstvě může rozhodovat, kudy se bude směrovat daný paket. Pro komunikaci se pracuje buď s datagramy nebo s virtuálními spoji.

Transportní vrstva
(Transport Layer) zajišťuje rozkládání zpráv na jednotlivé pakety a jejich opětovné skládání, navazování a rušení transportních spojů, koncové řízení toku a adresaci procesů v síti. Pokud nejsou k dispozici v nižších vrstvách virtuální spoje, pracuje pouze s datagramy a dokáže se vyrovnat i s jejich záměnou. Jsou zde služby jako CONNECT -- navázání spoje, LISTEN -- očekávání navázání spoje, SEND -- vysílá zprávu, ...

Relační vrstva
(Session Layer) definuje relaci jako časový úsek, ve kterém probíhá aktivita uživatele. Relace jsou otevírány, probíhá v nich přenos dat a po skončení jsou uzavírány. Ověřuje přístupová práva, eviduje provoz.

Prezentační vrstva
(Presentation Layer) se zabývá tvarem dat, ve kterém jsou předávána uživateli. Může data komprimovat, dekomprimovat, utajovat informaci či jinak kódovat.

Aplikační vrstva
(Application Layer), to jsou programy provozované uživateli jako elektronická pošta, přenos souborů, databázové programy, ...



  TCP/IP -- internetové protokoly

V unixu se pro přenos v sítích používají často protokoly třídy TCP/IP. V názvu slyšíme sice jen dva protokoly, ale patří k nim ještě další. Jejich vzájemné vazby a analogie k jednotlivým OSI vrstvám lze vysledovat z obr. 20.

Zde má být moc hezký obrázek 'tcpip', škoda, že ho nevidíte

Obrázek 20: Vrstvy OSI v protokolech TCP/IP

TCP
(Transmission Control Protocol) zajišťuje spolehlivé spojení, s plným duplexem. Tento protokol používá většina aplikací, proto je podle něj nazvána celá tato rodina protokolů.

UDP
(User Datagram Protocol) je protokol bez vytváření spojení, není zaručeno doručení adresátovi.

ICMP
(Internet Control Message Protocol) přenáší chyby a řídící informace. Tyto informace jsou přenášeny pomocí IP paketů. Aplikační programy s nimi většinou nepracují, generuje je sám síťový software.

IP
(Internet Protocol) přenáší vlastní pakety protokolů TCP, UDP, ICMP. Jak je vidět na obr. 20, uživatelské programy většinou nepotřebují pracovat s touto vrstvou.

ARP
(Address Resolution Protocol) převádí internetové adresy na hardwarové adresy. Tento protokol a následující, RARP, je potřeba jen v některých sítích.

RARP
(Reverse Address Resolution Protocol) převádí hardwarové adresy na internetové.



Internetové adresy

Internetová adresa je (zatím) 32 bitová a obsahuje jak číslo sítě (netid), tak i číslo počítače v síti (hostid). Tyto adresy se dělí do několika tříd, podle velikosti sítě, viz obr. 21. Někdy se využívá v části hostid i tzv. podsítě.

Zde má být moc hezký obrázek 'ipadresy', škoda, že ho nevidíte

Obrázek 21: Třídy IP adres

Internetové adresy se zapisují v desítkové soustavě, po bajtech a jsou oddělené tečkou, např. 147.32.80.1 -- což je jeden počítač na ČVUT FEL.



Sokety

Berkeleyovské sokety (sockets) jsou jednou z nadstaveb (API -- Application Program Interface) nad komunikačními protokoly. Další je např. System V Transport Layer Interface (TLI).

Sokety slouží jako nadstavba nad internetovskými protokoly, dále nad tzv. Unix domain protokoly, které slouží pro komunikaci mezi procesy na jednom počítači, a nad protokoly XNS -- Xerox Network Systems.

My se zde zaměříme hlavně na protokoly TCP/IP a Unix domain.

Většina síťových systémových volání používá ukazatel na tuto strukturu, popsanou v <sys/socket.h>:

  struct sockaddr {
    u_short  sa_family;   /* rodina adres; jedna z~AF_xxx */
    char     sa_data[14]; /* až 14 bytů; závisí na protokolu */
  }

Pro internetovskou rodinu jsou tyto definovány struktury v <netinet/in.h>:

  struct in_addr {
    u_long  s_addr;   /* 32 bitová adresa */
  };

  struct sockaddr_in {
    short           sin_family;  /* AF_INET */
    u_short         sin_port;    /* 16 bitové číslo portu */
    struct in_addr  sin_addr;    /* 32 bitová adresa */
    char            sin_zero[8]; /* nepoužito */
  }

Pro Unix doman je definována struktura v <sys/un.h>:

  struct sockaddr_un {
    short           sun_family;    /* AF_UNIX */
    char            sun_path[108]; /* cesta */

Oproti zvyklostem není sun_path ukončena nulou. Je také vidět, že struktura je delší, než obecná sockaddr. Naštěstí současné systémy podporují až 110 bajtovou strukturu. Ale např. směrovací tabulky v jádře podporují pouze 16 bajtovou strukturu, kterou však protokoly Unix domain nepotřebují...

Typický scénář komunikace pro spojované protokoly je vidět na obr. 22

Zde má být moc hezký obrázek 'spojovan', škoda, že ho nevidíte

Obrázek 22: Soketové funkce pro spojované protokoly

a pro nespojované je na obr. 23.

Zde má být moc hezký obrázek 'nespojov', škoda, že ho nevidíte

Obrázek 23: Soketové funkce pro nespojované protokoly



Funkce socket

První věcí, kterou proces při síťování dělá, je volání funkce socket, která specifikuje komunikační protokol.

#include <sys/types.h>
#include <sys/socket.h>
int socket (int family, int type, int protocol);
Vrací: deskriptor soketu když OK, -1 při chybě

Rodina adres family je jedna z:

AF_UNIX interní unixové protokoly
AF_INET internetové protokoly
AF_NS protokoly Xerox NS
AF_IMPLINK vrstva Interface Message Processor

Typ soketu type může být:

SOCK_STREAM proudový soket (spojovaná služba)
SOCK_DGRAM datagramový soket (nespojovaná služba)
SOCK_RAW surový soket
SOCK_SEQPACKET soketová posloupnost paketů (pro XNS)
SOCK_RDM soket pro spolehlivě doručitelné zprávy (ještě neimplementováno)

Jsou dovoleny jen některé kombinace family a type, viz tab. 15.

 

AF_UNIX AF_INET AF_NS
SOCK_STREAM Ano TCP SPP
SOCK_DGRAM Ano UDP IDP
SOCK_RAW IP Ano
SOCK_SEQPACKET SPP
Tabulka 15: Protokoly odpovídající family a type

Protokoly, v jejichž kolonkách v tabulce 15 je Ano, nemají specifické jméno. Prázdné kolonky znamenají, že protokol není implementován.

Argument protocol bývá většinou 0, což nastaví implicitní, který je vidět z tab. 15. Další internetovské protokoly lze najít v souboru <netinet/in.h> (např. IPPROTO_ICMP).



  Funkce socketpair

#include <sys/types.h>
#include <sys/socket.h>
int socketpair (int family, int type, int protocol, int sockvec[2]);
Vrací: 0 když OK, -1 při chybě

Funkce vrací dva nepojmenované soketové deskriptory v poli sockvec. Je podobná funkci pipe, viz kap. 9. Narozdíl od pipe vytváří ale obousměrnou, tzv. proudovou rouru.

Jsou dovolené jen dvě možnosti volání:

  int     rc, sockfd[2];

  rc = socketpair (AF_UNIX, SOCK_STREAM, 0, sockfd);
nebo
  rc = socketpair (AF_UNIX, SOCK_DGRAM, 0, sockfd);



Funkce bind

Funkce bind přiřadí jméno nepojmenovanému soketu.

#include <sys/types.h>
#include <sys/socket.h>
int bind (int sockfd, struct sockaddr *myaddr, int addrlen);
Vrací: 0 když OK, -1 při chybě

myaddr obsahuje identifikátor závisející na přenosových protokolech.



Funkce connect

K vytvoření spojení se serverem použije klient funkci connect.

#include <sys/types.h>
#include <sys/socket.h>
int connect (int sockfd, struct sockaddr *servaddr, int addrlen);
Vrací: 0 když OK, -1 při chybě

sockfd vrátila funkce socket. servadrs je adresa serveru. Pro datagramy se spojení nevytvoří.



Funkce listen

Funkce listen je používána serverem pro spojované služby, když si přeje, aby se s ním někdo spojil.

#include <sys/types.h>
#include <sys/socket.h>
int listen (int sockfd, int backlog);
Vrací: 0 když OK, -1 při chybě

backlog určuje, kolik požadavků se má uložit do fronty, než se spustí server. Většinou se nastavuje na 5, což je zatím maximální povolená hodnota.



Funkce accept

Funkci accept volá server většinou po listen.

#include <sys/types.h>
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *peer, int *addrlen);
Vrací: 0 když OK, -1 při chybě

accept vezme první požadavek z fronty požadavků a vytvoří další soket se stejnými vlastnostmi jako sockfd. Pokud nejsou ve frontě žádné požadavky, funkce čeká. Lze ji nastavit i tak, aby nečekala, pomocí funkcí v kap. "Nastavování parametrů soketů".

Adresu klienta server zjistí z peer a addrlen.



Funkce send, sendto, recv a recvfrom

Vlastní přenos dat se děje pomocí funkcí send, sendto, recv a recvfrom. Tyto funkce jsou podobné standardním funkcím read (kap. "Funkce read") a write (kap. "Funkce write") s dalšími argumenty.

#include <sys/types.h>
#include <sys/socket.h>
int send (int sockfd, char *buff, int nbytes, int flags);
int sendto (int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen);
int recv (int sockfd, char *buff, int nbytes, int flags);
int recvfrom (int sockfd, char *buff, int nbytes, int flags, struct sockaddr *from, int *addrlen);
Vrací: počet poslaných/přijatých znaků když OK, -1 při chybě

flags jsou buď 0 nebo logicky přičítají tyto konstanty:

MSG_OOB posílá/přijímá out-of-band data
MSG_PEEK podívá se na přijatá data, aniž je vymaže z fronty
MSG_DONTROUTE nebude směrovat při posílání

Funkce sendto a recvfrom se používají pro nespojované protokoly.



Funkce close

Na zavírání soketů se používá standardní funkce close, viz kap. Funkce close.

Systém se normálně z této funkce vrací okamžitě, ale jádro se snaží poslat data, která jsou ve frontě.



Funkce shutdown

Funkce shutdown nabízí více možností, jak uzavřít spojení, než close.

#include <sys/socket.h>
int shutdown (int sockfd, int howto);
Vrací: 0 když OK, -1 při chybě

Pokud je , soket nepřijme již žádná data, když je , soket již nic nevyšle, a když je , pak již přes soket neprojdou žádná data tam ani zpět.



Funkce na změnu pořadí bajtů

Tyto funkce mění pořadí bajtů ve slovech podle architektury počítače.

#include <sys/types.h>
#include <netinet/in.h>
u_long htonl (u_long hostlong);
u_short htons (u_short hostshort);
u_long ntohl (u_long netlong);
u_short ntohs (u_short netshort);
Vrací: překonvertované slovo

Tyto funkce byly navrženy pro internetové protokoly. Fungují však i pro XNS. Na systémech, které mají stejné pořadí, jako internetové protokoly (Motorola 68000), jsou to jen nulová makra.



Bajtové operace

Protože se při komunikaci používají řetězce, které nekončí 0, jak je zvykem v C, je nutno nad nimi definovat přenositelné operace.

#include <string.h>
void bcopy (char *src, char *dest, int nbytes);
void bzero (char *dest, int nbytes);
int bcmp (char *ptr1, char *ptr2, int nbytes);
Vrací: 0 když jsou stejné, jinak nenulovou hodnotu

bcopy kopíruje daný počet bajtů z řetězce src do dest. Řetězce mohou obsahovat i nuly. bzero plní řetězec daným počtem nul. bcmp porovnává dva řetězce, ve kterých mohou být i nuly.



Funkce na konverzi adres

Jak bylo řečeno v kap. "TCP/IP -- internetové protokoly", internetové adresy se píší po bajtech, odděleny tečkou. Následující funkce převádějí tuto formu na strukturu in_addr (viz též kap. "TCP/IP -- internetové protokoly") a naopak.

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long inet_addr (char *ptr);
char *inet_ntoa (struct in_addr inaddr);
Vrací: adresu

Funkce inet_addr je již trochu zastaralá, neboť dobře neindikuje chybu. Nová funkce se jmenuje inet_aton.



Jednoduchý příklad

Teď vyzkoušíme základní soketová systémová volání na jednoduchém příkladu:

Nejdříve si představíme jednotlivé podpůrné podprogramy, nejdříve pro spojované služby. První čte ze soketového deskriptoru.

/*
 * Read "n" bytes from a descriptor.
 * Use in place of read() when fd is a stream socket.
 */

int
readn(fd, ptr, nbytes)
register int    fd;
register char    *ptr;
register int    nbytes;
{
    int    nleft, nread;

    nleft = nbytes;
    while (nleft > 0) {
        nread = read(fd, ptr, nleft);
        if (nread < 0)
            return(nread);        /* error, return < 0 */
        else if (nread == 0)
            break;            /* EOF */

        nleft -= nread;
        ptr   += nread;
    }
    return(nbytes - nleft);        /* return >= 0 */
}

Další píše do soketového deskriptoru přes spojované služby.
/*
 * Write "n" bytes to a descriptor.
 * Use in place of write() when fd is a stream socket.
 */

int
writen(fd, ptr, nbytes)
register int    fd;
register char    *ptr;
register int    nbytes;
{
    int    nleft, nwritten;

    nleft = nbytes;
    while (nleft > 0) {
        nwritten = write(fd, ptr, nleft);
        if (nwritten <= 0)
            return(nwritten);        /* error */

        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(nbytes - nleft);
}

Následující funkce čte jeden řádek.
/*
 * Read a line from a descriptor.  Read the line one byte at a time,
 * looking for the newline.  We store the newline in the buffer,
 * then follow it with a null (the same as fgets(3)).
 * We return the number of characters up to, but not including,
 * the null (the same as strlen(3)).
 */

int
readline(fd, ptr, maxlen)
register int    fd;
register char    *ptr;
register int    maxlen;
{
    int    n, rc;
    char    c;

    for (n = 1; n < maxlen; n++) {
        if ( (rc = read(fd, &c, 1)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;
        } else if (rc == 0) {
            if (n == 1)
                return(0);    /* EOF, no data read */
            else
                break;        /* EOF, some data was read */
        } else
            return(-1);    /* error */
    }

    *ptr = 0;
    return(n);
}

Tato funkce je používána serverem k opakování řádku na vstupu zpět na výstup.
/*
 * Read a stream socket one line at a time, and write each line back
 * to the sender.
 *
 * Return when the connection is terminated.
 */

#define    MAXLINE    512

str_echo(sockfd)
int    sockfd;
{
    int    n;
    char    line[MAXLINE];

    for ( ; ; ) {
        n = readline(sockfd, line, MAXLINE);
        if (n == 0)
            return;        /* connection terminated */
        else if (n < 0)
            err_dump("str_echo: readline error");

        if (writen(sockfd, line, n) != n)
            err_dump("str_echo: writen error");
    }
}

Následující funkci používá klient.
/*
 * Read the contents of the FILE *fp, write each line to the
 * stream socket (to the server process), then read a line back from
 * the socket and write it to the standard output.
 *
 * Return to caller when an EOF is encountered on the input file.
 */

#include    <stdio.h>
#define    MAXLINE    512

str_cli(fp, sockfd)
register FILE    *fp;
register int    sockfd;
{
    int    n;
    char    sendline[MAXLINE], recvline[MAXLINE + 1];

    while (fgets(sendline, MAXLINE, fp) != NULL) {
        n = strlen(sendline);
        if (writen(sockfd, sendline, n) != n)
            err_sys("str_cli: writen error on socket");

        /*
         * Now read a line from the socket and write it to
         * our standard output.
         */

        n = readline(sockfd, recvline, MAXLINE);
        if (n < 0)
            err_dump("str_cli: readline error");
        recvline[n] = 0;    /* null terminate */
        fputs(recvline, stdout);
    }

    if (ferror(fp))
        err_sys("str_cli: error reading file");
}

Další funkci používá server. Je zde počítáno s více druhy protokolů.
/*
 * Read a datagram from a connectionless socket and write it back to
 * the sender.
 *
 * We never return, as we never know when a datagram client is done.
 */

#include    <sys/types.h>
#include    <sys/socket.h>

#define    MAXMESG    2048

dg_echo(sockfd, pcli_addr, maxclilen)
int        sockfd;
struct sockaddr    *pcli_addr;
                   /* ptr to appropriate sockaddr_XX structure */
int        maxclilen;    /* sizeof(*pcli_addr) */
{
    int    n, clilen;
    char    mesg[MAXMESG];

    for ( ; ; ) {
        clilen = maxclilen;
        n = recvfrom(sockfd, mesg, MAXMESG, 0, pcli_addr, &clilen);
        if (n < 0)
            err_dump("dg_echo: recvfrom error");

        if (sendto(sockfd, mesg, n, 0, pcli_addr, clilen) != n)
            err_dump("dg_echo: sendto error");
    }
}

Následující funkce je pro klienta, využívajícího nespojovaných služeb.
/*
 * Read the contents of the FILE *fp, write each line to the
 * datagram socket, then read a line back from the datagram
 * socket and write it to the standard output.
 *
 * Return to caller when an EOF is encountered on the input file.
 */

#include    <stdio.h>
#include    <sys/types.h>
#include    <sys/socket.h>

#define    MAXLINE    512

dg_cli(fp, sockfd, pserv_addr, servlen)
FILE        *fp;
int        sockfd;
struct sockaddr    *pserv_addr;
                   /* ptr to appropriate sockaddr_XX structure */
int        servlen;    /* actual sizeof(*pserv_addr) */
{
    int    n;
    char    sendline[MAXLINE], recvline[MAXLINE + 1];

    while (fgets(sendline, MAXLINE, fp) != NULL) {
        n = strlen(sendline);
        if (sendto(sockfd, sendline, n, 0, pserv_addr, servlen) != n)
            err_dump("dg_cli: sendto error on socket");

        /*
         * Now read a message from the socket and write it to
         * our standard output.
         */

        n = recvfrom(sockfd, recvline, MAXLINE, 0,
                (struct sockaddr *) 0, (int *) 0);
        if (n < 0)
            err_dump("dg_cli: recvfrom error");
        recvline[n] = 0;    /* null terminate */
        fputs(recvline, stdout);
    }

    if (ferror(fp))
        err_dump("dg_cli: error reading file");
}


Příklad na TCP

Nejdříve ukážeme hlavičkový soubor inet.h, který používáme i v příkladu na UDP.

/*
 * Definitions for TCP and UDP client/server programs.
 */

#include    <stdio.h>
#include    <sys/types.h>
#include    <sys/socket.h>
#include    <netinet/in.h>
#include    <arpa/inet.h>

#define    SERV_UDP_PORT    6000
#define    SERV_TCP_PORT    6000
#define    SERV_HOST_ADDR    "192.43.235.6"    /* host addr for server */

char    *pname;

A konečně je tu vlastní server.
/*
 * Example of server using TCP protocol.
 */

#include    "inet.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd, newsockfd, clilen, childpid;
    struct sockaddr_in    cli_addr, serv_addr;

    pname = argv[0];

    /*
     * Open a TCP socket (an Internet stream socket).
     */

    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_dump("server: can't open stream socket");

    /*
     * Bind our local address so that the client can send to us.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port        = htons(SERV_TCP_PORT);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
        < 0)
        err_dump("server: can't bind local address");

    listen(sockfd, 5);

    for ( ; ; ) {
        /*
         * Wait for a connection from a client process.
         * This is an example of a concurrent server.
         */

        clilen = sizeof(cli_addr);
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr,
                                     &clilen);
        if (newsockfd < 0)
            err_dump("server: accept error");

        if ( (childpid = fork()) < 0)
            err_dump("server: fork error");

        else if (childpid == 0) {    /* child process */
            close(sockfd);        /* close original socket */
            str_echo(newsockfd);    /* process the request */
            exit(0);
        }

        close(newsockfd);        /* parent process */
    }
}

Následuje klient.
/*
 * Example of client using TCP protocol.
 */

#include    "inet.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd;
    struct sockaddr_in    serv_addr;

    pname = argv[0];

    /*
     * Fill in the structure "serv_addr" with the address of the
     * server that we want to connect with.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
    serv_addr.sin_port        = htons(SERV_TCP_PORT);

    /*
     * Open a TCP socket (an Internet stream socket).
     */

    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_sys("client: can't open stream socket");

    /*
     * Connect to the server.
     */

    if (connect(sockfd, (struct sockaddr *) &serv_addr,
                            sizeof(serv_addr)) < 0)
        err_sys("client: can't connect to server");

    str_cli(stdin, sockfd);        /* do it all */

    close(sockfd);
    exit(0);
}


Příklad na UDP

Tento náš příklad není spolehlivý, tj. nezaručuje doručení paketů. Server vypadá takto:

/*
 * Example of server using UDP protocol.
 */

#include    "inet.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd;
    struct sockaddr_in    serv_addr, cli_addr;

    pname = argv[0];

    /*
     * Open a UDP socket (an Internet datagram socket).
     */

    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        err_dump("server: can't open datagram socket");

    /*
     * Bind our local address so that the client can send to us.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port        = htons(SERV_UDP_PORT);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
        < 0)
        err_dump("server: can't bind local address");

    dg_echo(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr));

        /* NOTREACHED */
}

A klient zase takto:
/*
 * Example of client using UDP protocol.
 */

#include    "inet.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd;
    struct sockaddr_in    cli_addr, serv_addr;

    pname = argv[0];

    /*
     * Fill in the structure "serv_addr" with the address of the
     * server that we want to send to.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
    serv_addr.sin_port        = htons(SERV_UDP_PORT);

    /*
     * Open a UDP socket (an Internet datagram socket).
     */

    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        err_dump("client: can't open datagram socket");

    /*
     * Bind any local address for us.
     */

    bzero((char *) &cli_addr, sizeof(cli_addr));    /* zero out */
    cli_addr.sin_family      = AF_INET;
    cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    cli_addr.sin_port        = htons(0);
    if (bind(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr))
        < 0)
        err_dump("client: can't bind local address");

    dg_cli(stdin, sockfd, (struct sockaddr *) &serv_addr,
           sizeof(serv_addr));

    close(sockfd);
    exit(0);
}


Příklad na Unixové protokoly spojované

Spojovaný server je podobný příkladu na TCP. Nejdříve si ukážeme hlavičkový soubor unix.h, využívaný v dalších příkladech.

/*
 * Definitions for UNIX domain stream and datagram client/server
 * programs.
 */

#include    <stdio.h>
#include    <sys/types.h>
#include    <sys/socket.h>
#include    <sys/un.h>

#define    UNIXSTR_PATH    "./s.unixstr"
#define    UNIXDG_PATH    "./s.unixdg"
#define    UNIXDG_TMP    "/tmp/dg.XXXXXX"

char    *pname;

Server je toto:
/*
 * Example of server using UNIX domain stream protocol.
 */

#include    "unix.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd, newsockfd, clilen, childpid, servlen;
    struct sockaddr_un    cli_addr, serv_addr;

    pname = argv[0];

    /*
     * Open a socket (a UNIX domain stream socket).
     */

    if ( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        err_dump("server: can't open stream socket");

    /*
     * Bind our local address so that the client can send to us.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, UNIXSTR_PATH);
    servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0)
        err_dump("server: can't bind local address");

    listen(sockfd, 5);

    for ( ; ; ) {
        /*
         * Wait for a connection from a client process.
         * This is an example of a concurrent server.
         */

        clilen = sizeof(cli_addr);
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr,
                                     &clilen);
        if (newsockfd < 0)
            err_dump("server: accept error");

        if ( (childpid = fork()) < 0)
            err_dump("server: fork error");

        else if (childpid == 0) {    /* child process */
            close(sockfd);        /* close original socket */
            str_echo(newsockfd);    /* process the request */
            exit(0);
        }

        close(newsockfd);        /* parent process */
    }
}

Klient je toto:
/*
 * Example of client using UNIX domain stream protocol.
 */

#include    "unix.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd, servlen;
    struct sockaddr_un    serv_addr;

    pname = argv[0];

    /*
     * Fill in the structure "serv_addr" with the address of the
     * server that we want to send to.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, UNIXSTR_PATH);
    servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);

    /*
     * Open a socket (an UNIX domain stream socket).
     */

    if ( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        err_sys("client: can't open stream socket");

    /*
     * Connect to the server.
     */

    if (connect(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0)
        err_sys("client: can't connect to server");

    str_cli(stdin, sockfd);        /* do it all */

    close(sockfd);
    exit(0);
}


Příklad na Unixové protokoly nespojované

V tomto datagramovém příkladu je vidět zajímavý problém, se kterým jsme se ještě nesetkali -- sokety jsou identifikovány jmény souborů a proto klient musí vyprodukovat pro bind unikátní jméno souboru.

Server:

/*
 * Example of server using UNIX domain datagram protocol.
 */

#include    "unix.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd, servlen;
    struct sockaddr_un    serv_addr, cli_addr;

    pname = argv[0];

    /*
     * Open a socket (a UNIX domain datagram socket).
     */

    if ( (sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
        err_dump("server: can't open datagram socket");

    /*
     * Bind our local address so that the client can send to us.
     */

    unlink(UNIXDG_PATH);    /* in case it was left from last time */
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, UNIXDG_PATH);
    servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0)
        err_dump("server: can't bind local address");

    dg_echo(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr));
        /* NOTREACHED */
}

Klient:
/*
 * Example of client using UNIX domain datagram protocol.
 */

#include    "unix.h"

main(argc, argv)
int    argc;
char    *argv[];
{
    int            sockfd, clilen, servlen;
    char            *mktemp();
    struct sockaddr_un    cli_addr, serv_addr;

    pname = argv[0];

    /*
     * Fill in the structure "serv_addr" with the address of the
     * server that we want to send to.
     */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, UNIXDG_PATH);
    servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);

    /*
     * Open a socket (a UNIX domain datagram socket).
     */

    if ( (sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
        err_dump("client: can't open datagram socket");

    /*
     * Bind a local address for us.
     * In the UNIX domain we have to choose our own name (that
     * should be unique).  We'll use mktemp() to create a unique
     * pathname, based on our process id.
     */

    bzero((char *) &cli_addr, sizeof(cli_addr));    /* zero out */
    cli_addr.sun_family = AF_UNIX;
    strcpy(cli_addr.sun_path, UNIXDG_TMP);
    mktemp(cli_addr.sun_path);
    clilen = sizeof(cli_addr.sun_family) + strlen(cli_addr.sun_path);

    if (bind(sockfd, (struct sockaddr *) &cli_addr, clilen) < 0)
        err_dump("client: can't bind local address");

    dg_cli(stdin, sockfd, (struct sockaddr *) &serv_addr, servlen);

    close(sockfd);
    unlink(cli_addr.sun_path);
    exit(0);
}


  Nastavování parametrů soketů

Je několik způsobů, jak měnit parametry soketů:

Funkce fcntl a ioctl byly popsány v kapitole "Souborový vstup a výstup".

Funkcí getsockopt lze získat informace o soketu.

#include <sys/types.h>
#include <sys/socket.h>
int getsockopt (int sockfd, int level, int optname, char *optval, int *optlen);
int setsockopt (int sockfd, int level, int optname, char *optval, int optlen);
Vrací: 0 když OK, -1 při chybě



Cvičení

  1. ??


další předchozí obsah
Další: Závěr Předchozí: Meziprocesní komunikace

Ladislav Dobias
Sat Nov 1 15:38:32 MET 1997