#pragma once #include "AftrConfig.h" #ifdef AFTR_CONFIG_USE_BOOST #include "NetMessengerMessageQueue.h" #include #include namespace Aftr { // Encapsulates a single TCP Session (aka NetMessengerServerSession) between this // Aftr instance and another end of the TCP session. Typically, // all data that flows over these TCP sessions are NetMsgs. // Updated Aug 2023 by Nykl to remove raw pointers and manual memory mgmt. class NetMessengerServerSession : public std::enable_shared_from_this { public: //After the Engine has initialized, sometimes a user wants a TCP session between itself and another endpoint. This //factory method is the *simplest* way to create a TCP session that hooks in to the asynchronous IO (really fast) //for sending and receiving NetMsgs... Internally, this wraps up the details of creating a NetMessengerServerSession //and connects the io_context and internal message queues with the singler NetMessengerServer already running. //If the GLView hasn't yet initialized or created the main NetMessengerServer, this method will return nullptr. //If a TCP connection to host:port is not able to be established, this method will return nullptr. //This method is identical / does the exact same thing as NetMessengerServer::createNew_TCP_Session. //After creating a TCP Session using this function, the user can connect by calling session->connectSessionToHost( ip, port ); static std::shared_ptr< NetMessengerServerSession > createNew_TCP_Session(); //This constructor is used by the NetMessengerServerListener to establish a new session between an initiating //remote client and this server. NetMessengerServerSession( boost::asio::io_context& io_context, NetMessengerMessageQueue* servListenerRecvQueue ); //This constructor is used by the NetMessengerServerListener to establish a new session between an initiating //remote client and this server. This c'tor is typically invoked from tcp_acceptor which has just created a //new tcp socket. The io_context is used by the handler's write strand. NetMessengerServerSession( boost::asio::ip::tcp::socket&& session_socket, boost::asio::io_context& io_context, NetMessengerMessageQueue* servListenerRecvQueue ); //This constructor can be used by a user to establish a session to a remote NetMessengerServerListener (a remote //instance of the AftrBurner engine running. NetMessengerServerSession( const std::string& host, const std::string& port, boost::asio::io_context& io_context, NetMessengerMessageQueue* recvQueue ); ~NetMessengerServerSession(); boost::asio::ip::tcp::socket& getSocket(); bool isStarted() { return this->IsStarted; } bool isConnected() const; //Returns true iff the socket is open -- does not necessarily change if an error occurs //Ownership is transferred to the NetMessengerServerSession, the message will be destroyed by this instance. bool enqueueMsgToSend( std::shared_ptr msg ); void start(); void close(); ///< Closes this TCP connection ///host and port are stored internally as a std::string, so we make the copy occur at the call site bool connectSessionToHost( std::string host, std::string port ); bool connectSessionToHost( std::string_view host, std::string_view port ); bool reConnectSessionToHost(); std::string toString() const; //When doing async sending and receiving, notifying the user of a send/recv error is not possible //at the time the user queues the request to send or recv. What we do instead, if an send or recv //error occurs, we set these values to non-null. The user can then check these flags to see if an //error has occurred and choose to take action at that point, such as by trying to reconnect to the //endpoint. //These two get Error msgs will return the latest error and THEN reset the error_code back to none. boost::system::error_code getLastSendErrorAndClearErrState() noexcept; boost::system::error_code getLastRecvErrorAndClearErrState() noexcept; std::optional< std::string > getLastErrorMsgs() const noexcept; std::optional< std::string > getLastErrorMsgsAndClearErrState() noexcept; //Normally, the engine instance creates a NetMessengerServer which has its *own* NetMessengerServerSession. //The NetMessengerServer ensures the NetMessengerServerSession is *processed* (checks for recv & send of //message queues) each frame. If a user creates their *own* instances of a NetMessengerServerSession (TCP Conn) //The sending will work properly, but to receive, the user must supply a servListenerRecvQueue. After supplying //a receive queue, the user needs to look in that queue to actually process the contained NetMsgs and invoke //the desired callbacks. private: bool isValidHeader( unsigned int headerCode ) const; //host and port are only used for the second constructor when the session is used to initiate a new connection (SYN) //versus serving as receiver of a new connection (the remote endpoint (SYN ACK)). std::string host{ "127.0.0.1" }; std::string port{ "12683" }; boost::asio::ip::tcp::socket sock; static const unsigned int HEADER_LEN_BYTES = 8; ///< Header length of a message in bytes (32-bits) std::vector< char > recvBuff; std::array recvHeader{ 0 }; void onRecvHeader( const boost::system::error_code& error, size_t bytesReceived ); void onRecvBody( const boost::system::error_code& error, size_t bytesReceived ); //This method is only execute inside asio handler via this->strand executor. //This method adds msg to sendQueue. If no existing messages are being sent, it will //begin an writing within the strand's asio context. void enqueueMsgHandler( std::shared_ptr msg ); //This method is only execute inside asio handler via this->strand executor. //This method is called by enqueueMsgHandler only to begin writing queued messages within //the strand's asio context. This will repeatedly invoke until the send queue is empty. void sendMsgHandler(); void onSentMsg( char* binDat, const boost::system::error_code& error, size_t bytes_transferred ); bool IsStarted = false; ///< Initially set to false, gets set to True once this->start() has been invoked and async operations have begun unsigned int currHeaderCode = 0; ///< Set by onRecvHeader, read by onRecvBody, indicates NetMsg subclass to instantiate unsigned int currMsgLenBytes = 0; ///< Set by onRecvHeader, read by onRecvBody, indicates length of binary data for the current message //Thread safe queue of pending messages to send to endpoint associated with this session //Only thread safe when the user calls lock() prior to using it and unlock after using it. NetMessengerMessageQueueT< std::shared_ptr > sendQueue; /** Pointer to corresponding NetMessengerServerListener's queue, which holds all newly received (and fully instantiated) messages until the engine processes their callbacks (NetMsg::onMessageArrived()). The callback is invoked when then engine calls NetMessengerServer::onUpdate(), which is typically in the GLView::onUpdateWorld(). This recvQueue is NOT owned by this instance, do not delete. This queue is actually owned by the corresponding NetMessengerServerListener which spawned this session's existence. */ NetMessengerMessageQueue* recvQueue = nullptr; //Not owned by this instance, don't delete boost::asio::io_context::strand writeStrand; // A strand guarantees that no two handlers execute concurrently. boost::system::error_code lastSendError; boost::system::error_code lastRecvError; }; } //namespace Aftr #endif