Необходимость работать асинхронно 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Необходимость работать асинхронно



Как я уже говорил, как правило, синхронное программирование гораздо проще, чем асинхронное. Потому что гораздо легче думать линейно (вызываем функцию А, после ее окончания вызываем ее обработчик, вызываем функцию В, после ее окончания вызываем ее обработчик и так далее, так что можно думать в манере событие-обработчик). В последнем случае вы можете иметь, скажем, пять событий и вы никогда не сможете узнать порядок, в котором они выполняются, и вы даже не будете знать выполнятся ли они все! Но даже при том, что асинхронное программирование сложнее вы, скорее всего, предпочтете его, скажем, в написании серверов, которые должны иметь дело с большим количеством клиентов одновременно. Чем больше клиентов у вас есть, тем легче асинхронное программирование по сравнению с синхронным. Скажем, у вас есть приложение, которое одновременно имеет дело с 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_ok\n");

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 для того же клиента, в конечном итоге это может привести к повреждению данных и возможно даже к зависанию системы.

Вы заметите, что код становится все более сложным. Возможен и третий вариант для синхронного кода, а именно иметь по одному потоку на каждого клиента. Но так как число одновременных клиентов растет, то это в значительной степени становится непозволительной операцией. А теперь рассмотрим асинхронные варианты. Мы постоянно делали асинхронной операцию чтения. Когда клиент делает запрос, вызывается операция 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_ok\n", 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() будет постоянно готовиться к следующей операции асинхронного чтения (смотрите последнюю строку).


Список источников

· http://cpp.com.ru/

· http://ru.cppreference.com/w/

· http://professorweb.ru/

· http://metanit.com

 

1) Информатика. Шауцукова Л. З.-Г.

2) C/C++. Программирование на языке высокого уровня / Т. А. Павловская. — СПб.

3) Программирование на языке высокого уровня С/С++. Автор: Хабибуллин И.Ш. Издательство: БХВ-Петербург. Учебное пособие. ISBN 5-94157-559-9; 2006 г.

4) Сетевое программирование http://xdejust.blogspot.ru/search/label/WinAPI

5) https://dcherukhin.tk/teaching/pc-sockets-4-2012.htm

6) Программирование на ЯВУ. Лабораторный практикум. – Нальчик: Каб.- Балк. Ун-т. 2004.

7) «Программирование на языке С#: разработка консольных приложений» (авторы: Кудрина Е.В., Огнева М.В., Портенко М.С.)

8) http://www.gamedev.ru/code/articles/?id=4262&page=3

9) http://www.e-reading.club/chapter.php/141823/394/Hart_-_Sistemnoe_programmirovanie_v_srede_Windows.html

10) MS Visual C++ 2010 в среде.NET. Библиотека программиста. Авторы: Виктор Владимирович Зиборов

 



Поделиться:


Последнее изменение этой страницы: 2016-12-11; просмотров: 210; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.129.247.196 (0.017 с.)