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

«Boost.Asio C Network Programming». Глава 6: – другие особенности

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

Продолжаю перевод книги John Torjo «Boost.Asio C Network Programming».

Оглавление:

В этой главе мы разглядим некоторые из не дюже знаменитых особенностей Boost.Asio. Объекты std streams и streambuf изредка немножко труднее в применении, но, как вы сами удостоверитесь, у них есть свои превосходства. Наконец, вы увидите достаточно позже добавление в Boost.Asio — co-routines, которое дозволит вам иметь асинхронный код, но легко читаемый (как буд-то бы он синхронный). Это достаточно чудесная специфика.

std потоки и std буферы ввода/вывода

Вы обязаны быть знакомы с такими объектами как STL streams и STL streambuf для того, Дабы понимать вещи, написанные в этом разделе.
В Boost.Asio есть два типа буферов для работы с вводом/выводом:

  • boost::asio::buffer()
  • boost::asio::streambuf

На протяжении каждой книги вы в основном видели приблизительно следующее:

size_t read_complete(boost::system::error_code, size_t bytes){ ... }
char buff[1024];
read(sock, buffer(buff), read_complete);
write(sock, buffer("echon"));

Обыкновенно вам будет этого довольно. Но, если вы хотите большей эластичности, то можете применятьstreambuf. Вот самое примитивное и худшее, что вы можете сделать с объектом streambuf:

streambuf buf;
read(sock, buf);

Это чтение будет идти до тех пор, пока не заполнится объект streambuf, а так как объект streambuf может перераспределить себя, Дабы вместить в себя огромнее места, то, в основном, чтение будет идти до тех пор, пока соединение не будет закрыто. Вы можете применять функцию read_until, Дабы прочитать до последнего знака:

streambuf buf;
read_until(sock, buf, "n");

Тут чтение будет идти до символа ‘n’, после этого в буфер добавится то, что прочтено и выйдет из функции чтения. Дабы написать что-то в объект streambuf вы будете делать что-ото схожее на следующее:

streambuf buf;
std::ostream out(&buf);
out << "echo" << std::endl;
write(sock, buf);

Это достаточно легко, вам нужно сделать поток STL, разместить туда объект streambuf при конструировании, записать в него сообщение, которое вы хотите отправить, а после этого применять функцию write для отправки содержимого буфера.

Boost.Asio и потоки STL

C Boost.Asio проделали огромную работу по интеграции STL потоков и сетей. А именно, если вы теснее обширно используете STL, то у вас теснее должно быть много классов с перегруженными операторами >> и <<. Чтение и запись в сокеты понравиться вам огромнее, чем прогулка по парку.
Скажем, у вас есть дальнейший фрагмент кода:

struct person 
{
	std::string first_name, last_name;
	int age;
};
std::ostream& operator<<(std::ostream & out, const person & p) 
{
	return out << p.first_name << " " << p.last_name << " " << p.age;
}
std::istream& operator>>(std::istream & in, person & p) 
{
	return in >> p.first_name >> p.last_name >> p.age;
}

Отправить данные человека по сети так же легко, как показано ниже:

streambuf buf;
std::ostream out(&buf);
person p;
// ... initialize p
out << p << std::endl;
write(sock, buf);

Иная сторона может так же легко это прочитать:

read_until(sock, buf, "n");
std::istream in(&buf);
person p;
in >> p;

Подлинно отличная сторона применения объектов streambuf и, безусловно, соответствующих std::ostreamдля записи либо std::istream для чтения, заключается в том, что в финальном выводе вы напишите код, тот, что будет считаться типичным:

  • При написании чего-то, что будет передаваться по сети, дюже возможно, что вы будете иметь огромнее одной части данных. Таким образом, в финальном выводе, вы добавите данные в буфер. Если эти данные не являются строкой, то вы обязаны в первую очередь преобразовать их в строку. Все это происходит по умолчанию при применении оператора <<.
  • То же самое происходит и на иной стороне, при чтении сообщения; вам необходимо разобрать его, то есть, прочитать одну часть данных за один раз и, если данные не являются строкой, нужно их преобразовать. Все это происходит по умолчанию, если вы при чтении используете оператор >>.

Наконец, знаменит достаточно резкий трюк, Дабы сбросить содержимое объекта streambuf в консоли, используйте дальнейший код:

streambuf buf;
...
std::cout << &buf << std::endl; // dumps all content to the console

Аналогичным образом, для реформирования его содержимого в строку, используйте дальнейший фрагмент кода:

std::string to_string(streambuf &buf) 
{
	std::ostringstream out;
	out << &buf;
	return out.str();
}

Класс streambuf

Как я теснее говорил, streambuf произведен от std::streambuf. Как и std::streambuf у него нет конструктора копирования.
Помимо того, у него есть несколько дополнительных функций, таких как:

  • streambuf ([max_size,] [allocator]): эта функция создает объект streambuf. При необходимости, вы можете опционально задать наивысший размер буфера и аллокатор, тот, что будет применяться для выделения/освобождения памяти.
  • prepare(n): эта функция возвращает под-буфер, применяемый для размещения постоянной последовательности из n символов. Он может быть использован для чтения либо записи. Итог работы этой функции может быть использован с всякий само­стоятельной функцией из Boost.Asio изготавливающей чтение/запись, а не только с теми, которые работают с объектами streambuf.
  • data(): эта функция возвращает каждый буфер в виде постоянной последовательности символов и применяется для записи. Итог работы этой функции может быть использован с всякий само­стоятельной функцией из Boost.Asio, изготавливающей запись, а не только с теми, которые работают с объектамиstreambuf.
  • consume(n): в этой функции данные удаляются из входной последовательности (из операции чтения).
  • commit(n): в этой функции данные удаляются из выходной последовательности (из операции записи) и добавляются к входной последовательности (в операции чтения).
  • size(): эта функция возвращает размер в символах каждого объекта streambuf.
  • max_size(): эта функция возвращает наивысшее число символов, которое может содержаться в объекте streambuf.

За исключением 2-х последних функций, остальные не так легко осознать. Раньше каждого, в большинстве случаев, вы будете посылать экземпляр streambuf в качестве довода для чтения/записи самостоятельной функции, как показано ниже:

<codeclass="cpp">read_until(sock, buf, "n"); // reads into buf
write(sock, buf); // writes from buf

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

read_until(sock, buf, 'n');
std::cout << &buf << std::endl;

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

read(sock, buf.prepare(16), transfer_exactly(16) );
std::cout << &buf << std::endl;

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

read(sock, buf.prepare(16), transfer_exactly(16) );
buf.commit(16);
std::cout << &buf << std::endl;

Подобно, если вы хотите записать в объект streambuf и если вы используете само­стоятельную функцию записи, то используйте дальнейший фрагмент кода:

streambuf buf;
std::ostream out(&buf);
out << "hi there" << std::endl;
write(sock, buf);

Дальнейший код пошлет hi there три раза:

streambuf buf;
std::ostream out(&buf);
out << "hi there" << std::endl;
for ( int i = 0; i < 3;   i) 
	write(sock, buf.data());

Это происходит, потому что буфер никогда не уничтожается и данные остаются там. Если вы хотите, Дабы данные уничтожались, то посмотрите как это реализуется:

streambuf buf;
std::ostream out(&buf);
out << "hi there" << std::endl;
write(sock, buf.data());
buf.consume(9);

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

Само­стоятельные функции, работающие с объектами streambuf

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

  • read (sock, buf [, completion_function]): эта функция читает из сокета в объект streambuf. Завершающая функция является не непременной. Если же она есть, то она вызывается позже всякой удачной операции чтения и уведомляет Boost.Asio, если операция закончена (если нет, то продолжает читать). Ее сигнатура выглядит дальнейшим образом: size_t completion(const boost::system::error_code & err, size_t bytes_transfered);. При заключении функция возвращает 0, имеется в виду, если операция чтения завершилась всецело; если они возвращает ненулевое значение, то это обозначает, что возвратилось наивысшее число байт для дальнейшего вызова потоковой функции read_some.
  • read_at(radom_stream, offset, buf [, completion_function]): эта функция читает из случайного потока. Обратите внимание, что это не относится к сокетам (так как они не моделируют доктрину случайного потока).
  • read_until(sock, buf, char | string | regex | match_condition): эта функция читает пока выполняется данное условие. Либо должен быть прочитан определенный символ, либо какая-либо строка либо регулярное выражение совпадет с одной из прочитанных строк, либо функция match_conditionскажет нам, что нужно выйти из функции. Сигнатура функции match_condition дальнейшая:match_conditionis pair<iterator,bool>match(iterator begin, iterator end); где основной итератор это buffers_iterator <streambuf::const_buffers_type>. Если совпадение обнаружено, то вернется пара (passed-end-of-match установиться в true), если же совпадений не выявлено, то вернется иная пара (begin установится в false).
  • write(sock, buf [, completion_function]): эта функция записывает все содержимое в объектstreambuf. Завершающая функция является необязательной и ее поведение схоже на завершающую функцию read(): возвращается 0, когда операция записи закончена либо ненулевое значение, когда указывается число байт, которое будет записано при дальнейшем вызове потоковой функцииwrite_some.
  • write_at(random_stream,offset, buf [, completion_function]): эта функция записывает в беспричинный поток. Вновь же не относится к сокетам.
  • async_read(sock, buf [, competion_function], handler): эта асинхронный двойник функции read(). Сигнатура обработчика дальнейшая: void handler(const boost::system::error_code, size_t bytes).
  • async_read_at(radom_stream, offset, buf [, completion_function] ,handler): это асинхронный двойник функции read_at().
  • async_read_until (sock, buf, char | string | regex | match_condition, handler): это асинхронный двойник функции read_until().
  • async_write(sock, buf [, completion_function] , handler): это асинхронный двойник функцииwrite().
  • async_write_at(random_stream,offset, buf [, completion_function], handler): это асинхронный двойник функции write_at().

Возможен, вы хотите читать до гласной буквы:

streambuf buf;
bool is_vowel(char c) 
{
	return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
size_t read_complete(boost::system::error_code, size_t bytes) 
{
	const char * begin = buffer_cast<const char*>( buf.data());
	if ( bytes == 0) 
		return 1;
	while ( bytes > 0)
	{
		if ( is_vowel(*begin  )) 
			return 0;
		else 
			--bytes;
	}
	return 1;
}
...
read(sock, buf, read_complete);

Если вы, скажем, хотите применять регулярные выражения, то это дюже легко:

read_until(sock, buf, boost::regex("^[aeiou] ") );

Либо дозвольте немножко модифицировать пример, и вы сумеете размещать функцию match_condition для работы:

streambuf buf;
bool is_vowel(char c) 
{
	return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
typedef buffers_iterator<streambuf::const_buffers_type> iterator;
std::pair<iterator,bool> match_vowel(iterator b, iterator e) 
{
	while ( b != e)
	{
		if ( is_vowel(*b  )) 
			return std::make_pair(b, true);
	}
	return std::make_pair(e, false);
}
...
size_t bytes = read_until(sock, buf, match_vowel);

Сопрограммы

Авторы Boost.Asio, около 2009-2010 годов, реализовали дюже изумительную идею сопрограмм, которые помогут вам создавать асинхронные приложения еще проще.
Они разрешают вам облегчить две вещи, то есть легко написать асинхронное приложение и так же легко следить за потоком управления, примерно как если бы приложение было написано ступенчато.

В первом случае отображается обыкновенный подход. Применяя сопрограммы, вы максимально приблизитесь ко второму случаю.
Проще говоря, сопрограмма разрешает применять множественные точки входа для приостановки и возобновления выполнения в определенных местах в пределах функции.
Если вы собираетесь применять сопрограммы, то вам нужно будет подключить два заголовочных файла, которые вы можете обнаружить только в boost/libs/asio/example/http/server4: yield.hpp иcoroutine.hpp. Тут в Boost.Asio определены два макроса и класс:

  • coroutine: данный класс есть производная от вашего либо применяемый вами connection класс в целях реализации сопрограмм.
  • reenter(entry): это тело сопрограммы. Входящий довод это указатель на подпрограмму, скажем, для применения в качестве блока внутри целой функции.
  • yield code: исполняет инструкции как часть сопрограммы.

Дабы отменнее разобраться, разглядим несколько примеров. Мы будем вторично реализовывать приложение из 4 главы, которое представляет собой примитивный заказчик, тот, что входит в систем),0)); } void postpone_ping() { timer_.expires_from_now(boost::posix_time::millisec(rand() % 7000)); timer_.async_wait( MEM_FN(do_ping)); } void do_ask_clients() { std::ostream out(&write_buf_); out << “ask_clientsn”; } };
Пример немножко больше трудный, так как мы обязаны проверять связь с сервером в беспричинный момент времени. Дабы сделать это, мы откладываем операцию пинговки позже того, как удачно запросили список заказчиков в 1-й раз. После этого на всякий ответный пинг от сервера мы откладываем иную операцию пинговки.
Дабы запустить все это, используйте дальнейший фрагмент кода:

int main(int argc, char* argv[]) 
{
	ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
	talk_to_svr::start(ep, "John");
	service.run();
}

Применяя сопрограммы мы сократили код на 15 строк, а так же он стал значительно больше читабельнее. Тут мы едва коснулись темы сопрограмм. Если вы хотите получить огромнее информации по данному вопросу, то можете посетить эту страницу.

Резюме

Мы увидели, как легко Boost.Asio работает с потоками STL и объектами streambuf. Так же мы посмотрели, как сопрограммы делают наш код больше суперкомпактным и облегчает его осознавание.
В дальнейшей главе мы разглядим такие темы как Asio вопреки Boost.Asio, прогрессивная отладка, SSL, а так же некоторые дуругие особенности, зависящие от платформы.

Источники к этой статье: ссылка

Каждому огромное спасибо за внимание, до новых встреч!

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

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