实时聊天,消息处理-Socket.io,PHP,MySQL,Apache [英] Real time chat, message handling - Socket.io, PHP, MySQL, Apache

查看:99
本文介绍了实时聊天,消息处理-Socket.io,PHP,MySQL,Apache的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Web开发的初学者.最近,我一直在一个完全基于PHP和JS/jQuery的实时聊天网站上工作(我没有使用任何框架).目前,我的设置只是简单的AJAX轮询,显然不如我希望的那样好.我的数据库是MYSQL数据库.

I am a beginner when it comes to web development. Recently i have been working on a real time chat website based completely on PHP and JS/jQuery (i'm not using any frameworks). Currently, my setup is just simple AJAX polling, which obviously isn't as good as i'd like it to be. My database is a MYSQL database.

我已经阅读了有关websockets的信息,我的新初始计划是使用Socket.io创建一个NodeJS服务器,该服务器将处理消息(带有Node.js的MySQL ).

I have read about websockets and my new initial plan was to create a NodeJS server with Socket.io which will handle messages (How to integrate nodeJS + Socket.IO and PHP?), and i thought about storing those messages in a MySQL database (MySQL with Node.js).

这是我目前所拥有的(不多,我想在实际取得进展之前弄清楚如何进行发展).这是我的测试设置,实际聊天中使用的HTML显然有所不同.

Here is what i have currently (not much, i'd like to clarify how to progress before i actually do progress). This is my test setup, the HTML used in actual chat is a bit different obviously.

Node.js服务器:

Node.js Server:

// 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:

My super-simple test 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变量来区分登录用户和匿名用户.匿名用户将$_SESSION['anon']设置为true,并且没有设置$_SESSION['username'].登录的用户显然会将其倒置.

PHP requires a bit explanation - At the moment when someone connects to my website i run session_start(). This is because i want to have something like anonymous sessions. I distinguish between logged in and anonymous users through $_SESSION variables. An anon user will have $_SESSION['anon'] set to true, as well as will NOT have $_SESSION['username'] set. Logged in user will obviously have it inverted.

在聊天中-登录用户和匿名用户均可使用.当用户匿名时,将从数据库或随机名称生成随机用户名.用户登录后,将选择自己的用户名.现在,我使用Ajax轮询的系统是这样的:

When it comes to the chat - it's available to both logged in users as well as anonymous users. When user is anonymous, a random username is generated from a database or random names. When user is logged in, his own username is chosen. Right now my system with Ajax polling works like this:

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

User inputs the message (in the current chat solution, not the testing HTML i sent above) and presses enter and an AJAX call is made to following function:

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

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

(Please don't hate my bad code and crappy exception handling, this is not any official project). This function inputs users message to the database.

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

To recieve new messages, i use setTimeout() function of JS, to run an AJAX check every 1s after new messages. I save the ID of last message that is displayed in JS, and send that ID as a parameter to this PHP function (and it's ran every 1s):

  /* 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服务器到PHP的ajax调用并传递消息数据,PHP会像现在一样完全将其插入数据库.但是,这种情况下的问题是如何将消息再次发送给客户端?用户名是在消息输入数据库时​​应用的,这意味着我必须调用AJAX才能保存到数据库,然后调用另一个AJAX来提取新输入的消息并将其发送给客户端,或者创建一个函数插入并提取并返回提取的消息.但是,当完全同时输入2条消息时,这不会引起问题吗?

The question is: How to implement something similar, but with my earlier mentioned setup of node.js server and websockets? I need to distinguish between logged in and anonymous users somehow. My first idea was to just run an ajax call from node.js server to PHP and pass message data, and PHP will insert it into DB exactly as it does right now. But the problem in this case is how to send the message out to the clients again? Usernames are applied while the message is being input into database, that means i'd have to call AJAX to save to the DB, and then call another AJAX to extract the newly input message and emit it to the clients, or make a function that inserts and extracts and returns extracted message. However, won't that cause problems when 2 messages are input at the exactly same time?

是否可以通过Node.js访问PHP会话变量?然后,我可以重写所有数据库查询以在Node.js服务器而不是PHP中工作.

Is it somehow possible to access PHP session variables in Node.js? Then i could rewrite all DB querying to work in the Node.js server instead of PHP.

如果我的代码或解释混乱,我再次道歉.

I apologize once more if my code or explanation is messy.

推荐答案

SO,对于所有想知道并且将来会发现此线程的人:我没有找到想要使用的解决方案的答案,无论如何,我都会想起什么,这是一个描述:

SO, for everyone that is wondering and will find this thread in the future: I DID NOT FIND AN ANSWER WITH THE SOLUTION I WANTED TO USE, HOWEVER I CAME UP WITH SOMETHING ELSE, AND HERE IS A DESCRIPTION:

我没有像以前那样让Node.js服务器发送AJAX请求,而是像以前一样,将来自客户端的jQuery $ .post()请求留给了PHP函数.

Instead of making Node.js server send the AJAX request, i left it as i had before, the jQuery $.post() request from the client, to a PHP function.

我接下来要做的是实现一个MySQL侦听器,该侦听器检查MySQL二进制日志中的更改.我使用了 mysql-events 模块.它检索包含所有数据的新添加的行,然后使用socket.io发出函数将其发送到连接的客户端.我还必须放弃SSL,因为它显然讨厌我.这是一个小型的业余项目,因此我真的不必为SSL操心.

What i did next was to implement a MySQL listener, that checked the MySQL binlog for changes. I used mysql-eventsmodule. It retrieves the newly added row with all data and then uses socket.io emit function to send it to connected clients. I also had to drop SSL because it apparently hates me. It's a small hobby project, so i don't really have to bother that much with SSL.

显然,最好的解决方案是在Node.js中对整个Web服务器进行编程,然后完全删除Apache. Node.js对于实时应用程序来说很棒,它是一种非常容易学习和使用的语言.

Best solution would be obviously to program the whole webserver in Node.js and just drop Apache completely. Node.js is awesome for real time applications, and it's a very easy language to learn and use.

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

My setup of Node.js + Socket.io + mysql-events: (ignore the unused 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魔术破坏了时间戳字符串,因此要清理它,我不得不替换字符串本身.

Somehow, the binlog magic destroys the timestamp string, so to clean it up i had to replace a bit of the string itself.

PHP DB插入功能:

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

我希望这对某人至少有帮助.随意使用此代码,因为无论如何我可能已经从互联网上复制了大部分代码:)我将不时检查此线程,因此,如果您有任何疑问,请发表评论.

I hope this helps someone at least a bit. Feel free to use this code, as i probably copied most of it from the internet already anyway :) I will be checking this thread from time to time, so if you have any questions leave a comment.

这篇关于实时聊天,消息处理-Socket.io,PHP,MySQL,Apache的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆