forked from mirror/openmw-tes3mp
Add simple external console server/client
parent
de7087caf4
commit
7357ea2102
@ -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;
|
||||
}
|
@ -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…
Reference in New Issue