プログラマーのすばらしい日を迎えた皆さん、おめでとうございます! 作業コード、自信のあるソケット、そして最も上級ユーザーの皆さんにお願いします!
コンサート代理店の自動化に取り組んでいる間、開発のある段階で通知システムが必要でした。 オートメーションは、私が書いたWebアプリケーションを介してアクセスされます。 そして、それに応じて、インスタント通知がユーザーのブラウザに届くはずです。
このタスクを実装するには、3つのソリューションがあります。
- 「Endless iframe」、
- XMLHttpRequest(別名Ajax)を使用して、
- WebSocketを使用します。
私はすぐに最初の解決策を「一掃」します(理由は説明しませんが、Web開発者は私を理解します)。
2番目のソリューションははるかに人気がありますが、欠点があります。
- ブラウザは毎秒リクエストを送信し、次のものに余分な負荷をかけます。
- サーバー
- ブラウザが動作するOS。
- また、サーバーは常に最新の通知を選択するためにデータベース要求を実行するためです。
- ユーザーのオンラインステータスを追跡するのは困難です(つまり、たとえば、セッションをデータベースに保存し、タイムアウト時にそれぞれを監視する必要があります)。
3番目の解決策は、まさに医師が注文したものです。
それで、WebSocket。
WebSocketの欠点の中で、これまでWebkitブラウザーのみがサポートしているという事実(Google ChromeとApple Safari)だけが重要です。
クライアントとサーバー間で全二重メッセージングの基本的な可能性を備えたWebアプリケーションとして単純なチャットを実装してみましょう。
クライアント側の実装
クライアント側でJavaScriptにWebSocketを実装するのは簡単で、あまり時間をかけません。 リストを見ています。
var socket = new WebSocket("ws://myserver.com:8081/"); socket.onopen = function () { console.log(" "); }; socket.onclose = function () { console.log (" "); }; socket.onmessage = function (event) { console.log (" :", event.data); };
send()メソッドを使用して、サーバーにメッセージを送信できます。
socket.send(messageString);
サーバー側の実装
サーバーにソリューションを実装すると、明らかに複雑になります。 ネットワーク上で、いくつかの実装オプションを見つけることができますが、そのうち最も近いものは次のとおりです。
- JWebSocket 、
- Jetty WebSocket、
- node.js WebSocket。
これらのうち、JWebSocketはWebSocketを操作するための大きなフレームワークであり、同時に信頼性の高いスタンドアロンサーバーでもあるという点で際立っています。 JWebSocketには別の投稿が必要です。 そして、J2EEで最もシンプルなソリューションであるJettyプラットフォームに焦点を当てます。
何がありますか? ダイアグラムを見てみましょう。
私の場合、ポート8080にGlassFishサーブレットコンテナがあります。ブラウザはGlassFish [1]にリクエストを送信し、ブラウザはチャットページ[2]をブラウザに送信します。ブラウザはWebSocket [3]プロトコル経由でポート8081 次に、ブラウザとサーバーの間で全二重データ交換が行われます[4]。
執筆時点で、Jettyの最新バージョンは8.0.1.v20110908です。 ダウンロード、アンパック(Mavenを使用しているすべての開発者に謝罪)、6つのライブラリに興味があるディストリビューションから:
- jetty-continuation-8.0.1.v20110908.jar、
- jetty-http-8.0.1.v20110908.jar、
- jetty-io-8.0.1.v20110908.jar、
- jetty-server-8.0.1.v20110908.jar、
- jetty-util-8.0.1.v20110908.jar、
- jetty-websocket-8.0.1.v20110908.jar。
これらのライブラリをプロジェクトに追加します。
クライアント側に戻って、 ここからダウンロードします (HTMLコードをリストして投稿を煩雑にしたくありません)。 chat.htmlファイルをプロジェクトのWebページに追加します。 デプロイメント記述子(web.xml)で、chat.htmlを「welcome-file」として指定します。
Jettyについて少し説明します。
Jetty Embedded Serverはorg.eclipse.jetty.server.Serverクラスにあります。 そして、コンストラクターには、サーバーアドレスまたは、この場合のようにポート番号のいずれかを含めることができます。
Server jetty = new Server(8081);
次に、必要なハンドラをjettyに追加して実行する必要があります。 Jettyは、それぞれstart()およびstop()パラメーターのないメソッドで開始および停止します。 しかし、ハンドラーは独自に作成し、新しいクラスを作成する必要があります。 WebSocket接続を処理するためのハンドラーを作成するには、org.eclipse.jetty.websocket.WebSocketHandlerから継承する必要があります。
public class ChatWebSocketHandler extends WebSocketHandler {
…
}
WebSocketHandlerクラスには、1つの抽象doWebSocketConnect()メソッドがあります。 実際には、ブラウザーがjettyとの新しい接続を開き、WebSocketオブジェクトを返すときに呼び出されます。
WebSocketクラスも定義する必要があり、org.eclipse.jetty.websocket.WebSocket.OnTextMessageインターフェースから継承します。このインターフェースは、テキストデータと同様に、WebSocketプロトコルを通過するデータで動作します。 org.eclipse.jetty.websocket.WebSocket.OnTextMessageインターフェースには3つのメソッドが含まれています。
- onOpen()-ソケットを開いた後に呼び出されます。
- onClose()-ソケットを閉じる前に呼び出されます。
- onMessage()-クライアントからメッセージが到着したときに呼び出されます。
簡単そうです! ChatWebSocketHandlerのリストを見てみましょう。
import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketHandler; public class ChatWebSocketHandler extends WebSocketHandler { /** * */ private final Set<ChatWebSocket> webSockets = new CopyOnWriteArraySet<ChatWebSocket>(); /** * * @param request * @param protocol ( ws wss) * @return */ @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { // // // throw new Exception(); // , // return new ChatWebSocket(); } private class ChatWebSocket implements WebSocket.OnTextMessage { /** * */ private Connection connection; /** * */ private String userName = null; /** * */ private final Pattern authCmdPattern = Pattern.compile("^\\/auth ([\\S]+).*"); /** * */ private final Pattern getUsersCmdPattern = Pattern.compile("^\\/getUsers.*"); /** * */ private final Pattern helpCmdPattern = Pattern.compile("^\\/help.*"); /** * * @param connection */ @Override public void onOpen(Connection connection) { // ChatWebSocket::connection this.connection = connection; // ChatWebSocketHandler::webSockets webSockets.add(this); } /** * * @param data */ @Override public void onMessage(String data) { // data = data.replaceAll("<", "<").replaceAll(">", ">"); // if (authCmdPattern.matcher(data).matches()) { Matcher matcher = authCmdPattern.matcher(data); matcher.find(); // userName = matcher.group(1); try { // ChatWebSocketHandler::webSockets for (ChatWebSocket webSocket : webSockets) { // , webSocket.connection.sendMessage("inf|" + (webSocket.equals(this) ? " " : (" <b>" + userName + "</b>"))); } } catch (IOException x) { // connection.disconnect(); } // } else if (getUsersCmdPattern.matcher(data).matches()) { String userList = ""; // ChatWebSocketHandler::webSockets for (ChatWebSocket webSocket : webSockets) { userList += webSocket.userName + ", "; } userList = userList.substring(0, userList.length() - 2); try { // connection.sendMessage("inf| : " + userList); } catch (IOException x) { // connection.disconnect(); } // } else if (helpCmdPattern.matcher(data).matches()) { String helpMessage = " " + " Enter.<br />" + " :<br />" + "<ul><li><b>/help</b> - </li>" + "<li><b>/getUsers</b> - </li>" + "<li><b>/auth <i></i></b> - </li></ul>"; try { // connection.sendMessage("inf|" + helpMessage); } catch (IOException x) { // connection.disconnect(); } // } else { try { // if (userName == null) { connection.sendMessage("err| <br />" + " <b>/help</b> "); return; } // ChatWebSocketHandler::webSockets for (ChatWebSocket webSocket : webSockets) { // in // , - out webSocket.connection.sendMessage((webSocket.equals(this) ? "out|" : ("in|" + userName + "|")) + data); } } catch (IOException x) { // connection.disconnect(); } } } /** * * @param closeCode * @param message */ @Override public void onClose(int closeCode, String message) { // ChatWebSocketHandler::webSockets webSockets.remove(this); } } }
そして、ChatWebSocketHandlerに表示されるもの:
- 1プロパティ-ソケットのセット。
- 新しいソケットを作成して返す1つのメソッド。
- このWebSocketを実装する1つのプライベートクラス。
ChatWebSocketクラスでは、onMessage()メソッドに関心があります。 その中で、クライアント部分とサーバー部分の間でデータを交換するためのプロトコルを実装します。 コメントは、その動作の原理を示しています。
交換プロトコルとは何ですか?
サーバーはクライアントからすべてのテキストメッセージを受信します; 3つのコマンドはそれらと区別できます:
- / auth nickname //認証用
- / getUsers //ユーザーのリスト用
- / help //ヘルプ
クライアントは、次のパターンでサーバーからメッセージを受信します。
- inf | information //情報が来ました
- in | nick | message //ユーザーnickからの着信メッセージ
- out | message //メッセージが送信されました
- err | error //エラー情報が到着しました
残りは再びペイントされません-すべてがコメントに記載されています。
それでは、最も重要な瞬間である突堤サーバーの起動に移りましょう。 したがって、タスクがあります。サーブレットコンテナを起動するときにjettyを実行します。 サーブレットコンテナを停止する前に、突堤を停止します。 GlassFishでこれを実装する最も簡単な方法は、ContextListenerを記述することです。 理由を見てみましょうか? 新しいクラスを作成し、javax.servlet.ServletContextListenerインターフェイスから継承します。 リストを参照してください。
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; public class ChatServerServletContextListener implements ServletContextListener { /** * Jetty */ private Server server = null; /** * * @param event */ @Override public void contextInitialized(ServletContextEvent event) { try { // Jetty 8081 this.server = new Server(8081); // ChatWebSocketHandler Jetty ChatWebSocketHandler chatWebSocketHandler = new ChatWebSocketHandler(); // WebSocketHandlerContainer chatWebSocketHandler.setHandler(new DefaultHandler()); // jetty server.setHandler(chatWebSocketHandler); // Jetty server.start(); } catch (Throwable e) { e.printStackTrace(); } } /** * * @param event */ @Override public void contextDestroyed(ServletContextEvent event) { // jetty - if (server != null) { try { // Jetty server.stop(); } catch (Exception e) { e.printStackTrace(); } } } }
javax.servlet.ServletContextListenerインターフェースには、わかりやすい名前の2つのメソッドがあります:contextInitialized()、contextDestroyed()。
あとは、ContextListenerをデプロイメント記述子(web.xml)にプラグインするだけです。 私は彼のリストを与えます:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>chat.html</welcome-file> </welcome-file-list> <listener> <listener-class>ChatServerServletContextListener</listener-class> </listener> </web-app>
これでプロジェクトを開始できます。 ブラウザーで、2つのウィンドウを一度に開き、お楽しみください。 時間に注意してください、メッセージはほぼ瞬時に到着します。 Ajaxでこのような速度を実現することはほとんど不可能です。
おわりに
完全なプロジェクトはここからダウンロードできます
このプロジェクトは、GlassFish 2.x、3.x、およびTomcat 5.0と完全に互換性があります。 プロジェクトはNetbeans 7.0.1で作成されました。 プロジェクト内で、Antにデプロイするant-deploy.xmlを見つけることができます。 繰り返しになりますが、Mavenを使用している開発者には大いに謝ります。
記事の第2部では、WebSocketを使用するときに発生する問題とその解決策について詳しく説明します。
3番目の部分では、Jettyサーバーに対するddos攻撃に対処する方法について説明します。
ご清聴ありがとうございました。 再び幸せな休日を!