Poco::Net::TCPServer

Poco::Net::TCPServer を紹介します。

TCPServerTest.cpp

・telnetで(複数の)クライアント端末からカーソルポジションをセット/ゲット/相対移動することができるサーバーという想定。
・複数のクライアントからの命令をシリアライズするためにPoco::ActiveDispatcherを利用。
・各コマンドに対応する処理ルーチンをPoco::HashMapで保持。
Poco::StringTokenizerでコマンドを分解。

#include <Poco/Net/TCPServer.h>
#include <Poco/String.h>
#include <Poco/StringTokenizer.h>
#include <Poco/HashMap.h>
#include <Poco/Util/ServerApplication.h>
#include <Poco/Util/Option.h>
#include <Poco/Util/OptionSet.h>
#include <Poco/Util/HelpFormatter.h>
#include <Poco/NumberParser.h>
#include <Poco/Format.h>
#include <Poco/ActiveMethod.h>
#include <Poco/ActiveDispatcher.h>
 
#include "ScopedLogMessage.h"
 
const int kNumRecieveBufferBytes = 2048;
 
class CommandProcessor
{
public:
	CommandProcessor(ScopedLogMessage& msg) :
		m_msg	(msg)
	{
	}
	virtual	~CommandProcessor()	{}
	virtual std::string Process(Poco::StringTokenizer& token, std::pair<int, int>& cursorPos)
	{
		return Poco::format("cursorPos={%d, %d}\n", cursorPos.first, cursorPos.second);
	}
 
protected:
	inline void Message(const std::string& msg) const
	{
		m_msg.Message(msg);
	}
 
private:
	CommandProcessor();
	CommandProcessor(const CommandProcessor&);
	CommandProcessor& operator = (const CommandProcessor&);
 
	ScopedLogMessage&	m_msg;
};
 
class SetProcessor : public CommandProcessor
{
public:
	SetProcessor(ScopedLogMessage& msg) :
		CommandProcessor(msg)
	{
	}
	virtual	~SetProcessor()	{}
	virtual std::string Process(Poco::StringTokenizer& token, std::pair<int, int>& cursorPos)
	{
		Message("     SetProcessor::Process called.");
 
		std::string rtn;
 
		if(3 != token.count())
		{
			rtn = "ERROR: set command requires two arguments.\n";
		}
		else
		{
			int x, y;
			if(Poco::NumberParser::tryParse(token[1], x) && Poco::NumberParser::tryParse(token[2], y))
			{
				cursorPos.first  = x;
				cursorPos.second = y;
				rtn = CommandProcessor::Process(token, cursorPos);
			}
			else
			{
				rtn = "ERROR: set command requires two integer arguments.\n";
			}
		}
		return rtn;
	}
 
private:
	SetProcessor();
	SetProcessor(const SetProcessor&);
	SetProcessor& operator = (const SetProcessor&);
};
 
class GetProcessor : public CommandProcessor
{
public:
	GetProcessor(ScopedLogMessage& msg) :
		CommandProcessor(msg)
	{
	}
	virtual	~GetProcessor()	{}
	virtual std::string Process(Poco::StringTokenizer& token, std::pair<int, int>& cursorPos)
	{
		Message("     GetProcessor::Process called.");
 
		std::string rtn;
 
		if(1 != token.count())
		{
			rtn = "WARNING: get command does not need argument(s).\n";
		}
		rtn += CommandProcessor::Process(token, cursorPos);
		return rtn;
	}
 
private:
	GetProcessor();
	GetProcessor(const GetProcessor&);
	GetProcessor& operator = (const GetProcessor&);
};
 
class MoveProcessor : public CommandProcessor
{
public:
	MoveProcessor(ScopedLogMessage& msg) :
		CommandProcessor(msg)
	{
	}
	virtual	~MoveProcessor()	{}
	virtual std::string Process(Poco::StringTokenizer& token, std::pair<int, int>& cursorPos)
	{
		Message("     MoveProcessor::Process called.");
 
		std::string rtn;
 
		if(3 != token.count())
		{
			rtn = "ERROR: move command requires two arguments.\n";
		}
		else
		{
			int x, y;
			if(Poco::NumberParser::tryParse(token[1], x) && Poco::NumberParser::tryParse(token[2], y))
			{
				cursorPos.first  += x;
				cursorPos.second += y;
				rtn = CommandProcessor::Process(token, cursorPos);
			}
			else
			{
				rtn = "ERROR: move command requires two integer arguments.\n";
			}
		}
		return rtn;
	}
 
private:
	MoveProcessor();
	MoveProcessor(const MoveProcessor&);
	MoveProcessor& operator = (const MoveProcessor&);
};
 
class CommandDispatcher : public Poco::ActiveDispatcher
{
public:
	CommandDispatcher(ScopedLogMessage& msg) :
		process(this, &CommandDispatcher::processImpl)
 	,	m_msg(msg)
	{
		m_msg.Message(" CommandDispatcher start");
 
		m_CursorPos.first  = 0;
		m_CursorPos.second = 0;
 
		m_processorMap["set" ] = new SetProcessor(msg);
		m_processorMap["get" ] = new GetProcessor(msg);
		m_processorMap["move"] = new MoveProcessor(msg);
	}
 
	struct second_deleter
	{
		template <typename T>
		void operator()(const T& m) const
		{
			delete m.second;
		}
	};
 
	~CommandDispatcher()
	{
		for_each(m_processorMap.begin(), m_processorMap.end(), second_deleter());
 
		m_msg.Message(" CommandDispatcher end");
	}
 
	Poco::ActiveMethod<std::string, std::string,
						CommandDispatcher, Poco::ActiveStarter<Poco::ActiveDispatcher> > process;
 
private:
	// Single Threaded Execution pattern
	std::string processImpl(const std::string& str)
	{
		std::string rtn;
 
		Poco::StringTokenizer token(str, " ");
		if(token.count())
		{
			Poco::HashMap<std::string, CommandProcessor*>::Iterator itr = m_processorMap.find(token[0]);
			if(m_processorMap.end() != itr)
			{
				rtn = itr->second->Process(token, m_CursorPos);
			}
			else
			{
				rtn = "ERROR: unknown command!\n";
			}
		}
 
		return rtn;
	}
 
	std::pair<int, int>		m_CursorPos;
	ScopedLogMessage&		m_msg;
	Poco::HashMap<std::string, CommandProcessor*>	m_processorMap;
};
 
class TCPConnection : public Poco::Net::TCPServerConnection
{
public:
	TCPConnection(const Poco::Net::StreamSocket& s, ScopedLogMessage& msg, CommandDispatcher& dispatcher):
		Poco::Net::TCPServerConnection(s)
	,   m_msg           (msg)
	,	m_dispatcher	(dispatcher)
	{
		m_msg.Message(Poco::format("   TCPConnection start (%s)", socket().peerAddress().toString()));
	}
 
	~TCPConnection()
	{
		m_msg.Message(Poco::format("   TCPConnection end   (%s)", socket().peerAddress().toString()));
	}
 
	void run(void)
	{
		const std::string prompt("TCPServer> ");
		char buffer[kNumRecieveBufferBytes+1] = {0};
		int numRead = 1;
 
		for(;numRead;)
		{
			try
			{
				socket().sendBytes(prompt.data(), static_cast<int>(prompt.length()));
 
				numRead = socket().receiveBytes(buffer, kNumRecieveBufferBytes);
				if(numRead)
				{
					buffer[numRead] = 0;
					std::string str(buffer);
					Poco::replaceInPlace(str, "\r", "");
					Poco::replaceInPlace(str, "\n", "");
					Poco::toLowerInPlace(str);
					m_msg.Message(Poco::format("    TCPConnection::run received: %s", str));
 
					Poco::ActiveResult<std::string> result = m_dispatcher.process(str);
					result.wait();
 
					if(result.data().length())
					{
						socket().sendBytes(result.data().c_str(), static_cast<int>(result.data().length()));
					}
				}
			}
			catch(Poco::Exception&)
			{
			}
		}
	}
 
private:
	ScopedLogMessage&	m_msg;
	CommandDispatcher&	m_dispatcher;
};
 
class TCPConnectionFactory : public Poco::Net::TCPServerConnectionFactory
{
public:
	TCPConnectionFactory(ScopedLogMessage& msg, CommandDispatcher& dispatcher):
		m_msg           (msg)
	,	m_dispatcher	(dispatcher)
	{
		m_msg.Message("  TCPConnectionFactory start");
	}
 
	~TCPConnectionFactory()
	{
		m_msg.Message("  TCPConnectionFactory end");
	}
 
	Poco::Net::TCPServerConnection* createConnection(const Poco::Net::StreamSocket& socket)
	{
		m_msg.Message("  TCPConnectionFactory createConnection");
		return new TCPConnection(socket, m_msg, m_dispatcher);
	}
 
private:
	ScopedLogMessage&	m_msg;
	CommandDispatcher&	m_dispatcher;
};
 
//----------------------------------------
//	MyTCPServer
//		To test the MyTCPServer you can use any terminal wuth "telnet localhost 9923".
//----------------------------------------
class MyTCPServer : public Poco::Util::ServerApplication
{
public:
	MyTCPServer() :
		m_helpRequested(false)
	{
	}
 
	~MyTCPServer()
	{
	}
 
protected:
	void initialize(Poco::Util::Application& self)
	{
		loadConfiguration(); // load default configuration files, if present
		Poco::Util::ServerApplication::initialize(self);
	}
 
	void uninitialize()
	{
		Poco::Util::ServerApplication::uninitialize();
	}
 
	void defineOptions(Poco::Util::OptionSet& options)
	{
		Poco::Util::ServerApplication::defineOptions(options);
 
		options.addOption(
			Poco::Util::Option("help", "h", "display help information on command line arguments")
							.required(false)
							.repeatable(false));
	}
 
	void handleOption(const std::string& name, const std::string& value)
	{
		Poco::Util::ServerApplication::handleOption(name, value);
 
		if(name == "help")
		{
			m_helpRequested = true;
		}
	}
 
	void displayHelp()
	{
		Poco::Util::HelpFormatter helpFormatter(options());
		helpFormatter.setCommand(commandName());
		helpFormatter.setUsage("OPTIONS");
		helpFormatter.setHeader("TCP server sample application.");
		helpFormatter.format(std::cout);
	}
 
	int main(const std::vector<std::string>& args)
	{
		ScopedLogMessage msg(" main() ", "start", "end");
 
		if(m_helpRequested)
		{
			displayHelp();
		}
		else
		{
			CommandDispatcher	commandDispatcher(msg);
 
			unsigned short port = (unsigned short) config().getInt("MyTCPServer.port", 9923);
			Poco::Net::ServerSocket svs(port);
			Poco::Net::TCPServer srv(new TCPConnectionFactory(msg, commandDispatcher),
										svs, new Poco::Net::TCPServerParams);
			srv.start();
 
			// wait for CTRL-C or kill
			waitForTerminationRequest();
 
			srv.stop();
		}
		return Poco::Util::Application::EXIT_OK;
	}
 
private:
	bool	m_helpRequested;
};
 
//----------------------------------------
//	main
//----------------------------------------
POCO_SERVER_MAIN(MyTCPServer)

Results of execution

・On Macintosh OSX 10.6.8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ ./TCPServerTest
[0]  main() start
[0]  CommandDispatcher start
[0]   TCPConnectionFactory start
[2]   TCPConnectionFactory createConnection
[2]    TCPConnection start (192.168.1.132:52195)
[2]     TCPConnection::run received: set 10 20
[1]      SetProcessor::Process called.
[2]     TCPConnection::run received: move 1 2
[1]      MoveProcessor::Process called.
[3]   TCPConnectionFactory createConnection
[3]    TCPConnection start (127.0.0.1:52196)
[3]     TCPConnection::run received: get
[1]      GetProcessor::Process called.
[3]     TCPConnection::run received: move -2 -3
[1]      MoveProcessor::Process called.
[2]     TCPConnection::run received: get
[1]      GetProcessor::Process called.
[2]    TCPConnection end   (192.168.1.132:52195)
[3]    TCPConnection end   (127.0.0.1:52196)
^C
[3]   TCPConnectionFactory end
[0]  CommandDispatcher end
[0]  main() end

・5-6行目でTerminal #1を、11-12行目でTerminal #2を接続。それぞれスレッド[2],[3]で動く。
・7,9行目でTerminal #1からsetとmoveを実行。
・13,15行目でTerminal #2からgetとmoveを実行。
・17行目でTerminal #1からgetを実行。
・19行目でTerminal #1を、20行目でTerminal #2を終了。
・21行目で^Cを入力し、TCPServerTestを終了。
・*::Processは、全てスレッド[1]で実行されている。

Ternimal #1

$ telnet 192.168.1.132 9923
Trying 192.168.1.132...
Connected to 192.168.1.132.
Escape character is '^]'.
TCPServer> set 10 20
cursorPos={10, 20}
TCPServer> move 1 2
cursorPos={11, 22}
TCPServer> get
cursorPos={9, 19}
TCPServer> ^]
telnet> q
Connection closed.

Ternimal #2

$ telnet localhost 9923
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
TCPServer> get
cursorPos={11, 22}
TCPServer> move -2 -3
cursorPos={9, 19}
TCPServer> ^]
telnet> q
Connection closed.

Downloads

ここをクリックすると、makefile や VC++ プロジェクトなど一式がダウンロードできます。
・2013年5月3日からのダウンロード数:430
(注意:2013/05/09に差し替えました)

Subversion

・フリーの Subversion ホスティングサービス Assemblaで、ソースコードを管理しています。

Powered by POCO Copyright © 2013 Round Square Inc. All rights reserved.