PUSH技術をPOCOを使ってどのように実装できるか詳解します。
その前に、PUSH技術とは何かおさらいしておきます。
PUSH技術とは、テレビのようにサーバから複数のクライアントへコンテンツを送信する技術で、実現方法は以下の3つです。
1. クライアントが定期的にリクエストを発行する。(ポーリング)・・・例:電子メール
2. ポーリングと同じくクライアントがリクエストを発行するが、新しいコンテンツが発生するまでレスポンスを返さない。(ロングポーリング)・・・例:Comet
3. コネクションを張りっぱなしにする。(ストリーミング)・・・例:金融取引リッチクライアント
ポーリングとロングポーリングについては、ググればサンプルソースがたくさん見つかりますが、ストリーミングについてはまったく見つかりませんでした。
ここでは、POCOを使ってストリーミングを実装していきます。
通信アプリケーション開発をサポートするために、POCOはNetライブラリを提供しています。
その中のPoco::Net::TCPServerクラスは、スレッドプールによるマルチスレッドのソケット通信処理を行うことができるので、ストリーミング実装にはこれを使うこととします。
TCPServerクラスを使うNetライブラリのサンプルとして、TimeServerプロジェクトがPOCOに付属しています。
TimeServerプロジェクトを、サーバの現在時刻をプッシュするように改造してみます。
まずは、サンプルをそのままコンパイルして実行してみましょう。
$ TimeServer
これで、サーバが起動されたので、telnetでサーバと通信してみます。
$ telnet 127.0.0.1 9911
以下のように、telnetにサーバの現在時刻が表示されて接続が切断されたと思います。
1 2 3 4 5 6 |
$ telnet 127.0.0.1 9911
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
2009-07-04T02:31:52Z
Connection closed by foreign host. |
接続をつなぎっぱなしにしてサーバからプッシュできるように、TimeServerConnection#runで現在時刻を送信していたのを、イベントに現在時刻送信メソッドを登録した後サスペンドするように変更します。イベント発生は、便宜的に1秒間隔で発生するものとしました。
このイベントの実装には、FoundationライブラリのPoco::BasicEventクラスを使うだけです。
Poco::BasicEventは、C#プログラマにはおなじみのC#のイベント機能のC++実装です。
変更したソースを以下に記載します。プロトタイプとして動作を確認するレベルですが、POCOを使うことでスレッドプールでの通信という高度なアプリケーションが、このように簡単に作れてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
#include "Poco/Net/TCPServer.h"
#include "Poco/Net/TCPServerConnection.h"
#include "Poco/Net/TCPServerConnectionFactory.h"
#include "Poco/Net/TCPServerParams.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Timestamp.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/Exception.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include <iostream>
#include "Poco/BasicEvent.h"
#include "Poco/Delegate.h"
using Poco::Net::ServerSocket;
using Poco::Net::StreamSocket;
using Poco::Net::TCPServerConnection;
using Poco::Net::TCPServerConnectionFactory;
using Poco::Net::TCPServer;
using Poco::Timestamp;
using Poco::DateTimeFormatter;
using Poco::DateTimeFormat;
using Poco::Util::ServerApplication;
using Poco::Util::Application;
using Poco::Util::Option;
using Poco::Util::OptionSet;
using Poco::Util::HelpFormatter;
static Poco::BasicEvent<int> push_event;
class PushRunnable: public Poco::Runnable
{
public:
PushRunnable(): _stopped(false)
{
}
void run()
{
int tmp = 0;
while (!_stopped)
{
++tmp;
push_event.notify(this, tmp);
Poco::Thread::sleep(1000);
}
}
void notify()
{
_stopped = true;
}
private:
bool _stopped;
};
class TimeServerConnection: public TCPServerConnection
/// This class handles all client connections.
///
/// A string with the current date and time is sent back to the client.
{
public:
TimeServerConnection(const StreamSocket& s, const std::string& format)
: TCPServerConnection(s)
, _format(format)
, _stopped(false)
{
}
void run()
{
Application& app = Application::instance();
app.logger().information("Request from " + this->socket().peerAddress().toString());
try
{
push_event += Poco::delegate(this, &TimeServerConnection::onPush);
while (!_stopped)
{
Poco::Thread::sleep(1);
}
}
catch (Poco::Exception& exc)
{
app.logger().log(exc);
}
}
void onPush(const void* pSender, int& i)
{
try
{
Timestamp now;
std::string dt(DateTimeFormatter::format(now, _format));
dt.append("\r\n");
socket().sendBytes(dt.data(), (int) dt.length());
}
catch (Poco::Exception& exc)
{
push_event -= Poco::delegate(this, &TimeServerConnection::onPush);
_stopped = true;
Application::instance().logger().log(exc);;
}
}
private:
std::string _format;
bool _stopped;
};
class TimeServerConnectionFactory: public TCPServerConnectionFactory
/// A factory for TimeServerConnection.
{
public:
TimeServerConnectionFactory(const std::string& format):
_format(format)
{
}
TCPServerConnection* createConnection(const StreamSocket& socket)
{
return new TimeServerConnection(socket, _format);
}
private:
std::string _format;
};
class TimeServer: public Poco::Util::ServerApplication
/// The main application class.
///
/// This class handles command-line arguments and
/// configuration files.
/// Start the TimeServer executable with the help
/// option (/help on Windows, --help on Unix) for
/// the available command line options.
///
/// To use the sample configuration file (TimeServer.properties),
/// copy the file to the directory where the TimeServer executable
/// resides. If you start the debug version of the TimeServer
/// (TimeServerd[.exe]), you must also create a copy of the configuration
/// file named TimeServerd.properties. In the configuration file, you
/// can specify the port on which the server is listening (default
/// 9911) and the format of the date/time string sent back to the client.
///
/// To test the TimeServer you can use any telnet client (telnet localhost 9911).
{
public:
TimeServer(): _helpRequested(false)
{
}
~TimeServer()
{
}
protected:
void initialize(Application& self)
{
loadConfiguration(); // load default configuration files, if present
ServerApplication::initialize(self);
}
void uninitialize()
{
ServerApplication::uninitialize();
}
void defineOptions(OptionSet& options)
{
ServerApplication::defineOptions(options);
options.addOption(
Option("help", "h", "display help information on command line arguments")
.required(false)
.repeatable(false));
}
void handleOption(const std::string& name, const std::string& value)
{
ServerApplication::handleOption(name, value);
if (name == "help")
_helpRequested = true;
}
void displayHelp()
{
HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.setHeader("A server application that serves the current date and time.");
helpFormatter.format(std::cout);
}
int main(const std::vector<std::string>& args)
{
if (_helpRequested)
{
displayHelp();
}
else
{
// get parameters from configuration file
unsigned short port = (unsigned short) config().getInt("TimeServer.port", 9911);
std::string format(config().getString("TimeServer.format", DateTimeFormat::ISO8601_FORMAT));
// set-up a server socket
ServerSocket svs(port);
// set-up a TCPServer instance
TCPServer srv(new TimeServerConnectionFactory(format), svs);
// start the TCPServer
srv.start();
// start push thread
Poco::Thread thread;
PushRunnable runnable;
thread.start(runnable);
// wait for CTRL-C or kill
waitForTerminationRequest();
// Stop the TCPServer
srv.stop();
}
return Application::EXIT_OK;
}
private:
bool _helpRequested;
};
int main(int argc, char** argv)
{
TimeServer app;
return app.run(argc, argv);
} |
終わりに、PUSH技術についての参考情報のポインタを示します。
http://www.atmarkit.co.jp/fjava/column/andoh/andoh38.html
http://feed.designlinkdatabase.net/feed/outsite_106801.aspx