StreamSession is the external peer of one Stream. The server or the client owns the connection, and the connection owns the stream. A session does not own either. It stores a pointer to the stream, and the stream stores a pointer back. One stream has one session. A second open() throws std::logic_error.
Binding is how an upgrade is accepted. On the server, Service::onUpgrade() receives the stream the server already owns. A derived session constructs itself with that stream, or calls open() from its own accept method. After onUpgrade() returns, a stream with no session is declined and the server closes it. On the client, Client::upgrade() returns the stream the client already owns, and the derived session opens that stream after the 101 reply.
This base does not transfer bytes. The derived type uses the stream it holds: Stream::buffer(), beginInput(), beginOutput(), and the ready signals of that stream. A framed protocol formats into the stream buffer and connects its own slots to the stream. Application code does not derive this base for WebSocket. The client uses WebSocket, and the server uses WebSocketSession.
open() and close() are protected because the derived type owns the handshake and the shutdown frame. A WebSocket close writes the close frame and then calls close(). A public close on this base would skip that frame.
The destructor unbinds and closes the stream. Destroying the stream unbinds this session and calls onClose(). The session object stays, and stream() is null. The two sides call each other through the peer pointer. isOpen() is false after either side ends.