DartでのシンプルなWebSocketチャット

こんにちは

この記事では、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.dartmain.dartファイルにはサーバーserver.dartクラスがserver.dartます。 main.dartファイルの内容を見てみましょう。

一般的なサーバー操作スキーム


サーバーが一般的にどのように機能するかについて話しましょう。 サーバーで最初に行うことは、サーバーを起動することです。 起動時に、ポート9224リッスンを開始します。

新しいユーザーがこのポートにリクエストを送信すると、サーバーはそのポート用のWebSocket接続を開き、名前を生成し、名前と接続を開いている接続でハッシュに保存します。 その後、クライアントはこの接続でメッセージを送信できるようになります。 サーバーは、これらのメッセージを他のユーザーに送信できるだけでなく、クライアントの接続および切断に関する通知を送信できます。

ユーザーが接続を閉じると、サーバーはアクティブな接続のハッシュからその接続を削除します。

サーバーエントリポイント


bin/main.dart最初に、 simplechat.binライブラリーであると定義します。 サーバーが機能するためには、 dart:asyncdart:convertdart: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での表現)として送信します。


このようなメッセージを作成する関数は次のとおりです。

 /** * 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接続を開くことができるアドレス、 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で確認できます。

間違いや不正確な点を見つけた場合はお知らせください。すみやかにすべてを修正します。

Source: https://habr.com/ru/post/J230351/


All Articles