[英]How should I use async_read_until and async_write simultaneously in the client app in boost::asio?
I am trying to build a simple messenger.我正在尝试构建一个简单的信使。 Currently I have written client and server apps that support "mail like functional", that is they lack chat interaction that you have in every instant messenger.目前,我已经编写了支持“类似功能的邮件”的客户端和服务器应用程序,即它们缺少您在每个即时消息中拥有的聊天交互。
Server: The server for every connected client has a dedicated Service
class that provides actual service.服务器:每个连接的客户端的服务器都有一个提供实际服务的专用Service
class。 An instance of the Service
class has an id. Service
class 的实例有一个 id。
Client : At particular moment simultaneously starts reading messages from and writing messages to the associated Service
instance. Client :在特定时刻同时开始从关联的 Service 实例读取消息并将消息写入关联的Service
实例。
Tracker : Records current sessions of users by saving their logins and Service
ids in a map.跟踪器:通过将用户的登录名和Service
ID 保存在 map 中来记录用户的当前会话。 Also records opened chats by saving key-value pairs (chat participant id 1, chat participant id 2).还通过保存键值对(聊天参与者 id 1,聊天参与者 id 2)记录打开的聊天。 I use logins and ids of users interchangeably because I have a database.我可以互换使用用户的登录名和 ID,因为我有一个数据库。
Service
instance with id 1. Then the user identified as Bob.一个用户正在尝试登录。服务器将 ID 为 1 的Service
实例专用于该用户。然后将用户标识为 Bob。Tracker
records that Bob uses Service
1 and that Bob opened the chat with Ann. Tracker
记录 Bob 使用Service
1 以及 Bob 打开了与 Ann 的聊天。Service
instance with id 2. Then the user identified as Ann.用户正在尝试登录。服务器将 ID 为 2 的Service
实例专用于用户。然后用户标识为 Ann。Tracker
records that Ann uses Service
2 and that Ann opened the chat with Bob. Tracker
记录了 Ann 使用Service
2 以及 Ann 打开了与 Bob 的聊天。Service
2 receives the message and asks Service
1 to send the message to Bob's chat if Bob has opened the chat with Ann.如果 Bob 已打开与 Ann 的聊天, Service
2 会收到消息并要求Service
1 将消息发送到 Bob 的聊天。 For that purpose I use Tracker
.为此,我使用Tracker
。 In our case Bob is in the chat so Bob's client app should read the message from Service
1. Otherwise Service
2 only stores the new message in the database.在我们的例子中,Bob 正在聊天中,所以 Bob 的客户端应用程序应该从Service
1 读取消息。否则, Service
2 只会将新消息存储在数据库中。 When a user opens a chat with somebody the client app simultaneously starts reading and writing messages to the associate Service
instance.当用户打开与某人的聊天时,客户端应用程序同时开始向关联的Service
实例读取和写入消息。
Here is a portion of my server code.这是我的服务器代码的一部分。 I have added some context but you probably want to look at Service::onMessageReceived
, Service::receive_message
, Service::send_to_chat
我添加了一些上下文,但您可能想查看Service::onMessageReceived
、 Service::receive_message
、 Service::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 that provides the actual service in the client-service model 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;
};
Definitions of methods方法的定义
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();
}
Here is a portion of my client code.这是我的客户端代码的一部分。 I have added some context but you probably want to look at AsyncTCPClient::onSentReady
, AsyncTCPClient::message_send_loop
, AsyncTCPClient::message_wait_loop
.我添加了一些上下文,但您可能想查看AsyncTCPClient::onSentReady
、 AsyncTCPClient::message_send_loop
、 AsyncTCPClient::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;
};
So, when I described the issue I do not have "'{}' in message_wait_loop"
logs for both clients at the point 3).因此,当我描述该问题时,我在第 3 点的两个客户端都没有"'{}' in message_wait_loop"
日志)。 But I have these logs at the point 2) for the Bob's client.但是我在第 2 点为 Bob 的客户提供了这些日志。
Also I use Console from answer here .我也在这里使用答案中的控制台。 It removes echo and controls standard input/output resources by means of mutexes.它通过互斥体去除回声并控制标准输入/输出资源。 However it does not solve my problem.但是它并不能解决我的问题。
Any help would be appreciated.任何帮助,将不胜感激。
There's too much code and too little.代码太多而太少。 Too much for the question, and too little to actually suggest improvements.这个问题太多了,而实际上建议改进的太少了。 I see an overuse of shared_ptr, threads, In particular it is very weird to run async-operations on their own threads.我看到过度使用 shared_ptr,线程,特别是在他们自己的线程上运行异步操作非常奇怪。 Let alone detached:更别说分离了:
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();
That whole thing is best replaced by the completely equivalent (but safer)整个事情最好用完全等效的(但更安全)代替
asio::async_read_until(session->m_sock, *(session->m_received_message.get()), "\n",
[this, session] () {
message_wait_loop(session);
});
I imagine the read loop is on a thread so that the input wouldn't block.我想读循环在一个线程上,这样输入就不会阻塞。 However, it becomes much easier if you consider the main thread the "UI thread" (it is), and accept that console IO is blocking there, instead posting the resulting requests to a single IO thread for all the non-blocking operations.但是,如果您将主线程视为“UI 线程”(确实如此)并接受控制台 IO 在那里阻塞,而不是将结果请求发布到单个 IO 线程以进行所有非阻塞操作,则会变得容易得多。
If you share a link to a repo or something I'm happy to look at it more.如果您分享指向回购或其他内容的链接,我很乐意多看它。
In the comments I reviewed the code from the github repo and posted a PR: https://github.com/cepessh/mymsg/pull/1在评论中,我查看了 github 存储库中的代码并发布了 PR: https://github.com/cepessh/mymsg/pull/1
This is a very raw proof-of-concept.这是一个非常原始的概念验证。 I have included many changes that aren't actually related to the suggested concurrency fix, but they happened:我已经包含了许多实际上与建议的并发修复无关的更改,但它们发生了:
- to allow me to run让我跑
- during review (you will probably want to look at a number of those changes and keep them anyways)在审查期间(您可能希望查看其中的一些更改并保留它们)
- fixes that were apparently missing from
main
branch (eg the default value forMessage.read_by_recipient
database column)main
分支中明显缺失的修复(例如Message.read_by_recipient
数据库列的默认值)You should be able to work out what changes were made and why by the commit messages .您应该能够通过提交消息了解进行了哪些更改以及更改的原因。
Only the last two commits actually focus on the idea discussed in chat .只有最后两个提交真正关注聊天中讨论的想法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.