ネットワークサーバの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                ← イベント(接続)を待つ