mirror of
				https://github.com/TES3MP/openmw-tes3mp.git
				synced 2025-10-31 20:26:48 +00:00 
			
		
		
		
	Add command output string and client/server response.
This commit is contained in:
		
							parent
							
								
									d44f322b8a
								
							
						
					
					
						commit
						7cc27d9b66
					
				
					 8 changed files with 172 additions and 60 deletions
				
			
		|  | @ -99,6 +99,7 @@ set(INPUT_HEADER | |||
| source_group(components\\engine\\input FILES ${INPUT} ${INPUT_HEADER}) | ||||
| 
 | ||||
| set(COMMANDSERVER | ||||
|     components/commandserver/command.hpp | ||||
|     components/commandserver/server.hpp | ||||
|     components/commandserver/server.cpp) | ||||
| source_group(components\\commandserver FILES ${COMMANDSERVER})     | ||||
|  |  | |||
|  | @ -9,6 +9,12 @@ using boost::asio::ip::tcp; | |||
| class Client | ||||
| { | ||||
| protected: | ||||
|     struct Header | ||||
|     { | ||||
|         char            magic[4]; | ||||
|         boost::uint32_t dataLength; | ||||
|     }; | ||||
| 
 | ||||
|     boost::asio::io_service mIOService; | ||||
|     tcp::socket* mpSocket; | ||||
| 
 | ||||
|  | @ -39,11 +45,6 @@ public: | |||
| 
 | ||||
|     bool send (const char* msg) | ||||
|     { | ||||
|         struct Header | ||||
|         { | ||||
|             char            magic[4]; | ||||
|             boost::uint32_t dataLength; | ||||
|         }; | ||||
|         const size_t slen = strlen(msg); | ||||
|         const size_t plen = sizeof(Header) + slen + 1; | ||||
|          | ||||
|  | @ -61,6 +62,33 @@ public: | |||
|          | ||||
|         return !ec; | ||||
|     } | ||||
| 
 | ||||
|     bool receive (std::string& reply) | ||||
|     { | ||||
|         Header header; | ||||
|         boost::system::error_code error; | ||||
|         mpSocket->read_some(boost::asio::buffer(&header, sizeof(Header)), error); | ||||
| 
 | ||||
|         if (error != boost::asio::error::eof) | ||||
|         { | ||||
|             if (strncmp(header.magic, "OMW0", 4) == 0) | ||||
|             { | ||||
|                 std::vector<char> msg; | ||||
|                 msg.resize(header.dataLength); | ||||
|                  | ||||
|                 boost::system::error_code error; | ||||
|                 mpSocket->read_some(boost::asio::buffer(&msg[0], header.dataLength), error); | ||||
|                 if (!error) | ||||
|                 { | ||||
|                     reply = &msg[0]; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|                 throw std::exception("Unexpected header!"); | ||||
|         }        | ||||
|         return false; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -79,14 +107,26 @@ int main(int argc, char* argv[]) | |||
|         bool bDone = false; | ||||
|         do | ||||
|         { | ||||
|             std::cout << "> "; | ||||
|             char buffer[1024]; | ||||
|             gets(buffer); | ||||
|             std::cout << "Client> "; | ||||
|             std::string buffer; | ||||
|             std::getline(std::cin, buffer); | ||||
| 
 | ||||
|             if (std::string(buffer) != "quit") | ||||
|                 bDone = !client.send(buffer); | ||||
|             else | ||||
|             if (buffer == "quit") | ||||
|                 bDone = true; | ||||
|             else | ||||
|             { | ||||
|                 if (client.send(buffer.c_str())) | ||||
|                 { | ||||
|                     std::string reply; | ||||
|                     if (client.receive(reply)) | ||||
|                         std::cout << "Server: " << reply << std::endl; | ||||
|                     else | ||||
|                         bDone = true; | ||||
|                 } | ||||
|                 else | ||||
|                     bDone = true; | ||||
|             } | ||||
|                  | ||||
|         } while (!bDone); | ||||
| 
 | ||||
|         client.disconnect(); | ||||
|  |  | |||
|  | @ -32,7 +32,8 @@ OMW::Engine::Engine() | |||
|     : mEnableSky   (false) | ||||
|     , mpSkyManager (NULL) | ||||
| { | ||||
|     mspCommandServer.reset(new OMW::CommandServer::Server(&mCommands, kCommandServerPort)); | ||||
|     mspCommandServer.reset( | ||||
|         new OMW::CommandServer::Server(&mCommandQueue, kCommandServerPort)); | ||||
| } | ||||
| 
 | ||||
| // Load all BSA files in data directory.
 | ||||
|  | @ -100,11 +101,16 @@ void OMW::Engine::enableSky (bool bEnable) | |||
| 
 | ||||
| void OMW::Engine::processCommands() | ||||
| { | ||||
|     std::string msg; | ||||
|     while (mCommands.pop_front(msg)) | ||||
|     Command cmd; | ||||
|     while (mCommandQueue.try_pop_front(cmd)) | ||||
|     { | ||||
|         ///\todo Add actual processing of the received command strings
 | ||||
|         std::cout << "Command: '" << msg << "'" << std::endl; | ||||
|         std::cout << "Command: '" << cmd.mCommand << "'" << std::endl; | ||||
| 
 | ||||
|         ///\todo Replace with real output.  For now, echo back the string in uppercase
 | ||||
|         std::string reply(cmd.mCommand); | ||||
|         std::transform(reply.begin(), reply.end(), reply.begin(), toupper); | ||||
|         cmd.mReplyFunction(reply); | ||||
|     }  | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| #include "apps/openmw/mwrender/mwscene.hpp" | ||||
| #include "components/misc/tsdeque.hpp" | ||||
| #include "components/commandserver/server.hpp" | ||||
| #include "components/commandserver/command.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace MWRender | ||||
|  | @ -31,7 +32,7 @@ namespace OMW | |||
|             bool                  mEnableSky; | ||||
|             MWRender::SkyManager* mpSkyManager; | ||||
| 
 | ||||
|             TsDeque<std::string>                      mCommands; | ||||
|             TsDeque<OMW::Command>                     mCommandQueue; | ||||
|             std::auto_ptr<OMW::CommandServer::Server> mspCommandServer; | ||||
| 
 | ||||
|             // not implemented
 | ||||
|  |  | |||
							
								
								
									
										19
									
								
								components/commandserver/command.hpp
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								components/commandserver/command.hpp
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| #ifndef COMMANDSERVER_COMMAND_HPP | ||||
| #define COMMANDSERVER_COMMAND_HPP | ||||
| 
 | ||||
| namespace OMW | ||||
| { | ||||
|     ///
 | ||||
|     /// A Command is currently defined as a string input that, when processed,
 | ||||
|     /// will generate a string output.  The string output is passed to the
 | ||||
|     /// mReplyFunction as soon as the command has been processed.
 | ||||
|     ///
 | ||||
|     class Command | ||||
|     { | ||||
|     public: | ||||
|         std::string                         mCommand; | ||||
|         boost::function1<void, std::string> mReplyFunction; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif COMMANDSERVER_COMMAND_HPP | ||||
|  | @ -9,6 +9,12 @@ using boost::asio::ip::tcp; | |||
| //
 | ||||
| namespace OMW { namespace CommandServer { namespace Detail { | ||||
| 
 | ||||
|     struct Header | ||||
|     { | ||||
|         char   magic[4]; | ||||
|         size_t dataLength; | ||||
|     } header; | ||||
| 
 | ||||
|     ///
 | ||||
|     /// Tracks an active connection to the CommandServer
 | ||||
|     ///
 | ||||
|  | @ -19,7 +25,9 @@ namespace OMW { namespace CommandServer { namespace Detail { | |||
| 
 | ||||
|         void         start();      | ||||
|         void         stop(); | ||||
| 
 | ||||
|         tcp::socket& socket(); | ||||
|         void         reply (std::string s); | ||||
| 
 | ||||
|     protected: | ||||
|         void handle (); | ||||
|  | @ -53,18 +61,29 @@ namespace OMW { namespace CommandServer { namespace Detail { | |||
|     { | ||||
|         return mSocket; | ||||
|     } | ||||
| 
 | ||||
|     void Connection::reply (std::string reply) | ||||
|     { | ||||
|         const size_t plen = sizeof(Header) + reply.length() + 1; | ||||
|          | ||||
|         std::vector<char> packet(plen); | ||||
|         Header* pHeader = reinterpret_cast<Header*>(&packet[0]); | ||||
|         strncpy(pHeader->magic, "OMW0", 4); | ||||
|         pHeader->dataLength = reply.length() + 1;   // Include the null terminator
 | ||||
|         strncpy(&packet[8], reply.c_str(), pHeader->dataLength);  | ||||
| 
 | ||||
|         boost::system::error_code ec; | ||||
|         boost::asio::write(mSocket, boost::asio::buffer(packet),  | ||||
|                            boost::asio::transfer_all(), ec);      | ||||
|         if (ec) | ||||
|             std::cout << "Error: " << ec.message() << std::endl;         | ||||
|     } | ||||
|         | ||||
|     void Connection::handle () | ||||
|     { | ||||
|         bool bDone = false; | ||||
|         while (!bDone) | ||||
|         { | ||||
|             struct Header | ||||
|             { | ||||
|                 char   magic[4]; | ||||
|                 size_t dataLength; | ||||
|             } header; | ||||
| 
 | ||||
|             // Read the header
 | ||||
|             boost::system::error_code error; | ||||
|             mSocket.read_some(boost::asio::buffer(&header, sizeof(Header)), error); | ||||
|  | @ -79,7 +98,7 @@ namespace OMW { namespace CommandServer { namespace Detail { | |||
|                     boost::system::error_code error; | ||||
|                     mSocket.read_some(boost::asio::buffer(&msg[0], header.dataLength), error); | ||||
|                     if (!error) | ||||
|                         mpServer->postMessage( &msg[0] ); | ||||
|                         mpServer->postCommand(this, &msg[0]); | ||||
|                     else | ||||
|                         bDone = true; | ||||
|                 } | ||||
|  | @ -98,10 +117,10 @@ namespace OMW { namespace CommandServer { | |||
| 
 | ||||
|     using namespace Detail; | ||||
|         | ||||
|     Server::Server (Deque* pDeque, const int port) | ||||
|         : mAcceptor   (mIOService, tcp::endpoint(tcp::v4(), port)) | ||||
|         , mpCommands  (pDeque) | ||||
|         , mbStopping  (false) | ||||
|     Server::Server (Deque* pCommandQueue, const int port) | ||||
|         : mAcceptor      (mIOService, tcp::endpoint(tcp::v4(), port)) | ||||
|         , mpCommandQueue (pCommandQueue) | ||||
|         , mbStopping     (false) | ||||
|     {        | ||||
|     } | ||||
|         | ||||
|  | @ -149,9 +168,12 @@ namespace OMW { namespace CommandServer { | |||
|         delete ptr; | ||||
|     } | ||||
| 
 | ||||
|     void Server::postMessage (const char* s) | ||||
|     void Server::postCommand (Connection* pConnection, const char* s) | ||||
|     { | ||||
|         mpCommands->push_back(s); | ||||
|         Command cmd; | ||||
|         cmd.mCommand = s; | ||||
|         cmd.mReplyFunction = std::bind1st(std::mem_fun(&Connection::reply), pConnection); | ||||
|         mpCommandQueue->push_back(cmd); | ||||
|     } | ||||
| 
 | ||||
|     void Server::threadMain() | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <boost/thread.hpp> | ||||
| 
 | ||||
| #include "components/misc/tsdeque.hpp" | ||||
| #include "components/commandserver/command.hpp" | ||||
| 
 | ||||
| namespace OMW { namespace CommandServer  | ||||
| { | ||||
|  | @ -27,9 +28,9 @@ namespace OMW { namespace CommandServer | |||
|     class Server | ||||
|     {  | ||||
|     public: | ||||
|         typedef TsDeque<std::string> Deque; | ||||
|         typedef TsDeque<Command> Deque; | ||||
| 
 | ||||
|         Server (Deque* pDeque, const int port); | ||||
|         Server (Deque* pCommandQueue, const int port); | ||||
| 
 | ||||
|         void start(); | ||||
|         void stop(); | ||||
|  | @ -39,7 +40,7 @@ namespace OMW { namespace CommandServer | |||
|         typedef std::set<Detail::Connection*> ConnectionSet; | ||||
| 
 | ||||
|         void removeConnection (Detail::Connection* ptr); | ||||
|         void postMessage      (const char* s); | ||||
|         void postCommand      (Detail::Connection*, const char* s); | ||||
| 
 | ||||
|         void threadMain();             | ||||
| 
 | ||||
|  | @ -53,8 +54,8 @@ namespace OMW { namespace CommandServer | |||
|         ConnectionSet                   mConnections; | ||||
|         mutable boost::mutex            mConnectionsMutex; | ||||
|          | ||||
|         // Pointer to output queue in which to put received strings
 | ||||
|         Deque*                          mpCommands; | ||||
|         // Pointer to command queue
 | ||||
|         Deque*                          mpCommandQueue; | ||||
|     }; | ||||
| 
 | ||||
| }} | ||||
|  |  | |||
|  | @ -3,32 +3,54 @@ | |||
| 
 | ||||
| #include <boost/thread.hpp> | ||||
| 
 | ||||
| template <typename T> | ||||
| class TsDeque | ||||
| { | ||||
| public: | ||||
|     void push_back (const T& t) | ||||
|     { | ||||
|         boost::mutex::scoped_lock lock(mMutex); | ||||
|         mDeque.push_back(t); | ||||
|     } | ||||
| 
 | ||||
|     bool pop_front (T& t) | ||||
|     { | ||||
|         boost::mutex::scoped_lock lock(mMutex); | ||||
|         if (!mDeque.empty()) | ||||
|         { | ||||
|             t = mDeque.front(); | ||||
|             mDeque.pop_front(); | ||||
|             return true; | ||||
|         } | ||||
|         else | ||||
|             return false; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     std::deque<T>        mDeque; | ||||
|     mutable boost::mutex mMutex; | ||||
| //
 | ||||
| // Adapted from http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html
 | ||||
| //
 | ||||
| template<typename Data> | ||||
| class TsDeque | ||||
| { | ||||
| private: | ||||
|     std::deque<Data> the_queue; | ||||
|     mutable boost::mutex the_mutex; | ||||
|     boost::condition_variable the_condition_variable; | ||||
| 
 | ||||
| public: | ||||
|     void push_back(Data const& data) | ||||
|     { | ||||
|         boost::mutex::scoped_lock lock(the_mutex); | ||||
|         the_queue.push_back(data); | ||||
|         lock.unlock(); | ||||
|         the_condition_variable.notify_one(); | ||||
|     } | ||||
| 
 | ||||
|     bool empty() const | ||||
|     { | ||||
|         boost::mutex::scoped_lock lock(the_mutex); | ||||
|         return the_queue.empty(); | ||||
|     } | ||||
| 
 | ||||
|     bool try_pop_front(Data& popped_value) | ||||
|     { | ||||
|         boost::mutex::scoped_lock lock(the_mutex); | ||||
|         if(the_queue.empty()) | ||||
|             return false; | ||||
|         | ||||
|         popped_value=the_queue.front(); | ||||
|         the_queue.pop_front(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     void wait_and_pop_front(Data& popped_value) | ||||
|     { | ||||
|         boost::mutex::scoped_lock lock(the_mutex); | ||||
|         while(the_queue.empty()) | ||||
|         { | ||||
|             the_condition_variable.wait(lock); | ||||
|         } | ||||
|          | ||||
|         popped_value=the_queue.front(); | ||||
|         the_queue.pop_front(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| #endif // TSDEQUE_H
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue