はじめに


概要

BBS.pmはPerlでサーバアプリケーションを作成するためのモジュールです。

「node.jsでパソコン通信ホストシステム「BIG-Model」を作ってみる」プロジェクトで、集めた資料を基にPerlで作成した動作検証用サンプルプログラムを実用化させるためのプロジェクト「Perlでパソコン通信ホストシステム「BIG-Model」を作ってみる」に転換、さらにそこからサーバ処理を抽出するプロジェクトとして立ち上がりました。

モジュール名はBBS.pmですが、パソコン通信ホストシステムに限らず、チャットやゲームなど、さまざまなアプリケーションサーバが作成できるよう再設計を行い、現在に至ります。


特徴

  • マルチユーザサーバが作成可能です。
  • イベント駆動型でイベントにサーバアプリケーションの処理をハンドリングすることで簡単に作成できます。
  • データ通信がTrickyです。


まず動かしてみましょうか?

package App;

use BBS;
my $bbs = new BBS;

$bbs->start(8888);

これでサーバを開始することができます。

telnetクライアントからTCP8888を指定して接続すればサーバに接続できますが、キーを入力しても何も反応しません。

BBS.pmはイベント駆動のためイベントが発生したときにハンドラを介してアプリケーション処理を呼び出す必要があります。

例えばノードが接続または切断したときにサーバのコンソールにメッセージを表示してみます。


イベントハンドリング

package App;

use BBS;
my $bbs = new BBS;

sub onconnect {
  my $self = shift;
  printf "\n(%d) 接続しました ", $self->from();
  print "\n接続ノード: [ ".join( ',', $self->nodes() )." ] ";
}

sub ondisconnect {
  my $self = shift;
  printf "(%d) 切断しました\n", $self->from();
}

$bbs->setsyshandler('onConnect',    sub { $bbs->App::onconnect(@_) });
$bbs->setsyshandler('onDisconnect', sub { $bbs->App::ondisconnect(@_) });

$bbs->start(8888);

telnetクライアントでサーバに接続してみるとサーバのコンソールに「接続しました」のメッセージが表示されます。

BBS.pmはマルチユーザサーバなのでtelnetクライアントを複数起動してサーバに接続してみると、メッセージとともに接続するノード番号が増加します。

telnetクライアントを閉じると接続が終了し「切断しました」のメッセージが表示されます。

依然、クライアントからキーを入力しても何も反応しませんので、ノードが入力したデータをノード側にエコーバック(オウム返し)してみます。


アプリケーション処理(エコーバック)

package App;

use BBS;
my $bbs = new BBS;

# 送信バッファ(オブジェクト内に作成)
$bbs->{'send_buffer'} = {};

sub onconnect {
  my $self = shift;
  printf "\n(%d) 接続しました ", $self->from();
  print "\n接続ノード: [ ".join( ',', $self->nodes() )." ] ";
}

sub ondisconnect {
  my $self = shift;
  printf "(%d) 切断しました\n", $self->from();
}

sub onrecv {
  my $self = shift;
  my $rcvdata = join(//, @_);                           # 受信データ

  $self->send( $self->from(), $rcvdata );               # 受信データを送信
}

sub onsend {
  my $self = shift;
  my $to_node = shift;                                  # 送信先ノード
  my $send_data = join(//, @_);                         # 送信データ

  $self->{'send_buffer'}->{$to_node} = $send_data;      # 送信バッファに送信データを保存
}

sub output {
  my $self = shift;
  my $to_node = $self->from();                          # 送信先ノード
  my $send_data = $self->{'send_buffer'}->{$to_node};   # 送信データ

  if ( defined($send_data) ) {
    $self->{'send_buffer'}->{$to_node} = undef;         # 送信バッファを消去
    return $send_data;                                  # 送信データを呼び出し元に返す
  }
}

$bbs->setsyshandler('onConnect',    sub { $bbs->App::onconnect(@_) });
$bbs->setsyshandler('onDisconnect', sub { $bbs->App::ondisconnect(@_) });
$bbs->setsyshandler('onRecv',       sub { $bbs->App::onrecv(@_) });
$bbs->setsyshandler('onSend',       sub { $bbs->App::onsend(@_) });
$bbs->setsyshandler('Output',       sub { $bbs->App::output(@_) });

$bbs->start(8888);

telnetクライアントでサーバに接続してキーを入力すると、入力した文字がクライアントのコンソールにエコーバックされます。

telnetクライアントを複数接続してそれぞれのクライアントから入力してみると、入力したクライアントにのみエコーバックされます。

この辺りは、ソケットプログラミングのサンプルプログラムとしてよく見かけるエコーサーバと同じものですが、 少し物足りないので、どのノードから入力しても接続する全てのノードに一斉送信してみるのはいかがでしょうか?


アプリケーション処理(ブロードキャスト)

package App;

use BBS;
my $bbs = new BBS;

# 送信バッファ(オブジェクト内に作成)
$bbs->{'send_buffer'} = {};

sub onconnect {
  my $self = shift;
  printf "\n(%d) 接続しました ", $self->from();
  print "\n接続ノード: [ ".join( ',', $self->nodes() )." ] ";
}

sub ondisconnect {
  my $self = shift;
  printf "\n(%d) 切断しました ", $self->from();
  print "\n接続ノード: [ ".join( ',', $self->nodes() )." ] ";
}

sub onrecv {
  my $self = shift;
  my $rcvdata = join('', @_);                            # 受信データ
  my @tonodes = $self->nodes();
  map { $self->send( $_, $rcvdata ) } @tonodes;          # 受信データを全ノードに送信
}
 
sub onsend {
  my $self = shift;
  my $to_node = shift;                                   # 送信先ノード
  my $send_data = join(//, @_);                          # 送信データ
 
  $self->{'send_buffer'}->{$to_node} = $send_data;       # 送信バッファに送信データを保存
}
 
sub output {
  my $self = shift;
  my $to_node = $self->from();                           # 送信先ノード
  my $send_data = $self->{'send_buffer'}->{$to_node};    # 送信データ
 
  if ( defined($send_data) ) {
    $self->{'send_buffer'}->{$to_node} = undef;          # 送信バッファを消去
    return $send_data;                                   # 送信データを呼び出し元に返す
  }
}

$bbs->setsyshandler('onConnect',    sub { $bbs->App::onconnect(@_) });
$bbs->setsyshandler('onDisconnect', sub { $bbs->App::ondisconnect(@_) });
$bbs->setsyshandler('onRecv',       sub { $bbs->App::onrecv(@_) });
$bbs->setsyshandler('onSend',       sub { $bbs->App::onsend(@_) });
$bbs->setsyshandler('Output',       sub { $bbs->App::output(@_) });
 
$bbs->start(8888);

telnetクライアントを複数起動してサーバに接続してそれぞれのクライアントからキーを入力すると、入力した文字が接続する全てのノードに送信されます。
この辺りまでになるとチャットサーバの基本的な処理とほぼ同じです。


次は何をする?

BBS.pmはもともと一つのサーバプログラムから、データ通信やノード管理などで構成するサーバシステムと、 プロトコルやデータ処理などのアプリケーションシステムを分離し、サーバシステムに特化したライブラリとして誕生しました。
これによって、サーバアプリケーション開発者がサーバシステムにかかわる負担を軽減し、サーバアプリケーションの作成に集中できるようになります。

これまで4回の修正でチャットサーバに近いものを作成しましたが、このようにサーバアプリケーションの機能拡張をわずかなコードで実現できるように配慮しています。
しかし、高度で複雑なサーバアプリケーションを作成するためには、BBS.pmの機能や仕様を熟知することはもちろん、開発者の持つ知識や経験をフル活用しなければ解決できないかもしれません。

興味がありましたら、引き続きドキュメントやチュートリアルなどをご覧いただき理解を深めていただけると幸いです。

ぜひ楽しんでみてください。

Task Runner