ネットワークサーバのHello World
第12回は少し趣向を変えて、ネットワークサーバのHello Worldです。具体的には、socketを使って所定のTCPのポートへの接続を待ち受け、そのポートへの接続が行われると、接続相手のクライアントに対してHello Worldのメッセージを返すようなプログラムを作成します。
C言語の場合
ネットワークサーバのHello WorldをC言語で記述すると、リスト1のようになります。ここでは、プログラム本体の記述がわかりやすいように、エラー処理はあえて省略しています。
リスト1 C言語の場合(socket_c_lang.c)
#include <sys/types.h> ← socket()を使うために必要
#include <sys/socket.h> ← socket()を使うために必要
#include <netinet/in.h> ← INETドメインソケットを使うため
#include <unistd.h> ← write()のため
#include <string.h> ← memset()のため
int
main()
{
int sv, sv_ac; ← socket()用とaccept()用のファイル記述子
struct sockaddr_in addr; ← INETドメインのソケットアドレス
int optval = 1; ← setsockopt()で使用
sv = socket(PF_INET, SOCK_STREAM, 0); ← INETドメインのTCPソケットを作成
setsockopt(sv, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
↑ ソケットアドレスの再利用を許可
memset(&addr, 0, sizeof addr); ← ソケットアドレスをゼロ初期化
addr.sin_family = AF_INET; ← INETドメインを指定
addr.sin_addr.s_addr = htonl(INADDR_ANY); ← IPアドレスは任意
addr.sin_port = htons(12345); ← ポート番号は12345
bind(sv, (struct sockaddr *)&addr, sizeof addr); ← ソケットアドレスを指定
listen(sv, SOMAXCONN); ← ソケットをLISTEN状態にする
sv_ac = accept(sv, 0, 0); ← 接続を受け付ける
write(sv_ac, "Hello World\n", 12); ← 接続相手にメッセージを出力
return 0;
}
ネットワークサーバに接続テスト
ネットワークサーバの動作を確認するためには、クライアント側のコマンドも必要です。これには、telnetコマンドまたはnc(netcat)コマンドを使います。なお、telnetコマンドなどは使いますが、TELNETプロトコルでログインするわけではありません。
まずは、実行例1のように、サーバー側を実行します。ここで、別の端末ウィンドウに切り替え、そこからクライアント側のコマンドとして実行例2のように実行します。すると、自分自身のホストであるlocalhostの、TCPの12345番ポートに接続が行われます。その直後、サーバからHello Worldのメッセージが返って来て、接続が終了します。
実行例1 ネットワークサーバの実行
$ ./socket_c_lang ← カレントディレクトリの実行ファイルを実行する
← この状態で、別の端末ウィンドウ等から実行例2を実行
$ ← 実行例2を実行後、シェルのプロンプトに戻る
実行例2 ネットワークサーバに接続テスト
$ telnet localhost 12345 ← localhostのTCPの12345番ポートに接続
Trying 127.0.0.1...
Connected to localhost. ← 接続確立
Escape character is '^]'.
Hello World ← サーバプログラムからのメッセージ
Connection closed by foreign host. ← 接続終了
$ ← シェルのプロンプトに戻る
Perlの場合
Perlを使って記述すると、リスト2のようになります。同じく、エラー処理は省略しています。このように、socket、bindなどの関数名や、PF_INETなどの定数ラベルはC言語のものと同じであり、プログラム全体がC言語と似ていることがわかります。
リスト2 Perlの場合(socket_perl)
#!/usr/bin/perl
use Socket; ← Socketモジュールを使用
socket(sv, PF_INET, SOCK_STREAM, 0); ← INETドメインのTCPソケットを作成
setsockopt(sv, SOL_SOCKET, SO_REUSEADDR, 1); ← ソケットアドレスの再利用を許可
bind(sv, sockaddr_in(12345, INADDR_ANY)); ← ポート番号12345、IPアドレス任意
listen(sv, SOMAXCONN); ← ソケットをLISTEN状態にする
accept(sv_ac, sv); ← 接続を受け付ける
print sv_ac "Hello World\n"; ← 接続相手にメッセージを出力
Javaアプリケーションの場合
Javaアプリケーションで記述したサーバプログラムはリスト3のとおりです。このように、ServerSocketクラスを使ってソケットを作成し、accept()したあと、PrintWriterクラスのprint()メソッドを使って文字列を出力します。
リスト3 Javaアプリケーションの場合(SocketJava.java)
import java.io.*; ← PrintWriterを使うため
import java.net.*; ← Socketを使うため
public class SocketJava {
public static void main(String[] args) {
try {
ServerSocket sv = new ServerSocket(12345); ← サーバソケットを作成
Socket sv_ac = sv.accept(); ← 接続を受け付ける
PrintWriter out = new PrintWriter(sv_ac.getOutputStream());
out.print("Hello World\n"); ←↑ 接続相手にメッセージを出力
out.flush(); ← 出力をフラッシュする
} catch (Exception e) {} ← エラー処理は省略
}
}
Pythonの場合
Pythonを使う場合はリスト4のようになります。コーディングは、Perlに類似したものとなっています。
リスト4 Pythonの場合(socket_python)
#!/usr/bin/python
from socket import * ← socketモジュールをインポート
sv = socket(AF_INET, SOCK_STREAM) ← INETドメインのTCPソケットを作成
sv.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) ← ソケットアドレスの再利用を許可
sv.bind(('', 12345)) ← ポート番号12345、IPアドレス任意
sv.listen(SOMAXCONN) ← ソケットをLISTEN状態にする
(sv_ac, cl_addr) = sv.accept() ← 接続を受け付ける
sv_ac.send('Hello World\n') ← 接続相手にメッセージを出力
Rubyの場合
Rubyでは、TCPserverが使えるため、コードの行数が少なくなります(リスト5)。acceptの前のlistenは不要です。
リスト5 Rubyの場合(socket_ruby)
#!/usr/bin/ruby
require 'socket' ← socketライブラリをロード
sv = TCPserver.open('', 12345) ← ポート番号12345のTCPのサーバソケットを作成
sv_ac = sv.accept ← 接続を受け付ける
sv_ac.write("Hello World\n") ← 接続相手にメッセージを出力
Tclの場合
Tcl/Tkのスクリプト部分であるTcl(tclsh)を使うと、リスト6のようになります。まず、接続時に呼び出されるプロシジャを定義し、Tclの組み込みコマンドのsocketの引数で、そのプロシジャとポート番号を指定します。
リスト6 Tclの場合(socket_tcl)
#!/usr/bin/tclsh
proc accept {sv_ac cl_addr cl_port} { ← 接続時に呼び出されるプロシジャを定義
puts $sv_ac {Hello World} ← 接続相手にメッセージを出力
close $sv_ac ← 接続をクローズ
exit 0 ← 1回の接続でサーバを終了する
}
socket -server accept 12345 ← プロシジャ/ポート番号指定でサーバソケットを作成
vwait forever ← イベント(接続)を待つ