今天我們在這裏要說的是wehsocket和node開發長連接問題,我們在真正的項目中,可能要實現的功能不知是簡單的聊天功能,我們現在要整合Redis,rabbitMQ,等實現o2o的提醒功能:
首先,整合一次redis:
我們建立一個chat文件夾,在其中寫入一個package.json文件,用於生成我們的node類庫
{
"name": "zefun",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"apn": "^1.5.2",
"amqp": "^0.2.4",
"co": "^3.0.6",
"co-redis": "^1.1.1",
"log4js": "^0.6.28",
"express": "^4.13.3",
"redis": "^0.10.3",
"redis-sentinel": "^0.1.3",
"socket.io": "^1.3.7"
},
"devDependencies": {},
"author": "",
"license": "ISC"
}
這時,我們通過運行node的命令進行安裝類庫,npm install
安裝結束後,我們會生成一個node_modules的文件,還要在本層的目錄創建一個logs的文件夾,因爲我們一會會使用一個日誌系統進行輸出,但是,node只有寫文件的權限,沒有輸出文件夾的權限
新建一個redisClient.js,作爲redis的客戶端連接
var PORT = 6379;
var HOST = '120.25.254.164';
var redis = require('redis');
var chatClient = redis.createClient(PORT,HOST);
var logger = require("./log").logger("redis");
chatClient.on('error', function(err){
logger.error(err);
});
var chat_sadd = function(key, field) {
chatClient.sadd(key, field);
logger.info("redis sadd --> key : " + key + ", field : " + field);
}
var chat_srem = function(key, field) {
chatClient.srem(key, field);
logger.info("redis srem --> key : " + key + ", field : " + field);
}
var chat_smembers = function(key) {
return chatClient.smembers(key);
}
var chat_hset = function(key, userId, sockedId) {
chatClient.hset(key, userId, sockedId);
logger.info("redis hset --> key : " + key + ", field : " + userId + ", value :" + sockedId);
}
var chat_hget = function(key, userId) {
return chatClient.hget(key, userId);
}
exports.chat_sadd = chat_sadd;
exports.chat_srem = chat_srem;
exports.chat_smembers = chat_smembers;
exports.chat_hset = chat_hset;
exports.chat_hget = chat_hget;
下面我們在主js中進行引用和傳值即可
var co = require('co');
var wrapper = require('co-redis');
var redis = wrapper(require("./redisClient"));
我們看到上面我們使用了一個叫co的類庫,並使用wrapper對redis進行了包裝,只有經過包裝,在操作redis的時候,才能處理了node中異步的程序,將redis取值變成了同步的方式,即取到值後才能進行下面程序的執行
-
co(function* () {
-
<span style="white-space:pre"> </span>var userSID = yield redis.chat_hget(USER_SOCKET_KEY, toUser);
-
io.sockets.connected[userSID].emit('getMessage',{msg:msg});
-
})();
對於存值不需要這麼同步
-
redis.chat_sadd(redis_key,data.userId);
redis就算是結束了,下面就開始rabbitMQ的搭建和操作:
此處我們將node作爲了rabbit的一個client進行監聽和處理程序,首先新建一個mqClient.js作爲隊列的客戶端進行監聽
-
conn.queue('queue_chat_notify', { autoDelete: false, durable: true }, function(queue) {
-
queue.subscribe(function (msg) {
-
receiveNotify(msg.data);
-
});
-
});
-
exports.createConnection = function(r1, r2) {
-
receiveLogout = r1;
-
receiveNotify = r2;
-
}
這是我們最核心的進行監聽的程序,是要subscribe這個方法,就可以進行監聽了,請注意receiveLogout這個方法,我們這個方式是需要在主js中作爲參數傳入的,當客戶端進行傳入一個方法作爲替代方法的時候,就可以進行讀取消息,但是如果沒有傳入,那麼該條數據丟失.下面是主js中的代碼
-
var mq = require("./mqClient");
-
mq.createConnection(receiveNotify);
-
//rabbitmq監聽到通知的回調操作
-
var receiveNotify = function(msg) {
-
logger.info("\r\n\r\n<-- receiveNotify begin -->");
-
sendMsg(msg);
-
}
這樣,在客戶端中判定登陸後,啓用該方法就可以讀取隊列中的消息,進行發送消息了,下面給出整個mqClient.js的全部內容
-
var amqp = require("amqp");
-
-
var exchName = "directExchange";
-
-
var connOptions = {
-
host: '120.25.254.164',
-
port: 5672,
-
login: 'zefun',
-
password: 'zefun'
-
};
-
-
var exchOption = {
-
type: 'direct',
-
durable: true,
-
autoDelete: false,
-
confirm: false
-
};
-
-
var conn;
-
var receiveNotify = null;
-
var logger = require('./log').logger("rabbitmq");
-
conn = amqp.createConnection(connOptions);
-
-
conn.on('ready',function() {
-
logger.info("rabbitmq is ready ... ");
-
conn.queue('queue_chat_notify', { autoDelete: false, durable: true }, function(queue) {
-
queue.subscribe(function (msg) {
-
logger.info("queue_chat_notify consumer msg : " + msg);
-
receiveNotify(msg.data);
-
});
-
});
-
});
-
-
conn.on('close', function(){
-
logger.error("rabbitmq is close");
-
});
-
-
conn.on('error', function (error) {
-
logger.error('Connection error : ' + error);
-
});
-
-
exports.createConnection = function(r1) {
-
logger.info("createConnection r1 : " + r1);
-
receiveNotify = r1;
-
}
-
exports.publish = function(routeKey, message) {
-
conn.publish(routeKey, message);
-
-
};
下面就是我們最後一步,看一下servier.js中的內容了
-
var app = require('express')();
-
var http = require('http').Server(app);
-
var io = require('socket.io')(http);
-
-
app.get('/', function(req, res){
-
res.send('<h1>Welcome Realtime Server</h1>');
-
});
-
-
http.listen(3000, function(){
-
console.log('listening on *:3000');
-
});
-
-
var mq = require("./mqClient");
-
-
var co = require('co');
-
var wrapper = require('co-redis');
-
var redis = require("./redisClient");
-
var redisClient = wrapper(redis.chatClient);
-
-
var logger = require("./log").logger("server");
-
-
var STORE_USER_KEY = "store_to_chat_user_set_";
-
var USER_SOCKET_KEY = "chat_user_to_socket_hash";
-
-
//rabbitmq監聽到通知的回調操作
-
var receiveNotify = function(msg) {
-
logger.info("\r\n\r\n<-- receiveNotify begin -->");
-
sendMsg(msg);
-
}
-
-
mq.createConnection(receiveNotify);
-
-
io.on('connection', function (socket) {
-
-
io.sockets.emit('connect',{hello:'connection success'});
-
socket.on('sendMsg', function (from,toUser,msg,msgType) {
-
co(function* () {
-
var userSID = yield redisClient.hget(USER_SOCKET_KEY, toUser);
-
io.sockets.connected[userSID].emit('getMessage',{msg:msg});
-
})();
-
});
-
-
socket.on('initUser',function(data){
-
var redis_key = STORE_USER_KEY + data.storeId;
-
-
//redis插入數據
-
redisClient.sadd(redis_key,data.userId);
-
redisClient.hset(USER_SOCKET_KEY, data.userId, socket.id);
-
-
logger.info('initUser --->> 門店 :' + data.storeId + ', 用戶 : ' + data.userId);
-
});
-
-
socket.on('disconnect', function () {
-
-
});
-
});
-
-
var sendMsg = function(obj){
-
co(function* () {
-
var socketId = yield redisClient.hget(USER_SOCKET_KEY, obj.toUser);
-
logger.info("socketId : " + socketId);
-
io.sockets.connected[socketId].emit("getMessage", obj);
-
})();
-
}
下面,我們寫一個客戶端,其實就是一個jsp文件
-
<%@ page language="java" contentType="text/html; charset=UTF-8"
-
pageEncoding="UTF-8"%>
-
<%
-
String chatPath = request.getContextPath();
-
String chatPasePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
-
+ chatPath + "/";
-
%>
-
<script src="<%=chatPath%>/js/common/socket.io.js"></script>
-
<script type="text/javascript">
-
var userId = '${session_key_user_id}';
-
var storeId = '${session_key_store_id}';
-
var user = {"userId" : userId, "storeId" : storeId};
-
if(!isEmpty(userId)){
-
var socket = io.connect('ws://localhost:3000');
-
socket.on('connect', function(data) {
-
//登錄聊天室
-
socket.emit('initUser', user);
-
-
//接收消息
-
socket.on('getMessage', function(data) {
-
console.log("" + data);
-
var fid = data.fid;
-
//PC通知類處理
-
if (fid == 2) {
-
var type = data.data.type;
-
//新預約
-
if (type == 2) {
-
//播放語音
-
textToVoice(0, data.data.msg);
-
}
-
}
-
});
-
});
-
}
-
</script>
可以進行觸發登陸,然後進行接收消息即可了.
記得在Linux上啓動的時候
, 使用 命令進行啓動,可作爲守護進程,這樣node就不會隨着終端的關閉而down掉。。。
到此我們的整套就寫完了,最後注意一下日誌,如果我們想將現在的類庫導入成爲package.json的話,在文件夾中使用npm init,自動生成json文件,就可以進行移植
給出一個該項目的下載鏈接地址 http://download.csdn.NET/detail/u014201191/9303347
#特別注意:
在本案例中 io.sockets.connected[socket.id].emit("method",msg) 和 io.sockets.sockets[socket.id].emit("method",msg) 這兩個方法都不可用,可以換成: io.to[socket.id].emit("method",msg) 觸發傳送事件了,好像是TM因爲版本的問題。。。