2017-07-27 111 views
2

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

我看过的WebSockets和我的新开始的计划是创建一个Socket.io服务器的NodeJS将处理消息(How to integrate nodeJS + Socket.IO and 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变量来区分登录和匿名用户。 anon用户将$_SESSION['anon']设置为true,并且不会设置$_SESSION['username']。登录用户显然会倒过来。

谈到聊天时 - 既可以登录用户,也可以匿名用户使用。当用户是匿名用户时,会从数据库或随机名称生成随机用户名。当用户登录时,他自己的用户名被选中。现在我用Ajax查询系统的工作原理是这样的:

用户输入的消息(在当前的聊天解决方案,而不是测试HTML我上面发),并按下回车和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服务器向PHP运行ajax调用并传递消息数据,PHP就会像现在一样将其插入到数据库中。但是这种情况下的问题是如何将消息再次发送给客户端?用户名是在消息被输入到数据库时应用的,这意味着我必须调用AJAX保存到数据库,然后调用另一个AJAX来提取新输入的消息并将其发送给客户端,或者创建一个函数插入并提取并返回提取的消息。但是,当两个消息完全同时输入时,这不会导致问题吗?

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

我再次道歉,如果我的代码或解释是凌乱。

+0

请检查所有聊天相关应用程序的开源插件,如视频聊天,文本聊天等。[webRTC](https://webrtc.org/) – Webinion

+0

@PandhiBhaumik这是一个非常有趣的插件,我一定会研究它。尽管如此,我仍想继续我开始的方式,至少对于这个项目来说。由于我是初学者,因此我从探索各种可能性中获得了大部分经验:) – Nae

+0

如果您有机会了解答案,请将其作为答案发布。我也很困难在这里:(@Nae –

回答

2

所以,大家想知道和会发现这个线程在未来的:我没有找到与溶液我想用一个答案,但是我想出了别的事情了,这里是一个描述:

而不是让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的事件设置:(忽略未使用的需要)

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


}); 

Cliend JavaScript RECIEVE MESSAGE:

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

我希望这可以帮助别人,至少有点。随意使用这段代码,因为我可能已经从互联网上复制了大部分内容:)我会不时地检查这个线程,所以如果您有任何问题请留言。

+0

我有一个问题,请问我在节点js新,我试着了解他的工作是什么......所以..在塑料exprimation中,节点js保持一个持续连接到数据库,监视它的变化,但是当发现变化时,它是如何将新信息注入页面的?代码处理它的部分是什么? –

+0

我的意思是... MySQLEvents将自己运行,没有触发器和永远?像一个圣杯? –

+1

@BoteaFlorin自从我与这个项目合作已经很久了,所以我有点忘了一些事情:)。 MySQLEvents监视MySQL二进制日志(https://dev.mysql.com/doc/refman/5.7/en/binary-log.html)中的更改,因此您需要启用它(iirc,默认情况下为5.6 )。它究竟如何监控它,我不太确定。如果需要,可以查看MySQLEvents的源代码。看看这里以及:https://github.com/nevill/zongji这是MySQLEvents的基础,也许你可以找到一些答案。 – Nae