Система чата сложна? Фронтенд-инженеры тоже могут это сделать!

Redis внешний интерфейс сервер JavaScript

socket.io

Введение

Написание чата приложений с использованием популярных стеков веб-приложений - например, PHP - часто сложно. Он включает в себя опрос сервера для обнаружения изменений и отслеживания отслеживания временных метров, и эта реализация относительно медленная.

Большинство систем чата в реальном времени обычно строятся на WebSocket,В частности, socket.io. WebSocket обеспечивает двусторонний механизм связи между клиентом и сервером.

Это означает, что сервер может передавать сообщения клиенту. Всякий раз, когда вы публикуете сообщение, сервер может получить его и передать другим клиентам, подключенным к серверу.

веб-фреймворк

Первое, что нужно сделать, это создать HTML-страницу с формой и списком сообщений. Мы использовали экспресс-веб-фреймворк на основе Node.JS. Убедитесь, что у вас установлен Node.JS.

Сначала создайте Package.json, чтобы описать наш проект. Рекомендуется создать пустой каталог.

экспресс уже установлен. Теперь мы создаем новый файл index.js для создания приложения.

var app = require('express')();
var http = require('http').Server(app);

app.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

http.listen(3000, function(){
  console.log('listening on *:4000');
});
    

Этот код делает следующее:

Express инициализирует приложение как функцию обратного вызова для HTTP-сервера.

Маршрут / определяется для обработки доступа к домашней странице.

Заставьте http-сервер прослушивать порт 4000.

HTML-сервер

В настоящее время в index.js мы возвращаем строку HTML через res.send. Если мы поместим HTML-код всего приложения в код приложения, структура кода станет очень запутанной. Альтернативой является создание нового файла index.html в качестве ответа сервера.

Теперь давайте рефакторим предыдущий обратный вызов с помощью sendFile :

app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});

Содержимое index.html выглядит следующим образом:


<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

интегрированныйSocket.IO

Socket.IOСостоит из двух частей:

  • Сервер для интеграции (или подключения) к HTTP-серверу Node.JS:socket.io
  • Клиент, загруженный в браузер: socket.io-client

Обе части будут использоваться

npm install --save socket.io

npm install --save socket.io-client

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  console.log('a user connected');
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});

Мы инициализировали, передав объект http (HTTP-сервер)socket.ioэкземпляр . Затем прослушайте событие подключения, чтобы получить сокеты и вывести информацию о подключении на консоль.

Добавьте в тег index.html следующее:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

Это загружаетsocket.io.socket.ioПредоставляет глобальную переменную io, а затем подключается к серверу.

Обратите внимание, что мы не указали никакого URL-адреса при вызове io(), так как по умолчанию он попытается подключиться к хосту, обслуживающему текущую страницу.

Перезагрузите сервер и веб-сайт, и вы увидите, что «пользователь подключен» напечатан на консоли.

Каждый сокет также запускает специальное событие разъединения:

io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});
    

триггерное событие

Socket.IOОсновная идея состоит в том, чтобы разрешить отправку и получение произвольных событий и произвольных данных. Любой объект, который может быть закодирован как JSON, может быть использован для передачи. Двоичные данные также поддерживаются.

Реализация здесь такова, что когда пользователь вводит сообщение, клиент отправляет событие сообщения чата, а сервер получает событие сообщения чата. Раздел сценария в файле index.html теперь должен выглядеть следующим образом:

<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
  $(function () {
    var socket = io();
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
  });
</script>

транслировать

Следующая цель — заставить сервер отправлять сообщения другим пользователям.

Чтобы отправить событие каждому пользователю,Socket.IOПредоставляется метод io.emit:

io.emit('some event', { for: 'everyone' });

Для простоты мы отправляем сообщение всем пользователям, включая отправителя.

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

Сводка по использованию

Сервер

1. Подключить

Слушайте клиентское соединение, функция обратного вызова будет передавать сокет этого соединения

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

2. Трансляция

(1) Рассылка сообщений всем клиентам

    io.sockets.emit('String',data);

(2) Рассылать сообщения другим клиентам, кроме себя

    socket.broadcast.emit("msg",{data:"hello,everyone"});

(3) Отправить сообщение указанному клиенту

    io.sockets.socket(socketid).emit('String', data);

3. Сообщение отправлено

(1) Мониторинг клиента

    socket.on('String',function(data));

(2) Отправить сообщение клиенту сокета

    socket.emit('String', data);

4. Группировка

io.of('/some').on('connection', function (socket) {
    socket.on('test', function (data) {
        socket.broadcast.emit('event_name',{});
    });
});

Расширенный - обработка данных, отправленных пользователем

image

1. Редис

Что такое Редис?

REmote DIctionary Server (Redis) — это система хранения «ключ-значение» (пара «ключ-значение»), написанная Сальваторе Санфилиппо.

Redis — это база данных типа журнала с открытым исходным кодом, написанная на языке ANSI C, соответствующая протоколу BSD, поддерживающая работу в сети, в памяти и в постоянном режиме, а также предоставляющая API-интерфейсы на нескольких языках.

Его часто называют сервером структуры данных, потому что значения могут быть таких типов, как String, Hash (Map), list (список), set (наборы) и sorted set (сортированные наборы).

Типы данных в Redis

Хэш (карта хэш-карты): хеш-таблица (также называемая хеш-таблицей) — это структура данных, которая напрямую обращается к ячейке памяти в соответствии с ключом (ключом).

Список. Список — это конечная последовательность элементов данных, то есть набор элементов данных, расположенных в определенном линейном порядке. (реализовано с использованием двусвязного списка в Redis)

Наборы: концепции, аналогичные тем, которые изучались в средней школе. Особенность в том, что элементы в коллекции не могут повторяться и уникальны. Разрез внутренне беспорядочный

Сортированные наборы (sorted set): тоже набор, но внутренние данные отсортированы.

установка редис

ссылка на установку редис

npm redis

Как использовать редис

0. Установите клиентское соединение node-redis npm я редис --сохранить

// redis 链接
var redis = require('redis');
var client = redis.createClient('6379', '127.0.0.1');

// redis 链接错误
client.on("error", function(error) {
    console.log(error);
});
// redis 验证 (reids.conf未开启验证,此项可不需要)
// client.auth("foobared");
module.exports = {
    client:client
}

1. Установить доступ

const {client} = require('./redis')

client.set('key001', 'AAA', function (err, response) {
    if (err) {
        console.log("err:", err);
    } else {
        console.log(response);
        client.get('key001', function (err, res) {
            if (err) {
                console.log("err:", err);
            } else {
                console.log(res);
                client.end(true);
            }
        });
    }
});

2. Хэш-доступ

Существует два способа установки и извлечения данных хеш-набора с одним ключом и несколькими ключами:

const {client} = require('./redis')

client.hset('filed002', 'key001', 'wherethersisadoor', function (err, res) {
    if (err) {
        console.log(err);
    } else {
        console.log('res:', res);
        client.hget('filed002', 'key001', function (err, getRslt) {
            if (err) {
                console.log(err);
            } else {
                console.log('getRslt:', getRslt);
                client.end(true);
            }
        });
    }
});

Примечание. Если метод hget не может найти указанный ключ в указанном поле, он передаст null в функцию обратного вызова вместо нулевого символа или неопределенного значения.

※ Установите значение нескольких ключей, когда значение будет получено, будет получено значение указанного одного или нескольких ключей в указанном поле.

const {client} = require('./redis')

var qe = {a: 2, b:3, c:4};
client.hmset('field003', qe, function(err, response) {
    console.log("err:", err);
    console.log("response:", response);
    client.hmget('field003', ['a', 'c'], function (err, res) {
        console.log(err);
        console.log(res);
        client.end(true);
    });
});

Заданным значением метода hmset могут быть данные в формате JSON, но значение ключа в redis сохраняется в виде строки.Если количество слоев данных JSON превышает один слой, значение будет '[object Объект]'.

Возвращаемое значение метода hmget представляет собой массив, в котором порядок элементов соответствует порядку в ключевом массиве параметра.Если в поле в массиве параметров есть несуществующий ключ, соответствующая позиция возвращаемый массив результатов будет нулевым, то есть независимо от того, может ли он быть извлечен или нет. Для значения позиции элементов в массиве результатов всегда взаимно-однозначны с позициями элементов в ключевом массиве параметра.

Метод получения всех ключей в хэше — client.keys(fieldname, callback); Следует отметить, что если количество ключей в хэше велико, этот метод может занять много времени.

3. Связанный список Подходит для хранения новостей из социальных сетей lpush key value [значение ...] добавить элемент слева от ключа связанного списка rpush key value [значение...] Добавить элемент справа от ключа связанного списка Клавиша lpop удаляет первый элемент слева от списка ключей Ключ rpop удаляет первый элемент в правой части списка ключей

const {client} = require('./redis')

client.lpush('test', 12345, function(err, response) {
    if(err){
        console.log("err:", err);
    }else{
        console.log("response:", response);
        client.rpop('test',function (err, res){
            if(err){
                console.log(err);
            }else{
                console.log(res);
                client.end(true);
            }
        });
    }
});

redis-cli

image

Получите доступ к redis в socket.io и создайте несколько пространств имен

How to use

const io = require('socket.io')(3000);
const redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));

Измените index.js на

const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const redis = require('socket.io-redis');
const {client} = require('./test/redis')
const moment = require('moment')


app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
});

io.adapter(redis({host: 'localhost', port: 6379}));

var nameBox = ['/chatroom','/live','/vod','/wechat','/broadcast'];

for(var item in nameBox){
    var nsp = io.of(nameBox[item])
    socketMain(nsp,nameBox[item])
}

function socketMain(nsp,roomName) {
    nsp.on('connection',function (socket) {
        console.log('a user connected')
        socket.on('disconnect', function(){
            console.log('user disconnected');
        });
        socket.on('chat message', function(msg){
            var data = {"socketid":socket.id,"cid":roomName,"msg":msg,createTime:moment().unix()};
            client.lpush('message',JSON.stringify(data),redis.print)
            
            console.log('message: ' + msg);
        });
    })
}

http.listen(4000, function(){
    console.log('listening on *:4000');
});

index.html

 var socket = io.connect("http://127.0.0.1:4000/live");

Доступ redis.
client.lpush('message',JSON.stringify(msg),redis.print)

2. Запустите другой сервер для обработки данных Redis

Вопрос: Как две службы взаимодействуют друг с другом?
Ответ: Конкретные шаги для использования socket.io-client следующие:
1.在数据处理程序中引入 socket.io-client 
var io = require('socket.io-client');

2.用socket.io-client 模拟了一个,连接到主程序io中的客户端
var socket = io.connect('ip+'/live'', {reconnect: true});

3.通过这个模拟的客户端,与主程序通信
socket.emit('redisCome', result);

Изменить redis.js

module.exports = {
    client:client,
    ip:'http://127.0.0.1:4000'
}

Новый sclient.js

const io = require('socket.io-client');
const async = require('async');
const moment = require('moment');
const redis = require('redis');

const {client,ip} = require('./test/redis');
const domain = require('domain');
const debug = require('debug')('socket-client:main');

var origin = io.connect(ip+'/', {reconnect: true});
var chatroom = io.connect(ip+'/chatroom', {reconnect: true});
var live = io.connect(ip+'/live', {reconnect: true});
var vod = io.connect(ip+'/vod', {reconnect: true});
var wechat = io.connect(ip+'/wechat', {reconnect: true});
var broadcast = io.connect(ip+'/broadcast', {reconnect: true});

var namBox = {root:origin,chatroom:chatroom,live:live,vod:vod,wechat:wechat,broadcast:broadcast};

var reqDomain = domain.create();
reqDomain.on('error', function (err) {
    console.log(err);
    try {
        var killTimer = setTimeout(function () {
            process.exit(1);
        }, 100);
        killTimer.unref();
    } catch (e) {
        console.log('error when exit', e.stack);
    }
});

reqDomain.run(function () {
    compute();
});

process.on('uncaughtException', function (err) {
    console.log(err);
    try {
        var killTimer = setTimeout(function () {
            process.exit(1);
        }, 100);
        killTimer.unref();
    } catch (e) {
        console.log('error when exit', e.stack);
    }
});

function compute() {
    client.llen('message', function(error, count){
        if(error){
            console.log(error);
        }else{
            if(count){
                //console.log('-------------has count',time);
                popLogs();
                process.nextTick(compute);
            }else{
                //console.log('-------------empty',time);
                setTimeout(function(){
                    compute();
                },100);
            }
        }
    });
}

function popLogs(){
    var time = moment().unix();
    console.log('-------------dealStart-------------',time);
    client.rpop('message',function(err,result){
        if(err){
            console.log(err);
        }else{
            var result = JSON.parse(result);
            try{
                var cid = result.cid;
                //console.log('place',result.place);
            }catch(e){
                console.log('empty data cid',result);
                return;
            }
            console.log(' start '+' nsp: '+cid +' time: '+time);
            if(namBox[cid]){
                console.log(result);
                namBox[cid].emit('redisCome',result);
            }
        }
    });
}

Измените index.js, чтобы добавить события прослушивателя redisCome.

/*接收redis发来的消息*/
socket.on('redisCome',function (data) {
    console.log('-------------redisCome',data.msg);
    try{
        var msg = data.msg
    }catch(e){
        var msg = '';
    }
    console.log(data);
    nsp.emit('message.add',msg);
});

Модифицировать index.html.

socket.on('message.add',function (msg) {
    $('#messages').append($('<li>').text(msg));
})

3. Увеличьте проверку информации, отправляемой пользователями.

Чтобы повысить безопасность информации, мы можем фильтровать информацию, отправляемую пользователями, на наличие конфиденциальных слов, атак sql-инъекций, атак xss и т. д. Пошаговый процесс с использованием асинхронности

Изменить sclient.js

async.waterfall([
    function (done) {
        user.messageDirty({msg:result.msg},function(err,res){
            //console.log('sql done'/*,res*/);
            done(err,res);
        });
    },
    function (res,done) {
        user.messageValidate({msg:result.msg},function(err,res){
            //console.log('key done'/*,res*/);
            done(err,res);
        });
    }
],function (err,res) {
    if(err){
        console.log('err!!!!',err,result);
        namBox[cid].emit('messageError',err);
    }else{
        if(namBox[cid]) {
            console.log(result);
            namBox[cid].emit('redisCome', result);
        }
    }
})

Изменить index.js

/*接收redis错误信息返回*/
socket.on('messageError',function(err){
    console.log('messageError');
    try{
        nsp.emit('message.error',err.msg);
    }catch(e){

    }
});

Изменить index.html

хранилище mysql

1. Установите базу данных mysql локально 2. Загрузите пакет node mysql

npm install mysql --save

3. Подключитесь к базе данных и установите пул соединений.

var mysql      = require('mysql');
var pool = mysql.createPool({
    host: 'localhost',
    user:'root',
    password:'123456',
    database : 'danmaku'
});

var query = function(sql,options,callback){
    pool.getConnection(function(err,conn){
        if(err){
            callback(err,null,null);
        }else{
            conn.query(sql,options,function(err,results,fields){
                //释放连接
                conn.release();
                //事件驱动回调
                callback(err,results,fields);
            });
        }
    });
};

Новый запрос.js

var {query} = require("./test/redis");

query("select * from demo", function(err,results,fields){
    //do something
    if(err){
        console.log(err)
    }else {
        console.log(results)
    }
});

Новый файл вставки.js

var {query} = require("./test/redis");
const moment = require('moment')

query('insert into demo(message,createTime) values(?,?)',[123,moment().unix()],function(err,results,fields){
    //do something
    if(err){
        console.log(err)
    }else {
        console.log(results)
    }
});

mysql -u root -p use danmaku; select * from demo;

4. Добавьте в программу этапы складирования

заградительный игрок

ABPlayerHTML5

адрес проекта

ссылка здесь