实时聊天、消息处理 - Socket.io、PHP、MySQL、Apache

6
我在Web开发方面是一个初学者。最近,我一直在开发一个基于PHP和JS/jQuery(我没有使用任何框架)的实时聊天网站。目前,我的设置只是简单的AJAX轮询,显然不如我想要的那么好。我的数据库是MYSQL数据库。
我已经了解了WebSockets,并且我的新初始计划是创建一个NodeJS服务器与Socket.io一起处理消息(如何集成nodeJS + Socket.IO和PHP?),并考虑将这些消息存储在MySQL数据库中(MySQL with Node.js)。
这是我目前的情况(不多,我想在真正进展之前澄清如何进展)。这是我的测试设置,实际聊天中使用的HTML显然有所不同。
Node.js服务器:
// NODE
var socket = require( 'socket.io' );
var express = require( 'express' );
var https = require( 'https' );
var http = require( 'http'); //Old
var fs = require( 'fs' );

var app = express();

//Working HTTPS server 
var server = https.createServer({ 
               key: fs.readFileSync('/etc/letsencrypt/live/%site%/privkey.pem'),
               cert: fs.readFileSync('/etc/letsencrypt/live/%site%/fullchain.pem')
             },app);

// var server = https.createServer( app ); Won't work cause no cert. 

var io = socket.listen( server );
console.log("Server Started"); 
io.sockets.on( 'connection', function( client ) {
    console.log( "New client !" );

    client.on( 'message', function( data ) {
        console.log( 'Message received ' + data); //Logs recieved data
        io.sockets.emit( 'message', data); //Emits recieved data to client.
    });
});
server.listen(8080, function() {
    console.log('Listening');
});

JS客户端脚本:

var socket = io.connect('https://%site%:8080');



document.getElementById("sbmt").onclick = function () {

socket.emit('message', "My Name is: " + document.getElementById('nameInput').value + " i say: " + document.getElementById('messageInput').value); 

};

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

我的超级简单的测试HTML:

<form id="messageForm">
<input type="text" id="nameInput"></input>
<input type="text" id="messageInput"></input>
<button type="button" id="sbmt">Submits</button>
</form>

PHP需要一些解释- 当有人连接到我的网站时,我运行session_start()。这是因为我想要像匿名会话这样的东西。我通过$_SESSION变量区分已登录和匿名用户。匿名用户将具有设置为true的$_SESSION ['anon'],并且不会设置$_SESSION ['username']。已登录用户将明确相反。

当涉及聊天时-对已登录用户和匿名用户均可用。当用户是匿名时,从数据库或随机名称生成随机用户名。当用户登录时,选择他自己的用户名。现在我的Ajax轮询系统工作方式如下:

用户输入消息(在当前聊天解决方案中,而不是我上面发送的测试HTML)并按Enter键,然后发出AJAX调用以下函数:

  function sendMessage($msg, $col) {
    GLOBAL $db;
      $un = "";


    if (!isset($_SESSION['username'])) {

        $un = self::generateRandomUsername();

    } else {
    $un = $_SESSION['username'];
    }

    try {
      $stmt = $db->prepare('INSERT INTO chat (id, username, timestamp, message, color) VALUES (null, :un, NOW(), :msg, :col)');
      $stmt->bindParam(':un', $un, PDO::PARAM_STR);
      $stmt->bindValue(':msg', strip_tags(stripslashes($msg)), PDO::PARAM_STR); //Stripslashes cuz it saved \\\ to the DB before quotes, strip_tags to prevent malicious scripts. TODO: Whitelist some tags. 
      $stmt->bindParam(':col', $col, PDO::PARAM_STR);
        } catch (Exception $e) {
            var_dump($e->getMessage());
    }
      $stmt->execute();
  }

(请不要讨厌我的糟糕代码和糟糕的异常处理,这不是任何官方项目)。此函数将用户消息输入数据库。

为了接收新消息,我使用JS的setTimeout()函数,在新消息后每1秒运行一次AJAX检查。我保存在JS中显示的最后一条消息的ID,并将该ID作为参数发送到此PHP函数(并且每1秒运行一次):

  /* Recieve new messages, ran every 1s by Ajax call */
  function recieveMessage($msgid) {
    //msgid is latest msg id in this case
    GLOBAL $db;
    $stmt = $db->prepare('SELECT * FROM chat WHERE id > :id');
    $stmt->bindParam(':id', $msgid, PDO::PARAM_INT);
    $stmt->execute(); 
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    return json_encode($result);

  }

问题是:如何实现类似的功能,但使用我之前提到的node.js服务器和websockets设置?我需要以某种方式区分已登录用户和匿名用户。我的第一个想法是从node.js服务器运行ajax调用到PHP,并传递消息数据,PHP将像现在一样将其插入DB中。但是,在这种情况下发送消息给客户端的问题是什么?当消息被输入到数据库时应用用户名,这意味着我必须调用AJAX保存到DB,然后调用另一个AJAX提取新输入的消息并将其发射到客户端,或者创建一个函数来插入、提取和返回提取的消息。然而,当同时输入2条消息时,这是否会引起问题?
有没有办法在Node.js中访问PHP会话变量?那么我就可以重写所有DB查询,使其在Node.js服务器而不是PHP中工作。
如果我的代码或解释混乱,请再次原谅。

请查看这个开源插件,它适用于您所有的聊天相关应用程序,如视频聊天、文本聊天等。webRTC - Bhaumik Pandhi
@PandhiBhaumik 这确实是一个非常有趣的插件,我一定会研究它。但为了完成这个项目,我想继续以我开始的方式进行。由于我是初学者,我通过探索所有可能性来获得最多的经验 :) - Nae
如果你碰巧知道答案,请将其发布为答案。我也卡在这里了 :( @Nae - Saad Suri
1个回答

9

那么,对于所有想知道问题答案并在未来查找此线程的人来说:我没有找到我想要使用的解决方案的答案,但我想出了另一种解决方法,以下是说明:

我没有让Node.js服务器发送AJAX请求,而是将其保留为之前的状态,即从客户端发送jQuery $.post()请求到PHP函数。

接下来我实现了一个MySQL监听器,用于检查MySQL binlog中的更改。我使用了mysql-events模块。它检索新添加的行及其所有数据,然后使用socket.io emit函数将其发送给连接的客户端。我还不得不放弃SSL,因为它显然不喜欢我。这是一个小型的爱好项目,所以我不必太过担心SSL。

最好的解决方案显然是在Node.js中编写整个Web服务器,并完全放弃Apache。Node.js非常适合实时应用程序,而且它是一种非常容易学习和使用的语言。

我的配置:Node.js + Socket.io + mysql-events:(忽略未使用的requires)

// NODE
var socket = require( 'socket.io' );
var express = require( 'express' );
var https = require( 'https' );
var http = require( 'http');
var fs = require( 'fs' );
var request = require( 'request' );
var qs = require( 'qs' );
var MySQLEvents = require('mysql-events');

var app = express();


/*Correct way of supplying certificates.
var server = https.createServer({
               key: fs.readFileSync('/etc/letsencrypt/live/x/privkey.pem'),
               cert: fs.readFileSync('/etc/letsencrypt/live/x/cert.pem'),
               ca: fs.readFileSync('/etc/letsencrypt/live/x/chain.pem')
       },app); */

var server = http.createServer( app ); // Won't work without cert.

var io = socket.listen( server );
console.log("Server Started");

//DB credentials
var dsn = {
  host:     'x',
  user:     'x',
  password: 'x',
};
var mysqlEventWatcher = MySQLEvents(dsn);

//Watcher magic, waits for mysql events.
var watcher = mysqlEventWatcher.add(
  'newage_db.chat',
  function (oldRow, newRow, event) {

     //row inserted
    if (oldRow === null) {
      //insert code goes here
      var res = JSON.stringify(newRow.fields); //Gets only the newly inserted row data
    res.charset = 'utf-8'; //Not sure if needed but i had some charset trouble so i'm leaving this. 
      console.log("Row has updated " + res);
      io.sockets.emit('message', "[" + res + "]"); //Emits to all clients. Square brackets because it's not a complete JSON array w/o them, and that's what i need. 
    }

     //row deleted
    if (newRow === null) {
      //delete code goes here
    }

     //row updated
    if (oldRow !== null && newRow !== null) {
      //update code goes here
    }

    //detailed event information
    //console.log(event)
  });

io.sockets.on( 'connection', function( client ) {
    console.log( "New client !" );



    client.on( 'message', function( data ) {
        //PHP Handles DB insertion with POST requests as it used to.
    });
});
server.listen(8080, function() {
    console.log('Listening');
});

客户端 JavaScript 发送消息:

$('#txtArea').keypress(function (e) {

  if (e.which == 13 && ! e.shiftKey) {

      var emptyValue = $('#txtArea').val();
      if (!emptyValue.replace(/\s/g, '').length) { /*Do nothing, only spaces*/ }
      else {
            $.post("/shana/?p=execPOST", $("#msgTextarea").serialize(), function(data) {

            });


  }

  $('#txtArea').val('');
  e.preventDefault();
}


});

客户端JavaScript接收消息:

socket.on( 'message', function( data ) {
          var obj = JSON.parse(data);

          obj.forEach(function(ob) {
          //Execute appends

          var timestamp = ob.timestamp.replace('T', ' ').replace('.000Z', '');
          $('#messages').append("<div class='msgdiv'><span class='spn1'>"+ob.username+"</span><span class='spn2'style='float: right;'>"+timestamp+"</span><div class='txtmsg'>"+ob.message+"</div>");
          $('#messages').append("<div class='dashed-line'>- - - - - - - - - - - - - - - - - - - - - - - - - - -</div>"); //ADD SCROLL TO BOTTOM
          $("#messages").animate({ scrollTop: $('#messages').prop("scrollHeight")}, 1000);
        });
    });

一些奇怪的原因,binlog 的魔法将时间戳字符串破坏了,因此为了清理它,我不得不替换一部分字符串本身。 PHP 数据库插入函数:
  function sendMessage($msg, $col) {
    GLOBAL $db;
      $un = "";


    if (!isset($_SESSION['username'])) {

        $un = self::generateRandomUsername();

    } else {
    $un = $_SESSION['username'];
    }
    try {
      $stmt = $db->prepare('INSERT INTO chat (id, username, timestamp, message, color) VALUES (null, :un, NOW(), :msg, :col)');
      $stmt->bindParam(':un', $un, PDO::PARAM_STR);
      $stmt->bindValue(':msg', strip_tags(stripslashes($msg)), PDO::PARAM_LOB); //Stripslashes cuz it saved \\\ to the DB before quotes, strip_tags to prevent malicious scripts. TODO: Whitelist some tags.
      $stmt->bindParam(':col', $col, PDO::PARAM_STR);
        } catch (Exception $e) {
            var_dump($e->getMessage());
    }
      $stmt->execute();
  }

我希望这能对某些人有所帮助。你可以自由地使用这段代码,因为我可能已经从互联网上复制了大部分内容 :) 我会不时地查看本主题,如果您有任何问题,请留言。

我有一个问题。我是node js的新手,我试图理解它的工作原理...所以,在简单的表述中,node js保持与数据库的持久连接,监视其变化,但当发现变化时,它如何将新信息注入页面?哪部分代码处理这个过程? - Botea Florin
我的意思是... MySQLEvents会自己运行,没有触发器并且永远运行吗?像一个哨兵一样? - Botea Florin
2
@BoteaFlorin,我已经有很长一段时间没有使用这个项目了,所以有些东西我有点忘记了:)。 MySQLEvents 监听 MySQL 二进制日志 (https://dev.mysql.com/doc/refman/5.7/en/binary-log.html) 的变化,因此你需要启用它(如果我没记错的话,自 5.6 版本开始默认启用)。它如何进行监测,我不是很确定。如果你想的话,可以查看 MySQLEvents 的源代码。同时,还可以看看这里:https://github.com/nevill/zongji ,MySQLEvents 就是基于它开发的,也许你能找到一些答案。 - Nae

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接