Echoプロトコル
最もシンプルなプロトコルの一つとして Echo(エコー)プロトコル があります。
Echoプロトコルの仕様は以下のようにドキュメント化されています。
https://tools.ietf.org/html/rfc862
以下はプロトコルの抜粋です。
TCP Based Echo Service
One echo service is defined as a connection based application on TCP. A server listens for TCP connections on TCP port 7. Once a connection is established any data received is sent back. This continues until the calling user terminates the connection.
TCPによるEchoサービスは、7番ポートでアクセスを待ち受け、確立されたコネクションに対して、送信されたデータと同じ内容を送り返すというプロトコルの内容が記されています。
Echoサービスを利用するクライアントプログラムを書いていきましょう。
Echoサーバーの動作確認
まずは、自分でEchoクライアントを実装する前に、コマンドを利用してEchoクライアントの動作を確認してみましょう。
Echoの動作を確認するために Telnet を利用できます。 Telnetは、元々は遠隔のコンピュータを操作するためのプロトコルでしたが、通信が暗号化されないなどの問題点があるため、現在は SSH をその用途に利用することが多いです。 しかし、Telnetを利用することで任意のTCPポートで待ち受けるアプリケーションと手動で通信をすることができます。 つまり、通常はアプリケーションがサーバーに送信するメッセージの文字列を、代わりにユーザーがキーボードで入力することサーバーと通信を行うことができます。
telnet
がインストールされていない場合は以下のようにインストールしましょう。
sudo apt install telnet
35.200.9.137でEchoサーバーが稼働しているので7番ポートにアクセスしてみましょう。 (授業時間外はサーバーが停止している場合があります。) 以下のようにTelnetコマンドを実行します。
telnet 35.200.9.137 7
サーバーに接続されたら、サーバーに送るメッセージを打ち込んでいきましょう。 エンターキーを押して送信するたびに、以下のように送信したメッセージがそのままレスポンスとして帰ってくることが確認できます。
test
test
my message
my message
hello
hello
Telnet を抜けるためには、 Ctrl+]
でコネクションを切断し、 Ctrl+d
でコマンドを終了しましょう。
簡単なEchoクライアントの実装
Echoサーバーの動きが確認できたところで、同じ動きができるようにProcessingでプログラミングしましょう。
プログラムを簡単にするために、まずはスタティックモードでの実装を行います。 以下のプログラムを実行してみましょう。
echo1.pde
import processing.net.*;
Client client = new Client(this, "35.200.9.137", 7);
client.write("Hello\n");
while (client.active()) {
if (client.available() > 0) {
String response = client.readString();
print(response);
}
}
プログラムを実行すると、コンソールに「Hello」と表示されるはずです。
4行目でEchoサーバーに「Hello」という文字列を送信しています。 そして、Clientクラスを用いて、7行目でEchoサーバーからの応答を受け取理、8行目でコンソールに出力しています。 送った文字列がそのまま返ってくるはずなので、送った「Hello」という文字列が8行目でコンソールに表示されるわけです。
このプログラムは、プログラム中に埋め込まれた文字列を一度だけサーバーに送信し、その結果を受け取るものでした。 これを改良し、ユーザーが自由に入力した文字列でEchoサーバーとの通信ができるように改良をしていきましょう。
Echoを利用したアプリケーション
キーボードのイベントハンドラを利用して、ユーザーのキー入力をクライアントのバッファーに貯めていき、エンターキーが押されたタイミングでバッファーに溜まった文字列をEchoサーバーに送信するようにします。 また、入力中の文字列と受信した文字列を画面に表示されるようにします。
以下のプログラムを実行してみましょう。
echo2.pde
import processing.net.*;
Client client;
int bufferIndex = 0;
char[] buffer = new char[1000];
String response = "";
void setup() {
size(400, 400);
client = new Client(this, "35.200.9.137", 7);
}
void draw() {
background(255);
textAlign(CENTER, CENTER);
fill(0);
text(buffer, 0, bufferIndex, width / 2, height / 3);
text(response, width / 2, height * 2 / 3);
if (client.active()) {
if (client.available() > 0) {
response += client.readString();
}
}
}
void keyPressed() {
if (key != CODED) {
buffer[bufferIndex++] = key;
if (key == '\n') {
for (int i = 0; i < bufferIndex; ++i) {
client.write(buffer[i]);
}
bufferIndex = 0;
}
}
}
以下は、プログラムを実行し、「test(エンターキー)hello(エンターキー)test」と入力している際の実行結果です。
まず、グローバル変数に宣言されたバッファーについて確認しましょう。
文字配列の buffer
と整数 bufferIndex
が宣言されています。
buffer
には入力途中の文字を順に格納していき、bufferIndex
でバッファーに格納された有効な文字の数を数えています。
text
関数は、画面に文字列を描画する関数でした。
text
関数の第一引数には、文字配列とその配列から表示する範囲の添字を受け取ることができます。
以下の部分では、buffer
に格納された0文字目から bufferIndex
文字目の範囲を描画しています。
text(buffer, 0, bufferIndex, width / 2, height / 3);
グローバル変数 response
には、サーバーから受信した文字列を格納していきます。
client
を利用して、コネクションが確立されており、かつ読み込み可能な文字列がある間、結果を受け取り response
に結合しています。
ユーザーのキー入力を処理しているのは keyPressed
関数です。
これは、Processingで決められたキーボード入力のイベントハンドラでした。
システム変数 key
は現在入力されているキーの文字を表します。
ShiftキーやCtrlキーなどの特殊キーは定数 CODED
で表されます。
今回は、それらの特殊キーは無視したいので、if文で条件判定を行います。
そして、以下の部分で buffer
の末尾に入力された文字を追加します。
buffer[bufferIndex++] = key;
さらに、入力された文字が改行コード( \n
)であれば、バッファーに格納された文字列の送信を行います。
バッファー中の有効な文字列の範囲は、0文字目から bufferIndex
文字目なので、ループによってその範囲を1文字ずつサーバーに送信します。
ここで、バッファーの長さが1000と固定長になっていることに注意しましょう。 長すぎる入力を受け取ると、バッファーの長さを超えて例外が発生します。 本来は、適切なエラー処理が必要となりますが、ここでは簡単のために無視しています。