いきなりマッチングサーバーを作るのは大変なので、必要だと思われることを少しずつ体験してみることにする。
環境の整備
開発環境
これから我々は簡単なPerlのプログラムを書いていくわけだ。
Perlのプログラムを書くにあたって、特別な環境は必要ない。MSNotePadに代表されるテキストエディタがあれば十分だ。このページを見ているピープルならば大抵HSPやその他のスクリプトを書くために日々愛でているエディタがあるはずだ。それでもいいよ。
とは言うものの、色分けされたりして見やすいので、ナイスなエディタを探してみるのも良いだろう。ちなみに筆者はTerapadを愛用している。
実行環境
作ったCGIをテストする環境が必要だ。PCをWEBサーバーとして動作させればローカルでテストできるがチョロッと遊ぶ程度のノリで設定するにはめんどくさすぎるので、作ったソースをFTPでレンタルしたサーバーにうpして動作チェックする、という手順を踏みたいと思う。いちいち上書き保存するたびにファイルをアップロードするのはそれはそれでめんどくさいが。
マッチングサーバーの構想
作らんとするマッチングサーバーには、以下の機能を搭載したい
- ホストの情報を登録しファイルに書きだす
- ホスト名、IPアドレス、コメント、プログラムのバージョン、を予定
- プログラムがCGIに要求した場合に、有効なホストの一覧を出力
- なるべく処理しやすい書式で!
- WEBブラウザでアクセスした場合には、有効なホストの、IP以外の情報を出力する
- なるべく見やすい書式で!
そしてゲーム側に以下の機能を搭載する必要がある
- 自分のIPアドレスを調べて必要な情報を鯖へ登録する機能
- 鯖へデータを要求する機能
- 送られてきたデータを、IPアドレス以外の情報をリストアップ、選択させる機能
- 選ばれたホストへ接続する機能
IPアドレスは、内部で接続処理をする際などには利用するが、リストアップしない。こうする事で、とりあえずIPアドレスを掲示板で晒したりしなくても対戦できる。*1
それでこのページでは、練習として以下の事を簡単にやって次回に備える事にする。
- HSPからデータをCGIに与え、計算させた結果をHSPで表示する
- HSPから文字列を送信し、鯖に最新の10件をファイルに出力させる
- ↑10件をHSPで取得して表示
- HSPから見ているのか、ブラウザから見ているのか判別してメッセージを変える
分かる人にとっては練習する必要すらない事柄だろうが、殆ど素人に近いボクちゃんをはじめ、予習が必要な人間はいるはずだ。最低限この辺を抑えておけば、俺が作りたいマッチングサーバーの機能は満たせる。
前置き
オイラは、ポララという架空の会社からCGIサーバーをレンタルしていると仮定する。
ポララが提供するサーバーのURLはhttp://cgi.polala.orz.jp/であり、オイラのアカウントはOTLである。
オイラが作成したunko.cgiにアクセスする際のURLは、http://cgi.polala.orz.jp/~OTL/unko.cgiとなる。
HSPからデータをCGIに与え、計算させる
つーわけでまずはこの辺から。HSPから数値型データを与え、結果を文字列として送信させよう。
#include
dataA=5:dataB=10;データの定義
tcpopen socket,"cgi.polala.orz.jp",80
repeat
tcpiscon socket
if stat=1:mes "接続しました":break
if stat=2:dialog "えろあ":end
await 10
loop
command="GET /~OTL/sendtocgi.cgi"
command+="?data1="+dataA+"&data2="+dataB+" HTTP/1.0\n"
command+="\n"
tcpput command,socket
送信まで。今回はデータの送信にGETメソッドを使用する。
アクセスしたいCGIのURLの後ろに?をつけ、データ名=値&データ名=値....と記述する事で、CGIに任意のデータを送りつけることが出来る。この後受信処理を挟むわけだが、ひとまずCGI側のソースを見ておこう。
#! /usr/local/bin/perl
print "Content-type: text/plain\n\n";
$dataquery=$ENV{'QUERY_STRING'};
@data=split(/&/, $dataquery);
$value=0;
foreach $nowdata (@data){
($tmp,$tvalue)=split(/=/,$nowdata);
$value+=$tvalue;
}
print("$value");
素人が書いたのでモノホンのプロフェッショナルからすればアレかも知れないがご勘弁を。
Perlの解説についてどのくらいやればいいのかわからんので適当にやるが、GETメソッドによって送信されてきた値は、環境変数なるところへ格納されている。QUERY_STRINGという名前らしいな。コイツをまずは適当な変数へ受け取っている。
ここでdataqueryの値を出力すると
data1=5&data2=10
という風に、&で区切られた一行の文字列が表示される。ここから値を取り出したいわけだが、まず最初のsplit関数により、&を区切り文字としてdata1=5、data2=10という二つの文字列に分ける。@dataは配列変数として扱われ、data[0]にdata1=5という文字、同[1]にdata2=10という文字列が代入されているわけだ。
次にその各々についてdata1と5、data2と10、という風に、変数名と値に分割している。split関数が、=を区切り文字にして$tmpに変数名、$tvalueに値を格納する。変数名は今回は使っていないので、特に何もせず、得られたtvalueをvalueに加算し、最後に計算結果であるvalueを出力する。
つまりこのCGIは、与えられた数値を全て加算して結果を返すモノである。
HSPからURLを呼び出すだけでなく、ブラウザに直接CGIのURLとデータを書いてアクセスしても同じ事が可能であり、その気になればdataに文字列を指定したり、逆にデータを指定しなかったり、という風に様々な状況が発生しうるが、今回は一切感知しない。HSPからとりあえず動かせればおk。
ではデータをHSP側で受信する。例によって、おまじないとして1秒程度待ってから受信をする。
wait 100
tcpcount datasize,socket;受信したサイズを調べる
sdim recivedata,datasize;領域の確保
mes ""+datasize+"バイト受信"
tcprecv recivedata,0,datasize,socket
こんなもんだろう。ここで受信したデータを見てみると
実際出力しているのは演算結果の15だけだが、こんな風にHTTPのヘッダー*2も含まれている。ここから必要なデータだけを取り出す必要がある。
今回は一番最後の行を取り出せばいいので、HSPのメモリーノートパッド命令を用いて以下のようにした。
notesel recivedata
noteget result,notemax-1
mes result
これで、演算結果を受信して表示する事ができた。
文字列の保存とか
文字列をCGIに送信し、それをサーバーに保存させる。
CGIは色々な人間が様々なタイミングで起動する可能性があるので、ファイルを扱う際にはロックをしなければならない。ロックについてはWikipediaの記事を見ればパツイチだろう。よく聞く話だがATMを例にとって教えてくれる。
それでCGI側のソースだが、2~3日悩んだ結果
#! /usr/local/bin/perl
print "Content-type: text/html\n\n";
($sec,$min,$hour,$day,$mon,$year,$wday)=localtime();
$dataquery=$ENV{'QUERY_STRING'};
@data=split(/&/, $dataquery);
foreach $nowdata (@data){
($tmp,$tvalue)=split(/=/,$nowdata);
}
$hstring=sprintf("%04d/%02d/%02d %02d:%02d:%02d --- ",→
1900+$year,1+$mon,$day,$hour,$min,$sec);
if(-e "logfile.txt"){
open(DATA,"<logfile.txt") or die("error!!");
@loglist=<DATA>;
close(DATA);
}
open(DATA,">logfile.txt") or die("error!!");
flock(DATA,2);
@loglist=(@loglist,sprintf("%s%s\n",$hstring,$tvalue));
$cnt=0;
$loglength=1+$#loglist;
foreach $nowdata (@loglist){
if(($loglength-$cnt)<=10){
print DATA $nowdata;
}
$cnt++;
}
close(DATA);
こうなった。
ロックについて解説を読みながらやったが、ちゃんとやろうとするとソースが長くなるので今回は適当にやった。読み込み→ロックまでの間に他のプロセスが働き出したら\(^o^)/のような気もするがまあ気にしてはいけない。また、送信されてきた文字列の改行の有無も一切考慮しない。記憶が曖昧だが、恐らく改行は別の文字に変換されるため、一行の文字列になる。
それで、送信されてきたデータの一番最後を文字列と解釈し、ファイルに書きだすわけだが、ファイルをloglistに読み込み、その最後尾に送信されてきた文字列と日時を追加した上で、最新ログ10件をファイルに保存する所までやってある。通常ログが11行より増える事はないが、万が一手を加えたりしてログが50行とかになっても、最新10件を残して全て削除する。
自分でやってあれだが、判り難いネこれ。それでどうしたらいいかと考えたわけだが。
出血大サービスで、似た様な動作をするプログラムをHSPで書いてやった。Perlで流れを追おうとするから大変なんだ。少なくともこの資料館をガン見しているユーザーなら、HSPならばプログラムの動きは把握出来るだろう。つーかしろ。
以下、recivedataに送られてきた文字列が入っているとする。
recivedata="data=ヴァルヴァロだぞおぉぉぉっ!!"
getstr tmp,recivedata,0,'='
getstr tvalue,recivedata,strsize,'='
exist "logfile.txt"
notesel loglist
if strsize!-1:{
noteload "logfile.txt"
}
noteadd tvalue
logmax=notemax
repeat logmax
notesel loglist
if notemax-cnt<10:{
noteget tmp,cnt
notesel loglist2
noteadd tmp
}
loop
notesel loglist2
notesave "logfile.txt"
多分こんな感じだ。
- data=文字列、となっているところを二つに分ける。
- logfileがあるかどうか調べる。ある場合は読み込む。
- loglistの最後尾に文字列を追加する
- loglistの最後から10件を出力する
という段取りになっている。ごっちゃごちゃしてるが、上のCGIも同じ動作をやってます><
保存した文字列を受信する
先ほどのソースは標準出力へ何も出力していないので、バッファに送られてくるのはヘッダーだけとなる。
ここでデータも出力し、HSP側で操作できるようにしよう。
foreach $nowdata (@loglist){
if(($loglength-$cnt)<=10){
print DATA $nowdata;
($tmp,$tvalue)=split(/ --- /,$nowdata);
print "$tvalue";
}
$cnt++;
}
出力部分にprintが一個追加されて終わり。あとはHSPで受信するだけだが・・・
前回のHSP側のソースを改変し、文字列を送信した後に出力を受け取ってみる。
sprintf("%s%s\n",$hstring,$tvalue)
このように出力しているため、サーバー側の配列に入っているデータは改行コードを含んでおり、順々に出力されたデータは美しく改行されているはずだが・・・
受信した文字列を表示してみると、何か変な↑矢印が表示されている。何ぞこれ。
この↑矢印だが、改行コードであることには間違いない。間違いないが、これはUNIXで使われる改行コードだ。Windowsの改行コードではないため、なんかこんな感じの文字だと認識されるらしい。*3バイナリモードでやるとうまくいくらしいが。
それで、まあ、バイナリモード云々ってのは予習範囲外だろうという事で、受信したHSP側でどうにかすることにした。*4
そこでKOITSUだ。
改行コード操作モジュール:http://lldev.jp/hsp/module/crlf.html
ここで公開されているモジュールを利用する事で、改行コードに対する操作がイージー且つリーズナブルに行えるようになる。今回は、UNIXで使われているLFコードを含む文字列を受信してしまったので、Windowsの改行コードであるCRLFに変換する処理を行う。上記モジュールを参考にすれば、よりコンパクトなモジュールも割と簡単に作成できるだろう。
それでまあ、受信した文字列を改行コード変換して出力した結果。
これなら、メモリーノートパッド命令各種を用いてHSPから簡単に利用できるだろう。
うまい具合に改良を加えないと、妙な値を書き込んでデータの末尾がおかしくなる可能性もあるのでまあよしなにやってちょ。
HSPからアクセスされたのかどうか判断する
#! /usr/local/bin/perl
if($ENV{'HTTP_USER_AGENT'}eq"ORZ-LOVE"){
print "Content-type: text/plain\n\n";
print "世界の半分(北半球)をお前にやろう"
}
else{
print "Content-type: text/html\n\n";
print <<OWARI;
<html>
<head><title>HSPからアクセスしなきゃいやん</title></head>
<body>
<Font Size="7">世界の半分(南半球)をお前にやろう</Font><br>
</body>
</html>
OWARI
}
まずは見てくれ。最初にif文で環境変数の中の値を参照している。このユーザーエージェントというのは、ブラウザがHTTPプロトコルによる自己申告(重要)で、自分はこういうものです、という風に名乗った結果がそのまま代入されている。
参考までに俺が使ってるIEでアクセスした場合のユーザーエージェントは
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1~.....
とこのようになっている。モジラ!?IEなのに!?という疑問が生じた場合には、ぐぐったらいい記事があるかもしれないぞ。
それはさておき、この値は全くの自己申告であり、書式も決まっていない。
従って他とかぶらないような、オリジナルなユーザーエージェントを名乗れば、自作プログラム、それ以外で表示を分けることが可能だ。今回はエージェントをORZ-LOVEとしてみた。
command="GET /~OTL/sendtocgi.cgi HTTP/1.0\n"
command+="User-Agent: ORZ-LOVE\n"
command+="\n"
HTTPの部分。URLの後に改行を入れて、User-Agentを設定する。たったこれだけ。
なおブラウザの中にはこのユーザーエージェントを簡単に変えることが出来るものがあるようで、ばれてしまうと後々の運用に支障をきたす恐れがあるので各々注意するべし。
蛇足だが、HSPInetのユーザーエージェントを調べてみたところ
HSPInet(HSP3.0; Windows)
となっていたが変更の仕方がわからなかった。ユーザーエージェントで決め細やかな処理を行う場合は、残念ながら選択肢から外れてしまうだろう。
netagent命令によって変更可能なことが判明した。いやあ良かった。
まとめ
かなりメモ書き的要素が強いが、一応俺が必要だと思ったことについては予習が完了した。次はいよいよマッチングサーバーを作成し、例のパズルゲームを対応させたい。
Perlについてはまだまだ勉強中なので、効率のよいコードの書き方があったら適宜改善していく。