サーバー側を実装するには?
解説
サーバー側を実装する場合、通常、ウェブサーバーや、フレームワーク、ライブラリ等による通信プロトコルの実装を利用して開発を行います。
ここでは、JavaのサーブレットコンテナであるJetty 8 での実装を例に説明を行います。サーバー側のAPIに関しては共通の規約は存在しませんが、イベント駆動モデルで実装がされている場合は、クライアント側とほぼ共通するイベントに対してコールバック関数を定義するという点で、それほど大きな違いはありません。
実装によっては、Jettyのようなイベント駆動モデルの抽象化がされておらず、通信プロトコルの生データに近いレベルを取り扱わなければならない場合がありますが、この記事の範囲を超えますので詳細には触れません。
Jetty 8 での実装では、org.eclipse.jetty.websocket.WebSocketServletクラスを継承するクラスを実装し、サーブレットとして公開することで、比較的容易にWebSocketを使用するウェブアプリケーションを作成することができます。
このサーブレットのdoWebSocketConnectメソッドで、WebSocketインターフェイスを実装したクラスをインスタンス化して返すことで、WebSocketプロトコルでの実際の通信を担当するクラスを指定します。
複数のサブプロトコルが送られてきた場合は、このメソッドがサブプロトコルの数だけ呼ばれますが、nullでない戻り値を返した時点でそのサブプロトコルが選ばれたことになり、以降は候補がまだ残っていても呼ばれません。
public class MyServlet extends WebSocketServlet{
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
if ("protocolA".equals(protocol)){
// WebSocketインターフェイス実装クラスA
return new MySocketA();
} else if ("protocolB".equals(protocol)){
// WebSocketインターフェイス実装クラスB
return new MySocketB();
} else {
return null;
}
}
}
WebSocketインターフェイスには、接続開始と切断のイベントハンドラが、WebSocketインターフェイスを継承した、OnTextMessageインターフェイスやOnBinaryMessageインターフェイスには、メッセージ受信のイベントハンドラが定義されています。
実際のWebSocket通信を担当するクラスでは、必要になるイベントに応じてWebSocketインターフェイス、またはその継承インターフェイスを実装します。
class MySocket
implements WebSocket.OnTextMessage, WebSocket.OnBinaryMessage{
public void onOpen(Connection connection){
// WebSocketインターフェイスで定義
// 接続開始。クライアント側のonopenイベントに対応。
}
public void onClose(int closeCode , String message){
// WebSocketインターフェイスで定義
// 切断。クライアント側のoncloseイベントに対応。
// closeCodeはステータスコード、messageは切断理由。
}
public void onMessage(String data){
// WebSocket.OnTextMessageインターフェイスで定義
// テキストデータを受け取った時に呼ばれる。
}
public void onMessage(byte[] data, int offset, int length){
// WebSocket.OnBinaryMessageインターフェイスで定義
// バイナリーデータを受け取った時に呼ばれる。
}
}
WebSocketインターフェイス実装クラスはクライアントとの間に維持されている接続の数だけインスタンス化されます。クライアントとのそれぞれの接続はonOpenイベントの引数であるConnectionインターフェイス実装クラスが表しています。
Connectionインターフェイスには以下のようなメソッドがあり、これを使用して、クライアントとの接続を制御します。
// テキストデータ送信
void sendMessage(String data) throws IOException
// バイナリーデータ送信
void sendMessage(byte[] data,int offset,int length) throws IOException
// 接続を閉じる
void disconnect()
クライアント側と異なり、プロトコルの詳細に関連したメソッドやイベントもサーバー側には存在しますが、ここでは詳しく触れません。
以上のように、発生するイベントや引数については、ほぼクライアント側APIに準じた考え方で開発を行うことができますが、一つ大きく異なるのは、サーバー側では同時に接続している複数のクライアントを考慮する必要があるという点です。
Jetty 8 の実装では、WebSocketインターフェイス実装クラスは、クライアントとの接続の数だけインスタンス化されるため、特定のクライアントから送られてきたデータをほかのクライアントに送るためには、ほかのクライアントとの接続を表すインスタンスへの参照をなんらかの形で保持し、切断された接続への参照は削除するという接続リストの管理が必要になります。
// socketListはWebSocketインターフェイス実装クラスMyWebSocketのリスト
// MyWebSocketクラスには接続をあらわすmConnection属性を定義しておく
for(MyWebSocket socket : socketList){
try{
// すべてのクライアントに送信
socket.mConnection.sendMessage(data);
} catch (IOException e) {
e.printStackTrace();
}
}
関連項目