Чат в реальном времени, обработка сообщений – Socket.io, PHP, MySQL, Apache

Я начинаю заниматься веб-разработкой. Недавно я работал над сайтом чата реального времени, полностью основанным на PHP и JS / jQuery (я не использую никаких фреймворков). В настоящее время моя настройка – просто простой опрос AJAX, который, очевидно, не так хорош, как хотелось бы. Моя база данных – это база данных MYSQL.

Я прочитал о веб-сайтах, и мой новый первоначальный план состоял в том, чтобы создать NodeJS-сервер с Socket.io, который будет обрабатывать сообщения ( как интегрировать nodeJS + Socket.IO и PHP? ), И я думал о сохранении этих сообщений в базе данных MySQL ( MySQL с 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-кода, отправленного выше) и нажимает 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(); } 

(Пожалуйста, не ненавидьте мой плохой код и дерьмовую обработку исключений, это не официальный проект). Эта функция вводит пользовательское сообщение в базу данных.

Чтобы получать новые сообщения, я использую функцию setTimeout() JS, чтобы запускать проверку AJAX каждые 1 с после новых сообщений. Я сохраняю идентификатор последнего сообщения, которое отображается в JS, и отправляет этот идентификатор в качестве параметра этой функции 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 и веб-узлов? Мне нужно каким-то образом различать зарегистрированные и анонимные пользователи. Моя первая идея состояла в том, чтобы просто запустить вызов ajax с сервера node.js на PHP и передать данные сообщения, а PHP будет вставлять его в DB точно так же, как сейчас. Но проблема в этом случае заключается в том, как отправить сообщение клиентам снова? Имена пользователей применяются, когда сообщение вводится в базу данных, это означает, что мне нужно будет вызвать AJAX для сохранения в БД, а затем вызвать другой AJAX, чтобы извлечь новое входное сообщение и передать его клиентам, или создать функцию, которая вставляет и извлекает и возвращает извлеченное сообщение. Однако не будут ли возникать проблемы, когда в одно и то же время вводятся 2 сообщения?

Как можно получить доступ к переменным сеанса PHP в Node.js? Затем я мог бы переписать все запросы БД для работы на сервере Node.js вместо PHP.

Приносим извинения еще раз, если мой код или объяснение бесполезны.

ТАК, для всех, кто задается вопросом и найдет этот поток в будущем: я НЕ НАЙТИ ОТВЕТ С РЕШЕНИЕМ, КОТОРЫЙ Я ХОЧУ ИСПОЛЬЗОВАТЬ, ОДНАКО Я ПРИНИМАЮ С ЧТО-ТО ЧТО-ТО, И ЗДЕСЬ ОПИСАНИЕ:

Вместо того, чтобы сервер Node.js отправил запрос AJAX, я оставил его, как и раньше, запрос jQuery $ .post () от клиента к функции PHP.

То, что я сделал дальше, – это реализовать слушателя MySQL, который проверил бинговый файл MySQL для изменений. Я использовал модуль mysql-events . Он извлекает вновь добавленную строку со всеми данными, а затем использует функцию socket.io emit для отправки ее подключенным клиентам. Мне также пришлось отказаться от SSL, потому что это, по-видимому, меня ненавидит. Это небольшой проект для хобби, поэтому мне не нужно сильно беспокоиться о SSL.

Лучшим решением было бы, очевидно, запрограммировать весь веб-сервер в Node.js и полностью отказаться от Apache. Node.js является отличным для приложений реального времени, и это очень простой язык для изучения и использования.

Моя настройка Node.js + Socket.io + mysql-events: (игнорировать неиспользуемые требует)

 // 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 ПОСЛЕ СООБЩЕНИЯ:

 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 уничтожает строку timestamp, поэтому для ее очистки мне пришлось заменить немного самой строки.

PHP DB INSERT 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_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(); } 

Надеюсь, это поможет кому-то хотя бы немного. Не стесняйтесь использовать этот код, поскольку я, вероятно, скопировал большую часть этого из Интернета уже в любом случае 🙂 Я буду проверять этот поток время от времени, поэтому, если у вас есть вопросы, оставляйте комментарий.