Hlavní navigace

Sokety a C/C++: datagramy a PF_UNIX

Radim Dostál

Dnes se podíváme na datagramový soket v rodině protokolů PF_UNIX. Dále si ukážeme funkci socketpair a na příkladu si ukážeme komunikaci dvou procesů pomocí anonymního datagramového soketu.

V minulém dílu (Sokety a C/C++ – rodina protokolů PF_UNIX) jsme si ukázali práci s rodinou protokolů PF_UNIX. V příkladu jsme vytvořili soket v doméně PF_UNIX, jehož typ byl SOCK_STREAM (proud dat). Stejně jako v doméně PF_INET je i v doméně PF_UNIX k dispozici datagramová služba.

Doposud jsme v souvislosti s datagramy používali v doméně PF_INET protokol UDP. V doméně PF_UNIX nemá smysl mluvit o komunikačním protokolu. Spíše je potřeba odlišit vlastnosti typu soketu.

Typ SOCK_STREAM

Na tento typ soketu lze pohlížet jako na proud dat. Při čtení ze soketu nejsme schopni rozeznat jak velké bloky dat byly na druhé straně do soketu zapisovány. My máme k dispozici posloupnost bytů. V jakých celcích došlo k zápisu dat na druhé straně, nejsme schopni zjistit. Data čtená ze soketu typu „stream“ jsou čtena ve stejném pořadí, v jakém byla na druhé straně vložena. Data se nemohou předbíhat ani ztratit.

Typ SOCK_DGRAM

Obě strany spolu komunikují pomocí datagramů (zpráv). Přijímající strana může od sebe odlišit jednotlivé datagramy. Tedy každé volání recv mělo odpovídající volání send. Obecně u datagramové služby nelze předpokládat, že datagram byl doručen a že jednotlivé datagramy budou doručeny ve stejném pořadí, v jakém byly odeslány. U protokolu UDP v rodině protokolů PF_INET tomu tak i bylo. Doména rodiny protokolů PF_UNIX, o které hovoříme nyní, je trochu jiná. Obecně sice platí o datagramech, co jsem napsal, ale v případě domény PF_UNIX můžeme předpokládat, že datagram bude vždy doručen a že se jednotlivé datagramy nebudou předbíhat.

Typ soketu je druhým parametrem funkce socket. Nyní bychom mohli jako příklad na datagramový soket uvést příklad z minulého dílu (Sokety a C/C++ – rodina protokolů PF_UNIX). Stačilo by pouze změnit vytváření soketu. Řádek by vypadal asi takto:

if ((sock = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1)

A příklad by byl s touto změnou funkční. Samozřejmě by ještě bylo nutné trochu upravit server. Oba procesy by používaly datagramový soket. Jiná možnost by byla bez volání funkce connect používat na obou stranách bind a potom recvfrom resp. sendto. Vše by v podstatě bylo v principu totožné jako v článku Protokol UDP 1. část nebo Protokol UDP 2. část. My si ale dnes ukážeme ještě něco navíc, takzvaný anonymní soket.

Anonymní soket

Anonymní soket je soket, který není svázán s žádným pseudosouborem (tak jako byl svázán soket v minulém dílu).

Funkce socketpair

  • int socketpair(int domain, int type, int protocol, int sv[2]);  – Prvním parametrem je rodina protokolů. V našem případě PF_UNIX. Mohou být ale i jiné. Druhým parametrem je typ soketu (např. SOCK_STREAM nebo SOCK_DGRAM). Třetím parametrem je protokol. Čtvrtý parametr je pole typu int o dvou prvcích. Nebo přesněji, jak už to v C bývá, ukazatel na první prvek tohoto pole. Funkce předpokládá, že ukazatel ukazuje na blok paměti velikosti 2 * sizeof(int). Funkce vytvoří dva navzájem propojené sokety ze zadané domény (rodiny protokolů), zadaného typu a použitého protokolu. Dva nové identifikátory soketů vloží do pole, které je dáno posledním parametrem. Funkce vrací 0 v případě úspěchu, v případě chyby –1.

V úvodu seriálu jsem pro představu napsal, že komunikace probíhá pomocí potrubí nebo hadice a soket je jeden konec tohoto potrubí. Funkce socketpair nám (z tohoto pohledu) vlastně vrací oba konce potrubí.

Příklad

Vytvoříme si jednoduchý příklad, kdy si dva procesy vymění několik informací. Nejprve pomocí socketpair vytvoříme pár propojených soketů, potom vytvoříme nový proces pomocí fork. Oba procesy spolu budou komunikovat pomocí těchto soketů.

Rodičovský proces bude ve funkci parent, synovský proces ve funkci child.

Formality

Vložíme hlavičkové soubory, deklarujeme funkce, začneme funkcí main a definujeme v ní lokální proměnné. Funkce main bude tentokrát malá a jednoduchá.

#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <vector>
#include <algorithm>

#define BUFFER_LEN 200

void child(int sock);
void parent(int sock)

int main(int argc, char *argv[])
{
  int sockets[2];
  pid_t pid;

Vytvoření páru anonymních soketů

Sokety budou z domény PF_UNIX, typu SOCK_DGRAM a protokol v rodině protokolů PF_UNIX není podstatný, proto 0.

  if (socketpair(PF_UNIX, SOCK_DGRAM,
   0, sockets) == -1)
  {
    perror("Volání socketpair selhalo");
    return -1;
  }

Vytvoření nového procesu

Nový proces se vytváří pomocí fork. Jen připomenu, že fork vrací –1 v případě selhání, 0 vrací v synovském procesu a nenulovou kladnou hodnotu (identifikátor nového synovského procesu) vrací v původním rodičovském procesu.

V původním rodičovském procesu zavoláme funkci parent. Po jejím skončení zavřeme soket a počkáme pomocí wait na ukončení potomka.

V synovském procesu zavoláme funkci child a poté uzavřeme soket.

  if ((pid = fork()) == -1)
  {
    perror("Volání fork selhalo");
    return -1;
  }
  if (pid == 0)
  {
    child(sockets[0]);
    close(sockets[0]);
  }
  else
  {
    parent(sockets[1]);
    close(sockets[1]);
    wait(NULL);
  }

A můžeme ukončit funkci main.

  return 0;
}

Synovský proces

Vše podstatné je ve funkci child. Její parametr je soket. Funkce odešle pomocí soketu úvodní řetězec a dále očekává příchozí data. Na každý příchozí datagram odpoví datagramem, kde sdělí, kolik měl příchozí datagram bytů, a svůj identifikátor procesu. Funkce se ukončí, přijde-li řetězec KONEC!.

void child(int sock)
{
  char firstMessage[] =
           "Ahoj rodiči, jsem připraven.";
  char endMessage[] = "Dobrá končím.";
  char pidMessage[BUFFER_LEN], buffer[BUFFER_LEN];
  fd_set mySet;

  int lenght;
  send(sock, firstMessage, strlen(firstMessage), 0);
  do
  {
    FD_ZERO(&mySet);
    FD_SET(sock, &mySet);
    if (select(sock + 1, &mySet, NULL, NULL, NULL)
           <= 0)
    {
      return;
    }
    if (FD_ISSET(sock, &mySet))
    {
      if ((lenght = recv(sock, buffer,
                 BUFFER_LEN - 1, 0)) <= 0)
      {
           return;
      }
      snprintf(pidMessage, BUFFER_LEN,
             "Přijal jsem %d bytů. Můj pid je %d.\n",
                 lenght, getpid());
      if (strcmp(buffer, "Konec!") == 0)
      {
           send(sock, endMessage,
              strlen(endMessage), 0);
      }
      else
      {
           if (send(sock, pidMessage,
                  strlen(pidMessage), 0) <= 0)
           {
                return;
           }
      }
    }
  } while (strcmp(buffer, "Konec!") != 0);
}

Rodičovský proces

Vše podstatné je ve funkci parent. Její parametr je soket. Rodičovský proces přijme data ze soketu, nějaká data mu pošle a nakonec odešle řetězec KONEC!

void parent(int sock)
{
  char buffer[BUFFER_LEN];
  fd_set mySet;
  int lenght, p;

  if ((lenght = recv(sock, buffer,
         BUFFER_LEN - 1, 0)) <= 0)
  {
    return;
  }
  buffer[lenght] = 0;
  printf(
   "Potomek poslal %d bytů.\nObsah zprávy:\n%s\n",
   lenght, buffer);
  printf("Můj pid je: %d\n", getpid());
  for(p = 0; p < 4; p++)
  {
    if (p == 3)
    {
      strncpy(buffer, "Konec!", 7);
    }
    else
    {
      buffer[0] = '0' + p;
      buffer[1] = '\0';
    }
    if (send(sock, buffer,
           strlen(buffer) + 1, 0) <= 0)
    {
      return;
    }
    FD_ZERO(&mySet);
    FD_SET(sock, &mySet);
    if (select(sock + 1, &mySet,
           NULL, NULL, NULL) <= 0)
    {
      return;
    }
    if (FD_ISSET(sock, &mySet))
    {
      if ((lenght = recv(sock,
                 buffer, BUFFER_LEN - 1, 0)) <= 0)
      {
            return;
      }
    }
    buffer[lenght] = 0;
    printf("Potomek poslal %d bytů.\nObsah
           zprávy:\n%s\n", lenght, buffer);
  }
}

Příklad ke stažení

Tabulka č. 500
Soubor OS
lin31.tgz Linux
Našli jste v článku chybu?

14. 10. 2003 21:28

Radim Dostál (neregistrovaný)

Opravdu tam nejde místo

addr = reinterpret_cast<struct sockaddr*>(sock);

dát

addr = (struct sockaddr*) sock;

(i když to první je v C++ správnější)

C++ přináší oproti C nové operátory pro přetypování. dynamic_cast, static_cast a reinterpret_cast. Zatímco použití dynamic_cast a static_cast je poměrně jasné, reinterpret_cast by se dal mnohokrát nahradit klasickým přetypováním z C. Podle normy by se asi měl používat reinterpret_cast, mně se ale v některých případech jeví jako…









14. 10. 2003 15:01

Dalxo (neregistrovaný)

Zabudol som dodat, ze hore uvedeny kod je sucast inicializacnej metody jednej triedy, teda nemal som ho len tak "nasucho" :-)), ale zase som nechcel "vypastovat" cely zdrojak.

DigiZone.cz: ČRo rozšiřuje DAB do Berouna

ČRo rozšiřuje DAB do Berouna

120na80.cz: 5 nejčastějších mýtů o kondomech

5 nejčastějších mýtů o kondomech

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Vychytané vály a válečky na vánoční cukroví

Vychytané vály a válečky na vánoční cukroví

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

DigiZone.cz: Flix TV: dva set-top boxy za korunu

Flix TV: dva set-top boxy za korunu

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Lupa.cz: Teletext je „internetem hipsterů“

Teletext je „internetem hipsterů“

Vitalia.cz: Pamlsková vyhláška bude platit jen na základkách

Pamlsková vyhláška bude platit jen na základkách

Podnikatel.cz: Snížení DPH na 15 % se netýká všech

Snížení DPH na 15 % se netýká všech