使用流行的 web 應用技術棧 —— 好比PHP —— 來編寫聊天應用一般是很困難的。它包含了輪詢服務器以檢測變化,還要追蹤時間戳,而且這種實現是比較慢的。html
大多數實時聊天系統一般基於 WebSocket 來構建,具體來講就是socket.io。 WebSocket 爲客戶端和服務器提供了雙向通訊機制。node
這意味着服務器能夠 推送 消息給客戶端。不管什麼時候你發佈一條消息,服務器均可以接收到消息並推送給其餘鏈接到服務器的客戶端。mysql
首先要製做一個 HTML 頁面來提供表單和消息列表。咱們使用了基於 Node.JS 的 web 框架 express 。 請確保安裝了 Node.JS。jquery
首先建立一個 package.json 來描述咱們的項目。 推薦新建一個空目錄。git
express 已經安裝好了。咱們如今新建一個 index.js 文件來建立應用。github
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');
});
複製代碼
這段代碼做用以下:web
Express 初始化 app 做爲 HTTP 服務器的回調函數。redis
定義了一個路由 / 來處理首頁訪問。sql
使 http 服務器監聽端口 4000。數據庫
目前在 index.js 中咱們是經過 res.send 返回一個 HTML 字符串。 若是咱們將整個應用的 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 由兩部分組成:
這個兩部分都會運用到
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 的一個實例。 而後監聽 connection 事件來接收 sockets, 並將鏈接信息打印到控制檯。
在 index.html 的 標籤中添加以下內容:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
複製代碼
這樣就加載了 socket.io。 socket.io 暴露了一個 io 全局變量,而後鏈接服務器。
請注意咱們在調用 io() 時沒有指定任何 URL,由於它默認將嘗試鏈接到提供當前頁面的主機。
從新加載服務器和網站,你將看到控制檯打印出 「a user connected」。
每一個 socket 還會觸發一個特殊的 disconnect 事件:
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
複製代碼
Socket.IO 的核心理念就是容許發送、接收任意事件和任意數據。任意能被編碼爲 JSON 的對象均可以用於傳輸。二進制數據 也是支持的。
這裏的實現方案是,當用戶輸入消息時,客戶端發送一個 chat message 事件,服務器接收一個 chat message 事件。index.html 文件中的 script 部分如今應該內容以下:
<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.鏈接
監聽客戶端鏈接,回調函數會傳遞本次鏈接的socket
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的客戶端發送消息
socket.emit('String', data);
複製代碼
4.分組
io.of('/some').on('connection', function (socket) {
socket.on('test', function (data) {
socket.broadcast.emit('event_name',{});
});
});
複製代碼
REmote DIctionary Server(Redis) 是一個由SalvatoreSanfilippo寫的key-value(鍵值對)存儲系統。
Redis是一個開源的使用ANSI C語言編寫、遵照BSD協議、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。
它一般被稱爲數據結構服務器,由於值(value)能夠是字符串(String), 哈希(Map), 列表(list), 集合(sets) 和有序集合(sorted sets)等類型。
Redis中的數據類型哈希(Map hashmap):散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。
列表(list):列表是一種數據項構成的有限序列,即按照必定的線性順序,排列而成的數據項的集合。(redis中使用雙向鏈表實現)
集合(sets):和中學時學習的概念是類似的。特色是集合中元素不能重複是惟一的。切內部是無序的
有序集合(sorted sets):也是一種集合,可是內部數據是通過排序的。
0、創建node-redis的client端鏈接 npm i redis --save
// 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
}
複製代碼
一、set的存取
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);
}
});
}
});
複製代碼
二、hash存取
hash set的設值和抽取數據都有單個key和多個key兩種方式:
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方法在指定field下找不到指定的key時,會傳給回調函數null,而非空字符或undefined。
※ 設定多個key的值,取值時獲取指定field下指定單個或多個key的值
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中key的值是以字符串形式存儲的,若是JSON數據層數超過一層,會出現值是'[object Object]'的狀況。
hmget方法的返回值是個數組,其中元素的順序對應於參數的key數組中的順序,若是參數數組中有在field內不存在的key,返回結果數組的對應位置會是null,也即不管是否能取到值,結果數組中的元素位置始終與參數的key數組中元素位置一一對應。
獲取hash中全部key的方法是client.keys(fieldname, callback); 須要注意的是若是hash中key的數目不少,這個方法的可能耗費很長時間。
3.鏈表 適合存儲社交網站的新鮮事 lpush key value [value ...] 向鏈表key左邊添加元素 rpush key value [value...] 向鏈表key右邊添加元素 lpop key 移除key鏈表左邊第一個元素 rpop key 移除key鏈表右邊第一元素
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
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");
複製代碼
client.lpush('message',JSON.stringify(msg),redis.print)
複製代碼
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));
})
複製代碼
增長信息的安全性,咱們能夠對用戶發送的信息進行敏感詞、sql注入攻擊、xss攻擊等進行過濾 使用async一步步操做流程
修改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
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);
});
}
});
};
複製代碼
新建query.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)
}
});
複製代碼
新建insert.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.在程序中添加入庫步驟