V minulém  čísle mě zaujal článek o IPX  síťování.   Ačkoliv  je IPX
      protokol použitelný na velké části  lokálních sítí (Novell NetWare a
      Windows 95), na větší  vzdálenost  s ním neprorazíte.    Dnes   však
      spousta programů    musí   pracovat  po internetu  a pak je  potřeba
      používat TCP/IP protokoly, o kterých jsem se rozhodl  sepsat  nějaké
      informace. Nejdříve trochu teorie.

      Adresy

      Každý počítač,  no vlastně každé síťové zařízení (routery  jich mají
      víc),  musí   dostat svoji  IP adresu. IP adresa  je  4-bytová  a je
      zvykem  ji zapisovat    jako čtveřici    čísel oddělených   tečkami.
      Například 192.168.82.13.   Ke každé  IP adrese  náleží síťová  maska
      (netmask) (např  255.255.255.0),   která adresu rozděluje na síťovou
      (net) část (v našem případě 192.168.82) a hostitelskou  (host)  část
      (v našem případě .13). Z toho se  pak odvodí takzvaná síťová  adresa
      (používaná  při směrování  -- v našem případě 192.168.82.0 -- adresa
      & netmask) a vysílací  (broadcast) adresa (buď nejvyšší možná adresa
      v síti -- tedy 192.168.82.255, nebo  síťová adresa (teoreticky  může
      být i jiná)),  což je adresa, ke které se budou znát všechny stanice
      v síti.

      Směrování (Routing)

      Každý počítač v TCP/IP síti   má takzvanou  routovací tabulku, která
      říká, co se má dělat s jednotlivými pakety. Ta vypadá nějak takhle:

          address         flag     mask            device
          127.0.0.1       up       255.0.0.0       lo
          192.168.81.1    up       255.255.255.0   plip0
          192.168.82.0    net, up  255.255.255.0   eth0
          default         net, up  0.0.0.0         slip0

      Tohle nám říká zhruba následující:  předpokládejme,   že náš počítač
      má tři  síťová  zařízení. Přes paralelní   linku je na něj  připojen
      počítač s adresou 192.168.81.1,  přes síťovou kartu několik počítačů
      se společnou  síťovou  adresou   192.168.82.0  a přes sériovou linku
      (řekněme  modem) je připojen  k internetu,  kde se nalézají  všechny
      ostatní možné  adresy.  Že  jsem neřekl nic o prvním řádku? No to je
      přece speciální loopback  device,  tedy zařízení pro komunikaci sama
      se sebou  (například  s X-serverem aplikace komunikují   přes TCP/IP
      protokol, aby bylo  jedno,  jestli   běží  vzdáleně, nebo ne).  Když
      nějaký program odešle paket, tak se kernel  (jádro OS) našeho milého
      počítače podívá do routovací  tabulky a postupně porovnává. Nejdříve
      nejpřesnější  definice  (1.  a 2.  řádek).  Pokud tyto neodpovídají,
      zkusí méně konkrétní  3. řádek. A když ani  tento nepasuje,   tak ze
      zoufalství  vezme za vděk položkou default a  pošle paket přes modem
      internetovému serveru, který pomocí o něco sofistikovanějších  metod
      rozhodne,    kam   s ním dál.    (Velké routery používají  dynamické
      routovací tabulky -- říkají si navzájem, co pod sebou mají za adresy
      a upravují si tabulky za běhu -- a různé další zběsilosti.)

      Navazování spojení

      Pod IP protokolem (který se stará o    směrování -- nese cílovou IP,
      odesilatelskou  IP a hardwarovou  adresu  routeru) existuje  několik
      protokolů pro výměnu  informace a jim odpovídajících  druhů přenosu.
      V souboru /etc/protocols na mém počítači je následující seznam:

        ip      0     IP    # internet protocol, pseudo protocol number
        icmp    1     ICMP  # internet control message protocol
        igmp    2     IGMP  # internet group multicast protocol
        ggp     3     GGP   # gateway-gateway protocol
        tcp     6     TCP   # transmission control protocol
        pup     12    PUP   # PARC universal packet protocol
        udp     17    UDP   # user datagram protocol
        idp     22    IDP   # WhatsThis?
        raw     255   RAW   # RAW IP interface

      Pokud  správně  rozumím tomuto  popisu, tak IP znamená   automatický
      výběr protokolu   (obvykle požadovaný   typ spojení    pracuje jen s
      jedním protokolem).  ICMP je  protokol,  kterým si počítače vyměňují
      provozní informace,   jako "zahodil jsem ti paket   --", když  dojde
      time-to-live,  nebo  "pakety  pro  X posílej Y a ne mě --", když  je
      potřeba upravit routovací  tabulky; IGMP mi vážně nic neříká a totéž
      platí o GGP. TCP je nejpoužívanější protokol pro  obecná data, který
      má podobné   vlastnosti jako   SPX, tedy  dodržuje pořadí paketů   a
      kontroluje přijetí.  Naproti    tomu UDP je protokol, který  podobně
      jako IPX prostě posílá pakety, takže ani přijetí, ani  pořadí nejsou
      zaručeny,  ale  zase se nevyměňuje ověřovací  informace. U PUP  opět
      nevím, o co  se   jedná  a u IDP to zjevně nevěděl  ani autor tohoto
      seznamu   (Fred N. van Kempen).   Konečně RAW znamená, že si  můžete
      posílat  co chcete (a dělat bordel) a mnoho systémů   vyžaduje rootí
      práva  k otevření   spojení v RAW módu (ne jako  v IPX, kde hlavičku
      vyplňujete sami a můžete tam naflákat  co chcete). Pokud se nebudete
      zrovna chtít  hrabat  v  routovacím  systému (v  takovém případě Vás
      odkazuji na zdrojáky Linuxu:-), budou vás zajímat  zejména protokoly
      TCP/IP a UDP/IP. Nejdříve k TCP/IP.

      První věc, kterou musíte udělat, je získat socketu.  Za tímto účelem
      použijete volání socket:

          #include<sys/types.h>
          #include<sys/socket.h>
          int socket(int domain, int type, int protocol);

      Této funkci předáte  v domain typ sítě (tzv. adresová rodina), který
      bude  AF_INET, ale například  můj linux  zná také    hodnoty AF_UNIX
      (UUCP --  unix-to-unix    copy), AF_AX25    (AX.25 amatérské rádio),
      AF_IPX   (Novell  IPX),  AF_APPLETALK    (Appletalk  DDF), AF_NETROM
      (NetROM  amatérské  rádio),    AF_BRIDGE  (přenos   mezi protokoly),
      AF_AAL5 (rezervováno  pro Werner's  ATM),   AF_X25  (rezervováno pro
      X.25) a kernely    2.1.x už znají AF_INET6,   což  je IP protokol  s
      adresami  nataženými    na   6  byte  (internet    na ně má postupně
      přecházet,   aby   bylo  dost místa na přidělování  adres).  Nicméně
      zůstaňme   u  AF_INET.   Pro AF_INET  přichází  v  úvahu tři hodnoty
      parametru type, a sice  SOCK_STREAM a SOCK_DGRAM. SOCK_STREAM je typ
      odpovídající  právě a pouze tcp  protokolu, a tak můžete jako  třetí
      argument uvést 0. Obdobně SOCK_DGRAM odpovídá právě udp protokolu, a
      tak můžete  jako protocol   opět  klidně uvést 0. Funkce  vrací file
      descriptor (i socket je soubor:-) otevřené sockety.  A co třetí typ?
      SOCK_RAW.

      TCP/IP client

      Tcp spojení  je  asymetrické.   Jedna strana   (server) musí otevřít
      poslouchací socket, na kterém klient  požádá  o spojení, což způsobí
      otevření  obousměrného  komunikačního  kanálu. Nejdříve k jednodušší
      práci  klienta. Ten  si  otevře  socketu  (výše    uvedeným  voláním
      socket),  případně   nastaví   nějaké  parametry (podrobnosti    viz
      manuálová    stránka  funkce setsockopt)   a naváže  spojení voláním
      connect:

          #include<sys/types.h>
          #include<sys/socket.h>
          int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

      Je snad zřejmé,   že sockfd je file  handle   vaší pracně  vytvořené
      sockety. Naproti tomu, pokud Vám následující parametr připadá divný,
      tak nejspíš nejste sami. Jedná  se totiž opravdu o pěknou čuňárnu. V
      sys/socket.h je struct sockaddr definována následovně:

          struct sockaddr {
              unsigned short  sa_family; /* adresová rodina AF_xxx */
              char            sa_data[14]; /* cosi:-) */
          };

      To  je  nádhera,  co?  Když  budete pátrat po tom, co  patří do toho
      cosi, tak se  pěkně   zapotíte,  a pak  zjistíte,      že  v souboru
      netinet/in.h   (tam   je tak akorát  #include<linux/in.h>:-)      je
      definovaná následující struktura:

          struct sockaddr_in {
              short int          sin_family; /* stejné definice :-) */
              unsigned short int sin_port; /* číslo portu */
              struct in_addr     sin_addr; /* inetová adresa */
          };

      A teď se podržte: struct in_addr  je výše v tomtéž souboru definovná
      konstrukcí:

          struct in_addr {
              __u32   s_addr;
          };

      Co že to  proboha je __u32. No to je obyčejný unsigned int! Takže si
      vytvoříte   strukturu   typu struct sockaddr_in (řekněme   foo),  do
      sin_family  dáte   AF_INET, do sin_port dáte port hledaného  serveru
      (ten si musíte předem domluvit  -- obvykle   bývá  konstantní)  a do
      sin_addr.s_addr  dáte adresu počítače, kde server běží, no a  můžete
      vesele zavolat:

          connect(fd, &foo, sizeof(sockaddr_in));

      což vám odpoví 0, pokud se dovoláte a -1 pokud se něco po....

      Jenže ono se po! Connect  totiž příslušná čísla chce v big endianess
      (vyšší byte první), kdežto  vy nevíte, jestli máte vyšší, nebo nižší
      byte první!  (nesnažte se mě přesvědčit,  že víte.  Unixy  běhají na
      počítačích  obou typů!)  Takže musíte přiřazovat  přes funkce ushort
      htons(ushort) a ulong  htonl(ulong) a číst přes ushort ntohs(ushort)
      a ulong  ntohl(ulong)  (jsou definované   v netinet/in.h),  případně
      převést   adresu    funkcí  int   inet_addr(char  *host)   nebo  int
      inet_aton(char *host,  struct  in_addr *inp),  které převádí řetězec
      s tečkovanou  decimální a (snad)  nebo symbolickým jménem  na adresu
      (v síťovém (big endianess)) tvaru.

      No a teď už si můžete vesele povídat  pomocí  read a write (případně
      send a recv) a nakonec voláním shutdown a close spojení ukončíte:

          shutdown(fd, 2); /* viz man 2 shutdown */
          close(fd); /* viz man 2 close */

      TCP/IP server

      Server má trochu víc práce. Nejdříve  si  vytvoří socketu a případně
      nastaví parametry   voláním setsockopt.  Pak musí nastavit port, což
      udělá voláním funkce bind:

          #include<sys/types.h>
          #include<sys/socket.h>
          int bind(int sockfd, struct sockaddr *my_addr, int addr_len);

      Kde parametry   mají obdobný     význam,    jako u  connect,  ale do
      my_addr->sin_addr.s_addr     můžete dát konstantu INADDR_ANY  (== 0;
      v netinet/in.h);   Tím jste  si  zabrali port.  Nyní  musíte systému
      říct, že posloucháte, což uděláte voláním listen:

          #include<sys/socket.h>
          int listen(int s, int backlog);

      Argument  s je handler  sockety    a  backlog je   délka  fronty pro
      requesty  (když přijdou moc rychle  za  sebou,   tak  tam  počkají).
      Nespoléhejte nicméně  na její hodnotu, protože  OS má  interní limit
      (Linux  má  128, ale některé UNIXy taky jenom 5), který vám nedovolí
      překročit.  Funkce vrací 0 při  úspěchu  a -1 při  chybě.   Poslední
      Funkcí, kterou musíte zavolat, je accept:

          #include<sys/types.h>
          #include<sys/socket.h>
          int accept(int s, struct sockaddr *addr, int *addrlen);

      Této  funkci   řeknete    handle   sockety  (s) a  v addrlen   délku
      vyhrazeného  místa a accept vám naoplátku  vrátí handle nové sockety
      (-1  při  chybě), přes kterou  budete  komunikovat, zpáteční  adresu
      v addr  a její délku   v addrlen. Socketa  s zůstane  otevřená   pro
      poslouchání. Pokud   není  žádný požadavek na spojení,  funkce   buď
      čeká, až nějaké přijde, nebo  skončí chybou  podle toho, zda socketa
      je  "non-blocking." To je   ovšem  malý problém. Z jednoho  zdrojáku
      (v manuálu  jsem  to  nenašel)     jsem  zjistil, že se  to dělá  na
      některých systémech přes setsockopt,  ale s  argumenty, které nejsou
      v manuálu a na jiných  přes  ioctl, kterých existuje několik dalších
      variant. Fuj:-(.    Podle  manuálové   stránky  však  lze  otestovat
      přítomnost požadavku voláním select na čtení této sockety.

      Na socketu vytvořenou tímto  voláním  nyní můžete    používat  read,
      write,  send a recv  stejně   jako klient  a  (doufám)   ji  stejným
      způsobem  (shutdown,  close) uzavřít (v AF_UNIX musíte ještě zavolat
      unlink (podle man)).

      UDP/IP spojení

      Když otevřete udp socketu,  je situace trochu jiná.   Vytvoříte   ji
      voláním socket a nastavíte   port voláním bind  stejně,   jako u tcp
      sockety,  ale uděláte to tentokrát  na obou stanách.  Vlastně   máte
      socketu,    která     je schopná  komunikace s libovolným  množstvím
      partnerů  pomocí funkcí sendto,  kterou  můžete   poslat   packet na
      libovolnou  adresu  a recvfrom,   která přečte další  paket a vyplní
      adresu   jeho odesilatele   (adresové   argumenty se chovají jako  u
      accept).

          #include<sys/types.h>
          #include<sys/socket.h>
          int sendto(int s, void *msg, int len, unsigned int flags, const
                     struct sockaddr *to, int tolen);
          int recvfrom(int s, void *buf, int len, unsigned int flags,
                       struct sockaddr *from, int *fromlen);

      Většina  argumentů  je doufám zcela jasná.  Trochu nejasný asi  bude
      argument flag, který u send(to) může mít hodnoty:

          MSG_OOB /* out-of-band data. Paket předbíhá, nebo jde do
                      * zvláštní fronty (podle systému) */
          MSG_NOROUTE /* obejití routování -- pouze pro diagnostiku */

      a u recv(from) hodnotu:

          MSG_OOB /* zpracovat out-of-band data (mají-li zvláštní frontu)*/
          MSG_PEEK /* paket se nevybere z fronty */
          MSG_WAITALL /* čeká se na zaplnění bufferu (len bytů) */

      Jedná  se o klasické flagy, tedy hodnoty je možné kombinovat  pomocí
      or.

      UDP/IP connect

      I u udp má smysl volání  connect, i když  není povinné.  Když na udp
      socketu pustíte  connect, tak tím systému řeknete,  že vaše  socketa
      bude posílat   všechna   data (od  teď přes   send   místo sendto) a
      přijímat  data (může přes recv)   pouze od specifikovaného  partnera
      (peer).

      Udp  socketu (asi)  zavřete   stejně jako  každou   jinou (shutdown,
      close).

      Jestli jste se opravdu  prokousali   až sem, na 283. řádek,  tak Vás
      opravdu obdivuji   a doufám, že se Vám  výše uvedené informace budou
      hodit,  až se někdy budete   prokousávat    nepořádnou   dokumentací
      síťového   rozhraní (třeba na ten byte ordering jsem narazil náhodou
      při prolézání jednoho zdrojáku).

      Měl bych ještě poznamenat, že výše uvedené informace se  vztahují ke
      standartní UNIXové knihovně (jak je popsána v dokumentaci    na  mém
      Linuxu). Vzhledem k tomu, že na  DOSu  není TCP/IP součástí systému,
      je zde  rozhraní  do určité  míry záležitostí  konkrétní  nadstavby.
      A na W95 si nemůžete byt  jisti ničím (Microsoft si prý  (viz článek
      ve  Výhni  #8   (Programování    pro   Microsoft    Windows/Shakul))
      přejmenovávají i funkci main  na  WinMain:-|).    O těchto systémech
      nemám přehled (V DOSu už akorát  hraju Heroesy:-), ale pokud jste se
      někdo zabýval, nebo hodláte  zabývat  komunikací po internetu z DOSu
      nebo Windows95/8,    byl  bych  rád, kdybyste  o  TCP/IP    v těchto
      systémech něco napsali.

                                                                        - Bulb -


      Poznámka redakce: Ve Windows 9x/NT se s TCP/IP pracuje skoro stejně.
      Jediný  rozdíl  spočívá  v  tom,  že program musí na začátku zavolat
      funkci  pro  inicializaci WinSocku WSAStartup a na konci WSACleanup.
      Poté je možné používat snad všechny Bulbem popsané funkce.


            výheň