亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

基于node+socket.io+redis的多房間多進(jìn)程聊天室

Guakin_Huang / 1142人閱讀

摘要:并且指定收到消息,以及端口的監(jiān)聽方法。四代碼示例多房間實(shí)時(shí)聊天室配置版本須在里配置定義,并設(shè)置。使同一個(gè)的請(qǐng)求能夠落在同一個(gè)機(jī)器同一個(gè)進(jìn)程中。通過主進(jìn)程統(tǒng)一管理維護(hù)子進(jìn)程,每個(gè)進(jìn)程監(jiān)聽一個(gè)端口。

一、相關(guān)技術(shù)介紹:

消息實(shí)時(shí)推送,指的是將消息實(shí)時(shí)地推送到瀏覽器,用戶不需要刷新瀏覽器就可以實(shí)時(shí)獲取最新的消息,實(shí)時(shí)聊天室的技術(shù)原理也是如此。傳統(tǒng)的Web站點(diǎn)為了實(shí)現(xiàn)推送技術(shù),所用的技術(shù)都是輪詢,這種傳統(tǒng)的模式帶來很明顯的缺點(diǎn),即瀏覽器需要不斷的向服務(wù)器發(fā)出請(qǐng)求。
短輪詢(Polling)

客戶端需要定時(shí)往瀏覽器輪詢發(fā)送請(qǐng)求,且只有當(dāng)服務(wù)有數(shù)據(jù)更新后,客戶端的下一次輪詢請(qǐng)求才能拿到更新后的數(shù)據(jù),在數(shù)據(jù)更新前的多次請(qǐng)求相當(dāng)于無效。這對(duì)帶寬資源造成了極大的浪費(fèi),若提高輪詢定時(shí)器時(shí)間,又會(huì)有數(shù)據(jù)更新不及時(shí)的煩惱。
commet
為了解決短輪詢的弊端,一種基于http長(zhǎng)連接的"服務(wù)器推"方式被hack出來。其與短輪詢的區(qū)別主要是,采用commet時(shí),客戶端與服務(wù)端保持一個(gè)長(zhǎng)連接,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),服務(wù)端主動(dòng)將數(shù)據(jù)推送到客戶端。Comet 又可以被細(xì)分為兩種實(shí)現(xiàn)方式,一種是長(zhǎng)輪詢機(jī)制,一種是流技術(shù)。

長(zhǎng)輪詢

長(zhǎng)輪詢跟短輪詢不同的地方是,客戶端往服務(wù)端發(fā)送請(qǐng)求后,服務(wù)端判斷是否有數(shù)據(jù)更新,若沒有,則將請(qǐng)求hold住,等待數(shù)據(jù)更新時(shí),才返回響應(yīng)。這樣則避免了大量無效的http請(qǐng)求,但即使采用長(zhǎng)輪詢方式,接受數(shù)據(jù)更新的最小時(shí)間間隔還是為2*RTT(往返時(shí)間)。

流技術(shù)

流技術(shù)(http stream)基于iframe實(shí)現(xiàn)。通過HTML標(biāo)簽iframe src指向服務(wù)端,建立一個(gè)長(zhǎng)連接。當(dāng)有數(shù)據(jù)推送,則往客戶端返回,無須再請(qǐng)求。但流技術(shù)有個(gè)缺點(diǎn)就是,在瀏覽器頂部會(huì)一直出現(xiàn)頁面未加載完成的loading標(biāo)示。

websocket

為了解決服務(wù)端如何更快地實(shí)時(shí)推送數(shù)據(jù)到客戶端以及以上推送方式技術(shù)的不足,HTML5中定義了Websocket協(xié)議,它是一種在單個(gè)TCP連接上進(jìn)行全雙工通訊的協(xié)議。與http協(xié)議不同的請(qǐng)求/響應(yīng)模式不同,Websocket在建立連接之前有一個(gè)Handshake(Opening Handshake)過程,建立連接之后,雙方即可雙向通信。當(dāng)然,由于websocket是html5新特性,在部分瀏覽器(IE10以下)是不支持的。
我們來看下websocket的握手報(bào)文:

請(qǐng)求報(bào)文:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com

"Upgrade "、"Connection": 告訴服務(wù)器這個(gè)請(qǐng)求是一個(gè)websocket協(xié)議,需要區(qū)別處理

"Upgrade: websocket": 表明這是一個(gè) WebSocket 類型請(qǐng)求,意在告訴 server 需要將通信協(xié)議切換到 WebSocket

"Sec-WebSocket-Key": 是 client 發(fā)送的一個(gè) base64 編碼的密文,要求 server 必須返回一個(gè)對(duì)應(yīng)加密的 "Sec-WebSocket-Accept" 應(yīng)答,否則 client 會(huì)拋出 "Error during WebSocket handshake" 錯(cuò)誤,并關(guān)閉連接

"Sec-WebSocket-Protocol":一個(gè)用戶定義的字符串,用來區(qū)分同URL下,不同的服務(wù)所需要的協(xié)議

"Sec-WebSocket-Version":Websocket Draft (協(xié)議版本)

響應(yīng)報(bào)文:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

"Sec-WebSocket-Accept": 這個(gè)則是經(jīng)過服務(wù)器確認(rèn),并且加密過后的 Sec-WebSocket-Key。加密方式為將Sec-WebSocket-Key與一段固定的 GUID 字符串進(jìn)行連接,然后進(jìn)行SHA-1 hash,接著base64編碼得到。

socket.io(http://socket.io)

是一個(gè)完全由JavaScript實(shí)現(xiàn),基于Node.js、支持WebSocket的協(xié)議用于實(shí)時(shí)通信、跨平臺(tái)的開源框架。Socket.IO除了支持WebSocket通訊協(xié)議外,還支持許多種輪詢機(jī)制以及其它實(shí)時(shí)通信方式,并封裝成了通用的接口,并能夠根據(jù)瀏覽器對(duì)通訊機(jī)制的支持情況自動(dòng)地選擇最佳的方式來實(shí)現(xiàn)網(wǎng)絡(luò)實(shí)時(shí)應(yīng)用。

首先,我們創(chuàng)建一個(gè)socket.io server對(duì)象,指定監(jiān)聽80端口。并且指定收到message消息,以及socket端口的監(jiān)聽方法。接著,當(dāng)socket建立連接后,通過socket.emit方法,可以往客戶端發(fā)送消息。

 var io = require("socket.io")();
 io.on("connection", function(socket) {
    //接受消息
    socket.on("message", function (msg) {
        console.log("receive messge : " + msg );
    });
    
    //發(fā)送消息
    socket.emit("message", "hello");
    
    //斷開連接回調(diào)
    socket.on("disconnect", function () { 
        console.log("socket disconnect");
    });
});
io.listen(80);

客戶端的代碼也非常簡(jiǎn)單,只要引入socket.io對(duì)應(yīng)的客戶端庫(https://github.com/socketio/s...。
在socket建立連接的回調(diào)中,使用socket.emit以及socket.on就可以分別做消息的發(fā)送以及監(jiān)聽了。

二、多節(jié)點(diǎn)集群架構(gòu)設(shè)計(jì)

若只是單機(jī)部署應(yīng)用,單純使用socket.io的消息事件監(jiān)聽處理即可滿足我們的需求。但隨著業(yè)務(wù)的擴(kuò)大,我們需要考慮多機(jī)集群部署,客戶端可以連接到任一節(jié)點(diǎn),并發(fā)送消息。如何做到多節(jié)點(diǎn)的同時(shí)推送,我們需要建立一套多節(jié)點(diǎn)之間的消息分發(fā)/訂閱架構(gòu)。這時(shí)我們引入redis的pub/sub功能。

redis
redis是一個(gè)key-value存儲(chǔ)系統(tǒng),在該項(xiàng)目中主要起到一個(gè)消息分發(fā)中心(publish/subscribe)的作用。用戶通過socket.io namespace 訂閱房間號(hào)后,socket.io server則往redis訂閱(subscribe)該房間號(hào)channel。當(dāng)在該房間中的某一用戶發(fā)送消息時(shí),則通過redis的publish功能往redis該房間號(hào)channel publish消息。這樣所有訂閱該房間號(hào)channel的websocket連接則會(huì)收到消息回調(diào),然后推送給客戶端。

nginx
由于采用了集群架構(gòu),則需要nginx來做反向代理。需要注意的是,websocket的支持需要nginx1.3以上版本。并且我們需要通過配置ip_hash做粘性會(huì)話(ip_hash)處理,避免在低版本瀏覽器socket.io使用兼容方案輪詢請(qǐng)求,請(qǐng)求到不同機(jī)器,造成session異常。

####三、架構(gòu)設(shè)計(jì)圖

客戶端通過socket.io namespace 指定對(duì)應(yīng)roomid,請(qǐng)求到nginx。nginx根據(jù)ip_hash反向代理到對(duì)應(yīng)機(jī)器的某一端口的socket.io server 進(jìn)程。建立websocket連接,并往redis訂閱對(duì)應(yīng)到房間(roomid)channel。到這個(gè)時(shí)候,一個(gè)訂閱了某一房間的websocket通道建立完成。
當(dāng)用戶發(fā)送消息時(shí),socket.io server捕獲到該房間到消息后,即往redis對(duì)應(yīng)房間id的channel publish消息。這時(shí)所有訂閱了該房間id channel的socket.io server就會(huì)收到訂閱響應(yīng),接著找到對(duì)應(yīng)房間id的webscoket通道,并將消息推送到客戶端。

四、代碼示例(多房間實(shí)時(shí)聊天室):

nginx配置(nginx版本須>1.3):
在http{}里配置定義upstream,并設(shè)置ip_hash。使同一個(gè)ip的請(qǐng)求能夠落在同一個(gè)機(jī)器同一個(gè)進(jìn)程中。 如果改節(jié)點(diǎn)掛了,則自動(dòng)重連到另外一個(gè)節(jié)點(diǎn),該方案對(duì)于后期擴(kuò)容也非常方便。

upstream io_nodes {
 ip_hash;
 server 127.0.0.1:6001;
 server 127.0.0.1:6002;
 server 127.0.0.1:6003;
 server 127.0.0.1:6004;
 server 127.0.0.1:6005;
 server 127.0.0.1:6006;
 server 127.0.0.1:6007;
 server 127.0.0.1:6008;
 server 10.x.x.x:6001;
 server 10.x.x.x:6002;
 server 10.x.x.x:6003;
 server 10.x.x.x:6004;
 server 10.x.x.x:6005;
 server 10.x.x.x:6006;
 server 10.x.x.x:6007;
 server 10.x.x.x:6008;
 }

在server中,配置location:

location / {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_pass http://io_nodes;
    proxy_redirect off;
}

cluster.js
我們采用了多進(jìn)程的設(shè)計(jì),充分利用cpu多核優(yōu)勢(shì)。通過主進(jìn)程統(tǒng)一管理維護(hù)子進(jìn)程,每個(gè)進(jìn)程監(jiān)聽一個(gè)端口。

var cupNum = require("os").cpus().length,
    workerArr = [],
    roomInfo = [];
var connectNum = 0;

for (var i = 0; i < cupNum; i++) {
    workerArr.push(fork("./fork_server.js", [6001 + i]));

    workerArr[i].on("message", function(msg) {
        if (msg.cmd && msg.cmd === "client connect") {
            connectNum++;
            console.log("socket server connectnum:" + connectNum);
        }
        if (msg.cmd && msg.cmd === "client disconnect") {
            connectNum--;
            console.log("socket server connectnum:" + connectNum);
        }
    });

fork_server.js

var process = require("process");

var io = require("socket.io")();

var num = 0;

var redis = require("redis");
var redisClient = redis.createClient;

//建立redis pub、sub連接
var pub = redisClient({port:13800, host: "127.0.0.1", password:"xxxx"});
var sub = redisClient({port: 13800, host:"127.0.0.1", password:"xxxx"});

var roomSet = {};

//獲取父進(jìn)程傳遞端口
var port = parseInt(process.argv[2]);

//當(dāng)websocket連接時(shí)
io.on("connection", function(socket) {

    //客戶端請(qǐng)求ws URL:  http://127.0.0.1:6001?roomid=k12_webcourse_room_1
    var roomid = socket.handshake.query.roomid;

    console.log("worker pid: " + process.pid  + " join roomid: "+ roomid);
    
    socket.on("join", function (data) {

        socket.join(roomid);    //加入房間
         
        // 往redis訂閱房間id
        if(!roomSet[roomid]){
            roomSet[roomid] = {};
            console.log("sub channel " + roomid);
            sub.subscribe(roomid);
        }

      roomSet[roomid][socket.id] = {};
      reportConnect();
      console.log(data.username + " join, IP: " + socket.client.conn.remoteAddress);
      roomSet[roomid][socket.id].username = data.username;
      // 往該房間id的reids channel publish用戶進(jìn)入房間消息
      pub.publish(roomid, JSON.stringify({"event":"join","data": data}));
  });
  
  //用戶發(fā)言 推送消息到redis
  socket.on("say", function (data) {
    console.log("Received Message: " + data.text);
    pub.publish(roomid, JSON.stringify({"event":"broadcast_say","data": {
      username: roomSet[roomid][socket.id].username,
      text: data.text
    }}));
  });


    socket.on("disconnect", function() {
        num--;
        console.log("worker pid: " + process.pid + " clien disconnection num:" + num);
        process.send({
            cmd: "client disconnect"
        });

        if (roomSet[roomid] && roomSet[roomid][socket.id] && roomSet[roomid][socket.id].username) {
      console.log(roomSet[roomid][socket.id].username + " quit");
      pub.publish(roomid, JSON.stringify({"event":"broadcast_quit","data": {
        username: roomSet[roomid][socket.id].username
      }}));
    }
    roomSet[roomid] && roomSet[roomid][socket.id] && (delete roomSet[roomid][socket.id]);

    });
});

/**
 * 訂閱redis 回調(diào)
 * @param  {[type]} channel [頻道]
 * @param  {[type]} count   [數(shù)量]  
 * @return {[type]}         [description]
 */
sub.on("subscribe", function (channel, count) {
    console.log("worker pid: " + process.pid + " subscribe: " + channel);
});

/**
 * 收到redis publish 對(duì)應(yīng)channel的消息
 * @param  {[type]} channel  [description]
 * @param  {[type]} message
 * @return {[type]}          [description]
 */
sub.on("message", function (channel, message) {
    console.log("message channel " + channel + ": " + message);
    //往對(duì)應(yīng)房間廣播消息
    io.to(channel).emit("message", JSON.parse(message));
});

/**
 * 上報(bào)連接到master進(jìn)程 
 * @return {[type]} [description]
 */
var reportConnect = function(){
    num++;
    console.log("worker pid: " + process.pid + " client connect connection num:" + num);
    process.send({
        cmd: "client connect"
    });
};


io.listen(port);

console.log("worker pid: " + process.pid + " listen port:" + port);

客戶端:


gihub源碼地址:https://github.com/493326889/...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/91657.html

相關(guān)文章

  • 基于微信公眾號(hào)房間實(shí)時(shí)彈幕消息系統(tǒng)

    摘要:同時(shí)借助實(shí)現(xiàn)在非接口中推送消息流。每分秒鐘最多的彈幕數(shù)目彈幕數(shù)量過多時(shí)優(yōu)先加載最新的。 項(xiàng)目起始原因 源于數(shù)據(jù)庫課設(shè)和以前的一次突發(fā)奇想。其實(shí)還有其他微信公眾號(hào)的彈幕系統(tǒng),但是我發(fā)現(xiàn)使用體驗(yàn)不佳,因?yàn)槟欠N彈幕系統(tǒng)都是私用,并且只支持同時(shí)進(jìn)行一個(gè)房間的使用。所以便萌生了自己寫一個(gè)的想法。(第一次寫md,有點(diǎn)不會(huì),希望諒解--) 主要技術(shù)點(diǎn) Redis(結(jié)合socket實(shí)現(xiàn)在非socke...

    Anonymous1 評(píng)論0 收藏0
  • vue+socket.io+express+mongodb 實(shí)現(xiàn)簡(jiǎn)易房間在線群聊

    摘要:項(xiàng)目簡(jiǎn)介主要是通過做一個(gè)多人在線多房間群聊的小項(xiàng)目來練手全棧技術(shù)的結(jié)合運(yùn)用。編譯運(yùn)行開啟服務(wù),新建命令行窗口啟動(dòng)服務(wù)端,新建命令行窗口啟動(dòng)前端頁面然后在瀏覽器多個(gè)窗口打開,注冊(cè)不同賬號(hào)并登錄即可進(jìn)行多用戶多房間在線聊天。 項(xiàng)目簡(jiǎn)介 主要是通過做一個(gè)多人在線多房間群聊的小項(xiàng)目、來練手全棧技術(shù)的結(jié)合運(yùn)用。 項(xiàng)目源碼:chat-vue-node 主要技術(shù): vue2全家桶 + socket....

    android_c 評(píng)論0 收藏0
  • 前言 項(xiàng)目開始是因?yàn)楣ぷ餍枰粋€(gè)聊天室功能,但是因?yàn)槟承┰蜃罱K選用的是基于xmpp協(xié)議的Strophe.js寫的。于是就想用node自己寫一套,本來只是想簡(jiǎn)單的寫個(gè)聊天頁面,但是寫完了又不滿意,所以不斷的重構(gòu)(似乎可以理解產(chǎn)品經(jīng)理為什么老是改需求了?乛?乛?)。很多東西,比如mongodb,我也是第一次用,以前只接觸過mysql。所以都是一邊學(xué)一邊寫,利用工作之余的時(shí)間,斷斷續(xù)續(xù)的寫了幾個(gè)月(...

    PAMPANG 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<