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

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

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

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

Оглавление:

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

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

Надобность трудиться асинхронно

Как я теснее говорил, как правило, синхронное программирование значительно проще, чем асинхронное. Потому что значительно легче думать линейно (вызываем функцию А, позже ее окончания вызываем ее обработчик, вызываем функцию В, позже ее окончания вызываем ее обработчик и так дальше, так что дозволено думать в повадке событие-обработчик). В последнем случае вы можете иметь, скажем, пять событий и вы никогда не сумеете узнать порядок, в котором они выполняются, и вы даже не будете знать выполнятся ли они все!
Но даже при том, что асинхронное программирование труднее вы, скорее каждого, выберете его, скажем, в написании серверов, которые обязаны иметь дело с огромным числом заказчиков единовременно. Чем огромнее заказчиков у вас есть, тем легче асинхронное программирование по сопоставлению с синхронным.
Скажем, у вас есть приложение, которое единовременно имеет дело с 1000 заказчиками, всякое сообщение от заказчика серверу и от сервера заказчику заканчивается символом ‘n’.
Синхронный код, 1 поток:

using namespace boost::asio;
struct client 
{
	ip::tcp::socket sock;
	char buff[1024]; // each msg is at maximum this size
	int already_read; // how much have we already read?
};
std::vector<client> clients;
void handle_clients() 
{
	while ( true)
	for ( int i = 0; i < clients.size();   i)
		if ( clients[i].sock.available() ) on_read(clients[i]);
}
void on_read(client & c) 
{
	int to_read = std::min( 1024 - c.already_read, c.sock.
	available());
	c.sock.read_some( buffer(c.buff   c.already_read, to_read));
	c.already_read  = to_read;
	if ( std::find(c.buff, c.buff   c.already_read, 'n') < c.buff   c.already_read) 
	{
		int pos = std::find(c.buff, c.buff   c.already_read, 'n') - c.buff;
		std::string msg(c.buff, c.buff   pos);
		std::copy(c.buff   pos, c.buff   1024, c.buff);
		c.already_read -= pos;
		on_read_msg(c, msg);
	}
}
void on_read_msg(client & c, const std::string & msg) 
{
	// analyze message, and write back
	if ( msg == "request_login")
		c.sock.write( "request_okn");
	else if ...
}

Одна вещь, которую вы хотите избежать при написании серверов (да и в основном всякого сетевого приложения) это Дабы код перестал отвечать на запросы. В нашем случае мы хотим, Дабы функцияhandle_clients() блокировалась как дозволено поменьше. Если функция заблокируется в какой-либо точке, то все входящие сообщения от заказчика будут ожидать, когда функция разблокируется и начнет их обработку.
Для того Дабы оставаться отзывчивым мы будем читать из сокета только тогда, когда в нем есть данные, то есть if ( clients[i].sock.available() ) on_read(clients[i]). В on_read мы будем читать только столько, сколько есть в наличии; вызов read_until(c.sock, buffer(...),'n') было бы не дюже отличной идеей, так как она блокируется, пока мы не прочитаем сообщение от определенного заказчика до конца (мы никогда не узнаем когда это произойдет).
Тесным местом тут является функция on_read_msg(); все входящие сообщения будут остановлены, до тех пор, пока выполняется эта функция. Отлично-написанная функция on_read_msg() будет следить, Дабы этого не случилось, но все же это может случиться (изредка запись в сокет может быть заблокирована, скажем, если заполнен его буфер).
Синхронный код, 10 потоков:

using namespace boost::asio;
struct client
 {
	// ... same as before
bool set_reading() 
	{
		boost::mutex::scoped_lock lk(cs_); 
		if ( is_reading_) return false; // already reading
		else { is_reading_ = true; return true; }
	}
	void unset_reading()
 	{
		boost::mutex::scoped_lock lk(cs_); 
		is_reading_ = false;
	}
private:
	boost::mutex cs_;
	bool is_reading_;
};
std::vector<client> clients;
void handle_clients() 
{
	for ( int i = 0; i < 10;   i)
	boost::thread( handle_clients_thread);
}
void handle_clients_thread() 
{
	while ( true)
	for ( int i = 0; i < clients.size();   i)
		if ( clients[i].sock.available() ) 
			if ( clients[i].set_reading()) 
			{
				on_read(clients[i]); 
				clients[i].unset_reading();
			}
}
void on_read(client & c)
 {
	// same as before
}
void on_read_msg(client & c, const std::string & msg) 
{
	// same as before
}

Для того, Дабы применять несколько потоков, нам необходимо их синхронизировать, что и делают функцииset_reading() и set_unreading(). Функция set_reading() является дюже главной. Вы хотите, Дабы «проверить дозволено ли читать и начать читать» выполнялось за один шаг. Если у вас это выполняется за два шага («проверить дозволено ли читать» и «начать чтение»), то вы можете завести два потока: один для проверки на чтение для какого-либо заказчика, иной для вызова функции on_read для того же заказчика, в финальном результате это может привести к повреждению данных и допустимо даже к зависанию системы.
Вы подметите, что код становится все больше трудным.
Допустим и 3-й вариант для синхронного кода, а именно иметь по одному потоку на всякого заказчика. Но так как число одновременных заказчиков растет, то это в существенной степени становится непозволительной операцией.
А сейчас разглядим асинхронные варианты. Мы непрерывно делали асинхронной операцию чтения. Когда заказчик делает запрос, вызывается операция on_read, мы отвечаем в результат, а после этого ожидаем, когда поступит дальнейший запрос (запускаем еще одну операцию асинхронного чтения).
Асинхронный код, 10 потоков:

using namespace boost::asio;
io_service service;
struct client
 {
	ip::tcp::socket sock;
	streambuf buff; // reads the answer from the client
}
std::vector<client> clients;
void handle_clients() 
{
	for ( int i = 0; i < clients.size();   i)
		async_read_until(clients[i].sock, clients[i].buff, 'n', boost::bind(on_read, clients[i], _1, _2));
	for ( int i = 0; i < 10;   i)
		boost::thread(handle_clients_thread);
}
void handle_clients_thread() 
{
	service.run();
}
void on_read(client & c, const error_code & err, size_t read_bytes) 
{
	std::istream in(&c.buff);
	std::string msg;
	std::getline(in, msg);
	if ( msg == "request_login")
		c.sock.async_write( "request_okn", on_write);
	else if ...
	...
	// now, wait for the next read from the same client
	async_read_until(c.sock, c.buff, 'n', boost::bind(on_read, c, _1, _2));
}

Обратите внимание, насколько проще стал код. Конструкция client имеет только два члена,handle_clients() легко вызывает async_read_until, а после этого создает десять потоков, всякий из которых вызывает service.run(). Эти потоки будут обрабатывать все операции асинхронного чтения либо записи заказчику. Еще одно необходимо подметить, что функция on_read() будет непрерывно подготавливаться к дальнейшей операции асинхронного чтения (глядите последнюю строку).

Асинхронные функции run(), run_one(), poll(), poll_one()

Для реализации цикла прослушивания класс io_service предоставляет четыре функции, такие как run(), run_one(), poll(), и poll_one(). Правда огромную часть времени вы будете трудиться с service.run(). Тут вы узнаете чего дозволено добиться с поддержкой других функций.

Непрерывно работающий

Еще раз, run() будет трудиться, пока ждущие операции завершаются либо пока вы сами не вызоветеio_service::stop(). Дабы сберечь экземпляр io_service работающим, вы, как правило, добавляете одну либо несколько асинхронных операций и когда они заканчиваются, вы продолжаете добавлять, как показано в дальнейшем коде:

using namespace boost::asio;
io_service service;
ip::tcp::socket sock(service);
char buff_read[1024], buff_write[1024] = "ok";
void on_read(const boost::system::error_code &err, std::size_t bytes) ;
void on_write(const boost::system::error_code &err, std::size_t bytes) 
{
	sock.async_read_some(buffer(buff_read), on_read);
}
void on_read(const boost::system::error_code &err, std::size_t bytes) 
{
	// ... process the read ...
	sock.async_write_some(buffer(buff_write,3), on_write);
}
void on_connect(const boost::system::error_code &err) 
{
	sock.async_read_some(buffer(buff_read), on_read);
}
int main(int argc, char* argv[]) 
{
	ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001);
	sock.async_connect(ep, on_connect);
	service.run();
}

Когда вызывается service.run(), то в ожидании находится правда бы одна асинхронная операция. Когда сокет подключается к серверу, вызывается on_connect, которая добавляет еще одну асинхронную операцию. Позже окончания работы on_connect у нас остается одна намеченная операция (read). Когда завершается операция on_read, пишем результат, добавляется еще одна намеченная операция(write). Когда вызывается функция on_write, мы читаем следующее сообщение от сервера, тот, что будет добавлять еще одну намеченную операцию. Когда завершается функция on_write у нас есть одна намеченная операция (read). И так цикл продолжается, пока мы не решим закрыть приложение.

Функции run_one(), poll(), poll_one()

Ранее было подмечено, что обработчики асинхронных функций вызываются в том же потоке, в котором вызывался io_service::run. Это отмечалось для облегчения, потому что по крайней мере от 90 до 95 процентов времени это исключительная функция, которую вы будете применять. То же самое объективно для вызовов run_one(), poll(), либо poll_one() в потоке.
Функция run_one() будет исполнять и отправлять больше одной асинхронной операции:

  • Если нет намеченных операций, то функция сразу же завершается и возвращается 0
  • Если есть отложенные операции, то выполняются функциональные блоки первой операции и возвращается 1

Вы можете разглядеть дальнейший равнозначный код:

io_service service;
service.run(); // OR
while ( !service.stopped()) service.run_once();

Вы можете применять run_once() для запуска асинхронной операции, а после этого ожидать когда она завершится:

io_service service;
bool write_complete = false;
void on_write(const boost::system::error_code & err, size_t bytes) 
{ write_complete = true; }
...
std::string data = "login ok";
write_complete = false;
async_write(sock, buffer(data), on_write);
do service.run_once() while (!write_complete);

Есть так же некоторые примеры, которые применяют run_one() в комплекте с Boost.Asio, скажемblocking_tcp_client.cpp и blocking_udp_client.cpp. Функция poll_one запускает не больше одной отложенной операции, которые готовы для запуска без блокировки:

  • Если правда бы одна отложенная операция, готовая к запуску без блокировки, то run_one() запустит ее и вернет 1
  • В отвратном случае функция сразу же завершается и возвращает 0.

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

  • Таймер, тот, что истек и должен вызваться его обработчик async_wait
  • Операция ввода/вывода, которая завершилась (скажем async_read) и должен быть вызван ее обработчик
  • Пользовательский обработчик, тот, что был заранее добавлен в очередь экземпляра io_services (это объясняется детально в дальнейшем разделе)

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

io_service service;
while ( true) 
{
	// run all handlers of completed IO operations
	while ( service.poll_one()) ;
	// ... do other work here ...
}

Функция poll() будет исполнять все операции, которые находятся в ожидании, и может быть запущена без блокировки. Дальнейший код равнозначен:

io_service service;
service.poll(); // OR
while ( service.poll_one()) ;

Все предыдущие функции выкинут исключения boost::system::system_error в случае неудачи. Но это никогда не должно случиться; оплошность, выброшенная тут обыкновенно приводит к падению, может быть это оплошность в ресурсах либо один из ваших обработчиков выдал исключение. Во каждом случае, всякая из функций имеет перегрузку, которая не выбрасывает исключений, а принимает в качестве доводаboost::system::error_code и устанавливает ее в качестве возвращаемого значения.

io_service service;
boost::system::error_code err = 0;
service.run(err);
if ( err) std::cout << "Error " << err << std::endl;

Асинхронная работа

Асинхронная работа это не только асинхронная обработка заказчиков, подключающихся к серверу, асинхронное чтение из и запись в сокет. Это охватывает всякие операции, которые могут выполняться асинхронно.
По умолчанию вы не знаете порядок, в котором вызываются обработчики всех асинхронных функций. Помимо того, обыкновенно следующие вызовы асинхронны (исходящие от асинхронного сокет чтение/запись/прием). Вы можете применять service.post() для добавления пользовательской функции, которая будет вызываться асинхронно, скажем:

#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <iostream>
using namespace boost::asio;
io_service service;
void func(int i) 
{
	std::cout << "func called, i= " << i << std::endl;
}
void worker_thread() 
{
	service.run();
}
int main(int argc, char* argv[]) 
{
	for ( int i = 0; i < 10;   i)
		service.post(boost::bind(func, i));
	boost::thread_group threads;
	for ( int i = 0; i < 3;   i)
		threads.create_thread(worker_thread);
	// wait for all threads to be created
	boost::this_thread::sleep( boost::posix_time::millisec(500));
	threads.join_all();
}

В предыдущем примере service.post(some_function) добавляет асинхронный вызов функции. Эта функция сразу же завершается, позже запроса экземпляра io_service на вызов данной some_function в одном из потоков, тот, что вызывает service.run(). В нашем случае один из 3 потоков мы сотворили предварительно. Вы не можете быть уверены в каком порядке будут вызваны асинхронные функции. Вы не обязаны ждать, что они будут вызваны в порядке их добавления (post()). Допустимый итог работы предыдущего примера выглядит дальнейшим образом:

func called, i= 0
func called, i= 2
func called, i= 1
func called, i= 4
func called, i= 3
func called, i= 6
func called, i= 7
func called, i= 8
func called, i= 5
func called, i= 9

Допустимо будет время, когда вы захотите назначить обработчик для некоторой асинхронной функции. Скажем, вы обязаны пойти в ресторан (go_to_restaurant), сделать заказ (order) и покушать (eat). Вы хотите вначале прийти в ресторан, сделать заказ и, только потом покушать. Для этого вы будете применятьio_service::strand, которая будет назначать какой асинхронный обработчик вызвать. Разглядим дальнейший пример:

using namespace boost::asio;
io_service service;
void func(int i) 
{
	std::cout << "func called, i= " << i << "/" << boost::this_thread::get_id() << std::endl;
}
void worker_thread() 
{
	service.run();
}
int main(int argc, char* argv[])
{
	io_service::strand strand_one(service), strand_two(service);
	for ( int i = 0; i < 5;   i)
		service.post( strand_one.wrap( boost::bind(func, i)));
	for ( int i = 5; i < 10;   i)
		service.post( strand_two.wrap( boost::bind(func, i)));
	boost::thread_group threads;
	for ( int i = 0; i < 3;   i)
		threads.create_thread(worker_thread);
	// wait for all threads to be created
	boost::this_thread::sleep( boost::posix_time::millisec(500));
	threads.join_all();
}

В приведенном выше коде мы видим, что первые пять и последние пять ID потоков выводятся ступенчато, а именно, func called, i = 0 будет выведено до func called, i = 1, тот, что будет выведен до func called, i = 2 и так дальше. То же самое для func called, i = 5, которое будет выведено до func called, i = 6 и func called, i = 6 будет выведено до func called, i = 7 и так дальше. Следует подметить, что даже если функции вызываются ступенчато, это не обозначает, что они все будут вызваны в одном потоке. Допустимый вариант выполнения этой программы может быть дальнейшим:

func called, i= 0/002A60C8
func called, i= 5/002A6138
func called, i= 6/002A6530
func called, i= 1/002A6138
func called, i= 7/002A6530
func called, i= 2/002A6138
func called, i= 8/002A6530
func called, i= 3/002A6138
func called, i= 9/002A6530
func called, i= 4/002A6138
Асинхронный post() вопреки dispatch() вопреки wrap()

Boost.Asio предусматривает три метода добавления обработчика функции для асинхронного вызова:

  • service.post(handler): эта функция гарантирует, что завершится сразу же позже того как сделает запрос экземпляру io_service на вызов заданного обработчика. Обработчик будет вызван позже в одном из потоков, тот, что вызвал service.run().
  • service.dispatch(handler): это запрос экземпляру io_service на вызов заданного обработчика, но, помимо того, он может вызвать обработчик внутри функции, если нынешний поток вызвалservice.run().
  • service.wrap(handler): эта функция создает функцию-обертку, которая будет вызыватьservice.dispatch(handler). Это немножко запутанно, скоро я поясню что это значит.

Вы видели пример применения service.post() в предыдущем разделе, а также допустимый итог выполнения программы. Изменим его и посмотрим как service.dispatch влияет на итог:

using namespace boost::asio;
io_service service;
void func(int i) 
{
	std::cout << "func called, i= " << i << std::endl;
}
void run_dispatch_and_post() 
{
	for ( int i = 0; i < 10; i  = 2) 
	{
		service.dispatch(boost::bind(func, i));
		service.post(boost::bind(func, i   1));
	}
}
int main(int argc, char* argv[])
 {
	service.post(run_dispatch_and_post);
	service.run();
}

Раньше чем объяснить, что здесь происходит, давайте посмотрим на итог, запустив программу:

func called, i= 0
func called, i= 2
func called, i= 4
func called, i= 6
func called, i= 8
func called, i= 1
func called, i= 3
func called, i= 5
func called, i= 7
func called, i= 9

Вначале пишутся четные числа, а потом нечетные. Это потому что мы используем dispatch() для записи четных чисел и post() для записи нечетных чисел. dispatch() вызовет обработчик раньше, чем он завершится, потому что нынешний поток вызвал service.run(), в то время как post() завершается сразу же.
Сейчас давайте побеседуем об service.wrap(handler). wrap() возвращает функтор, тот, что может быть использован в качестве довода иной функции:

using namespace boost::asio;
io_service service;
void dispatched_func_1() 
{
	std::cout << "dispatched 1" << std::endl;
}
void dispatched_func_2() 
{
	std::cout << "dispatched 2" << std::endl;
}
void test(boost::function<void()> func) 
{
	std::cout << "test" << std::endl;
	service.dispatch(dispatched_func_1);
	func();
}
void service_run() 
{
	service.run();
}
int main(int argc, char* argv[]) 
{
	test( service.wrap(dispatched_func_2));
	boost::thread th(service_run);
	boost::this_thread::sleep( boost::posix_time::millisec(500));
	th.join();
}

Строка test(service.wrap(dispatched_func_2)); будет оборачивать dispatched_func_2 и сделает функтор, тот, что будет передаваться в test в качестве довода. Когда вызовется test(), она перенаправит вызов вdispatched_func_1() и вызовет func(). На данный момент вы увидите, что вызов func() равнозначенservice.dispatch(dispatched_func_2), так как они вызываются ступенчато. Итог программы подтверждает это:

test
dispatched 1
dispatched 2

Класс io_service::strand (применяется для сериализации асинхронных действий) также содержит функцииpoll(), dispatch() и wrap(). Их значение такое же как и у функций poll(), dispatch() и wrap() изio_service. Тем не менее огромную часть времени вы будете применять только функциюio_service::strand::wrap() как довод для io_service::poll() либо io_service::dispatch().

Остаться в живых

Скажете вы, исполняя следующую операцию:

io_service service;
ip::tcp::socket sock(service);
char buff[512];
...
read(sock, buffer(buff));

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

io_service service;
ip::tcp::socket sock(service);
char buff[512];
void on_read(const boost::system::error_code &, size_t) {}
...
async_read(sock, buffer(buff), on_read);

В этом случае sock и buff обязаны испытать саму операцию read, но мы не знаем, когда это случится, так как она асинхронна.
При применении буферов сокета, вы можете иметь экземпляр buffer, тот, что испытал асинхронный вызов (применяя boost::shared_array<>). Тут мы можем применять тот же правило, сделав класс, тот, что внутри себя содержит сокет и буферы для чтения/записи. Тогда для всех асинхронных вызовов мы передаемboost::bind функтор с всеобщим указателем (shared pointer):

using namespace boost::asio;
io_service service;
struct connection : boost::enable_shared_from_this<connection>
{
	typedef boost::system::error_code error_code;
	typedef boost::shared_ptr<connection> ptr;
	connection() : sock_(service), started_(true) {}
	void start(ip::tcp::endpoint ep) 
	{
		sock_.async_connect(ep, 
		boost::bind(&connection::on_connect, shared_from_this(), _1));
	}
	void stop() 
	{
		if ( !started_) return;
		started_ = false;
		sock_.close();
	}
	bool started() { return started_; }
private:
	void on_connect(const error_code & err) 
	{
		// here you decide what to do with the connection: read or write
		if ( !err) do_read();
		else stop();
	}
	void on_read(const error_code & err, size_t bytes) 
	{
		if ( !started() ) return;
		std::string msg(read_buffer_, bytes);
		if ( msg == "can_login")               do_write("access_data");
		else if ( msg.find("data ") == 0)    process_data(msg);
		else if ( msg == "login_fail")         stop();
	}
	void on_write(const error_code & err, size_t bytes) 
	{
		do_read();
	}
	void do_read() 
	{
		sock_.async_read_some(buffer(read_buffer_), 
		boost::bind(&connection::on_read, shared_from_this(), _1, _2));
	}
	void do_write(const std::string & msg) 
	{
		if ( !started() ) return;
		// note: in case you want to send several messages before 
		// doing another async_read, you'll need several write buffers!
		std::copy(msg.begin(), msg.end(), write_buffer_);
		sock_.async_write_some(buffer(write_buffer_, msg.size()), 
		boost::bind(&connection::on_write, shared_from_this(), _1, _2));
	}
	void process_data(const std::string & msg) 
	{
		// process what comes from server, and then perform another write
	}
private:
	ip::tcp::socket sock_;
	enum { max_msg = 1024 };
	char read_buffer_[max_msg];
	char write_buffer_[max_msg];
	bool started_;
};

int main(int argc, char* argv[]) 
{
	ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
	connection::ptr(new connection)->start(ep);
}

Во всех асинхронных вызовах посылается функтор boost::bind в качестве довода. Данный функтор внутри себя хранит shared pointer на экземпляр connection. Пока асинхронная операция находится в ожидании, Boost.Asio будет беречь копию функтора boost::bind, тот, что в свою очередь хранит shared pointer наconnection. Задача решена!
Безусловно класс connection это только класс skeleton; вы обязаны будете приспособить его к своим надобностям (в случае сервера он будет выглядеть вовсе напротив). Обратите внимание, как легко вы создаете новое подключение connection::ptr(new connection)->start(ep). Это начинается (асинхронное) соединение с сервером. Если вы захотите закрыть соединение, то вы вызовите stop().
Как только экземпляр начал трудиться (start()), он будет ожидать подключений. Когда происходит подключение, вызывается on_connect(). Если ошибок нет, то вызывается операция чтения (do_read()). Как только операция чтения завершится, вы сумеете интерпретировать сообщение; скорее каждого в вашем приложении on_read() будет выглядеть напротив. Когда вы посылаете сообщение, вы обязаны скопировать его в буфер, а после этого отправить, как это сделано в do_write(), потому что, вновь же, буфер должен испытать операцию асинхронной записи. И последнее примечание – при записи помните, что вы обязаны указать, сколько писать, в отвратном случае будет посылаться каждый буфер.

Резюме

Сетевое API крайне громадно. Эта глава была реализована в виде ссылки, к которой вы обязаны возвратиться в то время, когда будете реализовывать собственное сетевое приложение.
В Boost.Asio реализована доктрина финальных точек, о которых вы можете думать как об IP адресе и порте. Если вы не знаете точного IP адреса, то вы можете применять объект resolver для включения имени хоста, такого как www.yahoo.com взамен одного либо нескольких IP адресов.
Мы так же разглядели классы сокетов, которые находятся в ядре API. Boost.Asio предоставляет реализации для TCP, UDP, и ICMP, но вы можете расширить его для ваших собственных протоколов, правда это работа не для слабонервных.
Асинхронное программирование является нужным злом. Вы видели, отчего изредка это необходимо, исключительно при написании серверов. Обыкновенно вам будет довольно вызова service.run() для создания асинхронного цикла, но изредка вам потребоваться пойти дальше и тогда вы сумеете применятьrun_one(), poll(), либо poll_one().
При применении асинхронного подхода вы можете иметь свои личные асинхронные функции, легко используйтеservice.post() либо service.dispatch().
Наконец для того, Дабы оба и сокет и буфер (для чтения либо записи) оставались валидными в течение каждого периода асинхронной операции (до заключения), мы обязаны принимать особые меры предосторожности. Ваш класс connection должен быть производным от enabled_shared_from_this, содержать внутри себя все нужные буферы и при всяком асинхронном вызове передавать shared pointer на эту операцию.
В дальнейшей главе будет много фактической работы; много прикладного кодирования при реализации таких приложений как эхо клиент/сервер.

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

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