一次与两个用户随机聊天(Socket.io)

Posted

技术标签:

【中文标题】一次与两个用户随机聊天(Socket.io)【英文标题】:Random chat with two users at a time (Socket.io) 【发布时间】:2016-05-24 23:03:34 【问题描述】:

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

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

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

只有两个用户应该随机连接并互相聊天,直到他们重新运行应用程序,如果他们回来,他们应该与在线队列中的其他人建立连接。

我需要做哪些改变才能有类似的行为?

更新

添加了客户端站点代码,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');

    
  

【问题讨论】:

如果我要设计这样一个应用程序,我会这样做:当用户连接时,将他(套接字)添加到全局数组,这将代表等待队列。当第二个客户端连接时,应用程序会发现队列中有人在等待。随机生成一些字符串并使用该名称创建room 并将这两个用户添加到那里。向两个套接字发送他们的聊天已在指定房间中准备好的事件。之后,当其中一个客户端断开连接时,您可以再次将第二个客户端添加到等待队列中。 @kecer 好吧,谢谢您的回复.. 但这种逻辑可能会奏效。我如何实际实施它?你能给我一个编码片段的例子吗?或者指点我一些教程。我不太确定如何动态创建房间并将 2 个用户添加到该房间。如何将事件发送到两个套接字。很抱歉我还在学习:'(谢谢你的帮助。 恐怕您需要的不仅仅是官方示例,我们可以为您提供更多帮助。我们可以帮助您解决编码错误,但不会为您生成代码。 google 一下,例子很多。快速查询reveals,但请注意,我没有阅读它,它可能已经过时了。不过可以给你一些方向。 【参考方案1】:

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

相关的介绍

您应该已经知道 Socket.io 是一个 WebSocket 实现。 WebSockets (WS) 允许服务器随时发送数据,只要连接仍处于打开状态,这与旧方式相反:客户端一直在查询服务器上是否有更新。

你可以想象一个女人和一个男人在派对结束时:“谢谢你今晚,我很想很快再重复一遍。你能给我你的电话号码吗?” - 问那个老人。 “呃,你知道吗,最好把你的给我,我保证我会打电话给你的!” 如果女孩给他她的电话号码,他会每天打几次电话,询问她是否会去某个地方(她会回答不)。反过来说,只有她想去,他才会去的时候,她才会给他打电话。他当然会。 我有点得意忘形,但你明白了。女人是服务员,男人是客户。

了解什么很重要

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

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

接下来的事情,取自您的代码

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

重要的是要知道,这里的 socket 基本上代表一个连接的客户端(在一个命名空间中,但可能在多个房间中)。你可以用它做各种各样的事情,最值得注意的是:

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

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

我们可以定义全局变量,例如队列。或者如果你想问。重点是,它可以是一个数组,用于存储 socket,或者更确切地说是客户端,这些客户端当前不在聊天室中。

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

你需要什么活动

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

事件名称 -> 数据给定

在服务器端处理的事件

login -> 用户名(应该如何称呼用户),如果您想启用注册,可能是密码 message -> 文本(正在发送的消息的内容) 离开房间 -> 房间名称 断开

在客户端处理的事件

连接 聊天开始 -> name(第二个客户的名字),房间(这样我们就可以离开了) 聊天结束 -> 如果您想同时只允许一个聊天,则不需要数据。如果有多个聊天,您还应该包括哪个聊天被关闭 断开

开始之前的最后一点

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

工作流程

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

    在服务器端

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

    在客户端

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

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

将他们都添加后,您需要通知他们他们的聊天已经开始,并向他们发送其他同伴的姓名。您将发出事件“聊天开始”。

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

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

注意:我现在对 socketio 代码感到很困惑。查看at this 并告诉我,如果socket.rooms 是an array 或an object(socket.rooms[room] = room; ?? 为什么?)

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

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

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

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

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

所以,如果你读到这里,你应该得到一些实际的代码。虽然我说的是实际,但它实际上是未经测试的。但你知道,它应该像这样工作。

一些代码

客户端

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 处理程序。

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

附言

上面的代码最后有点乱。它可以做得更好,我鼓励你做得比我做得更好。手头有这些材料,一步一步地阅读它并尝试理解。我想我评论最多,如果不是全部的话。祝你好运。

Tl;博士

我什至不感到惊讶。在这里,阅读comic

【讨论】:

非常感谢,我试图理解您的代码并运行它,但它给了我错误“房间未定义”,即使我声明了它..我不知道为什么会这样。为了您的信息,我在我的问题中添加了我的客户端代码。希望您能帮助我做错了什么。非常感谢.. 我一定会选择你的答案为最佳 @Faizan 错误是在服务器还是客户端?它出现在代码的什么地方? 客户端出现错误..因为房间变量正在房间= data.room中的客户端上使用;错误出现在 date.room 上,未定义。 @Faizan 是我的错。我们不是在“连接”事件中发送房间,而是在“聊天开始”中。我已经修改了答案中的代码 只是关于技术性的一个小注释。 Socket-io 不是 Websockets 实现。它不适用于ws:。它还使用长轮询,这是它的默认实现。只有在客户端支持且条件良好的情况下,调用才会升级为websocket连接。 socket io 通过 http 工作

以上是关于一次与两个用户随机聊天(Socket.io)的主要内容,如果未能解决你的问题,请参考以下文章

一次加入多个房间 Socket.io

node.js socket.io 应用程序 - 如何将某人踢出聊天室?

使用 MERN 堆栈和 socket.io 进行私人聊天

多个侦听器和 removeListener 删除所有内容 Socket.io

在 now.js/socket.io 聊天中增强安全性

使用 Socket.IO 开发聊天室