Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Websockets, and identifying unique peers[PHP]

i'm new to websockets. Ive just completed my first multipurpose socket server, which serves 3 types of messages in the same socket. This server's intended usage is as follows:

chat messages

real time market data

user specific alerts

The first two functions work great and i'm totally happy, however i am at a total loss when it comes to identifying and linking peers in the connection to specific accounts, so that i can serve pending alert notices to specific users.

basically, i store the alert messages in a database table like so:

Table: uc_notifications

account(varchar50) | message(varchar255) | id(primary,AI) | seen(default 0)

i want to store the peers in a table like so:

uc_notification_peers
account(varchar50) | peer_id(unique,varchar100) | id(primary,AI)

Basically, the problem i'm having is storing the peer's info in the db, and retrieving it so i can send the message to the select peer while reading the notification table. Everything else in the script worked.

The first thing i tried, was passing the account name to the websocket using onopen. this crashes the websocket.

The next thing i tried was accessing the user session through globals pertaining to the ip address. didn't work either, and resulted in messages being received by all accounts on same ip( i was testing on localhost in all 3 browsers).

The last thing i tried, was the first method. i set a timeout of 2 seconds on the on open event, then sent the account message. i was able to finally store a peer in the database! but, to my chagrin when i checked the database, it said ("resource id#6). i tried casting the $clients array to globals but its empty everytime. i googled around a bit, then i tried serializing the array. it all works until i get to the part where i send the message to the unique peer.

here's the function for sending the message

function send_message_single_client($msg,$client) 
{
    @socket_write($client,$msg,strlen($msg));
    return true;
}

here's an example of where i use this function to push the last 100 messages to the chatbox of the new peer.

if (in_array($socket, $changed)) {
        $socket_new = socket_accept($socket);
        $clients[] = $socket_new;
        $header = socket_read($socket_new, 1024);
        perform_handshaking($header, $socket_new, $host, $port);
        socket_getpeername($socket_new, $ip);
        $found_socket = array_search($socket, $changed);
        //need to push last 100 messages to new peer.
        $query = $mysqli->query("SELECT * FROM (SELECT * FROM uc_chat_msg WHERE `hidden`='0' ORDER BY `id` DESC LIMIT 100) as last100 ORDER BY id");
        while($row = $query->fetch_assoc()) {
            $response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>security($row["username"]), 'message'=>security($row["message"]), 'color'=>security($row["color"]))));
            send_message_single_client($response_text, $socket_new);//send old messages to the new client
        }
        $query->close();
        unset($changed[$found_socket]);
    }

That was a few weeks ago, and i put the project on the backburner so i could move on and focus on other things. Since, i've moved to a development vps to accomadate the growing size/complexity of the application. instead of php, i'm using facebooks HHVM, not relevant but thought i would mention it, since it has a few quirks that forced me to alter my code a bit.

like image 583
r3wt Avatar asked Jan 10 '23 19:01

r3wt


1 Answers

I have no idea about PHP, but I think this is more an approach issue rather than coding issue. So I hope these thoughts from my experience with push architectures bring some light.

Identifying users

Identifying by cookie:

WebSockets use a HTTP handshake that will send the cookies for that origin. You could attach an temporary ID (eg: GUID) to a special session cookie, that will be resend every time the user reconnects in the same session. You can deploy the cookie from the website as long you are honouring the Same Origin Policy, or in the HTTP handshake response itself.

Identifying by give token:

When the WebSocket connect, you can send from the server an initial message with a token that contains a temporary ID. That token can be resend on reconnection if the connection drops, but will be forgot if the user reload the page unless it is saved in local storage or any other kind of browser storage.

Note on temporary ID

How that temporary ID maps to a particular user or account, is an implementation detail you DO NOT WANT to leak to the web. So do not use that information to identify your WS connections.

You should keep a "session" registry that maps temporary IDs to accounts, that maybe from a in memory dictionary, to a table on the DB. Up to you, your requirements and your service. This element must keep reference to the WebSocket connection, so you can retrieve it by temporary ID when needed.

Never leak internal details, specially account ids (because they do not change).

Receiving messages

When receiving a message from a WebSocket, you must create a message object that contains the sender account using the "session" information you are keeping, and also the actual message content. Then, pass this message object to your business logic that will decide what to do.

Before sending messages

In order to send messages, you must define first several elements:

Presence:

Which accounts are online? Merge your "session" storage with the account table. When a connection drops, you should remove the drop user session.

Belonging:

Which accounts belong to a particular group or signal? You must design this according to your requirements. Some groups could be predefined and indicated in the "account" entities, some other may be dynamic like he belonging to a chat room. In any case, you need another element that maps "groups" to "accounts"

Sending messages

When your business logic decides it wants to send a message, either as response to an incoming message, as a timed action, own initiative or whatever... it must decide to which user account, user accounts or user group must be sent.

Then, using the session information that you keep, gather the list of temporary IDs that must receive that message, then the lists of WebSockets where that message must be forwarded to, and finally send the message to each of the WS of that list.

like image 198
vtortola Avatar answered Jan 22 '23 19:01

vtortola