-
-
Notifications
You must be signed in to change notification settings - Fork 11
chat_tcp_analysis.md
Mohamed edited this page Jun 10, 2025
·
1 revision
-
Location:
example/core_io/chat_tcp/
- Objective: This example demonstrates how to construct a classic client-server TCP chat application using the QB Actor Framework. It's an excellent case study for understanding how actors manage network connections, handle custom communication protocols, manage user sessions, and distribute workload across multiple cores.
By dissecting this example, you'll see practical applications of:
- Asynchronous TCP client and server patterns using
qb::io::use<>
. - Custom protocol definition for message framing (
ChatProtocol
). - Actor-based session management.
- Inter-actor communication for application logic.
- Multi-core actor distribution.
The chat_server
employs a distributed architecture, separating responsibilities across actors typically running on different VirtualCore
s for scalability.
-
Header:
server/AcceptActor.h
,server/AcceptActor.cpp
- Core Assignment (Typical): Core 0 (dedicated to accepting new connections).
- Role: Listens for incoming TCP connections on one or more network ports.
-
QB Integration: Inherits from
qb::Actor
andqb::io::use<AcceptActor>::tcp::acceptor
.- The
tcp::acceptor
base provides thetransport()
method (aqb::io::tcp::listener
) and handles the low-level asynchronous accept operations.
- The
- **Initialization (
onInit()
):It configures its// Inside AcceptActor::onInit() for (const auto& uri_str : _listen_uris) { qb::io::uri u(uri_str.c_str()); if (this->transport().listen(u) != 0) { /* error handling */ } } this->start(); // Start the internal async::input mechanism of the acceptor base
qb::io::tcp::listener
(viathis->transport()
) to listen on specified URIs (e.g., "tcp://0.0.0.0:3001").this->start()
activates the underlying event loop monitoring for new connections. - **Handling New Connections (
on(accepted_socket_type&& new_io)
):When a TCP connection is accepted by the// Inside AcceptActor::on(accepted_socket_type&& new_io) if (!_server_pool.empty()) { qb::ActorId target_server = _server_pool[_session_counter % _server_pool.size()]; _session_counter++; push<NewSessionEvent>(target_server, std::move(new_io)); }
listener
, this method is invoked by thetcp::acceptor
base.new_io
is aqb::io::tcp::socket
representing the newly connected client. TheAcceptActor
then dispatches this new socket to one of theServerActor
instances (from_server_pool
) using a round-robin strategy, wrapping the socket in aNewSessionEvent
. -
Shutdown: Its
on(qb::io::async::event::disconnected const&)
handler for the listener socket (if it gets closed or errors out) triggers a broadcast ofqb::KillEvent
to gracefully shut down other server components.
-
Header:
server/ServerActor.h
,server/ServerActor.cpp
-
Core Assignment (Typical): Core 1 (or a pool of
ServerActor
s across multiple cores, e.g., cores 1 & 2). -
Role: Manages a collection of active client connections (
ChatSession
instances). It acts as a bridge between individual client sessions and the centralChatRoomActor
. -
QB Integration: Inherits from
qb::Actor
andqb::io::use<ServerActor>::tcp::server<ChatSession>
.- The
tcp::server<ChatSession>
base provides theio_handler
functionality, automatically managing a map ofChatSession
objects (keyed byqb::uuid
).
- The
- **Handling New Sessions (
on(NewSessionEvent&)
):Receives the// Inside ServerActor::on(NewSessionEvent& evt) auto& session = registerSession(std::move(evt.socket)); // session is a ChatSession& // The registerSession method (from io_handler base) creates a ChatSession, // associates the socket, starts its I/O, and adds it to the managed session map.
NewSessionEvent
from anAcceptActor
. TheregisterSession(std::move(evt.socket))
call (provided by theio_handler
part of its base) instantiates aChatSession
, associates the client's socket with it, starts its asynchronous I/O operations, and adds it to an internal session map. - **Message Routing (from
ChatSession
toChatRoomActor
):-
ChatSession
calls methods likeserver().handleAuth(id(), username)
on its managingServerActor
. -
ServerActor
then creates specific events (e.g.,AuthEvent
,ChatEvent
) andpush
es them to theChatRoomActor
.
-
- **Message Routing (from
ChatRoomActor
toChatSession
):Receives// Inside ServerActor::on(SendMessageEvent& evt) auto session_ptr = sessions().find(evt.target_session_id); if (session_ptr != sessions().end() && session_ptr->second) { // Send the raw message content using the session's output stream *(session_ptr->second) << evt.message_container.message().payload; }
SendMessageEvent
fromChatRoomActor
. It looks up the targetChatSession
in its session map and sends the message payload directly to the client using the session'soperator<<
(which usespublish()
). - **Client Disconnects (
handleDisconnect(qb::uuid session_id)
):- Called by a
ChatSession
when its connection drops. Forwards aDisconnectEvent
to theChatRoomActor
.
- Called by a
-
Header:
server/ChatSession.h
,server/ChatSession.cpp
-
Context: Instantiated and managed by a
ServerActor
, runs on the sameVirtualCore
as its managingServerActor
. - Role: Represents and handles all I/O and protocol parsing for a single connected client.
-
QB Integration: Inherits from
qb::io::use<ChatSession>::tcp::client<ServerActor>
andqb::io::use<ChatSession>::timeout
.-
tcp::client<ServerActor>
: Provides the TCP transport and stream capabilities. TheServerActor
template argument allows the session to call back to its manager (e.g.,server().handleAuth(...)
). -
timeout
: Adds inactivity timeout functionality.
-
-
Protocol Handling:
-
using Protocol = chat::ChatProtocol<ChatSession>;
(defined inshared/Protocol.h
). - The constructor calls
this->template switch_protocol<Protocol>(*this);
to activate the custom protocol. -
on(chat::Message& msg)
: This method is invoked by the framework when theChatProtocol
successfully parses a complete message from the client. Based onmsg.type
(AUTH_REQUEST
,CHAT_MESSAGE
), it calls the appropriatehandleAuth(...)
orhandleChat(...)
method on its parentServerActor
instance (accessed viaserver()
).
-
-
Lifecycle Events:
-
on(qb::io::async::event::disconnected const&)
: Handles socket disconnection. Callsserver().handleDisconnect(this->id())
. -
on(qb::io::async::event::timer const&)
: Handles inactivity timeout. Also callsserver().handleDisconnect(this->id())
.
-
-
Header:
server/ChatRoomActor.h
,server/ChatRoomActor.cpp
- Core Assignment (Typical): Core 3 (a separate core for application logic).
-
Role: Manages the chat room's state, including the list of authenticated users and their associated
ServerActor
(for routing replies). It handles authentication, message broadcasting, and user presence. -
State:
-
_sessions
: A map fromqb::uuid
(client session ID) toSessionInfo { qb::ActorId server_id, qb::string username }
. -
_usernames
: A map fromqb::string username
toqb::uuid
for quick lookup.
-
-
Event Handlers:
-
on(AuthEvent&)
: Validates username. If valid, stores session info, sends anAUTH_RESPONSE
(chat::MessageType::RESPONSE
) back to the specific client via the correctServerActor
(usingpush<SendMessageEvent>(evt.server_id, ...)
), and broadcasts a join message to all other clients. -
on(ChatEvent&)
: Retrieves the username for the sending session. Formats the chat message (e.g., "username: message_content"). Broadcasts this formatted message to all connected clients via their respectiveServerActor
s. -
on(DisconnectEvent&)
: Removes the user and session information from its state maps. Broadcasts a leave message to remaining clients.
-
-
Message Broadcasting (
broadcastMessage
,sendToSession
helpers): These methods iterate through the_sessions
map andpush
aSendMessageEvent
to the appropriateServerActor
for each recipient. TheSendMessageEvent
contains theqb::uuid
of the targetChatSession
and the message payload (as achat::MessageContainer
, which usesstd::shared_ptr
for efficient sharing of message data).
The client is simpler, typically running actors on fewer cores.
-
Header:
client/InputActor.h
,client/InputActor.cpp
- Core Assignment (Typical): Core 0.
- Role: Reads user input from the console asynchronously.
-
QB Integration: Inherits from
qb::Actor
andqb::ICallback
. -
Functionality (
onCallback()
): Usesstd::getline(std::cin, line)
(note:std::cin
itself can be blocking if not handled carefully, thoughonCallback
is non-blocking with respect to other actors). If the input is "quit", it sends aqb::KillEvent
to theClientActor
. Otherwise, itpush
es aChatInputEvent
(containing the raw input string) to theClientActor
.
-
Header:
client/ClientActor.h
,client/ClientActor.cpp
- Core Assignment (Typical): Core 1.
- Role: Manages the TCP connection to the server, sends user messages, and displays incoming chat messages to the console.
-
QB Integration: Inherits from
qb::Actor
andqb::io::use<ClientActor>::tcp::client<>
. -
Protocol:
using Protocol = chat::ChatProtocol<ClientActor>;
. - **Connection (
onInit()
and connection callback):- Uses
qb::io::async::tcp::connect
to establish a non-blocking connection to the server URI. - The callback lambda, upon successful TCP connection, moves the new socket into
this->transport().transport()
, switches to theChatProtocol
(this->template switch_protocol<Protocol>(*this);
), starts I/O event monitoring (this->start();
), and then sends an initialAUTH_REQUEST
message to the server. - If connection fails, it uses
qb::io::async::callback
to schedule a reconnection attempt.
- Uses
-
Event Handling:
-
on(ChatInputEvent&)
: Receives raw command strings fromInputActor
. If connected and authenticated, formats them intochat::Message
objects (e.g.,CHAT_MESSAGE
type) and sends them to the server using*this << protocol_message << Protocol::end;
. -
on(chat::Message&)
: Receives messages from the server parsed byChatProtocol
. HandlesAUTH_RESPONSE
(updates authenticated state),CHAT_MESSAGE
(prints to console), andERROR
messages. -
on(qb::io::async::event::disconnected const&)
: Handles server disconnection, clears authenticated state, and attempts to reconnect usingqb::io::async::callback
.
-
-
Shutdown (
on(qb::KillEvent&)
): Callsthis->disconnect()
(which internally callsthis->close()
on the transport) and thenthis->kill()
.
- Client-Server Architecture with Actors: A classic networking pattern implemented using actor principles.
- Multi-Core Actor Distribution: Demonstrates assigning different roles (accepting, session handling, core logic, UI input) to actors potentially running on different cores.
-
Asynchronous TCP Client & Server: Extensive use of
qb::io::use<>
templates for TCP operations (tcp::acceptor
,tcp::server
,tcp::client
). -
Custom Protocol (
ChatProtocol
): Shows how to define a header-based binary protocol for message framing and howqb::allocator::pipe
can be specialized withput
for efficient serialization into the output buffer (seeProtocol.cpp
). -
Actor-Based Session Management: The
ServerActor
uses theio_handler
capabilities provided byqb::io::use<...>::tcp::server<ChatSession>
to manage multipleChatSession
objects. -
Centralized State Management: The
ChatRoomActor
acts as a central authority for shared application state (user lists, subscriptions), ensuring consistent access through sequential event processing. -
Inter-Actor Communication: Clear examples of
push
for reliable event delivery between actors, and howActorId
s are used for addressing. -
Connection Resilience (Client-Side): Basic reconnection logic implemented using
qb::io::async::callback
. -
Inactivity Timeouts (Session-Side):
ChatSession
usesqb::io::use<...>::timeout
to detect and handle idle client connections. - Separation of Concerns: Network I/O, user input, and core application logic are well-separated into distinct actor responsibilities.
By studying the chat_tcp
example, developers can gain a solid understanding of how to combine qb-core
and qb-io
to build complex, scalable, and robust networked applications.
(Next Example Analysis: distributed_computing Example Analysis**)