Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

«Boost.Asio C Network Programming». Глава 2: Основы Boost.Asio. Часть 1

Anna | 24.06.2014 | нет комментариев

Продолжаю перевод книги John Torjo «Boost.Asio C Network Programming». Вторая глава получилась огромная, следственно разобью ее на две части. В этой части мы побеседуем именно про основы Boost.Asio, а во 2-й части речь пойдет про асинхронное программирование.

Оглавление:

  • Глава 1: Приступая к работе с Boost.Asio
  • Глава 2: Основы Boost.Asio
    • Часть 1: Основы Boost.Asio
    • Часть 2: Асинхронное программирование
  • Глава 3: Echo Сервер/Клиент
  • Глава 4: Заказчик и Сервер
  • Глава 5: Синхронное вопреки асинхронного
  • Глава 6: Boost.Asio – другие особенности
  • Глава 7: Boost.Asio – добавочные темы

В этой главе мы разглядим то, что вам непременно знать, применяя Boost.Asio. Мы углубимся в асинхронное программирование, которое гораздо труднее, чем синхронное и значительно больше увлекательное.

Сетевое API

В этом разделе показано, что вам нужно знать, Дабы написать сетевое приложение с применением Boost.Asio.

Пространства имен Boost.Asio

Все в Boost.Asio находится в пространстве имен boost::asio либо его подпространстве, разглядим их:

  • boost::asio: Это где находятся все основные классы и функции. Основные классы это io_service иstreambuf. Тут находятся такие функции как read, read_at, read_until, их асинхронные копии, а так же функции записи и их асинхронные копии.
  • boost::asio::ip: Это место, где находится сетевая часть библиотеки. Основные классы это address, endpoint, tcp, udp, icmp, а основные функции это connect и async_connect. Обратите внимание, чтоsocket в boost::asio::ip::tcp::socket это легко typedef внутри класса boost::asio::ip::tcp.
  • boost::asio::error: Это пространство имен содержит коды ошибок, которые вы можете получить при вызове подпрограммы ввода/вывода
  • boost::asio::ssl: Это пространство имен содержит классы, имеющие дело с SSL.
  • boost::asio::local: Это пространство имен содержит POSIX-специфичные классы
  • boost::asio::windows: Это пространство имен содержит Windows-специфичные классы
IP адреса

Для работы с IP адресами Boost.Asio предоставляет классы ip::address, ip::address_v4 и ip::address_v6.
Они предоставляют уйма функций. Вот особенно значимые из них:

  • ip::address(v4_or_v6_address): Эта функция конвертирует v4 либо v6 адрес в ip::address
  • ip::address:from_string(str): Эта функция создает адрес из IPv4 адреса (поделенных точками) либо из IPv6 (шестнадцатиричный формат)
  • ip::address::to_string(): Эта функция возвращает представление адреса в благоприятном строчном виде
  • ip::address_v4::broadcast([addr, mask]): Эта функция создает broadcast адрес
  • ip::address_v4::any(): Эта функция возвращает адрес, тот, что олицетворяет всякий адрес
  • ip::address_v4::loopback(), ip_address_v6::loopback(): Эта функция возвращает шлейф адресов (из v4/v6 протокола)
  • ip::host_name(): Эта функция возвращает имя нынешнего хоста в виде строчки

Скорее каждого Почаще каждого вы будете применять функцию ip::address::from_string:

ip::address addr = ip::address::from_string("127.0.0.1");

Если вам нужно подключиться к имени хоста, читайте дальше. Дальнейший код не будет трудиться:

// throws an exception
ip::address addr = ip::address::from_string("www.yahoo.com");
Финальные точки (Endpoints)

Финальная точка это адрес подключения совместно с портом. Всякий тип сокетов имеет свой endpoint класс, скажем, ip::tcp::endpoint, ip::udp::endpoint, и ip::icmp::endpoint.
Если вы хотите подключиться к localhost по 80 порту, то вам необходимо написать следующее:

ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);

Вы можете сделать финальную точку тремя методами:

  • endpoint(): конструктор по умолчанию и он может быть изредка использован для UDP/ICMP сокетов
  • endpoint(protocol, port): обыкновенно применяется на серверных сокетах для приема новых подключений
  • endpoint(addr, port): создание финальной точки по адресу и порту

Вот несколько примеров:

ip::tcp::endpoint ep1;
ip::tcp::endpoint ep2(ip::tcp::v4(), 80);
ip::tcp::endpoint ep3( ip::address::from_string("127.0.0.1), 80);

Если же вы хотите подключится к хосту (не IP адресу), то вам необходимо сделать следующее:

 // outputs "87.248.122.122"
io_service service;
ip::tcp::resolver resolver(service);
ip::tcp::resolver::query query("www.yahoo.com", "80");
ip::tcp::resolver::iterator iter = resolver.resolve( query);
ip::tcp::endpoint ep = *iter;
std::cout << ep.address().to_string() << std::endl;

Можете заменить tcp на необходимый вам тип сокета. Во-первых, сделайте запрос с именем, к которому хотите подключиться, это дозволено реализовать с поддержкой функции resolve(). В случае триумфа вернется правда бы одна запись.
Получив финальную точку, вы можете получить из нее адрес, порт и IP протокол (v4 либо v6):

std::cout << ep.address().to_string() << ":" << ep.port() << "/" << ep.protocol() << std::endl;
Сокеты

Boost.Asio включает в себя три типа классов сокетов: ip::tcp, ip::udp, и ip::icmp, ну и, безусловно же, расширяется. Вы можете сделать свой личный класс сокета, правда это достаточно трудно. В случае если вы все же решите сделать это посмотрите boost/ asio/ip/tcp.hppboost/asio/ip/udp.hpp, иboost/asio/ip/icmp.hpp. Все они это достаточно маленькие классы с внутренними typedef ключевыми словами.
Вы можете думать о классах ip::tcp, ip::udp, ip::icmp как о заполнителях; они дают вероятность легко добраться до других классов/функций, которые определяются дальнейшим образом:

  • ip::tcp::socket, ip::tcp::acceptor, ip::tcp::endpoint, ip::tcp::resolver, ip::tcp::iostream
  • ip::udp::socket, ip::udp::endpoint, ip::udp::resolver
  • ip::icmp::socket, ip::icmp::endpoint, ip::icmp::resolver

Класс socket создает соответствующий сокет. Вы неизменно передаете экземпляр io_service в конструктор:

io_service service;
ip::udp::socket sock(service)
sock.set_option(ip::udp::socket::reuse_address(true));

Всякое имя сокета имеет typedef:

  • ip::tcp::socket= basic_stream_socket<tcp>
  • ip::udp::socket= basic_datagram_socket<udp>
  • ip::icmp::socket= basic_raw_socket<icmp>

Коды ошибок синхронных функций

Все синхронные функции имеют перегрузки, которые выбрасывают исключения либо возвращают код ошибки, как показано ниже:

sync_func( arg1, arg2 ... argN); // throws
boost::system::error_code ec;
sync_func( arg1 arg2, ..., argN, ec); // returns error code

В оставшейся части главы, Вы увидите много синхронных функций. Дабы не усложнять, я опустил показ перегрузок, которые возвращают код ошибки, но они существуют.

Функции сокетов

Все функции поделены на несколько групп. Не все функции доступны для всякого типасокета. Список в конце этого раздела покажет вам, какие функции к какому классу сокетов относятся.
Подметим, что все асинхронные функции отвечают мигом, в то время как их сотрудники синхронные отвечают только позже того, как операция была закончена.

Соединительно-объединяющие функции

Это функции, которые подключаются либо соединяются с сокетом, отключают его и делают запрос о подключении, энергично оно либо нет:

  • assign(protocol,socket): эта функция присваивает сырой(обычный) сокет к экземпляру сокета. Используйте ее при работе с наследуемым кодом (то есть когда сырые сокеты теснее сделаны).
  • open(protocol): эта функция открывает сокет с заданным IP-протоколом (v4 либо v6). Вы будете применять ее в основном для UDP/ICMP сокетов либо серверных сокетов
  • bind(endpoint): эта функция связывается с данным адресом.
  • connect(endpoint): эта функция синхронно подключается по данному адресу.
  • async_connect(endpoint): эта функция асинхронно подключается по данному адресу.
  • is_open(): эта функция возвращает true если сокет открыт.
  • close(): эта функция закрывает сокет. Всякие асинхронные операции на этом сокете неотлагательно прекращаются и возвращают error::operation_aborted код ошибки.
  • shutdown(type_of_shutdown): эта функция отключает операцию send , receive либо обе сразу же позже вызова.
  • cancel(): эта функция отменяет все асинхронные операции на этом сокете. Все асинхронные операции на этом сокете будут незамедлительно закончены и вернут error::operation_aborted код ошибки.

Приведем маленький пример:

ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
ip::tcp::socket sock(service);
sock.open(ip::tcp::v4()); n
sock.connect(ep);
sock.write_some(buffer("GET /index.htmlrn"));
char buff[1024]; sock.read_some(buffer(buff,1024));
sock.shutdown(ip::tcp::socket::shutdown_receive);
sock.close();
Функции чтения/записи

Это функции, которые исполняют ввод/вывод на сокете.
Для асинхронных функций обработчик имеем следующую сигнатуру void handler(const boost::system::error_code& e, size_t bytes);. А вот сами функции:

  • async_receive(buffer, [flags,] handler): эта функция запускает асинхронную операцию приобретения данных от сокета.
  • async_read_some(buffer,handler): эта функция эквивалента async_receive(buffer, handler).
  • async_receive_from(buffer, endpoint[, flags], handler): эта функция запускает асинхронное приобретение данных от определенного адреса.
  • async_send(buffer [, flags], handler): эта функция запускает операцию асинхронной передачи данных из буфера
  • async_write_some(buffer, handler): эта функция равнозначна async_send(buffer, handler).
  • async_send_to(buffer, endpoint, handler): эта функция запускает операцию асинхронной передачи данных из буфера по определенному адресу.
  • receive(buffer [, flags]): эта функция синхронно принимает данные в буфер. Функция заблокирована пока не начнут приходить данные либо если случилась оплошность.
  • read_some(buffer): эта функция равнозначна receive(buffer).
  • receive_from(buffer, endpoint [, flags]): эта функция синхронно принимает данные от определенного адреса в данный буфер. Функция заблокирована пока не начали приходить данные либо если случилась оплошность.
  • send(buffer [, flags]): эта функция синхронно отправляет данные из буфера. Функция заблокирована пока идет отправка данных либо если случилась оплошность.
  • write_some(buffer): эта функция равнозначна send(buffer).
  • send_to(buffer, endpoint [, flags]): эта функция синхронно передает данные из буфера по данному адресу. Функция заблокирована пока идет отправка данных либо если случилась оплошность.
  • available(): эта функция вvmk!code>true, то предотвращает маршрутизацию и использует только локальные интерфейсы bool enable_connection_aborted Если true, то переподключает оборванное соединение bool keep_alive Если true, то посылает keep-alives bool linger Если true, то сокет задерживается на close(), если есть не сохраненные данные bool receive_buffer_size Получает размер буфера для сокета int receive_low_watemark Предоставляет минимальное число байт при обработке входного сокета int reuse_address Если true, то сокет может быть связан с адресом, тот, что теснее применяется bool send_buffer_size Посылает размер буфера для сокета intsend_low_watermark Предоставляет минимальное число байт для отправки в выходном сокете intip::v6_only Если true, то разрешает применять только IPv6 связи bool
    Всякое имя представляет собой внутренний typedef сокета либо класса. Вот как их дозволено применять:

    ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); 
    ip::tcp::socket sock(service); 
    sock.connect(ep); 
    // TCP socket can reuse address 
    ip::tcp::socket::reuse_address ra(true); 
    sock.set_option(ra); 
    // get sock receive buffer size 
    ip::tcp::socket::receive_buffer_size rbs; 
    sock.get_option(rbs); 
    std::cout << rbs.value() << std::endl; 
    // set sock's buffer size to 8192 
    ip::tcp::socket::send_buffer_size sbs(8192); 
    sock.set_option(sbs);
    

    Сокет должен быть открыт для работы предыдущей функции, напротив вылетит исключение.

    TCP вопреки UDP и ICMP

    Как я теснее сказал, не все функции-члены доступны для всех классов сокетов. Я составил список, где функции-члены отличаются. Если функции-члена тут нет, то это обозначает, что она присутствует во всех классах сокетов:

    Name TCP UDP ICMP
    async_read_some Yes - -
    async_write_some Yes - -
    async_send_to - Yes Yes
    read_some Yes - -
    receive_from - Yes Yes
    write_some Yes - -
    send_to - Yes Yes
    Прочие функции

    Остальные функции, связанные с соединением либо вводом/выводом:

    • local_endpoint(): эта функция возвращает адрес, если сокет подключен локально.
    • remote_endpoint(): эта функция возвращает удаленные адреса, куда сокет был подключен.
    • native_handle(): эта функция возвращает чистый сокет. Ее необходимо применять только тогда, когда вы хотите применять функции для работы с чистыми сокетами, которые не поддерживаются Boost.Asio.
    • non_blocking(): эта функция возвращает true если сокет неблокирующий, напротив false.
    • native_non_blocking(): эта функция возвращает true если сокет неблокирующий, напротивfalse. Тем не менее он будет вызывать чистый API для обычного сокета. Как правило вам это не необходимо (non_blocking() неизменно кэширует данный итог); вы обязаны применять ее только тогда, когда вы непринужденно имеете дело с native_handle().
    • at_mark(): эта функция возвращает true, еси вы собираетесь читать в сокете OOB данные. Она необходима дюже редко.
    Другие соображения

    И на последок, экземпляр сокета не может быть скопирован, так как конструктор копирования иoperator= недостижимы.

    ip::tcp::socket s1(service), s2(service);
    s1 = s2; // compile time error
    ip::tcp::socket s3(s1); // compile time error
    

    В этом много смысла, так как всякий экземпляр хранит и управляет источниками (сам обычный сокет). Если бы мы применяли копирующий конструктор, то в финальном выводе мы имели два экземпляра одного и того же сокета; они обязаны были бы как то руководить правом собственности (либо один экземпляр имеет право собственности, либо применяется подсчет ссылок, либо какой-то иной способ). В Boost.Asio было принято запретить копирование (если вы хотите создавать копии, легко используйте всеобщий (shared) указатель).

    typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
    socket_ptr sock1(new ip::tcp::socket(service));
    socket_ptr sock2(sock1); // ok
    socket_ptr sock3;
    sock3 = sock1; // ok
    

    Буферы сокетов

    При чтении либо записи в сокет, вам потребоваться буфер, тот, что будет содержать входящие либо исходящие данные. Память буфера должна испытать операции ввода/вывода; вы обязаны удостовериться, что до тех пор пока она не освобождена, она не выйдет из области видимости пока длятся операции ввода/вывода.
    Это дюже легко для синхронных операций; безусловно, buff должен испытать обе операции receive иsend:

    char buff[512];
    ...
    sock.receive(buffer(buff));
    strcpy(buff, "okn");
    sock.send(buffer(buff));
    

    И это не так легко для асинхронных операций, как показано в дальнейшем фрагменте:

    // very bad code ...
    void on_read(const boost::system::error_code & err, std::size_t read_
    bytes)
    { ... }
    void func() 
    {
    	char buff[512];
    	sock.async_receive(buffer(buff), on_read);
    }
    

    Позже вызова async_receive(),buff выйдет из области видимости, таким образом его память будет освобождена. Когда мы собираемся на самом деле получить некоторые данные на сокете, то нужно скопировать их в память огромнее нам не принадлежащую; она может быть либо освобождена, либо перераспределена по коду для других данных, при этом каждому имеет повреждение памяти.
    Есть несколько решений поставленной задачи:

    • Применять всеобщий буфер
    • Сделать буфер и истребить его, когда операция завершится
    • Иметь объект связи для поддержки сокета и добавочные данные, такие как буфер (ы).

    Первое решение не дюже комфортно, так как мы все знаем, что всеобщии переменные это нехорошо. Помимо того, что произойдет, если два обработчика будут применять один и тот же буфер?
    Вот как вы можете реализовать второе решение:

    void on_read(char * ptr, const boost::system::error_code & err,
    std::size_t read_bytes) 
    {
    	delete[] ptr;
    }
    ....
    char * buff = new char[512];
    sock.async_receive(buffer(buff, 512), boost::bind(on_
    read,buff,_1,_2));
    

    Если вы хотите, Дабы буфер механически выходил из области видимости, когда завершается операция, то используйте всеобщий указатель (shared pointer):

    struct shared_buffer 
    {
    	boost::shared_array<char> buff;
    	int size;
    	shared_buffer(size_t size) : buff(new char[size]), size(size)
    	 {}
    	mutable_buffers_1 asio_buff() const
    	 {
    		return buffer(buff.get(), size);
    	}
    };
    // when on_read goes out of scope, the boost::bind object is released,
    // and that will release the shared_buffer as well
    void on_read(shared_buffer, const boost::system::error_code & err,
    std::size_t read_bytes) {}
    ...
    shared_buffer buff(512);
    sock.async_receive(buff.asio_buff(), boost::bind(on_read,buff,_1,_2));
    

    Класс shared_buffer содержит внутри себя shared_array<>, тот, что является копией экземпляраshared_buffer, так что shared_array <> будет оставаться в живых; когда конечный выйдет из области видимости, shared_array <> механически разрушится, как раз то, что мы хотели.
    Это работает как и следовало ждать, так как Boost.Asio будет удерживать копию завершающего обработчика, тот, что вызывается при заключении операции. Эта копия является функторомboost::bind, тот, что внутри хранит копию нашего экземпляра shared_buffer. Это дюже старательно!
    3-й вариант состоит в применении объекта связи, тот, что поддерживает сокет и содержит добавочные данные, такие как буферы, обыкновенно это положительное решение, но достаточно трудное. Оно будет рассмотрено в конце этой главы.

    Врапер функции буфера

    В коде, тот, что мы видели прежде, мы неизменно нуждались в буфере для операций чтения/записи, код оборачивался в объект реального буфера, в вызов buffer() и передачу его функции:

    char buff[512];
    sock.async_receive(buffer(buff), on_read
    

    В основном оборачивается всякий буфер, тот, что у нас есть в классе, что разрешает функциям из Boost.Asio итерироваться по буферу. Скажите, вы используете дальнейший код:

    sock.async_receive(some_buffer, on_read);
    

    Экземпляр some_buffer должен удовлетворять некоторым требованиям, а именно ConstBufferSequenceлибо MutableBufferSequence (вы можете посмотреть о них больше детально в документации по Boost.Asio). Подробности создания своего собственного класса для удовлетворения этих требований являются достаточно трудными, но Boost.Asio теснее содержит некоторые классы, моделирующие эти требования. Вы не обращаетесь к ним напрямую, вы используете функцию buffer().
    Довольно сказать, что вы можете обернуть все ниже следующее в функцию buffer():

    • константный массив символов
    • void* и размер в символах
    • строку std::string
    • константный массив POD[] (POD подходит для ветхих данных, то есть конструктор и деструктор ничего не делают)
    • массив std::vector из всяких POD
    • массив boost::array из всяких POD
    • массив std::array из всяких POD

    Дальнейший код рабочий:

    struct pod_sample { int i; long l; char c; };
    ...
    char b1[512];
    void * b2 = new char[512];
    std::string b3; b3.resize(128);
    pod_sample b4[16];
    std::vector<pod_sample> b5; b5.resize(16);
    boost::array<pod_sample,16> b6;
    std::array<pod_sample,16> b7;
    sock.async_send(buffer(b1), on_read);
    sock.async_send(buffer(b2,512), on_read);
    sock.async_send(buffer(b3), on_read);
    sock.async_send(buffer(b4), on_read);
    sock.async_send(buffer(b5), on_read);
    sock.async_send(buffer(b6), on_read);
    sock.async_send(buffer(b7), on_read);
    

    В всеобщем, взамен того, Дабы создавать свой личный класс для удовлетворения требованийConstBufferSequence либо MutableBufferSequence, вы можете сделать класс, тот, что будет содержать буфер до тех пор, пока это нужно и возвращать экземпляр mutable_ buffers_1, это то же самое, что мы делали в классе shared_buffer ранее.

    Само­стоятельные функции чтения/заvmk!ul>

  • async_read_at(stream, offset, buffer [, completion], handler): эта функция начинает операцию асинхронного чтения в данном потоке, начиная со смещенияoffset. При заключении операции будет вызван обработчик handler (const boost::system::error_code& err, size_t bytes);. Буфер может быть как традиционной обвязкой buffer() так и функцией streambuf. Если задать функцию заключения, то она будет вызываться позже всякого удачного чтения и информирует Boost.Asio если операция async_read_at operation завершилась (если нет, то чтение продолжится). Сигнатура завершающей функции дальнейшая size_t completion(const boost::system::error_code& err, size_t bytes);. Когда эта функция возвращает 0, то мы считаем, что операция чтения завершилась, если же возвратилось ненулевое значение, то это обозначает, что наивысшее число байт будет прочитано при дальнейшем вызове async_read_some_at в этом потоке.
  • async_write_at(stream, offset, buffer [, completion], handler): эта функция запускает операцию асинхронной записи. Параметры схожи с async_read_at.
  • read_at(stream, offset, buffer [, completion]): эта функция читает со смещением в данном потоке. Параметры схожи с async_read_at.
  • read_at(stream, offset, buffer [, completion]): эта функция читает со смещением в данном потоке. Параметры схожи с async_read_at.

Эти функции не имеют дело с сокетами. Они имеют дело с потоками случайного доступа; другими словами, с потоками, которые могут быть доступны в случайном порядке. Сокеты очевидно не данный случай (сокеты являются forward-only).
Вот как вы можете прочитать 128 байт из файла, начиная со смещением в 256:

io_service service;
int main(int argc, char* argv[]) 
{
	HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0,
	OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,0);
	windows::random_access_handle h(service, file);
	streambuf buf;
	read_at(h, 256, buf, transfer_exactly(128));
	std::istream in(&buf);
	std::string line;
	std::getline(in, line);
	std::cout << "first line: " << line << std::endl;
}

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB