Hlavní navigace

Sokety a C/C++: funkce poll a závěr

3. 11. 2003
Doba čtení: 6 minut

Sdílet

Dnešní díl je poslední. Podíváme se, jak používat funkci poll, která v Linuxu může nahrazovat funkci select.

V první polovině seriálu jsem popsal způsob, jak pracovat s více sokety najednou v jednom vlákně. Popsal jsem funkci select. Funkce byla převedena do WinSock knihovny, kde s ní lze pracovat jen se sokety. Tím pádem je pro sokety použitelná v Linuxu i v MS WindowsŽ. Slíbil jsem, že v budoucnu popíšu i funkci poll, která v MS WindowsŽk dispozici není. Funkce poll stejně jako funkce select čeká na nějakou událost na jakémkoliv identifikátoru souboru (file descriptor). V našem případě na soketu. Funkce pracuje se strukturou pollfd.

Struktura pollfd

Pro každý soket je nutné vytvořit instanci struktury pollfd. Struktura má tři atributy. Atribut fd, který je typu int, udává identifikátor soketu (obecně jakéhokoliv souboru). Druhý a třetí atribut jsou typu short int. Jmenují se events a revents. Oba atributy jsou binární masky. Atribut events udává, jaké události nás na soketu zajímají, které chceme hlídat. Atributrevents by před zavoláním funkce poll měl být nastaven na 0. Po ukončení volání poll bude obsahovat binární masku s událostmi, které na soketu nastaly.

Funkce poll

  • int poll(struct pollfd *ufds, unsigned int nfds, int timeout); – prvním parametrem je pole (ukazatel na první prvek pole) struktur pollfd. Druhým parametrem je počet prvků v poli prvního parametru. Poslední parametr udává čas v milisekundách, po který funkce čeká na událost na jednom ze soketů. Je-li poslední parametr záporný, časový limit pro událost je neomezený. Funkce vrací nezáporné číslo udávající počet soketů, na kterých nastala některá z požadovaných událostí. V případě, že poll selže, vrací –1.

Události na soketu

Každá událost na soketu je dána číselnou hodnotou, která je reprezentována makrem. Pro nás jsou důležité hodnoty:

  • POLLIN – na soketu jsou nepřečtená data.
  • POLLPRI – na soketu jsou nepřečtená urgentní data.
  • POLLOUT – na soketu je možné zapisovat. (Soketem je možné poslat data). Vhodné při práci se sokety v neblokovacím režimu.

Ostatní lze nalézt na manuálových stránkách funkce poll.

Příklad použití poll

V příkladu upravíme příklad z článku Sokety a C/C++ – Funkce select. Ve zmíněném článku jsme pomocí funkce select vytvořili jednoduchý server, který očekává spojení, při navázání spojení očekává příchozí data od jednoho z připojených klientů a ta pak rozešle všem připojeným klientům. Příklad rozšíříme o možnost odeslat serveru urgentní data. Ty server přijme a pošle je („normálně“ – neurgentně) všem klientům s řetězcem oznamujícím, že se původně jednalo o urgentní data. Po třech minutách nečinnosti se server ukončí. Oproti příkladu v článku nyní použijeme funkci poll.

Pro zjednodušení si budeme otevřené sokety uchovávat v kontejneru vector. V případě, že se změní obsah kontejneru vector, vytvoříme nové pole instancí strukturpollfd. Velikost pole budeme upravovat, pokud počet prvků vzroste o 5. Možná by se hodilo velikost pole snižovat, pokud se sníží počet otevřených soketů. Pro funkčnost jednoduchého příkladu to ale stačí.

Uvedu pouze podstatnou část zdrojového textu, kterou se příklad liší od obdobného příkladu využívajícího select.

Pro soket mainSocket máme zavoláno bindlisten. Celý příklad je na konci článku k dispozici ke stažení.

Naplnění pole struktur pollfd

V proměnné count je uchován počet soketů, se kterými pracujeme. V kontejneru sockets máme všechny otevřené sokety, které vrátila funkce accept (sokety na jednotlivé klienty). Jestliže se počet soketů změnil (je jiný než velikost kontejneru + 1, jednička je „hlavní soket“, kterým očekáváme příchozí spojení), musíme změnit pole struktur pollfd. Tento způsob je možná trochu zvláštní, ale velmi pohodlný. Kdybychom si nepomáhali kontejnerem, museli bychom implementovat některé operace v poli, které za nás u šablony vector udělal již někdo jiný. Proměnná maxAlloc udává velikost pole ufds.

do
{
  if (count != sockets.size() + 1)
  {
    count = sockets.size() + 1;
    if (maxAlloc < sockets.size() + 1)
    {
      maxAlloc = sockets.size() + 1 + CAPACITY_INCREMENT;
      temp = ufds;
      if ((ufds = (pollfd *)realloc(ufds, maxAlloc)) == NULL)
      {
        close(mainSocket);
        for_each(sockets.begin(), sockets.end(), close);
        free(temp);
        cerr << "Problem s pameti" << endl;
        return -1;
      }
    }


První prvek pole ufds je soket, na kterém čekáme příchozí spojení. Očekáváme zde událost „příchod dat“. Příchod dat znamená další žádost o TCP spojení. Zde nemusíme očekávat urgentní data.

    ufds[0].fd = mainSocket;
    ufds[0].events = POLLIN;
    ufds[0].revents = 0;

Pro všechny prvky v kontejneru vyplníme jednu instanci struktutypollfd. Očekávat budeme „normální“ a urgentní data.

    for(i = sockets.begin(), p = 1;
        i != sockets.end(); i++, p++)
    {
      ufds[p].fd = *i;
      ufds[p].events = POLLIN | POLLPRI;
      ufds[p].revents = 0;
    }
  }

Jsme připraveni zavolat funkci poll.

  if ((changed = poll(ufds, count, TIMEOUT)) < 0)
  {
    close(mainSocket);
    for_each(sockets.begin(), sockets.end(), close);
    free(ufds);
    cerr << "Problem s poll" << endl;
    return -1;
  }

Jsou na hlavním soketu nepřečtená data? Jestliže ano, vytvoříme (pomocí accept) nový soket, propojený s klientem, který žádá o spojení.

  if (ufds[0].revents & POLLIN != 0)
  {
    socklen_t addrlen = sizeof(clientInfo);
    int client = accept(mainSocket,
      (sockaddr*)&clientInfo, &addrlen);
    sockets.push_back(client);
  }

Na každý další soket v poli udfs zjistíme, jestli není v atributu odpovídající instance struktury pollfdnastaven příznak POLLIN nebo POLLPRI (příchozí data urgentní nebo neurgentní). Jestliže ano, zavoláme recv. Funkce recv vrací 0 v případě, že druhá strana uzavřela spojení.

  for(p = 1; p < count; p++)
  {
    if (ufds[p].revents & (POLLIN | POLLPRI) != 0)
    {
      char buffer[BUFSIZE];
      int lenght;
      if ((lenght =
        recv(ufds[p].fd, (void *)buffer, BUFSIZE - 1, 0)) <= 0)
      {
        close(ufds[p].fd);
        sockets.erase(find(sockets.begin(),
          sockets.end(), ufds[p].fd));
        break;
      }
      else
      {
        SendText s;
        buffer[lenght] = 0;
        if ((ufds[p].revents & POLLPRI) != 0)
        {
          s.Text = std::string("Data byly urgentni: ") + buffer;
        }
        else
        {
          s.Text = buffer;
        }
        for_each(sockets.begin(), sockets.end(), s);
      }
      ufds[p].revents = 0;
    }
  }
} while (changed > 0);


Tabulka č. 511
Soubor Operační systém
lin35.tgz Linux

Závěr

Tak je tady úplný konec seriálu. Byl plný nepřesností a překlepů, o to víc jsem rád, že existují čtenáři, kteří jej dočetli až sem. Seriál je bohužel trochu „rozházen“ po internetu. Jeho prvních jedenáct dílů je na adrese http://www.bu­ilder.cz/seri­al147.html. Původní snahou bylo popisovat soket jako komunikační nástroj v Linuxu a v MS WindowsŽ. Přesunem na roota jsem se systému MS WindowsŽ začal věnovat jen okrajově. Podstatnou část seriálu jsem se (někdy dost křečovitě) snažil udržet zdrojové texty pro Linux a pro MS WindowsŽ co nejvíce podobné.

root_podpora

Díky přechodu na roota jsem psal o věcech, o kterých jsem původně na builderu psát nechtěl. Naopak mě trochu mrzí, že jsem na builderu nestihl napsat nic a asynchronních soketech a s nimi související funkcí WSAAsyncSelect. Jedná se o typ soketu, který zavádí knihovna WinSock. Takové téma se tématicky na roota nehodí. Všem, kdo se zajímají o sokety v MS WindowsŽ, doporučuji se s asynchronním soketem seznámit.

Použitá literatura

Na úplný závěr je slušností uvést informační zdroje.

Byl pro vás článek přínosný?