まずクライアント実装を見る。
class Client: public nine::TransceiverHandler
{
public:
Client();
virtual ~Client();
void init(int nClients);
bool connectLater(const char* host, uint16_t port);
void run();
public:
virtual void onAcceptorListen(int id, bool succeed);
virtual void onAcceptorAccept(int id, int cid);
virtual void onAcceptorClose(int id);
virtual void onAcceptorError(int id);
inline virtual void onCommunicatorConnect(int id, bool succeed);
inline virtual void onCommunicatorAccept(int id, int cid);
inline virtual void onCommunicatorClose(int id);
inline virtual void onCommunicatorError(int id);
inline virtual void onCommunicatorReceiveTimeout(int id);
inline virtual void onCommunicatorSendTimeout(int id);
private:
int m_nConnects;
int m_n;
std::vector< int > m_ids;
nine::Transceiver* m_pTransceiver;
};
ストリームタイプの通信では、Transceiver と TransceiverHandler が必要になる。
ここではクラス図の StreamApplication のように、TransceiverHandler を継承したクラスが Transceiver を保有するようにする。
class Client: public nine::TransceiverHandler
{
...
private:
nine::Transceiver* m_pTransceiver;
};
Client::Client()
: m_pTransceiver(NULL)
{ }
Client::~Client()
{
if (m_pTransceiver) {
nine::Transceiver::Destroy(m_pTransceiver);
}
}
void Client::init(int n)
{
m_n = n;
m_pTransceiver = nine::Transceiver::Create(0, n);
m_pTransceiver->setHandler(this);
m_nConnects = 0;
}
init() で Transceiver を作成し、~Client() で破棄している。 生成の直後に setHandler によって、自身をハンドラとして登録している。
生成時の Create 関数の引数は、前から順に Acceptorの数と Communicatorの数である。
Acceptor の数とは、接続の受け入れ口の数である。クライアント専門ならばゼロ、サーバーでも通常は一つで良い。
Communicator の数とは、実際にデータを送受信する接続の数である。明示的に接続をした場合はもちろん、Acceptor が受け入れた接続も含める。 例えば、同時接続数 10000 をこなすサーバーでは 10000 を指定する必要がある。
このサンプルでは、接続を受け付けないので Acceptor はゼロ個である。 クライアント接続は複数張れるようにしているので、Communicator の数は引数n個要求している。
TransceiverHandler はいわゆるインターフェースクラスで、実装すべき関数を純粋仮想関数で指定している。 Client で定義されている多数の on 関数がそれである。
public:
virtual void onAcceptorListen(int id, bool succeed);
virtual void onAcceptorAccept(int id, int cid);
virtual void onAcceptorClose(int id);
virtual void onAcceptorError(int id);
inline virtual void onCommunicatorConnect(int id, bool succeed);
inline virtual void onCommunicatorAccept(int id, int cid);
inline virtual void onCommunicatorClose(int id);
inline virtual void onCommunicatorError(int id);
inline virtual void onCommunicatorReceiveTimeout(int id);
inline virtual void onCommunicatorSendTimeout(int id);
サンプル実装では onCommunicatorConnect で接続数をカウントしているだけである。
void Client::onCommunicatorConnect(int id, bool succeed)
{
++m_nConnects;
printf("connect: %s\n", (succeed)? "succeed": "failed");
nine::Communicator* p = m_pTransceiver->getCommunicator(id);
if (p) {
// p->dump(true);
}
}
実アプリケーションでは、ここで接続毎の初期化処理を行うことになるだろう。
繰り返し turn() を呼ぶ。 サンプルでは単純に wait 付きの無限ループを使っている。
void Client::run()
{
while (true) {
m_pTransceiver->turn();
...
nine::msleep(5);
}
}
接続は単に Transceiver::connectLater() を呼べば良い。 後の Transceiver::turn() で実際に接続を行う。
Client::connectLater() は多数の接続全ての connectLater を呼んでいる。
bool Client::connectLater(const char* host, uint16_t port)
{
m_ids.resize(m_n);
for (int i=0; i<m_n; ++i) {
m_ids[i] = m_pTransceiver->connectLater(host, port);
if (m_ids[i] < 0) {
return false;
}
}
return true;
}
Communicator::getSendBuffer() で Buffer を取得し、Buffer の書き込み関数を使って書き込みを行う。
Communicator は connectLater() で得られた id を引数に、Transceiver::getComunicator() で取得できる。 まずは Communicator 取得部分を見てみる。
void Client::run()
{
while (true) {
m_pTransceiver->turn();
if (m_nConnects == m_n) {
nine::Communicator* p = m_pTransceiver->getCommunicator(m_ids[0]);
...
ここの m_ids[0] は、上の connectLater() でセットしてある。
if (p != NULL) {
nine::Buffer* pBuf = p->getSendBuffer();
pBuf->writeArray("hello", 5);
}
Buffer操作は Buffer操作ドキュメントを参照のこと。
ここではサンプルコードの部分だけを簡単に説明する。
writeArray("hello", 5) で、"hello" 文字列を書き込んでいる。 writeArray() は単純にバイト列を書き込む関数なので、文字列を書き込む際はデータ表現に必要である。 ここでは生のC言語文字列なので、1文字1バイトのバイト列として扱って問題ない。 書き込み文字数は終端NULLを含まずに 5文字となっていることに注意しておく。
Client サンプルではデータ受信は実装していないが、もちろんクライアントでもデータの受信は可能である。 実装方法は Server サンプルを参照されたい。
注意すべき点として、受信したデータは読み込まれるまでメモリに保持されるようになっている。 メモリを無駄にしないよう、データを受信するならば必ず読み込み処理を実装するべきである。