1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/zhllxt-asio2

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
thread.md 13 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 10:53 f491b61

Основы фреймворка для сетевого программирования 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

Последнее редактирование: 23 июня 2022 года.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/zhllxt-asio2.git
git@api.gitlife.ru:oschina-mirror/zhllxt-asio2.git
oschina-mirror
zhllxt-asio2
zhllxt-asio2
main