Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

JSONsvc - a proxy-peer/peer protocol for Websockets

5.00/5 (2 votes)
1 Feb 2014CPOL5 min read 23.7K   386  
A JSON-based protocol for sharing data between multimple clients over Websockets

Introduction

JSONsvc defines a simple format for data to be sent between a Websocket server and its clients. It allows browser clients to view the connection as a type of "proxy-peer-to-peer" network. i.e. any client can send data to another client as if they were directly connected - the websocket server does the routing.

The protocol also allows multiple clients to automagically receive data they are interested in whenever anyone else sends it - again routed by the websocket server.

The intended interest here is in the use of general-purpose protocol with a server, rather than some ad-hoc scheme which means you would have to change client and server code to add or change any functionality.  

Background

This is a companion article to "Using Sec-Websocket-Protocol" which discussed the use of different protocols with websockets. This article describes a 'JSONsvc' protocol which could be used by any websocket server or client written in any language. The example implementation of the server happens to be in PHP with client code in HTML/javascript. For more information the use of Sec-Websocket-Protocol and implementation of the server, see the other article.

The JSONsvc protocol

JSONsvc allows clients to register for named 'services' (i.e. bits of data) and to be a 'service provider' if needed. Whenever a 'service provider' sends data for a 'service', the JSON2svc sends that data to all clients who are registered for it. The data is sent via a websocket opened with protocol: "JSONsvc"

The protocol handles data like this:

   { header_json }
or { header_json }>SERVICEDATA     i.e. a header '>' and some service data
 
the header can contain the following key:value pairs:
   "to":name                   = name of destination client or 'JSONsvc'
   "from":name                 = name of the sender - required
   "provides":service_name     = service provided
   "requests":service_name     = service client wants sent automatically
   "get":service_name          = request a service as soon as possible
   "put":service_name          = deliver a service (could have >SERVICEDATA attached)
 
note:"provides","requests" and "get" can also accept an array of service names
e.g. "provides":["this_service","that_service"]

So a client could send:  

{ "from":"Bob", "requests":["info","updates"], "get":"info", "provides":"junk"  }

which identifies the client as 'Bob' and that he wants to receive automatic updates whenever something provides 'info' or 'updates'. Since Bob has just started up, he also wants to "get":"info" as soon as possible, he also registers as a provider of 'junk'.

If another client, 'Alice', has registered as a provider of 'info', she would be automatically sent:

{ "from":"Bob", "to":"Alice" "get":"info" }

to which she is expected to reply: (to the websocket server, of course)

{ "from":"Alice", "to":"Bob" "put":"info" }>{.... whatever the info is ....}  

and the server automatically forwards the whole lot to 'Bob'. If there are multiple clients with the same name or have requested the same info, then they all get a copy.

Note that the embedded '>' character is to allow the server to extract the header without knowing anything about the data (which may or may not actually be in JSON format). This is fairly important when the data is actually an umpteen megabyte file.   

Since Bob has sent "requests":["info","updates"] , he will get those data services automatically whenever another client provides them. In the mean time he could make ad-hoc requests of any data service by issuing a "get":"servicename" 

Service providers 

In our example above, Bob has also registered as a provider of "junk", so if any other client requested that service he could receive...

{ "from":"some_user", "to":"Bob" "get":"junk" } 

...to which he is expected to reply with whatever 'junk' is.

Actually, Bob may not be the only provider of 'junk' connected to the server - any request is simply sent to the first provider the server finds in its list of 'junk providers'. In my application this is intended for delivering stuff from two or more client apps which share data (using another service)- if one becomes unavailable, the other can satisfy the 'junk' request.

"put" data format  

It was originally intended for the 'JSONDATA' following the header to actually be in JSON format, however, since websockets are quite capable of sending arbitrary binary data it has been useful to leave the format of that data to the 'service provider'.

Currently you can do something like this:

{"from":"fred", "put":"fileservice", "filename":"somefile.xyz" }>... binary data...

If you were generalizing the protocol, you'd probably want to strictly define some more keywords like the "filename" above. (You may wonder what a javascript client is going to do with a binary file - but remember that the websocket client could be anything: a cgi script, some desktop application or another type of websocket server Wink | ;)   

For sending data to be used by javascript, you'd probably stick to JSON, just separate that data from the websocket frame using something like (keeping it simple here):

JavaScript
host="ws://some_server/";
 
socket= new WebSocket( host, "JSONsvc");
 
socket.onopen = function(msg){ 
    socket.send( '{"from":"me", "requests":["alert","UserList"]}' );
}
 
socket.onmessage = function (msg) {
    var separatorIndex=msg.data.indexOf('>');
    if( separatorIndex > 0 )
        header= JSON.parse( msg.data.slice(0,separatorIndex) );
        data=   JSON.parse( msg.data.slice(separatorIndex+1) );
    }else{
        header= JSON.parse( msg.data );
        data= someDefaultThing;
    }
    if( header.put == "alertService" ) alert( data.msg );
    else if( header.put =="UserList" ) updateUserList( data );
    else ... 
}

The example websocket2.html demonstrates JSONsvc in a simplistic fashion by implementing a 'chat' server. Using the example; start JSONsvc protocol and type a message - the resulting data is shown. If you then click 'Log decoded' and open several browsers to talk to yourself (!), each client is auto-updated with messages and current client list.

In websocket2.html the JSONsvc request is formed by:

request= {from:"Anonymous", requests:["text","JSONsvc_ClientList"], put:"text" };
if( username != "" )
    request.from= username_from_textbox;

msg= JSON.stringify( request ) + ">" + message_from_textbox;

sending the request results in:

Send: {"from":"Bob","requests":["text","JSONsvc_ClientList"],"put":"text"}>Hello there
Recv: {"from":"Bob","put":"text","to":"Bob"}>Hello there
Recv: {"from":"JSONsvc","put":"JSONsvc_ClientList","to":"Bob"}>["Alice","Bob","Carol"] 

here we received two messages, the first is an echo of what we sent (because we provided and requested the "text" service), the second because we requested "JSONsvc_ClientList" which sends the list of connected clients whenever it changes. If another user sends a message we may get:

Recv: {"from":"Carol","put":"text","to":"Bob"}>hi from Carol
A more 'restful' example

A project which needs real-time communication between users who control a group of Emergency Response volunteers is looking at using Websockets with JSONsvc to serve status information pages. In this case we can do things like:

// on entry to a 'view' page we send:
{ "from","viewpage", "requests":["alerts","pageItems"], "get":["ItemNames","pageItems"]} 
 
// and receive:
{ "from","JSONsvc", "to","viewpage", "put":"ItemNames"}>{"name":40,"status":100} 
{ "from","JSONsvc", "to","viewpage", "put":"pageItems"}>
                                         {"name":"Fred Bloggs", "status","off duty"} 

The javascript uses the "pageItemNames" to build a page view for that list of items and whenever it receives "pageItems" it fills in the data. In this way the javascript does not need to know anything about the content of the central database and will refresh the page whenever something sends an updated version of "pageItems". This example also registers for "alerts" which may be used for urgent 'popup' messages. (in reality there's a few more tags added to identify pages, provide screen legends, tag editable items and so on)

JSONsvc2.php   

This is the example implementation of the "JSONsvc" protocol for a websocket server.

The code happens to be written in PHP, as is the example server, but I think it's pretty straightforward and doesn't take long to port to C, java/script or anything else. (e.g. it took a couple of evenings to re-implement this in C and embed it with a Civetweb server - well... it's nearly working Smile | :)  

Finally  

Most of the websocket examples I've seen so far simply sends strings or some arbitrary JSON encoded stuff from point-to-point rather than considering the pseudo peer-to-peer possibilities. So I hope this has given you some ideas on how to design and implement your own flexible data transport over websockets.

Yours,

TonyWilk

History 

Example version is 1v2 

Native browser support for websockets v13 at 1st Jan 2014:
PC: Internet Explorer 11, Chrome 32, Firefox 26.0 and Android: Chrome 31, Opera 18
on windows pc/laptop, Samsung S3/4, Amazon Fire and a Nexus 7

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)