Add simple external console server/client

actorid
athile 15 years ago
parent de7087caf4
commit 7357ea2102

@ -98,16 +98,22 @@ set(INPUT_HEADER
components/engine/input/poller.hpp)
source_group(input FILES ${INPUT} ${INPUT_HEADER})
set(MISC
components/misc/stringops.cpp
components/misc/fileops.cpp)
set(MISC_HEADER
components/misc/fileops.hpp
components/misc/slice_array.hpp
components/misc/stringops.hpp)
set(COMMANDSERVER
components/commandserver/server.hpp
components/commandserver/server.cpp)
source_group(commandserver FILES ${COMMANDSERVER})
set(MISC
components/misc/stringops.cpp
components/misc/fileops.cpp)
set(MISC_HEADER
components/misc/fileops.hpp
components/misc/slice_array.hpp
components/misc/stringops.hpp
components/misc/tsdeque.hpp)
source_group(misc FILES ${MISC} ${MISC_HEADER})
set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${MISC})
set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${COMMANDSERVER} ${MISC})
set(COMPONENTS_HEADER ${BSA_HEADER} ${NIF_HEADER} ${NIFOGRE_HEADER} ${ESM_STORE_HEADER}
${ESM_HEADER} ${OGRE_HEADER} ${INPUT_HEADER} ${MISC_HEADER})
@ -137,7 +143,7 @@ include_directories("."
${CMAKE_HOME_DIRECTORY}/extern/caelum/include)
link_directories(${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR})
ADD_SUBDIRECTORY( extern/caelum )
add_subdirectory( extern/caelum )
# Specify build paths
@ -189,6 +195,9 @@ if (APPLE)
target_link_libraries(openmw ${CARBON_FRAMEWORK})
endif (APPLE)
# Other apps and tools
add_subdirectory( apps/clientconsole )
# Apple bundling
if (APPLE)
set_source_files_properties(

@ -0,0 +1,2 @@
project(clientconsole)
add_executable(clientconsole client.cpp)

@ -0,0 +1,98 @@
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
#pragma warning( disable : 4966 )
class Client
{
protected:
boost::asio::io_service mIOService;
tcp::socket* mpSocket;
public:
bool connect(const char* port)
{
tcp::resolver resolver(mIOService);
tcp::resolver::query query("localhost", port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
mpSocket = new tcp::socket(mIOService);
boost::system::error_code error = boost::asio::error::host_not_found;
while (error && endpoint_iterator != end)
{
mpSocket->close();
mpSocket->connect(*endpoint_iterator++, error);
}
return (error) ? false : true;
}
void disconnect()
{
mpSocket->close();
mIOService.stop();
}
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;
std::vector<char> packet(plen);
Header* pHeader = reinterpret_cast<Header*>(&packet[0]);
strncpy(pHeader->magic, "OMW0", 4);
pHeader->dataLength = slen + 1; // Include the null terminator
strncpy(&packet[8], msg, pHeader->dataLength);
boost::system::error_code ec;
boost::asio::write(*mpSocket, boost::asio::buffer(packet),
boost::asio::transfer_all(), ec);
if (ec)
std::cout << "Error: " << ec.message() << std::endl;
return !ec;
}
};
int main(int argc, char* argv[])
{
std::cout << "OpenMW client console" << std::endl;
std::cout << "=====================" << std::endl;
std::cout << "Type 'quit' to exit." << std::endl;
std::cout << "Connecting...";
Client client;
if (client.connect("27917"))
{
std::cout << "success." << std::endl;
bool bDone = false;
do
{
std::cout << "> ";
char buffer[1024];
gets(buffer);
if (std::string(buffer) != "quit")
bDone = !client.send(buffer);
else
bDone = true;
} while (!bDone);
client.disconnect();
}
else
std::cout << "failed." << std::endl;
return 0;
}

@ -14,10 +14,25 @@
#include "apps/openmw/mwrender/playerpos.hpp"
#include "apps/openmw/mwrender/sky.hpp"
class ProcessCommandsHook : public Ogre::FrameListener
{
public:
ProcessCommandsHook(OMW::Engine* pEngine) : mpEngine (pEngine) {}
virtual bool frameStarted(const Ogre::FrameEvent& evt)
{
mpEngine->processCommands();
return true;
}
protected:
OMW::Engine* mpEngine;
};
OMW::Engine::Engine()
: mEnableSky (false)
, mpSkyManager (NULL)
{
mspCommandServer.reset(new OMW::CommandServer::Server(&mCommands, kCommandServerPort));
}
// Load all BSA files in data directory.
@ -83,6 +98,16 @@ void OMW::Engine::enableSky (bool bEnable)
mEnableSky = bEnable;
}
void OMW::Engine::processCommands()
{
std::string msg;
while (mCommands.pop_front(msg))
{
///\todo Add actual processing of the received command strings
std::cout << "Command: '" << msg << "'" << std::endl;
}
}
// Initialise and enter main loop.
void OMW::Engine::go()
@ -146,11 +171,17 @@ void OMW::Engine::go()
// Sets up the input system
MWInput::MWInputManager input(mOgre, player);
// Launch the console server
std::cout << "Starting command server on port " << kCommandServerPort << std::endl;
mspCommandServer->start();
mOgre.getRoot()->addFrameListener( new ProcessCommandsHook(this) );
std::cout << "\nStart! Press Q/ESC or close window to exit.\n";
// Start the main rendering loop
mOgre.start();
mspCommandServer->stop();
delete mpSkyManager;
std::cout << "\nThat's all for now!\n";

@ -6,6 +6,9 @@
#include <boost/filesystem.hpp>
#include "apps/openmw/mwrender/mwscene.hpp"
#include "components/misc/tsdeque.hpp"
#include "components/commandserver/server.hpp"
namespace MWRender
{
@ -18,6 +21,8 @@ namespace OMW
class Engine
{
enum { kCommandServerPort = 27917 };
boost::filesystem::path mDataDir;
Render::OgreRenderer mOgre;
std::string mCellName;
@ -26,6 +31,9 @@ namespace OMW
bool mEnableSky;
MWRender::SkyManager* mpSkyManager;
TsDeque<std::string> mCommands;
std::auto_ptr<OMW::CommandServer::Server> mspCommandServer;
// not implemented
Engine (const Engine&);
Engine& operator= (const Engine&);
@ -55,6 +63,9 @@ namespace OMW
/// Enables rendering of the sky (off by default).
void enableSky (bool bEnable);
/// Process pending commands
void processCommands();
/// Initialise and enter main loop.
void go();
};

@ -0,0 +1,177 @@
#include "server.hpp"
using boost::asio::ip::tcp;
//
// Namespace for containing implementation details that the
// rest of OpenMW doesn't need to worry about
//
namespace OMW { namespace CommandServer { namespace Detail {
///
/// Tracks an active connection to the CommandServer
///
class Connection
{
public:
Connection (boost::asio::io_service& io_service, Server* pServer);
void start();
void stop();
tcp::socket& socket();
protected:
void handle ();
tcp::socket mSocket;
Server* mpServer;
boost::thread* mpThread;
};
Connection::Connection (boost::asio::io_service& io_service, Server* pServer)
: mSocket (io_service)
, mpServer (pServer)
{
}
void Connection::start()
{
mpThread = new boost::thread(boost::bind(&Connection::handle, this));
}
///
/// Stops and disconnects the connection
///
void Connection::stop()
{
mSocket.close();
mpThread->join();
}
tcp::socket& Connection::socket()
{
return mSocket;
}
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);
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;
mSocket.read_some(boost::asio::buffer(&msg[0], header.dataLength), error);
if (!error)
mpServer->postMessage( &msg[0] );
else
bDone = true;
}
else
throw std::exception("Unexpected header!");
}
else
bDone = true;
}
mpServer->removeConnection(this);
}
}}}
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)
{
}
void Server::start()
{
mIOService.run();
mpThread = new boost::thread(boost::bind(&Server::threadMain, this));
}
void Server::stop()
{
// (1) Stop accepting new connections
// (2) Wait for the listener thread to finish
mAcceptor.close();
mpThread->join();
// Now that no new connections are possible, close any existing
// open connections
{
boost::mutex::scoped_lock lock(mConnectionsMutex);
mbStopping = true;
for (ConnectionSet::iterator it = mConnections.begin();
it != mConnections.end();
++it)
{
(*it)->stop();
}
}
}
void Server::removeConnection (Connection* ptr)
{
// If the server is shutting down (rather the client closing the
// connection), don't remove the connection from the list: that
// would corrupt the iterator the server is using to shutdown all
// clients.
if (!mbStopping)
{
boost::mutex::scoped_lock lock(mConnectionsMutex);
std::set<Connection*>::iterator it = mConnections.find(ptr);
if (it != mConnections.end())
mConnections.erase(it);
}
delete ptr;
}
void Server::postMessage (const char* s)
{
mpCommands->push_back(s);
}
void Server::threadMain()
{
// Loop until accept() fails, which will cause the break statement to be hit
while (true)
{
std::auto_ptr<Connection> spConnection(new Connection(mAcceptor.io_service(), this));
boost::system::error_code ec;
mAcceptor.accept(spConnection->socket(), ec);
if (!ec)
{
boost::mutex::scoped_lock lock(mConnectionsMutex);
mConnections.insert(spConnection.get());
spConnection->start();
spConnection.release();
}
else
break;
}
}
}}

@ -0,0 +1,62 @@
#ifndef CONSOLESERVER_H
#define CONSOLESERVER_H
#include <iostream>
#include <string>
#include <deque>
#include <set>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include "components/misc/tsdeque.hpp"
namespace OMW { namespace CommandServer
{
//
// Forward Declarations
//
namespace Detail
{
class Connection;
}
//
// Server that opens a port to listen for string commands which will be
// put into the deque provided in the Server constructor.
//
class Server
{
public:
typedef TsDeque<std::string> Deque;
Server (Deque* pDeque, const int port);
void start();
void stop();
protected:
friend class Detail::Connection;
typedef std::set<Detail::Connection*> ConnectionSet;
void removeConnection (Detail::Connection* ptr);
void postMessage (const char* s);
void threadMain();
// Objects used to set up the listening server
boost::asio::io_service mIOService;
boost::asio::ip::tcp::acceptor mAcceptor;
boost::thread* mpThread;
bool mbStopping;
// Track active connections
ConnectionSet mConnections;
mutable boost::mutex mConnectionsMutex;
// Pointer to output queue in which to put received strings
Deque* mpCommands;
};
}}
#endif // CONSOLESERVER_H

@ -0,0 +1,34 @@
#ifndef TSDEQUE_H
#define TSDEQUE_H
#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;
};
#endif // TSDEQUE_H
Loading…
Cancel
Save