IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Web development  >

ソケット・ベースの RIA 技術を使ってリアルタイムのサーバー・プッシュを Ajax アプリケーションに実装する

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

ダウンロード

原文はこちら

原文はこちら


レベル: 初級

Sandeep Malik, Tech Lead, IBM

2009年 9月 22日

新しい種類の高度な UI (User Interface) アプリケーションでは、サーバー・サイドで少しでも変更があった場合に、即座にクライアントに通知することができる「サーバー・プッシュ」機能が何らかの形で必要です。残念ながら HTTP の仕様では、サーバー・サイドから開始される通信に関する記述はありません。そのため、サーバー・プッシュは従来、クライアント・サイドによるポーリングによって実装されてきましたが、この手法では不要なトラフィックが大量に発生し、またアプリケーションを最適化できない傾向があります。幸いなことに、いくつかの RIA (Rich Internet Application) 技術を利用すると、専用のソケット・チャネルを開くことができ、バックエンド・サーバーでは Ajax (Asynchronous JavaScript and XML) アプリケーションがその API を利用してサーバー・プッシュを実装できるようになります。この記事では、この手法を読者のみなさんが十分理解して自ら実装を始められるように、さまざまな面からこの手法を掘り下げていきます。

はじめに

Ajax 技術が登場してからかなりの時間が経ちましたが、今や Ajax 開発の勢いは実に本格的なものになってきました。Ajax を想定して設計される Web サイトがますます増え、開発者は Ajax を限界まで使いこなそうとし始めています。また、ソーシャル・ネットワーキングの広まりや、共同レポート作成などが行われるようになった結果、まったく新たな一連の要求が生まれてきています。ユーザーは、注目しているアクティビティーが他のユーザーによって変更された場合に、通知を受けたいと望んでいます。あるいは、ある Web サイトが株価などの動的なデータを表示する場合には、すべてのユーザーに対して株価の変動を瞬時に通知する必要があります。

こうした状況に対処するには、「サーバー・プッシュ」という一般的な課題に取り組むことになります。通常、サーバーは中央にあって、発生するすべての変更に関する通知を最初に受信します。するとサーバーは、接続されているすべてのクライアントに対して、その変更に関する通知を行います。残念ながら、クライアント・サーバー間の通信プロトコルのデファクト・スタンダードは HTTP です。HTTP はステートレスであり、ある意味で片方向のプロトコルであるため、HTTP での通信はすべてクライアントで開始され、サーバーで終端されなければなりません。ところが、この記事で説明しようとしているシナリオでは、その逆を行う必要があります。サーバー・プッシュでは、サーバーが通信を開始してクライアントにデータを送信しますが、HTTP プロトコルにはこのシナリオが用意されていないため、Web サイトのアプリケーションの開発者達はさまざまな工夫によって、この問題に対処しています。例えばポーリングによる手法では、クライアントが一定の (あるいは構成可能な) 間隔でサーバーへの問い合わせを行い、何か新しい更新があるかどうかを知ろうとします。ほとんどの場合、サーバーには何も更新がないため、これらのポーリングは無駄になります。この手法には問題がないわけではなく、下記の 2 つの問題を伴います。

  1. この手法はネットワーク・リソースを大量に浪費します。ポーリング・リクエストごとに、通常は 1つの TCP ソケット接続が作成されます (ただし HTTP 1.1 で keepAlive の設定を有効にしている場合を除きます。keepAlive を有効にしている場合には、既に確立されているソケット接続が再利用されます)。ソケット接続そのものがリソースを浪費する上に、リクエストごとに何らかのデータがネットワーク上に送信されます。リクエストの結果サーバー上で何も更新が見つからない場合には、そのデータ転送は無駄になります。クライアント・マシンで他のアプリケーションが実行されている場合には、そうしたポーリングによって、データ転送に利用できる帯域幅が減少します。
  2. たとえリクエストが成功し、実際に更新内容がクライアントに返送された場合であっても、ポーリングの頻度によっては、その返送はリアルタイムではないかもしれません。例えば、20 秒ごとにポーリングを行うように設定してあるとし、サーバーからリクエストに対する応答が返送された直後に更新が発生したとします。この場合、20 秒後に次のリクエストが送信されるまで、その更新内容はクライアントには反映されません。従って、サーバー上に更新が存在していて、いつでもクライアントがその更新を取得できる状態にであっても、実際にその更新が取得されて反映されるまでには、しばらく待つ必要があります。こうした状況は、可能な限りリアルタイムに近い状態で実行する必要のあるアプリケーションにとっては受け入れられません。

この 2 つの問題を考えると、サーバー・サイドでの重要な更新にリアルタイムで対応することを要求するエンタープライズ・アプリケーションに対しては、ポーリングは適切な手法ではないかもしれません。この記事では、ポーリングに代わりうる、さまざまな手法について説明します。それぞれの手法には、使用に適した状況があります。ここではそうした状況について説明し、またリアルタイムのサーバー・プッシュが必要な一連の UI についても説明します。




上に戻る


Ajax アプリケーションでサーバーからの情報を更新する手法

サーバーからの情報を更新するために、擬似的なサーバー・プッシュとして使われる一般的な手法を少し詳細に調べてみましょう。

短い間隔でのポーリング

短い間隔でのポーリング、(すなわち頻繁なポーリング) は、この記事の最初に説明した手法です。この手法は次のような場合に最も効果的です。

  1. 帯域幅が十分な場合
  2. 統計的にほとんどの場合にリクエストによって更新が見つかる場合。例えば株式市場のデータには、通常は必ず更新があります。
  3. HTTP 1.1 プロトコルが使われている場合。この場合は keepAlive=true と設定することで同じソケット接続が維持され、再利用されます。

ロング・ポーリング

サーバーから更新データを取得する方法として、ロング・ポーリングも使われます。ロング・ポーリングの考え方としては、クライアントが接続を確立し、サーバーが (リクエスト・スレッドをある条件で待機させることで) その接続を保持します。サーバーは、データが用意できると、保持された接続を使ってそのデータを送信し、それから接続を閉じます。クライアントは更新を受信すると即座に接続を再度確立し、サーバーはこのプロセスを再度繰り返します。こうすることによって、クライアントとサーバーは常に 1 つのリクエストを処理待ちの状態にすることができ、ほとんどリアルタイムの通信を実現することができます。ただしロング・ポーリングには次のような欠点があります。

  1. ブラウザーは通常、デフォルトでサーバーごとに 2 つの接続を許可します。しかしロング・ポーリングでは、1つの接続が常にビジー状態になります。従って、UI にはユーザー・リクエストを処理するための接続が 1 つ (つまり半分の処理能力) しかありません。これは一部の操作でパフォーマンスの遅延を引き起こす可能性があります。
  2. HTTP 接続を開き、閉じる操作が相変わらず必要なため、接続を保持しないモード (keepAlive=false) の場合にはリソースを大量に消費する可能性があります。
  3. ほとんどリアルタイムで動作しますが、正確にはリアルタイムではありません。(もちろん、どのような手法を使用した場合にもネットワーク遅延などの外部要因が必ず存在し、それらを制御することはできません。)

ストリーミング・チャネル

ストリーミング・チャネルはロング・ポーリングとほとんど同じであり、違いはサーバーがレスポンス・ストリームを閉じない点です。サーバーは意図的にレスポンス・ストリームを開いた状態に保ち、さらにデータが送信されてくるものとしてブラウザーを錯覚させます。しかし、ストリーミング・チャネルには特有の欠点があります。

  1. 最大の問題はフラッシングです。従来、Web サーバーはレスポンス・データをキャッシュに入れ、十分なバイト数またはチャンクが受信されるまでレスポンス・データを送信しません。ストリーミング・チャネルを使用する場合、たとえアプリケーションがデータをフラッシュしたとしても、そのデータはサーバーによって最適化のためにバッファーに入れられたままになる可能性があります。さらに悪いことに、クライアントとサーバーとの間にプロキシー・サーバーがある場合には、そのプロキシーは後でデータをすぐに利用できるよう、データをバッファーに入れる可能性があります。
  2. 一部のブラウザーの実装では、長期間ソケットが開いたままになっていると判断すると、そのソケットを閉じてしまう場合があります。そうした場合には、チャネルを再度確立しなければなりません。

一般的に、1 番目の問題に対処するには、無意味なペイロードをすべてのストリーム・レスポンスに追加し、レスポンス・データによってバッファーが一杯になるようにします。2 番目の問題に対処するには、「キープ・アライブ」メッセージや「同期」メッセージを一定間隔で送信し、遅い速度でデータが送信されてくるものとしてブラウザーを錯覚させます。

これらの手法は、いずれもそれぞれが適する使用状況があり、インターネット上の特定のソリューションで使用されています。しかし、これらはすべて、スケーラビリティーに欠けるという同じ問題を抱えています。通常、リクエストを保持するためには、そのリクエストを処理するスレッドを保持する必要があります。現在のほとんどすべてのアプリケーション・サーバーはブロッキング I/O を実行するからです。たとえブロッキング I/O を実行しない場合であっても、J2EE (Java™ 2 Platform, Enterprise Edition) には、HTTP リクエストとレスポンスに対して非ブロッキング I/Oを実行するための標準が何も用意されていません。(Servlet 3.0 API は Comet サーブレットを含んでいるため、この問題は解決されているはずです。)

現時点で必要なものは、背後に NIO (非ブロッキング I/O) サーバーが置かれ、その NIO サーバーにクライアント・アプリケーションが接続されるというメカニズムです。そうしたソケットは純粋に TCP のバイナリー・ソケットであるため、以下の内容が実現されます。

  1. サーバー・サイドが NIO であるため、スケーラビリティーを高めることができます。
  2. アプリケーションがソケットを直接制御するため、レスポンスがバッファリングされてしまう問題がありません。

ただし、この手法には以下の 4 つの欠点があることに注意する必要があります。

  1. これはバイナリーの TCP ソケットであるため、アプリケーションは HTTPS レイヤーによる SSL のセキュリティーを利用することができません。そのため、データのセキュリティーを必要とするアプリケーションは独自の暗号化メカニズムを用意する必要があります。
  2. 一般的に、サーバーのソケットは 80 とは異なるポートで実行されます。そのため、ポート 80 からのトラフィック以外を許可しないファイアーウォールで問題が起こる可能性があります。従って何らかの方法でポートを構成する必要があります。
  3. Ajax クライアントが実際にバックエンドとの間で TCP ソケット接続を開くことはできません。
  4. たとえ Ajax クライアントがソケット接続を開けたとしても、Ajax は XML または JSON のテキスト・ベースのフォーマットであるため、Ajax クライアントがバイナリー・コンテンツを理解することはできません。

この記事では 3 番目と 4 番目の問題に、実際にどのように対処するかを重点的に説明します。セキュリティーとファイアーウォールの問題を処理できれば、それ以外の問題には対処可能であり、その成果は非常に大きなものになります。

(ネットワーク遅延などの外部要因を別として) 可能な限りリアルタイムに近いサーバー・プッシュ動作をアプリケーションで実現できると、同時に接続できるクライアント数に関して非常にスケーラブルなソリューションを得ることができます。

では、上記の 3 番目と 4 番目の問題に対処する方法を探ることにしましょう。




上に戻る


ソケット・ベースの RIA 技術

Ajax で実際に 3 番目と 4 番目の問題を解決することはできません。そのため、他の RIA 技術を使用するソリューションが必要です。Ajax アプリケーションともインターフェースを取ることが可能なソケット API を提供する RIA 技術は 2 つあり、それが Adobe Flex と OpenLaszlo です。この記事では、この 2 つの技術を完全に紹介することはできませんが (詳細は「参考文献」を参照)、これらの技術には以下の 2 つの特徴があります。

  1. どちらもバックエンドとの間で TCP バイナリー・ソケットを開くことができます。
  2. どちらも、同じブラウザー・ウィンドウで実行している Ajax アプリケーション (基本的に JavaScript) と非常にうまくやり取りすることができます。

ただし、これでは問題の一部にしか対処することができません。ソケットを開くことができ、確かにそれらのソケットを Ajax アプリケーションで使うことができますが、Ajax アプリケーションが純粋なバイナリー・データを処理することは相変わらずできません。この問題についてはどうすればよいのでしょう。実は、この 2 つの技術はどちらも、XMLSocket というバイナリー TCP ソケットのバリエーションを提供しており、この XMLSocket を使って純粋な XML データを送受信することができます。これこそが、まさに必要なものです。この 2 つの技術によってサーバーとのソケットを開くことができ、XML データを送受信できるなら、それですべてが解決します。それをフルに活用することで、Ajax アプリケーションはリアルタイムのサーバー・プッシュ機能を真似ることができます。では、実際にどうすればよいのかを見てみましょう。

Ajax によるサーバー・プッシュを実装する

ここではこの手法を、Adobe Flex と OpenLaszlo という 2 つの異なるツールを使って説明します。何よりもまず、接続を受け付け、その接続をキャッシュすることができるバックエンド・サーバーを作成する必要があります。この記事の本題から外れ過ぎないようにするために、サーバーをブロッキング I/O ベースのものにします。

ここで作成するサーバー・ソケットは、定義済みのアドレスで接続を受け付ける必要があります。


リスト 1. サーバー・ソケットを作成する
public class SimpleServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress("localhost",20340));
        Socket socket = serverSocket.accept();
    }
}

ここでは、サーバー・ソケットをローカルホストのポート 20340 にバインドしています。クライアントがこのサーバー・ソケットに接続すると、その接続を表すソケットが出力されます。すると Flex クライアントは、クライアント自身のセキュリティー・モデルを構成するポリシー・ファイルを要求しますが、このポリシー・ファイルは通常、リスト 2 のようになっています。


リスト 2. Flex クライアントのポリシー・ファイル
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM 
    "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy> 
<allow-access-from domain="*" to-ports="20340"/> 
</cross-domain-policy>         

このポリシー・ファイルを要求するためのリクエストは、接続すると即座に Flex クライアントから送信されます。このリクエストには <policy-file-request/> という 1 つの XML タグのみが含まれています。サーバーは、この要求されたポリシー・ファイルをレスポンスに入れて返す必要があります。それを行うコードがリスト 3 です。


リスト 3. ポリシー・ファイルのレスポンスを送信する
public static void main(String[] args) throws IOException {
   ServerSocket serverSocket = new ServerSocket();
   serverSocket.bind(new InetSocketAddress("localhost", 20340));
   Socket socket = serverSocket.accept();
   String POLICY_REQUEST = "<policy-file-request/>\u0000";
   String POLICY_FILE = "<?xml version=\"1.0\"?>\n" +
      "<!DOCTYPE cross-domain-policy SYSTEM 
         \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n" +
      "<cross-domain-policy> \n" +
      " <allow-access-from domain=\"*\" to-ports=\"20340\"/> \n" +
      "</cross-domain-policy>";
   byte[] b = new byte[POLICY_REQUEST.length()];
   DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
   dataInputStream.readFully(b);
   String request = new String(b);
   if (POLICY_REQUEST.equals(request)) {
       DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
       dataOutputStream.write(POLICY_FILE.getBytes());
       dataOutputStream.flush();
       dataOutputStream.close();
   } else throw new IllegalArgumentException("unknown request format " + request);
 }         

これによってクライアントとの接続が確立されます。するとサーバーはクライアントとの間で「ハンドシェーク」と似たようなプロトコルを開始することができます。サーバーは通常、そのプロトコルの中で一意の ID をクライアントに割り当て、その ID をクライアントに送信します。その後は、サーバーはその ID に対してソケットをキャッシュに入れることができます。そしてキャッシュした後は、サーバーが何らかのデータをクライアントにプッシュする必要がある場合には、サーバーは ID によってソケットを見つけ、その ID の出力ストリームを使います。幸いなことに、OpenLaszlo も同じポリシー・ファイル・ベースのメカニズムを使っているため、どちらのツールに対しても同じサーバー・コードを使うことができます。

では、Flex ソケットを作成して Ajax アプリケーションと接続する方法を調べてみましょう。

Adobe Flex を使ってクライアント・ソケットを開く

リスト 4 のコードは Flex からクライアント・ソケットを開く方法を示しています。


リスト 4. Flex からクライアントを開く
var socket : XMLSocket = new XMLSocket();
// register events:
socket.addEventListener(Event.CLOSE, closehandler);
socket.addEventListener(Event.CONNECT, connectHandler);
socket.addEventListener(Event.OPEN, openHandler);
socket.addEventListener(ProgressEvent.SOCKET_DATA, readHandler);
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
socket.connect("localhost",20340);         

Flex は socket.connect() を呼び出した後、ポリシー・ファイルを要求するリクエストをサーバーに送信し、XML レスポンスを待ちます。XML レスポンスを受信できると接続が確立されたことになり、このソケットを使ってサーバーからデータをプッシュすることができます。

パズルの最後のピースとして、Flex がアプリケーションとしての Ajax をどのように呼び出すかを調べてみましょう。そのためには、サーバー・サイドのメッセージを処理する汎用の JavaScript 関数を作成します。このメソッドを handleServerMessageReceived(message) と呼ぶことにしましょう。このメソッドはサーバーから XML コードを取得しますが、このメソッドがメッセージをどのように処理するかはアプリケーションに依存します。リスト 5 のコードは Flex が JavaScript 関数を呼び出す方法を示しています。このコードは、サーバーの XML メッセージが受信されると呼び出される readHandler メソッドのコードです。


リスト 5. handleServerMessageReceived(message) を使用する readhHandler のコード
public  function readHandler(e :  DataEvent) : void {
  var message   : XML = e.data as XML;
  ExternalInterface.call("handleServerMessageReceived", message);
}         

これで終わりです。たったこれだけの単純な作業です。これで XML のソケット接続を作成することができました。サーバーからのデータを受信したら、それらのメッセージを処理することができる何らかの汎用ハンドラー関数を Ajax アプリケーションの中で呼び出せばよいのです。完全なソース・コードはダウンロードで入手することができます (「ダウンロード」を参照)。

では、同じことを OpenLaszlo ではどのように実現しているかを調べてみましょう。

OpenLaszlo を使ってクライアント・ソケットを開く

OpenLaszlo アプリケーションは Flash プラットフォームと DHTML プラットフォームの両方をターゲットにしているため、OpenLaszlo アプリケーションの API やスクリプト言語は Flash や JavaScript の API やスクリプト言語と少し似ています。これは主に、RIA に代わる手段として OpenLaszlo に移行しようとする Web 開発者への便宜のためです。

OpenLaszlo には、バックエンドとの永続的な接続を作成するために 2 つの方法が用意されています。1 つの方法は、Lz (Laszlo の省略形) 標準ライブラリーの一部として提供されている ConnectionManager API を使う方法です。ただし、OpenLaszlo のドキュメントには明確に以下の記述があります。

警告: この機能は暫定的なものです。この機能は特定の処理能力がある状況下で動作します。この機能を開発に使用しても構いませんが、実際のデプロイメントへの使用は (あまり処理能力を必要とせず、ミッション・クリティカルではないデプロイメントを除いて) 推奨しません。このバージョンの永続的な接続を使用するアプリケーションの堅牢さについて質問がある場合には、直接 Laszlo Systems にお問い合わせください。」

この技術は、現状ではおそらく実験的なものであり、OpenLaszlo の今後のバージョンではもっと実証されたものになるでしょう。

2 番目の方法は Flex の場合と似ており、手動で XML のソケット接続を開き、READ_DATA イベントが発生するのを待ちます。リスト 6 はこの方法を示しています。


リスト 6. XMLSocket クラスを定義する
<class name="ClientSocket" extends="node">
	<attribute name="host" />
	<attribute name="port" />
	<XMLSocket name='xml_socket'/>
	<handler name="oninit">
		// connect the socket here:
		xml_socket.connect(host,port);
	</handler>
	<handler name='onData' reference='xml_socket' args='messageXML'>
 <![CDATA[
		ExternalInterface.call(‘handleServerMessageReceived',messageXML);
	]]>
	</method>	
</class>         

(簡単のため、他のハンドラーは省略してあります。完全なコード・リストは、この記事の「ダウンロード」セクションにあります。)

これで終わりです。たったこれだけで、ソケット・オブジェクトの作成、そしてソケット・オブジェクトへの接続ができました。このコード・リストでは、ClientSocket という新しいクラスを作成し、その後で「xml_socket」という XML ソケット・オブジェクトを宣言しています。このソケット・オブジェクトは、サーバーからデータを読み取ると必ず onData イベントを起動し、onData イベントは onData 用に定義されているハンドラーによって処理されます。それ以降のフローは Flex クライアントの場合と同じです。

ClientSocket オブジェクトを作成するためには、このオブジェクトを下記のように宣言すればよいだけです。


リスト 7. ClientSocket を宣言する
<canvas>
	<ClientSocket id='serverPushSocket' host='localhost' port='20340'/>
</canvas>         

ClientSocket に対して init イベントが起動されると、init イベントは指定されたホストとポートでバックエンドへの接続を試みます。(リスト 6 の oninit ハンドラーを参照。)




上に戻る


まとめ

この記事ではサーバー・プッシュを真似た手法として、純粋なポーリングからリアルタイムのサーバー・プッシュまで、さまざまな手法を説明し、それぞれの手法の長所と短所を調べました。そして最後に、サーバーのスケーラビリティーとリアルタイムのサーバー・プッシュ動作という点で最高の結果が得られる 1 つの手法に焦点を当てました。

サーバー・プッシュはすべてのアプリケーションに必要なわけではありません。実際には、ほとんどのアプリケーションは通常のリクエストとレスポンスによるシナリオで非常に適切に動作します。それ以外のアプリケーションも、ポーリングやポーリングに似た手法を使うことで適切に動作します。この記事で説明した手法がどうしても必要となるアプリケーションは、サーバーの更新が必ず発生し、即座にクライアントに通知する必要がある、本当に重たいアプリケーションのみです。ただし繰り返しますが、この手法にも相変わらず下記の 2 つの大きな欠点があります。

  1. HTTPS によってデータを転送する必要がある場合、クライアント・ソケットは SSL による暗号化機能を利用することができません。
  2. ファイアーウォールは、クライアント・ソケットをサーバーに接続するためのポートとして (80 以外の) 非標準的なポートを許可する必要があります。

ただし、市場に数多くあるオープンソースのライブラリーを使用すれば、カスタムの暗号化ルーチンを容易に作成することができます。同様に、ファイアーウォールの構成もそれほど面倒ではなく、リアルタイムのサーバー・プッシュという強力な機能を得るための対価としては僅かなものにすぎません。





上に戻る


ダウンロード

内容ファイル名サイズダウンロード形式
Source codesamples.zip9KBHTTP
ダウンロード形式について


参考文献



著者について

Sandeep Malik photo

Sandeep Malik は IBM Cognos NOW! の技術リーダーであり、インドの Pune にある India Software Labs に勤務しています。彼は Cognos NOW! のための新世代 UI の設計やアーキテクチャーのフェーズに、また OBI (Operation Business Intelligence) エンジンのメモリーやリアルタイム・ストリーミングの業務に従事してきています。彼は本格的なグラフィックスやチャート・ライブラリー、クライアントサイド・ストリーミング、ノンブロッキング I/O、そして非同期システム全般を幅広く経験してきています。IBM に入社する前にはネットワーク・セキュリティーの分野で働いており、ネットワーク・トラフィックの分布を変化させるパターン (ボットネット、ワーム・スキャンなど) の分析をしていました。また以前勤務していた会社の 1 つでサーブレット 2.4 仕様の実装にも従事していました。彼は時間のあるときにはクリケット観戦を楽しみ、生まれ変わったら自分自身もクリケット選手になりたいと思っています。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ