-
Notifications
You must be signed in to change notification settings - Fork 0
Design
The server sits atop the lwes-java library for listening on the network for LWES events, and sits beneath the tyrus library for its WebSocket support. (No longer uses Java-WebSocket).

The design of the server optimizes performance by:
- Creating only one listener per channel, regardless of number of clients requesting events on that channel.
- Processing events on a per client configuration (not per client) basis. So if multiple instances of the same client specify the same configuration, each event is only processed once for the entire set of clients.
The client configurations are stored in a persistent map. As each client comes and goes, each running in its own thread, the map is updated without affecting other threads using the map.
There are two main things going on in the code. The first one is simple. Each incoming request from a WebSocket client specifies a channel (IP:port) to listen on for events. One and only one Listener is created for each channel, regardless of how many requests arrive wanting to listen on that channel. All the Listener does is pass every event it sees to Listener.handleEvent(). That's it. When the number of clients for a Listener drops to zero, the Listener is destroyed. This is important because listening is CPU-intensive.
The other part of the code is the handling of the LWES event. For each event passed to Listener.handleEvent(), the ConfigMap data structure is traversed in order to find all the clients listening for that type of event. To understand how that happens, we need to step back and see how the ConfigMap gets built from incoming WebSocket client requests.
Let's say our first client submits this config:
{"ip": "224.0.0.69",
"port": 9191,
"batchSize": 5,
"maxSecs": 60,
"requests": { "Search" : ["term", "lat", "lon"] }
}
This get inserted into the ConfigMap as the CC1 in the diagram below:

The ConfigMap key, 224.0.0.69:9191Search, is the "channel" (ip:port) concatenated with LWES event name, Search. the C1 object is a Connection object which contains a thread for delivering the events back to the Client over the WebSocket.
How is this map used? When an LWES event with the name Search comes in on 224.0.0.69:9191, CC1 is checked, and an Event object containing the values for term, lat and lon is enqueued on C1. When either the batchSize or maxSecs thresholds are reached, all the Events enqueued on C1 are sent over the WebSocket to the client.
Now let's say a second client connects with the same config, except that the batchSize and maxSecs values are different:
{"ip": "224.0.0.69",
"port": 9191,
"batchSize": 500,
"maxSecs": 5,
"requests": { "Search" : ["term", "lat", "lon"] }
}
A new Connection object gets created, but the ClientConfig object is the same, and thus shared (see ClientConfig.equals()):

C2 has its own thread for monitoring its event queue against batchSize and maxSecs.
Now, a third client submits a similar request, but it asks for a different set of attributes (includes count):
{"ip": "224.0.0.69",
"port": 9191,
"batchSize": 500,
"maxSecs": 5,
"requests": { "Search" : ["term", "lat", "lon", "count"] }
}
This results in a new ClientConfig object:

Finally, we get a client asking for Ad not Search LWES events.
{"ip": "224.0.0.69",
"port": 9191,
"batchSize": 500,
"maxSecs": 5,
"requests": { "Ad" : ["text"] }
}
This results in a new key in ConfigMap:
