Pamācības

Boost.Asio pamācība

Boost.Asio pamācība

Elviss Strazdiņš, 12.01.2010

Mājaslapa: http://www.boost.org/doc/libs/1_41_0/doc/html/boost_asio.html

Lejupielādēt: šeit

Komentāri: 5

Pēdējā laikā aizvien biežāk sanāk programmēt tīkla aplikācijas C++, tāpēc beidzot izdomāju neizmantot WinSock, bet kaut ko, kas strādātu uz visām platformām. Kādēļ neizmantoju parastu select, kas strādā uz visām platformām? Tādēļ, ka tam pastāv viens ierobežojums, tas spēj apkalpot tikai 256 socket'us (kas pavisam neder lielākām sistēmām). Tādēļ izdomāju pameklēt, kādi tad ir risinājumi šai problēmai. Internetā uzmeklēju šādas tīkla bibliotēkas: POCO (nevajadzīgi sarežģīts), RakNet (maksas priekš komerciāliem produktiem), OpenTNL (projekts tikpat kā miris) un Boost.Asio (manuprāt, ļoti sakarīgs rīks), par ko tad arī būs šī pamācība.

Tā kā spēļu server izveidei sinhronais (synchronous)  socket's nav vajadzīgs, tad šajā pamācībā apskatīšu tikai asinhrono (asynchronous) socket'u. Ja kādam tomēr interesē pamācība par sinfrono socketu, tad angliski to var izlasīt šeit. Nedaudz paskaidrošu: asinhronais sokets nozīmē to, ka socket'u operācijas nebloķēs visu programmu un tā paralēli socket'u apstrādei varēs darīt arī citas lietas. Protams, ir iespēja palaist socket'u apkalpošanu vienā thread'ā, bet programmas loģiku otrā, bet vairāku apsvērumu dēļ šādu tehniku parasti neizmanto. Pie tam izmantošu tieši TCP socket'u, nevis UDP. Par UDP socket'u varat palasīt tieši tur pat, kur par sinhronajiem socket'iem.

Tātad ķeramies pie pamācības! Lai vispār strādātu ar Boost.Asio, jums jābūt uzinstalētai Boost pakotnei. Priekš windows instalāciju var lejuplādēt šeit, pārējām platformām gan būs jākompilē pašiem.

Lai mūsu piemērs strādātu, ir jāiekļauj (#include) šādas bibliotēkas:

#include 
using namespace std;

#include 
#include 
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;

šajā gadījumā tiks izmantots tikai paziņojumu izvadei konsolē. Nākošā mums ir jāizveido tīkla klase, kas apstrādās klientu pieslēgšanos (connection).

class Network
{
public:
  Network(): _acceptor(_service, tcp::endpoint(tcp::v4(), 1026))
  //norādam pie kura porta slēgties
  {
    startAccept(); //sākam gaidīt klientus
  }

  void update()
  {
    _service.run_one(); //izsuacam servisa update funkciju
  }
private:
  void startAccept()
  {
    Client* client = new Client(_acceptor.io_service()); //izveidojam jaunu klientu
    _acceptor.async_accept(client->socket(),
    bind(
      &Network::handleAccept, this,
      client,
      placeholders::error)); //sākam gaidīt
  }

  void handleAccept(Client* client, const system::error_code& error)
  {
    if (!error) //nav notikusi kļūme
    {
      cout<<"Client connected from "<socket().remote_endpoint().address().to_string()<startReceive(); //sākam gaidī datus no klienta

      startAccept(); //turpinam gaidīt citus klientus
    }
    else
    {
      cout<<"Accept error"<

Nedaudz pastāstīšu, kas tad īsti notiek šajā klasē. Klasē ir divi lokālie mainīgie: _service un _acceptor. _service atbild par tīkla apkalpošanu (uzturēšanu pie dzīvības), bet _acceptor pieņem klientus, kas vēlas pieslēgties pie servera. Pašā sākumā tiek izveidots _service padodot tam portu (mūsu gadījumā 1026). Pēc tam mēs izsaucam metodi startAccept(), kas liek _acceptor'am gaidīt klientus. Interesanti Boost.Asio man liekas tas, ka Client's (Client* client = new Client(_acceptor.io_service());) ir jāizveido pirms tas ir pieslēdzies. Un pati pēdējā metode ir handleAccept, kas pieņem klientus. Ja savienojums ir noticis veiksmīgs, tad ar client->startReceive(); sākam pieņemt no klienta datus, bet ar startAccept(); turpinam gaidīt klientus.

Kad tīkla klase ir izveidota, laiks ķerties pie klienta klases:

class Client
{
public:
  Client(io_service& io_service):
    _socket(io_service) //izveidojam klienta socket'u
  {
  }

  tcp::socket& socket() //atgriež klienta socket'u
  {
    return _socket;
  }

  void startReceive()
  {
    _socket.async_read_some(buffer(_data, max_length), //sākam gaidīt datus

      bind(&Client::handleRead, this,
        placeholders::error,
        placeholders::bytes_transferred));
  }

  void close() //aizvēr socket'u
  {
    _socket.close();
  }

private:
  void handleRead(const system::error_code& error,
  //tiek izsaukts, kad ir saņemti dati
    size_t bytes_transferred)
  {
    if (!error) //nav notikusi kļūme
    {
      cout<

Klienta klasei ir 3 lokāliem mainīgie: _socket, max_length un _data. _socket sevī glabā socket'u (savienojumu) un informāciju par klientu (piemēram, adresi), max_length ir konstante, kas norāda bufera izmēru, bet _buffer sevī glabā saņemtos datus no klienta. Šajā klasē, lai arī tā ir garāka par Network, nekā sarežģīta nav. handleRead un Handle write funkcijas tiek izsauktas, kad dati tiek attiecīgi saņemti, vai izsūtīti. Metode startRead() sāk gaidīt datus no klienta. Tiklīdz dati ir saņemti, izsaucas handleRead, kura savukārt aizsūta saņemtos datus atpakaļ klientam (ar async_write) un turpina gaidīt datus no klienta. Vēl papildus pievienoju vienu metodi: close(), ar to var aizvērt klienta socket'u.

Kopumā mans secinājums par Boost.Asio ir tāds, ka tas strādā nedaudz dīvaini, jo, pirmkārt, klienta objekts tiek izveidots vēl pirms savienojuma, un, otrkārt, katru reizi, kad klients pievienojas, vai ienāk dati, ir jāatsāk gaidīt klientus (startAccept) vai saņemt datus (startReceive). Līdzīga pieeja ir C# asinhronajiem socketiem, kur arī pēc katras pieslēgšanās vai datu saņemšanas tā ir jāatsāk. Lai arī šī pieeja ir nedaudz savāda, programmēt ar Boost.Asio ir ļoti ērti, tādēļ iesaku katram, kas saskaras ar tīklu, iemest aci šajā bibliotēkā. Ja gadījumā neizdodas programmu salikt kopā, tad programmas pirmkods ir pielikumā.

Jūs varētu interesēt arī šādi raksti:

Aptauja

Kura profesija tevi raksturo visprecīzāk? [0]





Citas aptaujas

Autorizācija

Lietotājs

Parole


Reģistrēties Aizmirsi paroli?