Основы фреймворка для сетевого программирования ASIO2: 3. Обзор различных функций обратного вызова и многопоточности
В ASIO2 есть много информации о многопоточности. Необходимо обобщить информацию о «сервере, клиенте, TCP и UDP».
Также важно понимать, в каком потоке выполняется каждая функция, а также нужно ли использовать блокировки при многопоточном программировании и защите переменных при выполнении конкретных задач.
ASIO2 — это модель многопоточности «один io_context на процессор». Это основная концепция ASIO, и если вы не знакомы с ней, вы можете поискать информацию, её достаточно много.
Сервер:
TCP-сервер:
Поскольку TCP является протоколом, ориентированным на соединение, сервер обычно разрабатывается как многопоточный, распределяя несколько соединений (например, 1000 соединений) по разным потокам для обработки.
Конструктору tcp_server_impl_t по умолчанию можно передать следующие параметры:
explicit tcp_server_impl_t(
std::size_t init_buffer_size = tcp_frame_size,
std::size_t max_buffer_size = (std::numeric_limits<std::size_t>::max)(),
std::size_t concurrency = std::thread::hardware_concurrency() * 2
)
Параметр 1 представляет начальный размер буфера приёма (то есть каждому соединению выделяется буфер такого размера для приёма данных).
Параметр 2 представляет максимальный размер буфера приёма (когда объём принимаемых данных становится всё больше и больше, ASIO автоматически увеличивает буфер, по умолчанию он может увеличиваться без ограничений).
Параметр 3 указывает, сколько потоков запускает сервер, значение по умолчанию — cpu * 2.
Пример кода:
int main()
{
std::string_view host = "0.0.0.0";
std::string_view port = "8028";
// Поскольку tcp_server по умолчанию запускает количество потоков, равное cpu * 2, предположим, что у нас 4 ядра процессора, то есть 8 потоков,
// предположим, что они называются «поток 0, поток 1, ..., поток 7».
// Здесь основной поток программы называется «поток main» (в новой версии ASIO2 нет событий, которые запускаются в основном потоке).
asio2::tcp_server server;
// Запускаем таймер для объекта server
server.start_timer(123, std::chrono::seconds(1), []()
{
// Этот таймер обратного вызова функции всегда запускается в «потоке 0».
});
// Отправляем асинхронное событие для этого session_ptr
server.post([]()
{
// Это асинхронное событие, отправленное для этого объекта server, всегда запускается в «потоке 0».
printf("Асинхронное событие было выполнено\n");
});
server.bind_init([]()
{
// Всегда запускается в «потоке 0».
}).bind_start([&]()
{
// Всегда запускается в «потоке 0».
}).bind_accept([](std::shared_ptr<asio2::tcp_session>& session_ptr)
{
// Всегда запускается в «потоке 0».
}).bind_connect([&](auto & session_ptr)
{
// После успешного подключения можно запустить таймер для этого соединения (т. е. session_ptr).
session_ptr->start_timer("123", std::chrono::seconds(1), []()
{
// Функция обратного вызова таймера этого session_ptr и bind_recv всегда запускаются в одном и том же потоке
// (см. описание bind_recv ниже).
});
// Отправить асинхронное событие этому session_ptr.
session_ptr->post([]()
{
// Функции обратного вызова асинхронных событий этого session_ptr и bind_recv всегда выполняются в одном потоке
// (см. описание bind_recv ниже).
printf("Асинхронное событие было выполнено\n");
});
}).bind_recv([&](std::shared_ptr<asio2::tcp_session> & session_ptr, std::string_view data)
{
// Распределяется между «потоком 0, потоком 1, ... потоком 7».
// Предположим, что bind_recv для сеанса A запускается в «потоке 2», а bind_recv для сеанса B запускается в «потоке 3»,
// тогда bind_recv сеанса A всегда будет запускаться только в «потоке 2» и никогда не будет чередоваться между «потоками 2 и 3».
// То же самое относится и к сеансу B.
// Для функции async_send отправки данных, когда данные действительно отправляются,
// поток, в котором находится линия, совпадает с потоком, в котором запускается bind_recv.
// Предположим, что поток, в котором выполняется bind_recv для сеанса А, — «поток 2», тогда async_send для сеанса А также отправляет данные в «поток 2».
// (Независимо от того, где вызывается async_send или в каком потоке она вызывается, она в конечном итоге отправляется в «поток 2»).
session_ptr->async_send(data, [](std::size_t bytes_sent)
{
// async_send может установить функцию обратного вызова, которая будет вызвана после завершения отправки данных (независимо от успеха или неудачи).
// Эта функция обратного вызова также всегда выполняется в «потоке 2».
if (asio2::get_last_error())
printf("Ошибка отправки данных, причина ошибки:%s\n", asio2::last_error_msg().c_str());
else
printf("Данные отправлены успешно, отправлено%d байт\n", int(bytes_sent));
});
}).bind_disconnect([&](auto & session_ptr)
{
// Всегда выполняется в «потоке 0».
}).bind_stop([&]()
{
// Всегда выполняется в «потоке 0».
});
server.start(host, port);
while (std::getchar() != '\n'); // нажмите Enter, чтобы выйти из этой программы
server.stop();
return 0;
}
HTTP, WebSocket и RPC основаны на протоколе TCP, поэтому функции обратного вызова сервера этих компонентов работают точно так же, как и TCP.
Однако есть некоторые отличия: SSL имеет рукопожатие, WebSocket имеет согласование, например:
// Для SSL:
server.bind_handshake([&](авто & session_ptr)
{
// Всегда запускается в «потоке 0».
})
// Для WebSocket:
server.bind_upgrade([&](авто & session_ptr)
{
// Всегда запускается в «потоке 0».
})
UDP-сервер:
Параметры конструктора udp_server_impl_t по умолчанию:
explicit udp_server_impl_t(
std::size_t init_buffer_size = udp_frame_size,
std::size_t max_buffer_size = (std::numeric_limits<std::size_t>::max)()
)
Здесь есть только параметры 1 и 2 для установки размера буфера приёма, нет параметра для указания количества запускаемых потоков, потому что UDP не имеет соединения, поэтому здесь установлено фиксированное количество коммуникационных потоков 1. Нет смысла запускать несколько потоков. Потому что UDP-сервер имеет только один поток, все события, включая recv, timer, post и другие, происходят в «потоке 0».
server.bind_init([&]()
{
// Фиксируется в «потоке 0».
}).bind_start([&]()
{
// Фиксируется в «потоке 0».
}).bind_connect([](auto & session_ptr)
{
// Фиксируется в «потоке 0».
}).bind_handshake([](auto & session_ptr)
{
// Фиксируется в «потоке 0».
// Обратите внимание: bind_handshake относится к надёжному UDP, то есть при использовании KCP происходит событие handshake, обычный UDP не имеет события handshake. Но независимо от того, используете ли вы KCP или нет, вы можете вызывать bind_handshake или не вызывать его, это не имеет значения, просто событие не будет инициировано.
}).bind_recv([](std::shared_ptr<asio2::udp_session> & session_ptr, std::string_view s)
{
// Фиксируется в «потоке 0».
session_ptr->async_send(s, []() {});
}).bind_disconnect([](auto & session_ptr)
{
// Фиксируется в «потоке 0».
}).bind_stop([&]()
{
// Фиксируется в «потоке 0»;
});
server.start(host, port);
while (std::getchar() != '\n');
return 0;
}
Параметры конструктора tcp_client по умолчанию в asio2:
explicit tcp_client_impl_t(
std::size_t init_buffer_size = tcp_frame_size,
std::size_t max_buffer_size = (std::numeric_limits<std::size_t>::max)()
)
Также не было запущено несколько потоков для этого параметра, потому что все клиенты, будь то TCP или UDP, имеют фиксированный один коммуникационный поток.
#include <asio2/asio2.hpp>
int main()
{
std::string_view host = "127.0.0.1";
std::string_view port = "8028";
asio2::tcp_client client;
// Потому что у всех клиентов есть только один поток, все события, включая recv,timer,post и другие, происходят в «потоке 0».
// Примечание: у клиента нет событий start и stop, на самом деле они были заменены событиями connect и disconnect.
client.start_timer(1, std::chrono::seconds(1), []() {});
client.bind_init([&]()
{
// Фиксируется в «потоке 0».
}).bind_connect([&]()
{
// Фиксируется в «потоке 0».
client.async_send("abc", [](std::size_t bytes) {});
}).bind_recv([&](std::string_view sv)
{
// Фиксируется в «потоке 0».
}).bind_disconnect([&]()
{
// Фиксируется в «потоке 0»;
});
client.start(host, port);
while (std::getchar() != '\n');
return 0;
}
github : https://github.com/zhllxt/asio2 码云 : https://gitee.com/zhllxt/asio2
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )