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

OSCHINA-MIRROR/sogou-workflow

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
about-connection-context.md 11 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 01.03.2025 10:20 552780a

О контексте соединения

Контекст соединения является продвинутой темой при программировании с использованием нашего фреймворка. При работе с ним могут возникнуть некоторые сложности. Из предыдущих примеров можно заметить, что ни в клиентских, ни в серверных задачах нет способа указать конкретное соединение. Однако есть бизнес-сценарии, особенно на стороне сервера, где может потребоваться поддерживать состояние соединения. Это значит, нам нужно связывать некоторый контекст с соединением. Наш фреймворк предоставляет механизм контекста соединения для использования пользователями.

Примеры применения контекста соединения

Протокол HTTP можно считать полностью бессостоятельным протоколом, где сессия HTTP реализуется через cookies. Такой подход наиболее дружественный для нашего фреймворка. Аналогично работает Kafka. Redis и MySQL имеют явно состоятельные соединения. Redis использует команду SELECT для указания текущей базы данных на соединении. А MySQL представляет собой чисто состоятельный протокол. При использовании клиентских задач для Redis или неатомарных MySQL, поскольку URL уже содержит всю информацию о выборе соединения, включая:

  • имя пользователя и пароль
  • название базы данных или её номер
  • набор символов для MySQL

Фреймворк автоматически выполняет вход и выбирает повторно используемые соединения на основе этой информации, поэтому пользователю не требуется беспокоиться о контексте соединения. Поэтому в нашем фреймворке запрещены команды SELECT для Redis и USE для MySQL, так как они используются для изменения базы данных, которая должна быть создана новым URL. Для атомарных MySQL задач можно использовать фиксированное соединение, подробнее об этом см. в документации по MySQL. Однако если мы реализуем сервер для протокола Redis, то нам понадобится знать состояние текущего соединения.

Кроме того, мы можем воспользоваться событиями освобождения контекста соединения для обнаружения закрытия соединения удалённой стороной.

Как использовать контекст соединения

Важно отметить, что обычно только серверные задачи требуют использования контекста соединения, и это должно происходить только внутри функции process. Это самый безопасный и простой метод. Однако задачи также могут использовать или изменять контекст соединения внутри callback, но тогда следует учитывать проблему конкурентного доступа. Мы подробно рассмотрим эту проблему ниже. Любая сетевая задача может вызвать интерфейсы для получения объекта соединения, чтобы получить или изменить контекст соединения. В файле WFTask.h используется следующий шаблон:

template<class REQ, class, RESP>
class WFNetworkTask : public CommRequest
{
public:
    virtual WFConnection *get_connection() const = 0;
    ...
};

Файл WFConnection.h содержит интерфейсы для работы с объектами соединения:

class WFConnection : public CommConnection
{
public:
    void *get_context() const;
    void set_context(void *context, std::function<void (void *)> deleter);
    void set_context(void *context);
    void *test_set_context(void *test_context, void *new_context,
                           std::function<void (void *)> deleter);
    void *test_set_context(void *test_context, void *new_context);
};

Метод get_connection() может быть вызван только внутри процесса или callback, и если он вызывается внутри callback, необходимо проверить, что возвращаемое значение не равно NULL. Если удалось получить объект WFConnection, можно работать с контекстом соединения. Контекст соединения представлен указателем типа void *. Установка контекста соединения позволяет передать функцию-удалителю, которая будет автоматически вызвана при закрытии соединения. Если вызывается интерфейс без параметра функции-удалителя, можно установить новый контекст, сохранив прежнюю функцию-удалитель.

Время обращения к контексту соединения и проблемы конкурентного доступа

Клиентская задача создаётся до того, как соединение определяется, поэтому использование контекста соединения возможно только внутри callback. Серверная задача может использовать контекст соединения в двух местах — внутри процесса и callback. При использовании контекста соединения внутри callback следует учитывать проблему конкурентного доступа, так как одно и то же соединение может быть переиспользовано многими задачами одновременно. Поэтому рекомендуется обращаться к контексту соединения только внутри процесса, так как во время выполнения процесса соединение не будет переиспользовано или освобождено, что делает этот метод самым простым и безопасным. Обратите внимание, что процесс здесь относится только к внутреннему содержанию функции process, после завершения которой вызов get_connection всегда вернёт NULL. Метод test_set_context() класса WFConnection предназначен для решения проблемы конкурентного доступа при использовании контекста соединения внутри callback, однако его использование не рекомендуется. В общем случае, если вы не очень хорошо знакомы с системой, рекомендуется использовать контекст соединения только внутри функции process серверной задачи.

Пример: Уменьшение передачи заголовков HTTP/1.1HTTP можно рассматривать как протокол без состояния, где каждый запрос на одном и том же соединении должен отправляться вместе с полным заголовком.

Предположим, что cookie в запросе очень большой, тогда очевидно, что это увеличивает объём передаваемых данных. Мы можем решить эту проблему с помощью контекста соединения на стороне сервера.
Мы соглашаемся, что cookie в запросе HTTP действует для всех последующих запросов на данном соединении, и последующие запросы могут не отправлять cookie в своих заголовках.
Вот пример кода на стороне сервера:

void process(WFHttpTask *server_task)
{
    protocol::HttpRequest *req = server_task->get_req();
    protocol::HttpHeaderCursor cursor(req);
    WFConnection *conn = server_task->get_connection();
    void *context = conn->get_context();
    std::string cookie;

    if (cursor.find("Cookie", cookie))
    {
        if (context)
            delete (std::string *)context;
        context = new std::string(cookie);
        conn->set_context(context, [](void *p) { delete (std::string *)p; });
    }
    else if (context)
        cookie = *(std::string *)context;

    ...
}

Этот подход позволяет экономить трафик, согласовывая передачу cookie только для первого запроса на соединении.
На стороне клиента для этого требуется новый callback, который используется следующим образом:


using namespace protocol;

void prepare_func(WFHttpTask *task)
{
    if (task->get_task_seq() == 0)
        task->get_req()->add_header_pair("Cookie", my_cookie);
}

int some_function()
{
    WFHttpTask *task = WFTaskFactory::create_http_task(...);
    task->set_prepare(prepare_func);
    ...
}

В этом примере, когда http-task является первым запросом на соединении, мы устанавливаем cookie. Для последующих запросов, согласно соглашению, cookie больше не передается.
Кроме того, внутри функции prepare можно безопасно использовать контекст соединения, так как prepare не происходит конкурентно для одного и того же соединения.

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

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

1
https://api.gitlife.ru/oschina-mirror/sogou-workflow.git
git@api.gitlife.ru:oschina-mirror/sogou-workflow.git
oschina-mirror
sogou-workflow
sogou-workflow
master