Socketの練習

2008/09/17

こんにちは。なんだか金融情勢が危ういことになってきてますね。バンカメとかリーマンとか、名前だけきくとなんとなく楽しい気分になってくるんですが、それどころじゃなさそうです。

さて、サウンドプログラミングをちょっとやってみたのですが、少しお休みしてソケットについて書いてみたいと思います。サウンドプログラミング楽しみにしてた方がもしいたら申し訳ないです。

まず何でこんなことをやろうかと思ったというと、uranodaiさんの作ったiphoneasというライブラリがあります。これは、iPhoneとASをつなぐことができるものでして、すぐれものなんです。それで、ネイティブアプリ版ではなくjavascript版の方でためしてみました。するとiPhoneにタッチしたらASにデータが入ってくるではありませんか!「おお!」とうなってしまいました。ただ、Javascriptからいろいろと経由(たぶんJavaとかhttpサーバとか)してASに届くらしく少々重いです。このところは、ネイティブアプリ版の方が数段早いようです。(たぶん。)なにはともあれ、外部とつながることは感動しました。うーんすごい!

それで、中身をちろちろと見ていたのですが、どうやらSocketクラスというものを使っているようです。そこからSocketクラスをいろいろと調べてみたところ、以下のようなことになりました。

・ソケットを使うことで、サーバと接続を保つことができる。
・つまりサーバー側からも情報を発信することができる。(双方向になる)
・チャットやゲームなどを作ることができる。

などなど、すごいんですが作る方としては、いろいろと面倒みたいですね。iphoneasでもそうなんですが、クロスドメインポリシーにまずひっかかります。これはhttpのものと違って、もっと制限があるみたいです。

Flash Player バージョン 9.0.115.0 以降でソケットが機能しない
セキュリティに関するFlash Player 9の変更点

などなど、よくわかんないんですが、とにかくポート843にしとけばいいんでしょうか?
ここでポートとかでてきたんですが、調べてみたところこんなページが、

ネットワークプログラミングの基礎知識

ここでクライアントとサーバの基本的なネットワークプログラミングが書いてありました。Perlでサンプルプログラムが書いてあったので、試したところ「おお!」という風にまたまた驚いてしまいました。なんかブラウザで普段していることを自分でプログラムしてみるのは新鮮な経験です。

ところで、話はASに戻るんですが、とりあえず簡単な仕組みを使ってサーバとASを通信させてみようと思いました。サーバ側をPerlで、クライアント側をASで作ってみます。
サーバのプログラムはさきほどのところからいただきました。ありがとうございます。ていうか、Perlってこんなことできるなんて知らなかったです。すごいんですね。

Perlが動く環境で、まず
1) server_demo.plを起動
2) SocketSample.swfを起動
3) swfの「接続」ボタンを押す
4) 上のテキストエリアに文字を書く
5) 「送信」ボタンを押す
6) 下のテキストエリアに、サーバからの応答がかえってくる(サーバはただ送られたものを送りかえしているだけです)

こんな感じです。このサンプルプログラムは、クライアントであるswfを複数たちあげても大丈夫なんですが、この場合、サーバと1クライアントという状態で閉じてしまっています。サーバ側で、各クライアントを認識して、1クライアントからメッセージを受け取ったら、全てのクライアントにメッセージを流すというようなことをやりたいのですが、どうすればいいのかまだわからない状態です。
個別のクライアントごとにメッセージを返すことができれば、iPhoneからソケット接続したものをASに返すようにできると思うのですが、どうすればいいんだ!?

デモデータはこちらにまとめておきました。よろしかったらどうぞ

コードです。ASは標準でUTF-8を使っているんで、Perl側もそうしたかったのですが、Windowsのコマンドプロンプトだと文字化けしちゃうので、Shift_JISになっています。コマンドプロンプトをUTF-8対応にする方法もあるようなのですが、うまくいきませんでした。残念。

server_demo.pl

#!H:perlbinperl.exe -w

use strict;
use warnings;

#use utf8;
#binmode STDIN,  ":utf8";
#binmode STDOUT, ":utf8";
use IO::Socket;           # IO::Socket モジュールを使う。

my $port = shift || 843;      # ポート番号を設定
my @ports;
my $num = 0;
my $ref_ports = @ports;

my $listening_socket = IO::Socket::INET->new(LocalPort => $port,
            Listen  => SOMAXCONN,
            Proto   => 'tcp',
            Reuse   => 1,
            );

if ( ! $listening_socket ){
  die "listen できませんでした。 $!n";
}

print "親プロセス($$): ポート $port を見張ります。n";

while (1){
  my $new_socket = $listening_socket->accept();
  $num++;
  push(@ports, $new_socket);

  my $client_sockaddr = $new_socket->peername();
  (my $client_port,my $client_iaddr) = unpack_sockaddr_in($client_sockaddr);
  my $client_hostname = gethostbyaddr($client_iaddr,AF_INET);
  my $client_ip = inet_ntoa($client_iaddr);

  print "接続: $client_hostname($client_ip) ポート $client_portn";

  if (my $pid = fork() ){       # 親プロセス
  print "親プロセス($$): 引続きポート $port を見張ります。n";
  print "クライアントの相手は子プロセス $pid が行います。n";

  $new_socket->close();

  } else {
           # 子プロセス
  $listening_socket->close();
  select($new_socket); $|=1; select(STDOUT);

  while (<$new_socket>){
    print "子プロセス($$): メッセージ $_";
    print $new_socket $_;
  }
  $new_socket->close();
    print "子プロセス($$): 接続が切れました。終了します。n";
  exit;
  }
}

SocketSample.as

package
{
  import fl.controls.Button;
  import fl.controls.Label;
  import fl.controls.TextArea;

  import flash.display.Sprite;
  import flash.errors.IOError;
  import flash.events.Event;
  import flash.events.IOErrorEvent;
  import flash.events.MouseEvent;
  import flash.events.ProgressEvent;
  import flash.events.SecurityErrorEvent;
  import flash.net.Socket;
  import flash.text.TextFieldAutoSize;

  [SWF(width="400",height="300",frameRate="30",backgroundColor="#d4ecd2")]

  public class SocketSample extends Sprite
  {
    private var s:Socket;
    private var num:uint;
    private var response:String;
    private var inputField:TextArea;
    private var callBackField:TextArea;
    private var sendButton:Button;
    private var connectButton:Button;

    public function SocketSample()
    {
      super();
      response = "";
      initializeField();
      initializeButton();
      s = new Socket();
      s.addEventListener(Event.CLOSE, closeHandler);
      s.addEventListener(Event.CONNECT, connectHandler);
      s.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
      s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
      s.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
      sendButton.addEventListener(MouseEvent.CLICK,sendButtonclick);
      connectButton.addEventListener(MouseEvent.CLICK, connectButtonClick);
    }

    private function sendButtonclick(e:MouseEvent):void
    {
      var text:String = inputField.text;
      try {
        s.writeUTFBytes(text + "n");
        s.flush();
      }catch(e:IOError) {
        trace(e);
      }
    }

    private function connectButtonClick(e:MouseEvent):void
    {
      if(s.connected)
      {
        s.close();
        connectButton.label = " 接  続 ";
        sendButton.enabled = false;
      }
      else
      {
        s.connect("localhost",843);
        connectButton.label = " 切  断 ";
        sendButton.enabled = true;
      }
    }

    private function readResponse():void {
      response = s.readUTFBytes(s.bytesAvailable);
    }

    private function closeHandler(event:Event):void {
      var s:String = "closeHandler: " + event;
      trace(s);
    }

    private function connectHandler(event:Event):void {
      var s:String = "connectHandler: " + event;
      trace(s);
    }

    private function ioErrorHandler(event:IOErrorEvent):void {
      var s:String = "ioErrorHandler: " + event;
      trace(s);
    }

    private function securityErrorHandler(event:SecurityErrorEvent):void {
      var s:String = "securityErrorHandler: " + event;
      trace(s);
    }

    private function socketDataHandler(event:ProgressEvent):void {
      var s:String = "socketDataHandler: " + event;
      readResponse();
      trace("callback: " + response);
      callBackField.text = response.toString();
    }

    private function initializeField():void
    {
      var label:Label;
      label = new Label();
      label.text = "クライアントからサーバーへの送信";
      label.autoSize = TextFieldAutoSize.LEFT;
      addChild(label);
      label.move(10, 10);
      inputField = new TextArea();
      inputField.width = stage.stageWidth - 20;
      inputField.height = 80;
      inputField.move(label.x, label.y + label.height);
      addChild(inputField);

      label = new Label();
      label.text = "サーバからクライアントへの返信";
      label.autoSize = TextFieldAutoSize.LEFT;
      addChild(label);
      label.move(inputField.x, inputField.y + inputField.height + 10);
      callBackField = new TextArea()
      callBackField.width = inputField.width;
      callBackField.height = inputField.height;
      callBackField.move(label.x, label.y + label.height);
      addChild(callBackField);
    }

    private function initializeButton():void
    {
      sendButton = new Button();
      sendButton.width = 100;
      sendButton.height = 20;
      sendButton.label = " 送  信 ";
      addChild(sendButton);
      sendButton.move(220, callBackField.y + callBackField.height + 20);
      sendButton.enabled = false;

      connectButton = new Button();
      connectButton.width = 100;
      connectButton.height = 20;
      connectButton.label = " 接  続 ";
      addChild(connectButton);
      connectButton.move(70, callBackField.y + callBackField.height + 20);
    }
  }
}

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る