App Design question: Multiple WebSockets?

Hi all,

I need your advice on using WebSockets from QML.

So I have made a QML client for a web site (using their JSON API, as you do).

That website has several “Rooms”, i.e. chat rooms. These support updates and sending of messages using a WebSocket.

My application starts up, and loads a summary information via XHR, lets say a “Lobby”.
The Lobby data consists of a list of the rooms, which has things like

  • room UUID
  • room display name
  • room URI
  • room latest message

I load and display this, and allow the user to click on any “room summary” to open the chat proper. This will push a new Page onto the stack.
I can now use the UUID and URI properties to open a WebSocket, which will receive any new messages.

The user may navigate back into the Lobby at any time, and I would like to keep the socket active so I can alert the user when new messages arrive.

Questions:

  • If a Websocket is created in RoomPage.qml, it will be destroyed when that page is popped form the stack, correct?
  • If I instantiate the WebSocket in LobbyPage, and hand over the object as a property to RoomPage, will it survive the navigation back? (See example below)?
  • As the room summary is element of a ListView, I run the risk of it being destroyed along with the WebSocket, when scrolled out of view, correct? Could this be avoided?
  • Are there any considerations wrt. opening potentially many Websockets (it will be 5-10, hot hundreds) in parallel?
  • Would you use a completely different design? Please share your idea.

So this would be my plan at the moment:

LobbyPage

Page { id: lobby
    ListModel { id: roomsModel }
    ListView {
        model: roomsModel
        delegate: Item { id: roomSummary
           Label { text: roomName; text.bold: true }
           Label { text: lastMessage }
           WebSocket { id: socket }
           onClicked: pageStack.push("RoomPage.qml", { "socket": socket, "uuid": uuid } )
        }
    }
}

RoomPage

Page { id: room
    property string uuid
    property WebSocket socket

    component.onCompleted: {
        ws.url = "wss://example.org/socket"  + "/" + room.uuid
        ws.active = true
    }
    Connections {
        target: socket
        onTextMessageReceived: function(msg) { 
            messagesModel.push ({ "author": msg.user, "message": msg.text })
        }
    }

    ListModel { id: messagesModel }
    ListView {
        model:  messagesModel
        delegate: ChatBubble { user: author; text: message }
    }
}

I actually have a working implementation of this app, but that one uses a WebSocket as a singleton, and can therefore only monitor one chat room at a time, not several. I want to improve on this.

Thanks for any comments.

1 Like

In qml - Create an array of websockets, allocate them with new?
Perhaps though this could be done more easily on the C++ side; do too much on the qml side and you risk “clogging” the QML thread.

1 Like

tooter solves this in a somewhat odd but plausible way. it uses a SlideshowView to hold all the timelines (/public /home /local), bookmarks, notifications, etc. The WorkerScript is always live. It’s a bit evil :slight_smile: And I’m refactoring, although I’ll probably hold on to the method just trying to refine it for search

It’s not all WS, though, but you get the picture.

2 Likes

Another way to do this, of course, is to instantiate all your websockets in the main qml page and just refer to them. EDIT: I’d instantiate my models in the main page, too. So you’re global state stuff is all visible on a single page.

1 Like

Thanks for the hints, much appreciated.

It would seem which ever method, I will have to enforce some kind of serialization as to not go against the RFC:

If the client already has a WebSocket connection to the remote
host (IP address) identified by /host/ and port /port/ pair, even
if the remote host is known by another name, the client MUST wait
until that connection has been established or for that connection
to have failed. There MUST be no more than one connection in a
CONNECTING state. If multiple connections to the same IP address
are attempted simultaneously, the client MUST serialize them so
that there is no more than one connection at a time running
through the following steps.
RFC 6455: The WebSocket Protocol

I didn’t implement delays between connections for the purpose of conforming to the RFCs but I did implement delays. More to avoid latency on active displays and to reduce unnecessary traffic. In any case, I just start each worker with an offset. They ‘could’ still collide, so it’s an open issue. For reasons also not related to the RFC I’m moving all connections to do a HEAD first since I need to retrieve data from a header that would not be in the response otherwise. You could use a signal on first connection succeeding in getting a HEAD response?

Aha! Found something!

Instantiator can do what you both said nicely and without having to manually handle creation and destruction.

// pragma Singleton
import QtQuick 2.6
import QtQml.Models 2.3
import QtWebSockets 1.0

Instantiator { id: socketManager

    property var names: ["one", "two", "three"]
    delegate: WebSocket{}
    model: names
    active: (names.length > 0)

    function getSocket(name) { ... }

}
2 Likes