本文介绍了一次与两个用户随机聊天 (Socket.io)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚开始学习 NodeJS 和 Socket.io.到目前为止,我有这个演示代码,来自官方 socket.io 站点:

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/

我能够获得连接的每个用户(套接字)的唯一客户端 ID,我仍在尝试弄清楚,当有人运行应用程序时,如何使我的代码一次只与 1 个随机用户连接.我只想像 Omegle 一样进行随机聊天(http://www.omegle.com/).

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?

添加客户端代码,ma​​in.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'});

    }
  }

推荐答案

虽然我会关闭这个问题,因为它太模糊了,但我觉得有必要给你一些见解,因为我在过去几年里对 websockets 做了太多工作(虽然socketio 和 nodejs 没有那么多).我想一些简单的指南和相关链接可以帮助你.首先,

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,

您应该已经知道 Socket.io 是一个 WebSocket 实现.WebSockets (WS) 允许服务器随时发送数据,只要连接仍然打开,而不是旧的方式:客户端一直查询服务器上是否有更新.

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.

(绝对基础,你应该知道这个=>)当客户端连接到您的服务器时,应该为他提供一个 html 页面和一些 javascript,它们建立到您的 WS 服务器的连接.在您发布的代码中,Express 用作 http 服务器.检查 这个例子 看看你应该如何给用户 html&js.

(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.

您还会注意到命名空间rooms 在这些教程中的大多数.这些用于将用户分成子类别.一台服务器可能包含多个命名空间(默认只有一个),每个命名空间可能包含多个房间.您可能不需要为命名空间而烦恼,一个命名空间就足够了.但是,您需要了解房间(稍后会详细介绍).

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).

接下来,取自你的代码

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

重要的是要知道,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:

  • 在其上安装事件处理程序(这就是您在调用 socket.on(event, handler(data)) 时所做的事情
  • 使用 socket.emit(event, data)
  • 向它发送事件
  • 使用socket.broadcast.emit(event, data)
  • 向所有用户发送广播事件
  • 分别使用 socket.join(room)socket.leave(room) 将其添加到 room 或从 room 中删除.
  • 像处理普通变量一样使用 - 将其存储在您想要的任何位置,然后重复使用
  • 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

您在代码中看到 numUsers 的定义了吗?这是一个与所有客户端共享的全局变量,因为 nodejs 是单线程.在示例中,它在事件处理程序之一内递增.认为我们可以使用类似的东西吗?.

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.

我们可以定义全局变量,例如队列.或者 Q 如果你愿意.重点是,它可以是用于存储 socket 的数组,或者更确切地说是当前不在聊天室中的客户端.

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.

在本节的末尾,我想指出另一件显而易见的事情.io.on('connection', handler);io 对象(WS 服务器)上发生的 'connection' 事件定义了一个 事件处理程序.每次客户端连接到您的 WS 服务器时都会触发此事件(在您的情况下,通过在客户端浏览器中运行的 javascript).该方法的参数是 socket,正是在这个方法中,您应该为每个客户端添加事件侦听器(您已经在代码中这样做了,特别是处理事件新消息"、添加用户"、输入"、停止输入"和断开连接").

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').

这实际上取决于您希望应用的复杂程度.在我看来,最低限度是(请注意,您可以更改事件名称,但断开连接"应保持断开连接"):

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'):

事件名称 -> 给出的数据

  • 登录 -> 用户名(应该如何称呼用户),如果您想启用注册,可能需要密码
  • message -> 文本(发送消息的内容)
  • 离开房间 -> 房间名称
  • 断开连接
  • 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
  • 连接
  • 聊天开始 -> 名字(第二个客户的名字),房间(所以我们可以离开它)
  • 聊天结束 -> 如果您只想同时允许一个聊天,则不需要任何数据.如果有多个聊天,您还应该包括关闭的聊天
  • 断开连接
  • 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

这只是一个粗略的草图.一路上有多个不同的十字路口,你走哪条路主要取决于你对应用程序的想法.如果您想同时打开多个聊天,则需要进行一些修改.如果您想让两个以上的人连接到同一个聊天中,情况也是如此.在这里,我将描述最简单的情况,一次聊天,对人,无需注册.从你的帖子来看,可能是你想要的.可能是错的.

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.

用户在他们的网络浏览器中打开您的页面.你为他们提供 html 和 javascript.javascript 将开始与您的 websocket 服务器的新连接.此外,此时应定义所需事件的处理程序.建立连接后,会发生这种情况:

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. 在服务器端

io.on('connection', handler) 将被触发.只会安装新套接字的适当处理程序,此时不执行任何其他操作.

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

  1. 在客户端

socket.on('connect', handler) 将被触发.客户此时应该将用户名存储在某处.如果没有,没问题.连接将存活相当长的一段时间.您可以在连接后随时调用 socket.emit('login', {'username':name) (在下面的示例中,我设置了变量 connected,默认为 false 但将在建立连接后立即设置为 true.)

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.)

在您从客户端发送 login 事件后,服务器会注册它并将其保存在某处.可能性是无限的,在这种情况下,我将创建将 socket.id 映射到用户名的全局字典.之后,用户套接字应该与另一个配对或添加到队列中.因此,如果队列为空,只需将套接字附加到全局变量(它不必是一个数组,因为我们会将第一个可用的套接字配对在一起,但是您可能想要实现用户的一些历史记录,这样他们就不会再次与同一个人建立联系).如果队列不为空,我们从 Q 中拉出一个套接字并将它们添加到同一个房间.房间名称可以是随机的,也可以是任何你想要的,我会用 (socket1.id+'#'+socket2.id (如果你想在一个聊天中拥有更多的用户,这将不得不改变).

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'.

客户端将捕获事件并打开新窗口.之后,每当用户键入内容并发送它时,客户端都会发出带有有效负载 {'message': user_inserted_text} 的事件 'message'.服务器将在 .on('message' 处理程序和 broadcast 到房间.注意:

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:

广播意味着向除启动它的套接字之外的其他所有人发送消息.

注意:我现在对 socketio 代码真的很困惑.看看这个并告诉我,如果socket.rooms是 一个数组一个对象 (socket.rooms[room] = room; ?? 为什么?)

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?)

为了避免处理这种不直接的代码,让我们创建另一个全局对象 rooms,它将为我们存储房间名称.我们将在那里映射 socket.id -> roomName.

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.

所以当消息来的时候,我们可以通过调用rooms[socket.id]来获取房间的名字.然后我们像这样广播消息:

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);

其中 data 是我们从发送者那里收到的,因此对象 {'text': 'some nice message'}.然后你的对等方会收到它(你不会)并显示它(你应该在发送时显示它).

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).

所以聊天就这样持续了一段时间,然后其中一个用户决定他要离开/与其他人聊天.他们将关闭窗口,客户端将发出事件离开房间".服务器将捕获它并将其发送给她/他的对等方已断开连接的另一方.如果客户端断开连接,也会发生同样的情况.一切都关闭后,将两个用户添加到队列(或仅一个,如果另一个已与服务器断开连接).在我的代码中,我不会确保它们不会再次配对.那是让 OP 进行编码(不难).

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.

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 = '';
    }
};

服务器端

不包括初始要求和 html/js 服务.仅包括全局定义和主 io 处理程序.

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.

我什至不感到惊讶.在这里,阅读漫画

I am not even surprised. Here, read a comic

这篇关于一次与两个用户随机聊天 (Socket.io)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-18 08:16
查看更多