common/lib/common.dart
. This file contains the definition of the library simplechat.common
. library simplechat.common; const String ADDRESS = 'simplechat.rudart.in'; const int PORT = 9224; const String SYSTEM_CLIENT = 'Simple Chat';
pub build
) we can get an error from the pub
: Exception: Can not read {build} .pubspec.yaml
definition of our package to the dependencies
section of the pubspec.yaml
file: dependencies: simplechat.common: path: ./common
pubspec.yaml
file (but you can look at it on github ). You will also need to add the pubspec.yaml
file to the common
directory in which you simply specify the name of our package: name: simplechat.common
bin
folder. The main.dart
file main.dart
the entry point to the server, and the server.dart
file server.dart
class of our server. Let's start by looking at the contents of the main.dart
file.9224
.bin/main.dart
we will determine that this is the library simplechat.bin
. For the server to work, we will need to connect the dart:async
libraries dart:async
, dart:convert
, dart:io
, the route
package (set via pub
) and the file with the application settings. Also in bin/main.dart
we include the file bin/server.dart
, which contains the main code of our server (consider it a bit later).main()
function, we create an instance of the server and start it. 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()
function, the function for setting up the router is bindRouter()
, which we will discuss below.bindRouter()
function. The incoming stream to /
we will change using WebSocketTransformer
and listen in the createWs()
function. /** * 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()
function, we generate a name for the connection using the user_{counter}
scheme and save this connection as connections
. /** * Build message */ String buildMessage(String from, String message) { Map<String, String> data = { 'from': from, 'message': message, 'online': connections.length }; return JSON.encode(data); }
/** * Sending message */ void send(String to, String message) { connections[to].add(message); }
notifyAbout(String connectionName, String message)
accepts the name of the connection and the message (about connecting or disconnecting). This feature notifies all active clients in addition to whom this notification is made. Those. if user_3 has joined us, then all users will receive a notification except him. In order to filter clients by a certain condition (in our case we need to get the names of all clients that do not match the current one), we will use the where () method of the abstract class Iterable . /** * 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)
takes the name of the sender and his message. If the message body ( message
) specifies the names of the recipients by the mask @{user_name}
, then the message will be delivered only to them. Let's look at the sendMessage
function 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)
function takes the name of the connection that was closed and removes it from the list of connections: /** * Close user connections */ closeConnection(String connectionName) { if (connections.containsKey(connectionName)) { connections.remove(connectionName); } }
createWs
function createWs
on the user's connection. send
- sends a message to the specified user. sendWelcome
- sends a message with a greeting to a new user. notifyAbout
- notifies chat participants (except the initiator) about any actions of the initiator (enable / disable). sendMessage
- sends a message to all or only specified users.createWs
function so that we can use it all. Last time, we stopped at adding a connection to the list. After that, we need to notify all other members of the chat about the new user, and send the message to the new user with a greeting. 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'); }); }
web/dart/index.dart
file. Let's look at its contents: 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
contains the definition of the class MessageView
, which deals with the display of messages. We will not consider it (the code can be viewed on github ). The file ./controllers/web_socket_controller.dart
contains the definition of the class WebSocketController
, which we will focus on in more detail.main()
function permanently creates an instance of this controller.WebSocketController
class: 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
has the following properties:WebSocket ws
- here we store our websocket connection;HtmlElement output
- the element to which we will display messages;TextAreaElement userInput
- the text area in which the user enters messages;DivElement online
- an element in which the number of active users is displayed.output
, userInput
and online
elements. At the very beginning he finds the elements in the tree. Then a websocket connection to the server is created using the WebSocket
constructor: ws = new WebSocket(connectTo);
onOpen
event onOpen
triggered when the connection is successfully established. Its handler displays a message indicating that the connection has been established and sets up a listener of keystroke events on the message input element so that when you press Enter
, the message is sent. Here is the bindSending()
function code: bindSending() { userInput.onKeyUp.listen((KeyboardEvent key) { if (key.keyCode == 13) { key.stopPropagation(); sendMessage(userInput.value); userInput.value = ''; } }); }
keyUp
event keyUp
you can notice the call to the function sendMessage(String message)
, which deals with sending a message. Sending a message via a websocket connection is performed using the send () method of the WebSocket class. Here is the code for this function: sendMessage(String message) { Map data = { 'message': message }; String jdata = JSON.encode(data); ws.send(jdata); }
onClose
event onClose
triggered when the connection is closed. The handler for this event simply displays a message indicating that the connection has been dropped.onMessage
event fires when a message is received from the server. The listener is passed the MessageEvent object. The event handler for this event passes the data received from the server to the processMessage
function, which simply displays the message. Here is its code: processMessage(String message) { var data = JSON.decode(message); showOnline(data['online']); showMessage(data['message'], data['from']); }
showOnline
and showMessage
, since nothing particularly interesting happens in them. But if you are interested in their content, then you can always find the full controller code on github .Source: https://habr.com/ru/post/230351/
All Articles