120

I'm starting with socket.io + node.js, I know how to send a message locally and to broadcast socket.broadcast.emit() function:- all the connected clients receive the same message.

Now, I would like to know how to send a private message to a particular client, I mean one socket for a private chat between 2 person (Client-To-Client stream). Thanks.

Sangram Singh
  • 7,161
  • 15
  • 50
  • 79
Nizar B.
  • 3,098
  • 9
  • 38
  • 56
  • 1
    Sorry psiphi75 but this link doesn't reply to my answer, isn't a duplicate question. – Nizar B. Jul 05 '13 at 04:13
  • 2
    @psiphi75, it's no way a duplicate – softvar Oct 12 '13 at 14:34
  • 1
    Can't stand people like @psiphi. Are you even a developer? How does an HTML5 specific question relate to a standalone library? And for what it's worth, WebSockets are NOT Socket.io. Socket.io is a library that can USE WebSockets, but I digress. This is also a more specific question relating to the library about sending data to only specific clients, not about the technology itself. – Levi Roberts Jun 25 '15 at 02:31
  • Possible duplicate of [Send message to specific client with socket.io and node.js](https://stackoverflow.com/questions/4647348/send-message-to-specific-client-with-socket-io-and-node-js) – bugwheels94 Jul 03 '17 at 16:27
  • @bugwheels94 Not really, this post is from 2011 and since nodejs had loads of changes code-wise. this post is definitely a valid question/answer for this issue. – Nizar B. Jul 03 '17 at 16:50
  • @Katcha There is an option called bounty in which there is again an option called "Current answers are outdated". Generally, this method should be used for this type of case – bugwheels94 Jul 04 '17 at 03:45
  • @bugwheels94 Not sure what do you mean again but anyway. – Nizar B. Jul 04 '17 at 15:43

8 Answers8

322

You can use socket.io rooms. From the client side emit an event ("join" in this case, can be anything) with any unique identifier (email, id).

Client Side:

var socket = io.connect('http://localhost');
socket.emit('join', {email: user1@example.com});

Now, from the server side use that information to create an unique room for that user

Server Side:

var io = require('socket.io').listen(80);

io.sockets.on('connection', function (socket) {
  socket.on('join', function (data) {
    socket.join(data.email); // We are using room of socket io
  });
});

So, now every user has joined a room named after user's email. So if you want to send a specific user a message you just have to

Server Side:

io.sockets.in('user1@example.com').emit('new_msg', {msg: 'hello'});

The last thing left to do on the client side is listen to the "new_msg" event.

Client Side:

socket.on("new_msg", function(data) {
    alert(data.msg);
}

I hope you get the idea.

stan
  • 4,885
  • 5
  • 49
  • 72
az7ar
  • 5,187
  • 2
  • 19
  • 23
  • 1
    that client side looks like it would not be tied to a particular room. – chovy Dec 09 '13 at 04:24
  • client side does not have to know about rooms, your server subscribes your client to specific rooms. Client just has to listen on events and server side makes sure that client gets the events of client's subscribed rooms. – az7ar Dec 09 '13 at 10:06
  • 3
    please change this line io.socket.in('user1@example.com').emit('new_msg', {msg: 'hello'}); like this io.sockets.in('user1@example.com').emit('new_msg', {msg: 'hello'}); – silvesterprabu Apr 02 '14 at 10:46
  • 50
    This answer is _much_ better than the currently accepted answer. Here's why: 1) You don't have to manage and clean up the global array of clients. 2) It works even if a user has multiple tabs open on the same page. 3) It can be easily extended to work with a node cluster (multiple processes) with `socket.io-redis`. – Daniel Que Aug 13 '14 at 00:54
  • I know this solution works very well and is the one I'm also actually using but since the premise here is that you create a unique room for each user (i.e. no room ever has more than one user connected to it), is there any caveat to creating a one-to-one correspondence between a live user and room (like how it is done here) , versus creating a single room and connecting all users to that room? Of course you have to manage the socket connections in the latter option as has been pointed out but in the first option, if say I have 1000 live users then this would mean I have to create 1000 rooms. – turntwo May 05 '15 at 06:49
  • 2
    how do you make `{email: user1@example.com}` different for all connection wouldnt all clients that go to the app have user `user1@example.com` how would i make it user `user2@example.com` for the second client that connects so I could have different rooms. sorry if this is an annoying question. – jack blank Aug 09 '15 at 08:35
  • 2
    loved the solution, but I believe security is compromised here. What if I change the email in the script on the client side. Now I am able to read other private messages too. What are you saying? – Gopinath Shiva Mar 03 '16 at 04:03
  • 3
    it does not have to be email I mentioned any unique identifier – az7ar Mar 06 '16 at 08:47
  • @az7ar hi, could you yo helo me on this topic? http://stackoverflow.com/questions/38817680/nodejs-could-not-detect-online-soket – mahdi pishguy Aug 07 '16 at 19:55
  • 1
    "if say I have 1000 live users then this would mean I have to create 1000 rooms." from @turntwo . Is it good to create so many rooms for 1-to-1 chat. Also its oct'16 . is there any new/better approach for this. – Aman Gupta Oct 20 '16 at 06:17
  • 1
    @GopinathShiva That is a solid point raised which could be easily missed. Hopefully by the time the server sends back the response, it would've authenticated the client and if they have access to the given room. Otherwise it should not fetch the data from the room. – lmenus Aug 27 '17 at 17:07
  • @GopinathShiva You could create a hashed roomId. – kiwicomb123 Aug 30 '18 at 22:28
  • 2
    My question is How will the second user join the room as the code above is joining the room with their own emails and it seems their is nothing common to join – Abhi Burk Oct 13 '19 at 16:44
  • This aproachj looks nice but,seems to me than with socket.io we can only imitate private message logic,not to implement real one,must be other tool for js full chat apps.Cheers. – Goran_Ilic_Ilke Sep 10 '20 at 09:14
112

When a user connects, it should send a message to the server with a username which has to be unique, like an email.

A pair of username and socket should be stored in an object like this:

var users = {
    'userA@example.com': [socket object],
    'userB@example.com': [socket object],
    'userC@example.com': [socket object]
}

On the client, emit an object to the server with the following data:

{
    to:[the other receiver's username as a string],
    from:[the person who sent the message as string],
    message:[the message to be sent as string]
}

On the server, listen for messages. When a message is received, emit the data to the receiver.

users[data.to].emit('receivedMessage', data)

On the client, listen for emits from the server called 'receivedMessage', and by reading the data you can handle who it came from and the message that was sent.

Adam
  • 2,204
  • 2
  • 15
  • 15
  • 32
    If you use this solution then you have to understand: 1. When user disconnects you have to clean up 'users' object 2. It doesnt support second connection - for instance from another browser. So if user connects from another browser - old connection will be overriden. – Vladimir Kurijov Dec 05 '13 at 06:03
  • 1
    how would you store the socket object in a datastore? I'm assuming this doesn't work if you have more than one node process. – chovy Dec 09 '13 at 04:23
  • @chovy you have to use redis. Check this https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO – Vladimir Kurijov Jan 14 '14 at 14:18
  • 13
    I'd suggest to not use this solution. I ended up wasting a lot of time trying to overcome the limitations of this approach. See [@az7ar's solution](http://stackoverflow.com/a/17535099/2901178) and this [explanation for why it's better](http://stackoverflow.com/questions/17476294/how-to-send-a-message-to-a-particular-client-with-socket-io#comment39387846_17535099). – Daniel Que Aug 13 '14 at 01:07
  • @DanielQue +1. Way better to used native functionality. Thanks! – Aaron Lelevier Feb 12 '15 at 15:24
  • @AronYsidoro Glad my internet comment helped someone out! – Daniel Que Feb 12 '15 at 19:02
  • `{ to:[the other receiver's username as a string], from:[the person who sent the message as string], message:[the message to be sent as string] }` why as sting ?? –  Sep 17 '15 at 18:15
  • loved the solution, but I believe security is compromised here. What if I change the email in the script on the client side. Now I am able to read other private messages too. What are you saying? – Gopinath Shiva Mar 03 '16 at 04:24
  • The purpose of my answer was only to give people an understanding of how to transmit messages to others via a server in the most simple way I could think of without relying on the abstractions of rooms. I do not suggest that this is a complete solution for most applications. – Adam Apr 23 '16 at 16:07
  • @GopinathShiva In a real application you would need to implement some type of user authentication when establishing the connection. – Adam Apr 23 '16 at 16:14
  • @Adam hi, could you yo helo me on this topic? http://stackoverflow.com/questions/38817680/nodejs-could-not-detect-online-soket – mahdi pishguy Aug 07 '16 at 19:55
45

SURE: Simply,

This is what you need :

io.to(socket.id).emit("event", data);

whenever a user joined to the server, socket details will be generated including ID. This is the ID really helps to send a message to particular people.

first we need to store all the socket.ids in array,

var people={};

people[name] =  socket.id;

here name is the receiver name. Example:

people["ccccc"]=2387423cjhgfwerwer23;

So, now we can get that socket.id with the receiver name whenever we are sending message:

for this we need to know the receivername. You need to emit receiver name to the server.

final thing is:

 socket.on('chat message', function(data){
io.to(people[data.receiver]).emit('chat message', data.msg);
});

Hope this works well for you.

Good Luck!!

antzshrek
  • 9,276
  • 5
  • 26
  • 43
Trojan
  • 1,396
  • 2
  • 16
  • 19
  • Can you create a client side and server side code.? I am new in scoket programming. hmahajan.dmi@gmail.com – Harish Mahajan Dec 10 '16 at 11:33
  • @HARISH, refer below link ,explains what really happening from client side..i hope this helps you more..http://socket.io/get-started/chat/ – Trojan Dec 15 '16 at 09:36
  • 2
    But what if the user is chatting with 2 others at the same time. Won't the message from both user come in both windows? – Sharan Mohandas May 16 '17 at 10:16
  • 2
    If there are more than two users, it won't work right – Mohhamad Hasham May 03 '18 at 19:18
  • 1
    each time users connects socket id changes so in case user opens your app in multiple tabs it will get response to one which you have stored and sent to other socket connections – Vikas Kandari May 16 '20 at 05:17
  • this is not working with socket.io latest version(3.0.3) – Deepak saini Nov 27 '20 at 08:28
  • if it is a group just use the unique group id, but if it is between two users only one user can enter this chat first - so check for combination of both user id existence - if the room does not exist create one - use the combination and reverse combination of both user id to be checked – DragonFire Jan 18 '21 at 11:27
  • this method possible to cross domain emit event? io.to(socket.id).emit("event", data); – maranR Oct 11 '22 at 18:17
13

You can refer to socket.io rooms. When you handshaked socket - you can join them to named room, for instance user.#{userid}.

After that, you can send private message to any client by convenient name, for instance:

io.sockets.in('user.125').emit('new_message', {text: "Hello world"}) 

In operation above we send "new_message" to user "125".

thanks.

Crann Moroney
  • 356
  • 2
  • 4
  • 13
Vladimir Kurijov
  • 382
  • 2
  • 10
  • Hi mate, thanks you for your first answer, I will try it and let you know about that, because I don't want to build a chat but a private messenger like you can use on Facebook network. – Nizar B. Jul 05 '13 at 14:29
  • Can someone help me with this matter, I'm really stuck, I would like to add a username linked to my socket and allow my application to send a message to a specific user, no chat room, it's a Facebook like messenger. Thanks if you know! – Nizar B. Jul 06 '13 at 12:15
  • At first you have to understand that this username have to be unique across all users. And the second one - didn't you forget to subscribe for emitted message on the client side? – Vladimir Kurijov Jul 08 '13 at 11:49
  • The problem with this is that you loose the ability to use callbacks, as they are not allowed in broadcasts, which is what you really are doing here, a broadcast to his private room. – bluehallu May 08 '14 at 10:43
  • @VladimirKurijov, Mr Katcha doesn't want to use Rooms and I agree it's not the solution to his problem. – miksiii Feb 16 '15 at 13:52
  • I think than people in explanation cant see main and esential difference between "rooms" && "namespace-s" and real private chat,i am trying to make that job with node ,socket.io && react whithout success months. – Goran_Ilic_Ilke Mar 23 '20 at 12:54
6

In a project of our company we are using "rooms" approach and it's name is a combination of user ids of all users in a conversation as a unique identifier (our implementation is more like facebook messenger), example:

|id | name |1 | Scott |2 | Susan

"room" name will be "1-2" (ids are ordered Asc.) and on disconnect socket.io automatically cleans up the room

this way you send messages just to that room and only to online (connected) users (less packages sent throughout the server).

Rami
  • 61
  • 1
  • 1
  • 1
    I am surprised no one has looked at your solution... I posted the answer after that only I saw your answer... – DragonFire Jan 19 '21 at 08:42
6

As the az7ar answer is beautifully said but Let me make it simpler with socket.io rooms. request a server with a unique identifier to join a server. here we are using an email as a unique identifier.

Client Socket.io

socket.on('connect', function () {
  socket.emit('join', {email: user@example.com});
});

When the user joined a server, create a room for that user

Server Socket.io

io.on('connection', function (socket) {
   socket.on('join', function (data) {    
    socket.join(data.email);
  });
});

Now we are all set with joining. let emit something to from the server to room, so that user can listen.

Server Socket.io

io.to('user@example.com').emit('message', {msg: 'hello world.'});

Let finalize the topic with listening to message event to the client side

socket.on("message", function(data) {
  alert(data.msg);
});

The reference from Socket.io rooms

Lalit Mohan
  • 2,685
  • 2
  • 16
  • 16
  • 3
    it seems your answer is a copy of @az7ar's [answer](https://stackoverflow.com/a/17535099/7948109) from below, please give credits if you are using someone's answer with modification – Rahul Gaur Oct 31 '20 at 05:34
  • Thank you @RahulGaur, Yes it is similar not a copy. But definitely my answer has changed. He used `in` to emit an event. But in my case, I have used `to` to emit an event. I hope you have seen this before – Lalit Mohan Oct 31 '20 at 14:46
  • 1
    That's good to hear it's not a copy, but it seems inspired, so it is a good things to give credit – Rahul Gaur Nov 01 '20 at 10:51
  • 1
    Noted Brother, Will do it definitely Thank you @RahulGaur. – Lalit Mohan Nov 02 '20 at 03:41
1

Here is the full solution for Android Client + Socket IO Server (Lot of code but works). There seems to be lack of support for Android and IOS when it comes to socket io which is a tragedy of sorts.

Basically creating a room name by joining user unique id from mysql or mongo then sorting it (done in Android Client and sent to server). So each pair has a unique but common amongst the pair room name. Then just go about chatting in that room.

For quick refernce how room is created in Android

 // Build The Chat Room
        if (Integer.parseInt(mySqlUserId) < Integer.parseInt(toMySqlUserId)) {
            room = "ic" + mySqlUserId + toMySqlUserId;
        } else {
            room = "ic" + toMySqlUserId + mySqlUserId;
        }

The Full Works

Package Json

"dependencies": {
    "express": "^4.17.1",
    "socket.io": "^2.3.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.6"
  }

Socket IO Server

app = require('express')()
http = require('http').createServer(app)
io = require('socket.io')(http)

app.get('/', (req, res) => {

    res.send('Chat server is running on port 5000')
})

io.on('connection', (socket) => {

    // console.log('one user connected ' + socket.id);

    // Join Chat Room
    socket.on('join', function(data) {

        console.log('======Joined Room========== ');
        console.log(data);

        // Json Parse String To Access Child Elements
        var messageJson = JSON.parse(data);
        const room = messageJson.room;
        console.log(room);

        socket.join(room);

    });

    // On Receiving Individual Chat Message (ic_message)
    socket.on('ic_message', function(data) {
        console.log('======IC Message========== ');
        console.log(data);

        // Json Parse String To Access Child Elements
        var messageJson = JSON.parse(data);
        const room = messageJson.room;
        const message = messageJson.message;

        console.log(room);
        console.log(message);

        // Sending to all clients in room except sender
        socket.broadcast.to(room).emit('new_msg', {
            msg: message
        });

    });

    socket.on('disconnect', function() {
        console.log('one user disconnected ' + socket.id);
    });

});

http.listen(5000, () => {

    console.log('Node app is running on port 5000')
})

Android Socket IO Class

public class SocketIOClient {

    public Socket mSocket;

    {
        try {
            mSocket = IO.socket("http://192.168.1.5:5000");
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    public Socket getSocket() {
        return mSocket;
    }
}

Android Activity

public class IndividualChatSocketIOActivity extends AppCompatActivity {

    // Activity Number For Bottom Navigation Menu
    private final Context mContext = IndividualChatSocketIOActivity.this;

    // Strings
    private String mySqlUserId;
    private String toMySqlUserId;

    // Widgets
    private EditText etTextMessage;
    private ImageView ivSendMessage;

    // Socket IO
    SocketIOClient socketIOClient = new SocketIOClient();
    private String room;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        // Widgets
        etTextMessage = findViewById(R.id.a_chat_et_text_message);
        ivSendMessage = findViewById(R.id.a_chat_iv_send_message);

        // Get The MySql UserId from Shared Preference
        mySqlUserId = StartupMethods.getFromSharedPreferences("shared",
                                                              "id",
                                                              mContext);

        // Variables From Individual List Adapter
        Intent intent = getIntent();

        if (intent.hasExtra("to_id")) {

            toMySqlUserId = Objects.requireNonNull(Objects.requireNonNull(getIntent().getExtras())
                                                          .get("to_id"))
                                   .toString();
        }

        // Build The Chat Room
        if (Integer.parseInt(mySqlUserId) < Integer.parseInt(toMySqlUserId)) {
            room = "ic" + mySqlUserId + toMySqlUserId;
        } else {
            room = "ic" + toMySqlUserId + mySqlUserId;
        }

        connectToSocketIO();

        joinChat();

        leaveChat();

        getChatMessages();

        sendChatMessages();

    }

    @Override
    protected void onPause() {
        super.onPause();

    }

    private void connectToSocketIO() {

        socketIOClient.mSocket = socketIOClient.getSocket();
        socketIOClient.mSocket.on(Socket.EVENT_CONNECT_ERROR,
                                  onConnectError);
        socketIOClient.mSocket.on(Socket.EVENT_CONNECT_TIMEOUT,
                                  onConnectError);
        socketIOClient.mSocket.on(Socket.EVENT_CONNECT,
                                  onConnect);
        socketIOClient.mSocket.on(Socket.EVENT_DISCONNECT,
                                  onDisconnect);
        socketIOClient.mSocket.connect();
    }

    private void joinChat() {

        // Prepare To Send Data Through WebSockets
        JSONObject jsonObject = new JSONObject();

        // Header Fields
        try {

            jsonObject.put("room",
                           room);

            socketIOClient.mSocket.emit("join",
                                        String.valueOf(jsonObject));

        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    private void leaveChat() {
    }

    private void getChatMessages() {

        socketIOClient.mSocket.on("new_msg",
                                  new Emitter.Listener() {
                                      @Override
                                      public void call(Object... args) {
                                          try {
                                              JSONObject messageJson = new JSONObject(args[0].toString());
                                              String message = String.valueOf(messageJson);

                                              runOnUiThread(new Runnable() {
                                                  @Override
                                                  public void run() {
                                                      Toast.makeText(IndividualChatSocketIOActivity.this,
                                                                     message,
                                                                     Toast.LENGTH_SHORT)
                                                           .show();
                                                  }
                                              });
                                          } catch (JSONException e) {
                                              e.printStackTrace();
                                          }
                                      }
                                  });
    }

    private void sendChatMessages() {

        ivSendMessage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String message = etTextMessage.getText()
                                              .toString()
                                              .trim();

                // Prepare To Send Data Thru WebSockets
                JSONObject jsonObject = new JSONObject();

                // Header Fields
                try {
                    jsonObject.put("room",
                                   room);

                    jsonObject.put("message",
                                   message);

                    socketIOClient.mSocket.emit("ic_message",
                                                String.valueOf(jsonObject));

                } catch (JSONException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    public Emitter.Listener onConnect = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(IndividualChatSocketIOActivity.this,
                                   "Connected To Socket Server",
                                   Toast.LENGTH_SHORT)
                         .show();

                }
            });

            Log.d("TAG",
                  "Socket Connected!");
        }
    };

    private Emitter.Listener onConnectError = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                }
            });
        }
    };
    private Emitter.Listener onDisconnect = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                }
            });
        }
    };

}

Android Gradle

// SocketIO
implementation ('io.socket:socket.io-client:1.0.0') {
    // excluding org.json which is provided by Android
    exclude group: 'org.json', module: 'json'
}
DragonFire
  • 3,722
  • 2
  • 38
  • 51
0

Instead of making rooms (as you only want to send the message to 1 person) what I did was make an in-memory array:

clientSessions: Map<string, UserIdentifier> = new Map()

I did it this way for my use-case where I want to get the socket ID I want based on an identifier I set. The key in the map will be the identifier I set, and the value includes the ID of that socket connected. See server-side code below.

Client-side:

const socket = io('http://localhost:3200', {query: {randomKeyID: '123'}});

Server-side:

  handleDisconnect(client: Socket) {
    const query = client.handshake.query;
    const randomKeyID = <string> query.randomKeyID
    this.clientSessions.delete(randomKeyID);
    console.log(`Client disconnected: ${client.id}`);
  }

  handleConnection(client: Socket, ...args: any[]) {
    const query = client.handshake.query;
    const user: UserIdentifier = {
      id: client.id,
      randomKeyID: <string> query.randomKeyID
    }
    this.clientSessions.set(user.randomKeyID, user);
    console.log(`Client connected: ${client.id}`);
  }

Added this in server-side as well to easily access the websocket (type Server is from socket.io):

  @WebSocketServer()
  server: Server;

So, if I want to send to a particular client, I can:

const { randomKeyID } = this.clientSessions.get(something);
this.server.to(randomKeyID).emit('clientListener', 'Hello');
heneftees
  • 13
  • 2
  • And in case server-side restarts, client-side continues to search for your server and establishes a new connection. Your in-memory array is cleared on server-restarts, so no problem there – heneftees Sep 15 '22 at 09:17
  • (edited) Oops my code under the context of NestJS; still hope logic helps though! – heneftees Sep 15 '22 at 09:27