この記事では、DartでWebソケットを操作する方法を示すために、Dartで簡単なWebSocketチャットを作成する方法を説明します。 アプリケーションコードはgithubで入手でき、その操作の例はhttp://simplechat.rudart.inにあります。
アプリケーションは、サーバーとクライアントの2つの部分で構成されます。 サーバー部分を詳細に分析し、クライアントからは、接続の処理に責任を負うもののみを検討します。
アプリケーションの要件は非常に単純です-ユーザーからすべての、または選択したチャット参加者のみにメッセージを送信します。
アプリケーション設定
すべてのアプリケーション設定と定数は、
common/lib/common.dart
ファイルに保存されます。 このファイルには、
simplechat.common
ライブラリーの定義が含まれています。
library simplechat.common; const String ADDRESS = 'simplechat.rudart.in'; const int PORT = 9224; const String SYSTEM_CLIENT = 'Simple Chat';
ファイル自体をパッケージとして接続します。 相対パスを使用すると、アプリケーションを
pub build
とき(
pub build
)、
pub
からエラーを取得できます 。 例外:ビルド環境の外にあるため、{file}を読み取ることができません 。
マシンのどこかにあるパッケージを接続するために、 pub pathdependencyを使用します 。 これを行うには、
pubspec.yaml
ファイルの
dependencies
セクションにパッケージ
pubspec.yaml
定義を
pubspec.yaml
だけです。
dependencies: simplechat.common: path: ./common
pubspec.yaml
ファイルの内容全体を提供するわけではありません(ただし、 githubで確認できます)。 また、
pubspec.yaml
ファイルを
common
ディレクトリに追加する必要があります。ここで、パッケージの名前を単に指定します。
name: simplechat.common
サーバー
サーバーファイルは
bin
フォルダーにあります。
main.dart
ファイルにはサーバーへのエントリポイント
main.dart
、
main.dart
ファイルにはサーバー
server.dart
クラスが
server.dart
ます。
main.dart
ファイルの内容を見てみましょう。
一般的なサーバー操作スキーム
サーバーが一般的にどのように機能するかについて話しましょう。 サーバーで最初に行うことは、サーバーを起動することです。 起動時に、ポート
9224
リッスンを開始します。
新しいユーザーがこのポートにリクエストを送信すると、サーバーはそのポート用のWebSocket接続を開き、名前を生成し、名前と接続を開いている接続でハッシュに保存します。 その後、クライアントはこの接続でメッセージを送信できるようになります。 サーバーは、これらのメッセージを他のユーザーに送信できるだけでなく、クライアントの接続および切断に関する通知を送信できます。
ユーザーが接続を閉じると、サーバーはアクティブな接続のハッシュからその接続を削除します。
サーバーエントリポイント
bin/main.dart
最初に、
simplechat.bin
ライブラリーであると定義します。 サーバーが機能するためには、
dart:async
、
dart:convert
、
dart:io
ライブラリ、
route
パッケージ(
pub
を介して入力)、およびアプリケーション設定ファイルを接続する必要があります。 また、
bin/main.dart
には、サーバーのメインコードを含むファイル
bin/main.dart
を含めます(これについては後で検討します)。
main()
関数で、サーバーのインスタンスを作成して実行します。
library simplechat.bin; import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:route/server.dart' show Router; import 'package:simplechat.common/common.dart'; part 'server.dart'; /** * Entry point */ main() { Server server = new Server(ADDRESS, PORT); server.bind(); }
サーバー基本クラス、ポート盗聴
以下は基本的なサーバーコードで、単に目的のポートにバインドされます。
part of simplechat.bin; /** * Class [Server] implement simple chat server */ class Server { /** * Server bind port */ int port; /** * Server address */ var address; /** * Current server */ HttpServer _server; /** * Router */ Router _router; /** * Active connections */ Map<String, WebSocket> connections = new Map<String, WebSocket>(); int generalCount = 1; /** * Server constructor * param [address] * param [port] */ Server([ this.address = '127.0.0.1', this.port = 9224 ]); /** * Bind the server */ bind() { HttpServer.bind(address, port).then(connectServer); } /** * Callback when server is ready */ connectServer(server) { print('Chat server is running on "$address:$port"'); _server = server; bindRouter(); } }
connectServer()
関数の最後で、ルーターを構成する関数は
bindRouter()
ます。これについては以下で説明します。
ルーターの構成とWebSocket接続の作成
ルーターを構成するには、
bindRouter()
関数を作成します。
WebSocketTransformer
を使用して着信ストリームを
/
変更し、
createWs()
関数でリッスンします。
/** * Bind routes */ bindRouter() { _router = new Router(_server); _router.serve('/') .transform(new WebSocketTransformer()) .listen(this.createWs); } createWs(WebSocket webSocket) { String connectionName = 'user_$generalCount'; ++generalCount; connections.putIfAbsent(connectionName, () => webSocket); }
createWs()
関数では、
createWs()
user_{counter}
スキームに従って接続の名前を生成し、この接続を
connections
保存し
connections
。
サーバーからのメッセージ構造とメッセージ作成機能
サーバーは、次のキーを持つメッセージをMapオブジェクト(またはjsonでの表現)として送信します。
- from-メッセージの送信元。
- message-メッセージテキスト。
- オンライン - オンラインのユーザー数。
このようなメッセージを作成する関数は次のとおりです。
/** * Build message */ String buildMessage(String from, String message) { Map<String, String> data = { 'from': from, 'message': message, 'online': connections.length }; return JSON.encode(data); }
サーバーからメッセージを送信する
クライアントにメッセージを送信するには、 WebSocketクラスのadd()メソッドを使用する必要があります。 以下は、ユーザーにメッセージを送信する関数です。
/** * Sending message */ void send(String to, String message) { connections[to].add(message); }
サーバーは、すべてのアクティブなクライアントにユーザーの接続または切断に関する通知を送信できます。 この機能を見てみましょう。
notifyAbout(String connectionName, String message)
関数
notifyAbout(String connectionName, String message)
名とメッセージ(接続または切断に関する
notifyAbout(String connectionName, String message)
ます。 この関数は、この通知の送信者に加えて、すべてのアクティブなクライアントに通知します。 つまり user_3が参加した場合、それ以外のすべてのユーザーに通知が送信されます。 特定の条件でクライアントをフィルタリングするには(この場合、現在のクライアントと一致しないすべてのクライアントの名前を取得する必要があります) 、 Iterable抽象クラスのwhere()メソッドを使用します。
/** * Notify users */ notifyAbout(String connectionName, String message) { String jdata = buildMessage(SYSTEM_CLIENT, message); connections.keys .where((String name) => name != connectionName) .forEach((String name) { send(name, jdata); }); }
また、新しいユーザーに参加した後、彼を歓迎します。
/** * Sending welcome message to new client */ void sendWelcome(String connectionName) { String jdata = buildMessage(SYSTEM_CLIENT, 'Welcome to chat!'); send(connectionName, jdata); }
ユーザーからの着信メッセージを処理して、すべての(または指定された)チャット参加者に送信する機能を見てみましょう。
sendMessage(String from, String message)
関数は、送信者の名前とそのメッセージを受け入れます。 メッセージ本文(
message
)がマスク
@{user_name}
によって受信者の名前を指定している場合、メッセージは受信者にのみ配信されます。
sendMessage
関数
sendMessage
見てみましょう:
/** * Sending message to clients */ sendMessage(String from, String message) { String jdata = buildMessage(from, message); // search users that the message is intended RegExp usersReg = new RegExp(r"@([\w|\d]+)"); Iterable<Match> users = usersReg.allMatches(message); // if users found - send message only them if (users.isNotEmpty) { users.forEach((Match match) { String user = match.group(0).replaceFirst('@', ''); if (connections.containsKey(user)) { send(user, jdata); } }); send(from, jdata); } else { connections.forEach((username, conn) { conn.add(jdata); }); } }
ユーザーが接続を閉じると、アクティブな接続のリストから削除する必要があります。
closeConnection(String connectionName)
関数は、閉じられた接続の名前を受け入れ、接続リストから削除します。
/** * Close user connections */ closeConnection(String connectionName) { if (connections.containsKey(connectionName)) { connections.remove(connectionName); } }
接続リスナーに機能を追加する
私たちが今持っているすべてを要約します。
createWs
関数
createWs
ユーザー接続を
createWs
ます。
send
指定されたユーザーにメッセージを送信します。
sendWelcome
新しいユーザーに挨拶メッセージを送信します。
notifyAbout
開始者のアクション(接続/切断)をチャット参加者(開始者を除く)に通知します。
sendMessage
すべてまたは指定されたユーザーのみにメッセージを送信します。
createWs
関数を変更して、
createWs
すべてを使用できるようにしましょう。 リストへの接続の追加で最後に停止したとき。 その後、他のすべてのチャット参加者に新しいユーザーについて通知し、新しいユーザーに挨拶メッセージを送信する必要があります。
次に、ユーザーからのメッセージへのユーザーのWebSocket接続をリッスンし、参加者にメッセージを送信する必要があります。 また、websocket接続を閉じるハンドラーを追加します。このハンドラーでは、リストから削除し、すべての参加者に切断するよう通知します。
createWs(WebSocket webSocket) { String connectionName = 'user_$generalCount'; ++generalCount; connections.putIfAbsent(connectionName, () => webSocket); // notifyAbout(connectionName, '$connectionName joined the chat'); // sendWelcome(connectionName); webSocket .map((string) => JSON.decode(string)) .listen((json) { sendMessage(connectionName, json['message']); }).onDone(() { closeConnection(connectionName); notifyAbout(connectionName, '$connectionName logs out chat'); }); }
以上で、簡単なサーバーの準備が整いました。 それでは、クライアント側に移りましょう。
お客様
ここでは、クライアント部分のレイアウト とメッセージの表示 については説明しません。 このパートでは、サーバーへのWebSocket接続を開き、メッセージを送受信する方法についてのみ説明します。
クライアントアプリケーションのエントリポイント
クライアントアプリケーションのエントリポイントは、
web/dart/index.dart
ファイルにあります。 その内容を見てみましょう:
library simplechat.client; import 'dart:html'; import 'dart:convert'; import 'package:simplechat.common/common.dart'; part './views/message_view.dart'; part './controllers/web_socket_controller.dart'; main() { WebSocketController wsc = new WebSocketController('ws://$ADDRESS:$PORT', '#messages', '#userText .text', '#online'); }
最初の行では、ライブラリを宣言します。 次に、必要なファイルとライブラリの一部を接続します。
./views/message_view.dart
ファイルに
./views/message_view.dart
メッセージの表示を担当する
MessageView
クラスの定義が
./views/message_view.dart
ます。 考慮しません(コードはgithubで表示できます)。
./controllers/web_socket_controller.dart
ファイル
./controllers/web_socket_controller.dart
は、
WebSocketController
クラスの定義が
./controllers/web_socket_controller.dart
ます。これについては、さらに詳しく説明します。
main()
関数では、このコントローラーのインスタンスが常に作成されます。
WebSocketController-クラスコンストラクターと接続の作成
WebSocketController
クラスのプロパティとコンストラクターを見てみましょう。
class WebSocketController { WebSocket ws; HtmlElement output; TextAreaElement userInput; DivElement online; WebSocketController(String connectTo, String outputSelector, String inputSelector, String onlineSelector) { output = querySelector(outputSelector); userInput = querySelector(inputSelector); online = querySelector(onlineSelector); ws = new WebSocket(connectTo); ws.onOpen.listen((e){ showMessage('onnection is established', SYSTEM_CLIENT); bindSending(); }); ws.onClose.listen((e) { showMessage('Connection closed', SYSTEM_CLIENT); }); ws.onMessage.listen((MessageEvent e) { processMessage(e.data); }); ws.onError.listen((e) { showMessage('Connection error', SYSTEM_CLIENT); }); } // ... }
このコードは、
WebSocketController
に次のプロパティがあることを示しています。
-
WebSocket ws
ここにwebsocket接続を保存します。 -
HtmlElement output
メッセージが出力される要素。 -
TextAreaElement userInput
ユーザーがメッセージを入力するテキスト領域。 -
DivElement online
-アクティブなユーザーの数が表示される要素。
クラスのコンストラクターは、websocket接続を開くことができるアドレス、
userInput
セレクター、
userInput
および
online
要素を
userInput
online
。 最初の段階で、彼はツリー内の要素を見つけます。 次に、
WebSocket
コンストラクターを使用して、サーバーへのwebsocket接続が作成されます。
ws = new WebSocket(connectTo);
次に、イベントハンドラーを接続に割り当てます。
接続が正常に確立されると、
onOpen
イベントが発生します。 そのハンドラーは、接続が確立されたことを示すメッセージを表示し、リスナーをメッセージ入力要素のキーストロークに配置して、
Enter
を押すとメッセージが送信されるようにします。
bindSending()
関数のコードは次の
bindSending()
です。
bindSending() { userInput.onKeyUp.listen((KeyboardEvent key) { if (key.keyCode == 13) { key.stopPropagation(); sendMessage(userInput.value); userInput.value = ''; } }); }
keyUp
イベント
keyUp
の本文で、メッセージの送信に関与している
sendMessage(String message)
関数の呼び出しに注目できます。 WebSocket接続を介したメッセージの送信は、 WebSocketクラスのsend()メソッドを使用して行われます。 この関数のコードは次のとおりです。
sendMessage(String message) { Map data = { 'message': message }; String jdata = JSON.encode(data); ws.send(jdata); }
接続が閉じられると、
onClose
イベントが発生します。 このイベントのハンドラーは、接続が切断されたことを示すメッセージを表示するだけです。
onMessage
イベントは、サーバーからメッセージを受信すると発生します。 リスナーにはMessageEventオブジェクトが渡されます。 このイベントのハンドラーは、サーバーから受信したデータを
processMessage
関数に渡します
processMessage
関数は、単にメッセージを表示します。 彼女のコードは次のとおりです。
processMessage(String message) { var data = JSON.decode(message); showOnline(data['online']); showMessage(data['message'], data['from']); }
showOnline
および
showMessage
関数コードを引用しません。 特に興味深いことは何も起こりません。 ただし、コンテンツに興味がある場合は、 githubで完全なコントローラーコードをいつでも見つけることができます。
以上です。 これがすべてクライアント部分の主要な機能です。
動作中のアプリケーションは、 http : //simplechat.rudart.inで確認できます。
間違いや不正確な点を見つけた場合はお知らせください。すみやかにすべてを修正します。