Random chat with two users at a time (Socket.io)

13,383

Although I would close this question because it's too vague, I feel obliged to give you some insight since I worked way too much with websockets in the last years (although not that much with socketio & nodejs). I suppose some simple guide and relevant links could help you. So first,

Kind of relevant intro

You should already know that Socket.io is a WebSocket implementation. WebSockets (WS) allow server to send data whenever it wants, as long as the connection is still open, as opposed to old way: client querying all the time asking, if there is an update on the server.

You can imagine a woman and a man at the end of a party: "Thanks for tonight, I'd love to repeat it sometimes soon. Would you give me your number?" - asks the old man. "Ughhh, you know what, better give me yours, I promise I will call you!" If the girl were to give him her number, he'd call a few times a day asking if she'd go somewhere (and she'd reply no). The other way around, she would call him only if she wanted to go and he would go. Of course he would. I got a bit carried away, but you get the picture. The woman is a server, the guy is a client.

What is important to understand

(Absolute basic, you should know this =>) When client connect to your server, (s)he should be served a html page and some javascript, which establishes connection to your WS server. In the code you've posted, Express is used as http server. Check this example to see how you should give user html&js.

You'll also notice namespaces and rooms in most of these tutorials. These are used for separating users into subcategories. One server may contain multiple namespaces (by default only one) and each namespace may contain multiple rooms. You probably won't need to bother with namespaces, one is just enough for your case. You will, however, need to understand rooms (more on that later).

Next thing, taken from your code

io.on('connection', function (socket) {

It's important to know, that socket here basically represent one connected client (in one namespace, but possibly in multiple rooms). You can do all sort of stuff with it, most notably:

  • install event handlers on it (that's what you do when you call socket.on(event, handler(data))
  • send events to it with socket.emit(event, data)
  • send broadcast event to all users with socket.broadcast.emit(event, data)
  • add/remove it to/from room with socket.join(room), socket.leave(room) respectively.
  • work with it as with an ordinary variable - store it wherever you want and then reuse it

Do you see the definition of numUsers in your code? That's a global variable which is shared with all clients, since nodejs is single-threaded. In the example it is being incremented inside one of the event handlers. Think we could use something like that? YES.

We can define global variable, queue for example. Or Q if you want. Point is, it can be an array used to store sockets, or rather clients, which are not currently in chat room.

At the end of this section I'd like to point out another obvious thing. io.on('connection', handler); defines an event handler for 'connection' event happening on the io object (WS server). This is triggered each time client makes connection to your WS server (in your case, through javascript ran inside client's browser). Argument to the method is socket and it is this method where you should add event listeners for each client (that you already do in the code, particularly handling events 'new message', 'add user', 'typing', 'stop typing' and 'disconnect').

What events shall you need

That really depends on how complex you want your app to be. In my opinion, the bare minimum would be (note that you can change the event names, but 'disconnect' should stay 'disconnect'):

event name -> data given

Events handled on server side

  • login -> username (how the user should be called), possibly password if you want to enable registration
  • message -> text (content of the message being sent)
  • leave room -> room name
  • disconnect

Event handled on client side

  • connect
  • chat start -> name (second client's name), room (so we can leave it)
  • chat end -> no data required if you want to allow only one chat at the same time. In case of multiple chats you should also include which chat got closed
  • disconnect

Last note before we get started

This is only a rough sketch. There are multiple different crossroads along the way and which path you take mostly depends on your idea of the app. If you want to have multiple chats opened at the same time, you'll need to do some modifications. The same goes if you want to have more than two people connected to the same chat. Here I'll describe the simplest case possible, one chat, to people, no registration. Possibly what you want, judging from your post. Could be wrong.

Workflow

User opens your page in their web browser. You serve them html and javascript. The javascript will start new connection to your websocket server. Also, handlers for desired events should be defined at this point. When the connection is established, this will be happening:

  1. ON SERVER SIDE

io.on('connection', handler) will be fired. Only appropriate handlers for new socket will be installed, not doing anything else at this point.

  1. ON CLIENT SIDE

socket.on('connect', handler) will be fired. Client should at that point have username stored somewhere. If not, no problem. The connection will be alive for quite some time. You can just call socket.emit('login', {'username':name) any time you wish after you are connected (in the example below I set up variable connected, which defaults to false but will be set to true as soon as connection is established.)

After you send login event from client, server registers it and saves it somewhere. Possibilities are endless, in this case I'll create global dictionary which maps socket.id to username. After that, user socket should be either paired with another one or added to queue. So, if the queue is empty, simply append socket to global variable (it doesn't have to be an array, since we will pair the first available sockets together, however you may want to implement some history of users so they won't get connected to the same person again). If the queue is not empty, we pull one socket out of the Q and add them to the same room. Room name can be random or whatever you want, I'll use (socket1.id+'#'+socket2.id (if you wanted to have more users in one chat, this would have to be changed).

After you add them both, you'll need to notify them that their chat has started and send them the other peer's name. You will emit event 'chat start'.

Clients will catch the event and open new window. After that, whenever user types something and sends it, client emits event 'message' with payload {'message': user_inserted_text}. Server will capture it in the .on('message' handler and broadcast it to the room. Note:

Broadcasting means sending a message to everyone else except for the socket that starts it.

Note: I am really confused about socketio code right now. Look at this and tell me, if socket.rooms is an array or an object (socket.rooms[room] = room; ?? why?)

To avoid dealing with this not-straightforward code, lets create another global object, rooms, which will store the room names for us. We will map socket.id -> roomName there.

So when message comes, we can get name of the room by calling rooms[socket.id]. Then we broadcast the message like this:

socket.broadcast.to(room).emit('message', data);

Where data is what we received from the sender, therefore object {'text': 'some nice message'}. Your peer will then receive it (you won't) and display it (you should display it when you are sending it).

So the chat continues like this for a while, then one of the users decides (s)he wants to leave / chat with somebody else. They will close window and client will emit event 'leave room'. Server will capture it and send to the other party that her/his peer has disconnected. The same should happen if the client disconnects. After everything is closed, add both users to queue (or only one, if the other has disconnected from the server). In my code I will not make sure they won't get paired again. That is for the OP to code (can't be hard).

So, if you read this far, you deserve some actual code. Although I say actual, it's actually untested. But you know, it should work like this.

Some code

Client side

var connected = false;
var username = 'Faizan';
var room = '';
var socket = io('http://localhost');
socket.on('connect', function (data) { // we are connected, should send our name
    connected = true;
    if (username) socket.emit('login', {'username' : username});
});
socket.on('chat start', function(data) {
    room = data.room;
    show_chat_window(data.name); // some method which will show chat window
});
socket.on('chat end', function(data) {
    hide_chat_window(); // this will close chat window and alert user that the peer ended chat
    socket.leave(room); // it's possible to leave from both server and client, hoever it is better to be done by the client in this case
    room = '';
});
socket.on('disconnect', function(data) { // handle server/connection falling
    console.log('Connection fell or your browser is closing.');
});
var send_message = function(text) { // method, which you will call when user hits enter in input field
    if (connected) socket.emit('message', {'text': text});
};
var leave_chat = function() { // call this when user want to end current chat
    if (connected) {
        socket.emit('leave room');
        socket.leave(room);
        room = '';
    }
};

Server side

Not including initial requires and html/js serving., only global definitions and main io handler.

var queue = [];    // list of sockets waiting for peers
var rooms = {};    // map socket.id => room
var names = {};    // map socket.id => name
var allUsers = {}; // map socket.id => socket

var findPeerForLoneSocket = function(socket) {
    // this is place for possibly some extensive logic
    // which can involve preventing two people pairing multiple times
    if (queue) {
        // somebody is in queue, pair them!
        var peer = queue.pop();
        var room = socket.id + '#' + peer.id;
        // join them both
        peer.join(room);
        socket.join(room);
        // register rooms to their names
        rooms[peer.id] = room;
        rooms[socket.id] = room;
        // exchange names between the two of them and start the chat
        peer.emit('chat start', {'name': names[socket.id], 'room':room});
        socket.emit('chat start', {'name': names[peer.id], 'room':room});
    } else {
        // queue is empty, add our lone socket
        queue.push(socket);
    }
}

io.on('connection', function (socket) {
    console.log('User '+socket.id + ' connected');
    socket.on('login', function (data) {
        names[socket.id] = data.username;
        allUsers[socket.id] = socket;
        // now check if sb is in queue
        findPeerForLoneSocket(socket);
    });
    socket.on('message', function (data) {
        var room = rooms[socket.id];
        socket.broadcast.to(room).emit('message', data);
    });
    socket.on('leave room', function () {
        var room = rooms[socket.id];
        socket.broadcast.to(room).emit('chat end');
        var peerID = room.split('#');
        peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
        // add both current and peer to the queue
        findPeerForLoneSocket(allUsers[peerID]);
        findPeerForLoneSocket(socket);
    });
    socket.on('disconnect', function () {
        var room = rooms[socket.id];
        socket.broadcast.to(room).emit('chat end');
        var peerID = room.split('#');
        peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
        // current socket left, add the other one to the queue
        findPeerForLoneSocket(allUsers[peerID]);
    });
});

P.S.

The code above got a bit messy in the end. It can be done better and I encourage you to do better job than I did. Having this material at hand, go through it step by step and try to understand. I think I commented most, if not all of it. Good luck.

Tl;dr

I am not even surprised. Here, read a comic

Share:
13,383
Faizan
Author by

Faizan

A computer science geek ! Portfolio I am a freelancer, web designer and front-end developer. Just say Hi ! www.faizanzahid.me

Updated on August 18, 2022

Comments

  • Faizan
    Faizan over 1 year

    I just started learning NodeJS and Socket.io. Until now I have this demo code, from official socket.io site:

    http://socket.io/demos/chat/

    I am able to get the unique client's ID of each user (socket) which connects, I am still trying to figure out, How can I make my code to only connect with 1 random user at a time when somebody runs the application. I just want to make random chat like Omegle (http://www.omegle.com/).

    Only two users should randomly connect and chat with each other till they re-run the app, if they come back they should get connected with someone else who is in the online queue.

    What changes do I need to do to have a similar behaviour?

    Update

    Added Client site code, main.js

    $(function() {
      var FADE_TIME = 150; // ms
      var TYPING_TIMER_LENGTH = 400; // ms
      var COLORS = [
        '#e21400', '#91580f', '#f8a700', '#f78b00',
        '#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
        '#3b88eb', '#3824aa', '#a700ff', '#d300e7'
      ];
    
      // Initialize variables
      var $window = $(window);
      var $usernameInput = $('.usernameInput'); // Input for username
      var $messages = $('.messages'); // Messages area
      var $inputMessage = $('.inputMessage'); // Input message input box
    
      var $loginPage = $('.login.page'); // The login page
      var $chatPage = $('.chat.page'); // The chatroom page
    
      // Prompt for setting a username
      var username;
      var connected = false;
      var typing = false;
      var lastTypingTime;
      var $currentInput = $usernameInput.focus();
    
    
      //Own Global
    
      var room = '';
    
      var socket = io();
    
      function addParticipantsMessage (data) {
        var message = '';
        if (data.numUsers === 1) {
         // message += "there's 1 participant";
         // Status Message
            message += "Waiting to connect with someone";
    
        } else {
         // message += "there are " + data.numUsers + " participants";
         //Status message update
          message = "You are connected to a stranger! Say Hey!";
        }
        log(message);
      }
    
      // Sets the client's username
      function setUsername () {
        username = cleanInput($usernameInput.val().trim());
    
        // If the username is valid
        if (username) {
          $loginPage.fadeOut();
          $chatPage.show();
          $loginPage.off('click');
          $currentInput = $inputMessage.focus();
    
          // Tell the server your username
          socket.emit('add user', username);
    
          // Own
          socket.emit('login', {'username' : 'Faizan'});
    
        }
      }
    
  • Faizan
    Faizan about 8 years
    Thanks a lot, I have tried to understand your code and ran it but it gives me error "Room undefined" Even though I declared it.. i am not sure why this is happening. For your info, I have added my client code in my question.. Hopefully you can help me what i am doing wrong. Many Thanks.. I will surely choose your answer as best
  • kecer
    kecer about 8 years
    @Faizan the error is on the server or the client? Where in the code does it occur?
  • Faizan
    Faizan about 8 years
    The error is coming on the client side.. as room var is being used on client in room = data.room; the error was coming on date.room as undefined.
  • kecer
    kecer about 8 years
    @Faizan yeah my fault. We are not sending room in 'connect' event, but in 'chat start'. I've modified the code in my answer
  • kecer
    kecer about 8 years
    @Faizan socket.disconnect() should work on client. Then just do create another connection. Either completly destroy socket, create it again and install handlers or just disconnect and then connect. see this. Although it indeed didn't take me long, asking different questions in comments can be counterproductive. Rather create new question or just read the docs/google. Good luck
  • iamkdblue
    iamkdblue over 5 years
    @kecer nice answer Thanks !
  • Sriram R
    Sriram R about 4 years
    Just one small note on technicality. Socket-io is not a Websockets implementation. It does not work over the ws:. It also uses long polling which is it's default implementation. Only if the client supports and conditions are fine, the call is upgraded to a websocket connection. socket io works over http
  • Karan Kumar
    Karan Kumar over 3 years
    @kecer Can you PLEASE help me out stackoverflow.com/questions/63442767/…