RSS LinkedIn Twitter

JUICE chat (Stratus Peer to Peer)

One of my ongoing weekend projects is an AIR chat application, recently rechristened “JUICE” a.k.a. “John’s Ultimate Internet Chat Experience”. I realize that may only be the case for myself, but the project has been very educational and entertaining for me.

The initial release used AIR/Flex as the front-end, ColdFusion and MySQL for the backend, and BlazeDS messaging for message transmission and “peer discovery”.  I worked on it over a couple weeks adding features like encryption, toast-style notifications, as well as a few other less notable things.

I knew the architecture was awful and would never scale beyond a couple hundred users (if that many). Here’s the basics of how it worked:

  1. The user would login, ColdFusion/MySQL would send back a list of chat buddies.
  2. Each logged in user would ping the messaging server every 10-30 seconds to announce “I’m still here!”
  3. The pings would be broadcast to everyone logged in (and connected to the messaging server). The application would filter out the ones it was interested in based on user id.
  4. Sending messages worked similarly, the user would send the message (which included information about the intended recipient), which would then be broadcast to EVERYONE.
  5. Everyone would filter this information, and the recipient would know it was for him, and would display it.

You can see why this would not scale well, the amount of traffic and pinging going on.  Eventually even a user with only 1 or 2 buddies would notice a significant drop in performance as the number of total users increased.  There are some applications of “chat” for which BlazeDS messaging and polling is well suited for (like a chat room).  A chat application involving “buddies”  is much better suited for P2P.

Enter Stratus.  I discovered Stratus a few months ago but was unwilling to delve into it on account of my hectic schedule.  Fortunately one of my friends went and did all the dirty work figuring out how to get it up and running (turns out it’s pretty easy) and I was able to apply it to my chat client.

I’m very pleased with the results, though it’s still very much a work in progress.  I’ve even been able to add video and voice chat features, which is why I went through the trouble in the first place.  Here is how my peer discovery mechanism operates.  It requires only 3 calls to the server per client (which I could easily condense to 2 if I wanted).

  1. The user logs in.
  2. If the user successfully authenticated, he connects to Stratus and receives his peer ID.
  3. The user updates his peer ID in the database (which was set to 0) and gets a list of his buddies and their peer IDs.  I also update information like whether or not I have a camera/mic.
  4. The user processes his buddy.  If the peer ID is something other than 0, then I know this buddy logged in before me, so I must send him my peer ID.
  5. Any buddies that have a peer ID of 0 are not logged in, I need to listen for them though since they will get my peer ID when they log in.
  6. When I log out or close the application, I call the server and reset my peer ID to 0.  I also notify all of my connected peers I’m logging out.

There are a few more steps in the process, but it decreases the load on the server significantly, transferring the “burden of proof” onto the clients.

Interesting to note is the way I get the peer ID of a client who connects after me (step 4).  I don’t actually have to make a call for this, rather he simply has to start broadcasting to me on the NetStream I set up to listen for him (step 5).  When the connection is made, an event will be dispatched by the NetStream class.  In fact, 3 events will be fired, I just chose to act on one of them.

The peer ID can be obtained from the peerStreams array on the publishing NetStream.  I then use that information to set up my subscribing NetSream.

[as3]
if (this.peerId == null || this.peerId == "0") {
this.peerId = publishNetStream.peerStreams[0].farID;

this.subscribeNetStream = new NetStream(model.netConnection, this.peerId);
this.subscribeNetStream.client = new NetStreamClient();
this.subscribeNetStream.play(streamName);
}
[/as3]

Anyway,I’m planning on doing another post to outline my video and voice handshake protocols.  They’re nothing to write home about, but I’d love some feedback if there’s a better way.  I’m also planning on releasing JUICE after I bring it’s level of functionality back up to where the original version was, so stay tuned and you should see my install badge at the bottom of my blog.

Tags: , , , , , , ,

9 Responses to “JUICE chat (Stratus Peer to Peer)”

  1. John
    November 26th, 2010 at 17:58
    1

    I don’t understand this portion:
    “I don’t actually have to make a call for this, rather he simply has to start broadcasting to me on the NetStream I set up to listen for him (step 5).”

    How would you set up a listener at all if you don’t know his peerID?

  2. November 26th, 2010 at 22:13
    2

    The last user to log in gets the updated list of peer ids from the database. When I say I didn’t have to make a call, what that really means is that it was bundled into call used to log in.

    If I’m the last user, I get all active peer ids, and I broadcast my peer id to all those who logged in before me.

  3. John
    November 28th, 2010 at 09:28
    3

    I sorta now. I had figured if you didn’t make a call when you logged in that you would just make one that gave the list of peer ids after (what I did). I was mostly confused on how these two clients will contact one another when the peerid is given on the receiving end not the sending end. Thus, because you want to give all the users who logged in before you your peer id, they need to first listen for you, and your peer id to the client already logged in is 0. My question would probably be better worded like: “how do you broadcast your peerid to those that log in before you”?

    Another read through of the code you gave enlightened me on this regard. Clearly as you said, you don’t want to just make another call to the server. Are you just continually looking for the far id on the peerStreams array? My understanding is that: a user logs in and easily has the information to listen to all of his friends that are already logged in (from the call) and sending to them doesn’t require a peer id. However for the logged in user to get their message they must listen and therefore must know the peerid. No matter how I slice it, the user that was logged in first needs to know the peerid of the user who just logged in, so they could send them their peer id which just doesn’t make sense…

    Also, at the moment I have a NetStream listener for each one of my users. Seems slightly inefficient despite how fast p2p is. I don’t really think it’s hurting performance because I think they are only used when data is actually exchanged. Are you doing the same? I don’t know if there is any better way, the chat I have set up is based on a 1:1 user interaction for now, but I’m planning on implementing multicast (group chat/multiuser video, etc) later. I love the blog. Thanks so much for the help.

  4. John
    November 28th, 2010 at 10:03
    4

    Actually, taking a look at the documentation helped me understand peerStreams better. That’s awsome. The user who just logged in subscribes to the user he knows is logged in, that logged in user get the farid from the subscription (I suppose the exchange of information then is some unique identifier to tell the logged in user who this peer id belongs to and change their name to green or something). All I need to know now is how that code runs before the user who just logged in sends a message. Maybe telling me the context of that code would answer that or something. Thanks again and good coding

  5. John
    December 1st, 2010 at 21:55
    5

    Any help?

  6. John Dusbabek
    December 2nd, 2010 at 06:08
    6

    sorry, I’ve been out of town the past two weeks and haven’t had easy access to the source code of my project. I’ll be back tomorrow, and will be able to get some links to source code that you can take a look at.

  7. John
    December 3rd, 2010 at 19:10
    7

    Thanks, looking forward to seeing it.

  8. December 3rd, 2010 at 20:52
    8

    Hey John

    I just scanned through the post real quick and it looks like you are confused as to how a new user can send a message to users that are already logged in. It also sounds like you think there has to be an individual ‘server’ stream for each p2p connection. Something that might help is that a server stream (a netstream set to be the outgoing stream) can handle up to 5 connections by default (can be changed), and it also will automatically accept any incoming connections. So if I were a new user logging on, I would receive all my friends peerids, and for each one of those I would open up a new stream to them. The friends ‘server stream’ would then accept my connection, and they could listen for connection. When that connection occurred, they would then open up a netstream back to that peerid that just came in. (this is the part you mentioned learning about) Then there would be a 2 way communication. Messages notifying each other of who they are could be exchanged etc.

    Hopefully that answers your question.

  9. John
    December 4th, 2010 at 16:52
    9

    So as we know the receiving netstream is where we specify the peerid. I understand that the user who is logging in gets the userid of friends already logged in from a call to the server. That user starts listening to the peer id. From my understanding every user just has one outgoing stream. Now the user who is already logged in knows the peer id of the user who just logged because in peerStreams there is a user that’s listening. My question was how does the user who was already logged in check the peerStreams array? Do you check it every so often or better yet is there an event that says when the peerStreams array has been modified? I just don’t understand when the user who is logged in checks for the peerStreams array.

Leave a Comment

*