簡體   English   中英

我應該如何在 boost::asio 的客戶端應用程序中同時使用 async_read_until 和 async_write?

[英]How should I use async_read_until and async_write simultaneously in the client app in boost::asio?

我的動力。

我正在嘗試構建一個簡單的信使。 目前,我已經編寫了支持“類似功能的郵件”的客戶端和服務器應用程序,即它們缺少您在每個即時消息中擁有的聊天交互。

這是我使用的 model。

服務器:每個連接的客戶端的服務器都有一個提供實際服務的專用Service class。 Service class 的實例有一個 id。

Client :在特定時刻同時開始從關聯的 Service 實例讀取消息並將消息寫入關聯的Service實例。

跟蹤器:通過將用戶的登錄名和Service ID 保存在 map 中來記錄用戶的當前會話。 還通過保存鍵值對(聊天參與者 id 1,聊天參與者 id 2)記錄打開的聊天。 我可以互換使用用戶的登錄名和 ID,因為我有一個數據庫。

這是一個典型的使用場景。

  1. 一個用戶正在嘗試登錄。服務器將 ID 為 1 的Service實例專用於該用戶。然后將用戶標識為 Bob。
  2. Bob 開始與 Ann 聊天。 Tracker記錄 Bob 使用Service 1 以及 Bob 打開了與 Ann 的聊天。
  3. 用戶正在嘗試登錄。服務器將 ID 為 2 的Service實例專用於用戶。然后用戶標識為 Ann。
  4. Ann 開始與 Bob 聊天。 Tracker記錄了 Ann 使用Service 2 以及 Ann 打開了與 Bob 的聊天。
  5. Ann 給 Bob 寫了一條消息。 如果 Bob 已打開與 Ann 的聊天, Service 2 會收到消息並要求Service 1 將消息發送到 Bob 的聊天。 為此,我使用Tracker 在我們的例子中,Bob 正在聊天中,所以 Bob 的客戶端應用程序應該從Service 1 讀取消息。否則, Service 2 只會將新消息存儲在數據庫中。

當用戶打開與某人的聊天時,客戶端應用程序同時開始向關聯的Service實例讀取和寫入消息。

問題

  1. Bob 開始與 Ann 聊天。 Ann 開始與 Bob 聊天。
  2. 安發消息。 它們顯示在 Bobs 聊天中。
  3. 鮑勃發送消息。 它不會顯示在 Ann 的聊天中。 此外,Ann 的其他消息不再顯示在 Bob 的聊天中。

這是我的服務器代碼的一部分。 我添加了一些上下文,但您可能想查看Service::onMessageReceivedService::receive_messageService::send_to_chat

/// Struct to track active sessions of clients
struct Tracker {
  static std::mutex current_sessions_guard; ///< mutex to lock the map of current sessions between threads
  static std::map<long, long> current_sessions; 
  static std::map<long, int> client_to_service_id;
};

Class 在客戶端服務 model 中提供實際服務

class Service {
public:
  void send_to_chat(const std::string& new_message) {
    asio::async_write(*m_sock.get(), asio::buffer(new_message),
      [this]() {
        onAnotherPartyMessageSent(); 
      });
  } 

private:
  void onReceivedReady();
  void receive_message() {
    /// Server loop for reading messages from the client
    spdlog::info("[{}] in receive_message", service_id);

    asio::async_read_until(*m_sock.get(), *(m_message.get()), '\n',
      [this]() {
        onMessageReceived();
      });
  } 
  void onMessageReceived();

private:
  std::shared_ptr<asio::ip::tcp::socket> m_sock; ///< Pointer to an active socket that is used to communicate
                                                 ///< with the client
  int service_id;  
  long dialog_id = -1, client_id = -1, another_party_id = -1;
  std::shared_ptr<asio::streambuf> m_message;
};

方法的定義


void Service::onMessageReceived() {
  /// Updates the database with the new message and asks Service instance of another participant
  /// to send the message if they opened this chat.

  std::istream istrm(m_message.get());
  std::string new_message;
  std::getline(istrm, new_message);
  m_message.reset(new asio::streambuf);

  std::unique_lock<std::mutex> tracker_lock(Tracker::current_sessions_guard);

  if (Tracker::current_sessions.find(another_party_id) != Tracker::current_sessions.end()) {
    if (Tracker::current_sessions[another_party_id] == client_id) {
      int another_party_service_id = Tracker::client_to_service_id[another_party_id];
      std::string formatted_msg = _form_message_str(login, new_message);
      
      spdlog::info("[{}] sends to chat '{}'", another_party_service_id, new_message);

      Server::launched_services[another_party_service_id]->send_to_chat(formatted_msg);
    }
  }
  tracker_lock.unlock();
  receive_message();
} 

這是我的客戶端代碼的一部分。 我添加了一些上下文,但您可能想查看AsyncTCPClient::onSentReadyAsyncTCPClient::message_send_loopAsyncTCPClient::message_wait_loop

/// Struct that stores a session with the given server
struct Session {
  asio::ip::tcp::socket m_sock; //!< The socket for the client application to connect to the server
  asio::ip::tcp::endpoint m_ep; //!< The server's endpoint
  std::string current_chat;

  std::shared_ptr<asio::streambuf> m_chat_buf;
  std::shared_ptr<asio::streambuf> m_received_message;
};

/// Class that implements an asynchronous TCP client to interact with Service class
class AsyncTCPClient: public asio::noncopyable {

  void onSentReady(std::shared_ptr<Session> session) {
  
    msg_wait_thread.reset(new std::thread([this, session] {
      asio::async_read_until(session->m_sock, *(session->m_received_message.get()), "\n", 
        [this, session] () {
          message_wait_loop(session);
        });
      }));
    msg_wait_thread->detach();

    msg_thread.reset(new std::thread([this, session] {
      message_send_loop(session);
      }));

    msg_thread->detach();
  } 


  void message_send_loop(std::shared_ptr<Session> session) {
    /// Starts loop in the current chat enabling the client to keep sending messages to another party
    logger->info("'{}' in message_send_loop", session->login);

    clear_console();
    m_console.write(session->current_chat);
    m_console.write("Write your message: ");

    std::string new_message;

    // We use a do/while loop to prevent empty messages either because of the client input
    // or \n's that were not read before

    do {
      new_message = m_console.read();
    } while (new_message.empty());
    

    std::unique_lock<std::mutex> lock_std_out(std_out_guard);
    session->current_chat.append(_form_message_str(session->login, new_message));
    lock_std_out.unlock();

    asio::async_write(session->m_sock, asio::buffer(new_message + "\n"), 
      [this, session] () {
        message_send_loop(session);
      }); 
  } 

  void message_wait_loop(std::shared_ptr<Session> session) {
    /// Starts loop in the current chat enabling the client to keep reading messages from another party

    logger->info("'{}' in message_wait_loop", session->login);

    std::istream istrm(session->m_received_message.get());
    std::string received_message;
    std::getline(istrm, received_message);

    session->m_received_message.reset(new asio::streambuf);

    std::unique_lock<std::mutex> lock_std_out(std_out_wait_guard);
    session->current_chat.append(received_message + "\n");
    lock_std_out.unlock();

    clear_console();
    m_console.write(session->current_chat);
    m_console.write("Write your message: ");
    
    asio::async_read_until(session->m_sock, *(session->m_received_message.get()), "\n", 
      [this, session] (std::size_t) {
        message_wait_loop(session);
      });
  }

private:
  asio::io_context m_ios;
};

因此,當我描述該問題時,我在第 3 點的兩個客戶端都沒有"'{}' in message_wait_loop"日志)。 但是我在第 2 點為 Bob 的客戶提供了這些日志。

我也在這里使用答案中的控制台。 它通過互斥體去除回聲並控制標准輸入/輸出資源。 但是它並不能解決我的問題。

任何幫助,將不勝感激。

代碼太多而太少。 這個問題太多了,而實際上建議改進的太少了。 我看到過度使用 shared_ptr,線程,特別是在他們自己的線程上運行異步操作非常奇怪。 更別說分離了:

msg_wait_thread.reset(new std::thread([this, session] {
      asio::async_read_until(session->m_sock, *(session->m_received_message.get()), "\n", 
        [this, session] () {
          message_wait_loop(session);
        });
      }));
    msg_wait_thread->detach();

整個事情最好用完全等效的(但更安全)代替

  asio::async_read_until(session->m_sock, *(session->m_received_message.get()), "\n", 
    [this, session] () {
      message_wait_loop(session);
    });

我想讀循環在一個線程上,這樣輸入就不會阻塞。 但是,如果您將主線程視為“UI 線程”(確實如此)並接受控制台 IO 在那里阻塞,而不是將結果請求發布到單個 IO 線程以進行所有非阻塞操作,則會變得容易得多。

如果您分享指向回購或其他內容的鏈接,我很樂意多看它。

更新

在評論中,我查看了 github 存儲庫中的代碼並發布了 PR: https://github.com/cepessh/mymsg/pull/1

這是一個非常原始的概念驗證。 我已經包含了許多實際上與建議的並發修復無關的更改,但它們發生了:

  • 讓我跑
  • 在審查期間(您可能希望查看其中的一些更改並保留它們)
  • main分支中明顯缺失的修復(例如Message.read_by_recipient數據庫列的默認值)

您應該能夠通過提交消息了解進行了哪些更改以及更改的原因。

只有最后兩個提交真正關注聊天中討論的想法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM