【原創】node+express+socket搭建一個實時推送應用

技術背景

Web領域的實時推送技術,也被稱做Realtime技術。這種技術要達到的目的是讓用戶不須要刷新瀏覽器就能夠得到實時更新。javascript

應用場景:css

  • 監控系統:後臺硬件熱插拔、LED、溫度、電壓發生變化
  • 即時通訊系統:其它用戶登陸、發送信息
  • 即時報價系統:後臺數據庫內容發生變化

技術實現方案:ajax long polling(ajax長輪詢),comet(http長鏈接)、sockethtml

這裏有篇文章介紹了這幾種技術,能夠看一下。html5

http://www.ibm.com/developerworks/cn/web/wa-lo-comet/java

http://jingyan.baidu.com/article/08b6a591e07ecc14a80922f1.htmlnode

websocket簡介

HTTP是一種基於消息(message)的請求(request )/應答(response)協議。當咱們在網頁中點擊一條連接(或者提交一個表單)的時候,瀏覽器給服務器發一個request message,而後服務器算啊算,答覆一條response message。主動發起TCP鏈接的是client,接受TCP鏈接的是server。HTTP消息只有兩種:request和response。client只能發送request message,server只能發送response message。一問一答,所以按HTTP協議自己的設計,服務器不能主動的把消息推給客戶端。jquery

所以,若是讓服務器端也能夠主動發送信息到客戶端,就能夠很大程度改進這些不足。WebSocket就是一個實現這種雙向通訊的新協議。git

WebSocket是基於HTTP的功能追加協議github

WebSocket最初由html5提出,但如今已經發展爲一個獨立的協議標準。WebSocket能夠分爲協議( Protocol )和 API 兩部分,分別由 IETF 和W3C制定了標準。web

先來看看WebSocket協議的創建過程。

爲了實現WebSocket通訊,首先須要客戶端發起一次普通HTTP請求(也就是說,WebSocket的創建是依賴HTTP的)。請求報文可能像這樣:

GET ws://websocket.example.com/ HTTP/1.1
Host: websocket.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com
Sec-WebSocket-Key:pAloKxsGSHtpIHrJdWLvzQ==
Sec-WebSocket-Version:13

 

其中HTTP頭部字段 Upgrade: websocket 和 Connection: Upgrade 很重要,告訴服務器通訊協議將發生改變,轉爲WebSocket協議。支持WebSocket的服務器端在確認以上請求後,應返回狀態碼爲 101 Switching Protocols 的響應:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: nRu4KAPUPjjWYrnzxDVeqOxCvlM=

其中字段 Sec-WebSocket-Accept 是由服務器對前面客戶端發送的 Sec-WebSocket-Key 進行確認和加密後的結果,至關於一次驗證,以幫助客戶端確信對方是真實可用的WebSocket服務器。

驗證經過後,這個握手響應就確立了WebSocket鏈接,此後,服務器端就能夠主動發信息給客戶端了。此時的狀態比較像服務器端和客戶端接通了電話,不管是誰有什麼信息想告訴對方,開口就行了。

一旦創建了WebSocket鏈接,此後的通訊就再也不使用HTTP了,改成使用WebSocket獨立的數據幀

整個過程像這樣:

開始碼磚

1.創建項目文件,安裝node 和express框架 

socket.io http://socket.io/docs/

服務器端安裝socket.io

$ npm install socket.io
客戶端下載socket.io.js

client是客戶端文件 server是服務器端文件。

2.寫界面

個人界面是這樣的

html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>聊天室</title>
    <link rel="stylesheet" type="text/css" href="css/index.css"/>
</head>
<body>
    <div class="main">
        <div class="main-top">
            socket.io demo
        </div>
        <div class="main-body">
            <section class="chatRoomInfo">
                <div class="info">當前共有<span class="chatNum">0</span>人在線。在線列表:&nbsp;<span class="chatList"></span></div>
            </section>
            <!--<section class="chatRoomTip">
                <div>子木加入到聊天室</div>
            </section>
            <section class="user clearfix">
                <span>子木</span>
                <div>
                    測試測試測試測試測試測試測試測試測試試測試測試測試測試測試測試測試測試測試測試測試
                </div>
            </section>
            <section class="server clearfix">
                <span>子木</span>
                <div>
                    測試測試測試
                </div>
            </section>-->
        </div>
        <div class="main-footer clearfix">
            <div class="input">
                <input type="text" name="msg" id="msg" value="" />
            </div>
            <button type="button" class="send">發送</button>
        </div>
    </div>
    <script src="js/jquery-2.1.0.js" type="text/javascript" charset="utf-8"></script>
    <script src="js/socket.io.js" type="text/javascript" charset="utf-8"></script>
    <script>
       //do something</script>
</body>
</html>

css

* {
    margin: 0;
    padding: 0;
}

.clearfix {
    zoom: 1;
}

.clearfix:after {
    clear: both;
    content: '.';
    display: block;
    width: 0;
    height: 0;
    visibility: hidden;
}

.main {
    width: 100%;
    height: 100%;
    font-size: 14px;
}

.main-top {
    height: 30px;
    background-color: #3d3d3d;
    text-indent: 15px;
    color: #ffffff;
    font-size: 16px;
    line-height: 30px;
}

.main-body {
    background-color: #efeff4;
    position: absolute;
    top: 30px;
    bottom: 50px;
    width: 100%;
    overflow-y: scroll;
    scrollbar-3dlight-color: ;
}

.chatRoomInfo {
    padding: 10px;
    font-size: 12px;
    color: #666;
}

.chatRoomTip {
    text-align: center;
    padding: 10px;
    font-size: 12px;
    color: #444;
}

.user {
    width: 100%;
    min-height: 38px;
    min-width: 36px;
    margin-bottom: 15px;
}

.user span {
    float: right;
}

.user div {
    float: right;
    min-height: 38px;
    min-width: 38px;
    max-width: 70%;
    line-height: 38px;
    padding: 0 15px;
    color: #FFFFFF;
    margin-right: 10px;
    word-break: break-all;
    background-color: #007aff;
    position: relative;
    border-radius: 5px;
}

.user div:after {
    content: "";
    position: absolute;
    right: -5px;
    top: 4px;
    width: 0;
    height: 0;
    border-top: solid transparent;
    border-left: 7px solid #007aff;
    border-bottom: 4px solid transparent;
}

.server {
    width: 100%;
    min-height: 38px;
    min-width: 36px;
    margin-bottom: 15px;
}

.server span {
    float: left;
}

.server div {
    float: left;
    min-height: 38px;
    min-width: 38px;
    max-width: 70%;
    line-height: 38px;
    padding: 0 15px;
    color: #FFFFFF;
    margin-left: 10px;
    word-break: break-all;
    background-color: #007aff;
    position: relative;
    border-radius: 5px;
}

.server div:after {
    content: "";
    position: absolute;
    left: -5px;
    top: 4px;
    width: 0;
    height: 0;
    border-top: solid transparent;
    border-right: 7px solid #007aff;
    border-bottom: 4px solid transparent;
}
.main-footer{
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 50px;
}
.input{
    float: left;
    width: 80%;
    height: 40px;
    margin-top: 5px;
    margin-left: 1%;
    margin-right: 1%;
    border: 1px solid #666666;
}
.input input{
    width: 100%;
    height: 40px;
    outline: none;
    border: none;
    font-size: 14px;
    color: #333;
}
.send{
    float: left;
    width: 16%;
    height: 40px;
    margin-top: 5px;
    margin-left: 1%;
    border: none;
    background-color: #e8e8e8;
    color: #007aff;
    outline: none;
}

如今開始寫邏輯

客戶端代碼實現

     /*按鈕點擊效果*/
        $('.send').mousedown(function(){
            $(this).css({'background':"#007aff",'color':"#ffffff"});
        })
        $('.send').mouseup(function(){
            $(this).css({'background':"#e8e8e8",'color':"#ffffff"});
        })
        /*socket*/
        window.onload=function () {
              var username=prompt('請輸入您的姓名');
              if (!username){
                  alert('姓名必填');
                  history.go(0);
              }
//          username="子木";
            userId=genUid();
            var userInfo={
                'userid':userId,
                'username':username
            };
            //鏈接socket後端服務器
            var socket=io.connect("ws://127.0.0.1:4000");
            //通知用戶有用戶登陸
            socket.emit('login',userInfo);
            //監聽新用戶登陸
            socket.on('login',function (o) {
                updateMsg(o, 'login');
               });
            //監聽用戶退出
            socket.on('logout',function (o) {
                updateMsg(o, 'logout');
            });
            //發送消息
            socket.on('message',function (obj) {
                if(obj.userid==userId) {
                    var MsgHtml='<section class="user clearfix">'
                                        +'<span>'+obj.username+'</span>'
                                        +'<div>'+obj.content+'</div>'
                                     +'</section>';
                }else{
                    var MsgHtml='<section class="server clearfix">'
                                         +'<span>'+obj.username+'</span>'
                                         +'<div>'+obj.content+'</div>'
                                      +'</section>';
                }
                $('.main-body').append(MsgHtml);
                $('.main-body').scrollTop(99999);
            })
            $('.send').click(function () {
                var content=$('input[name="msg"]').val();
                if (content){
                    var obj={
                        'userid':userId,
                        'username':username,
                        'content':content
                    }
                    socket.emit('message',obj);
                    $('input[name="msg"]').val("");
                }
            })

        }

        /*用戶id生成*/
        function  genUid() {
            return new Date().getTime()+""+Math.floor(Math.random()*899+100);
        }
        function logout(){
            socket.disconnect();
            location.reload();
        }
        /*監聽函數*/
        function updateMsg(o,action) {
            //當前在線列表
            var onlineUser=o.onlineUser;
            //當前在線數
            var onlineCount=o.onlineCount;
            //新加用戶
            var user=o.user;
            //更新在線人數
            var userList='';
            var separator = '';
            for(key in onlineUser){
                userList+=separator+onlineUser[key];
                separator = '、';
            }
            //跟新房間信息
            $('.chatNum').text(onlineCount);
            $('.chatList').text(userList);
            //系統消息
            if(action=='login'){
                var sysHtml='<section class="chatRoomTip"><div>'+user.username+'進入聊天室</div></section>';
            }
            if(action=="logout"){
                var sysHtml='<section class="chatRoomTip"><div>'+user.username+'退出聊天室</div></section>';
            }
            $(".main-body").append(sysHtml);
            $('.main-body').scrollTop(99999);
        }

服務器代碼實現 app.js

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

app.get('/socket/client/index.html',function (req,res) {
    res.send('<h1>welcome</h1>');
})
//在線用戶
var onlineUser={};
var onlineCount=0;

io.on('connection',function (socket) {
    console.log('新用戶登陸');

    //監聽新用戶加入
    socket.on('login',function (obj) {
        socket.name=obj.userid;
        //檢查用戶在線列表
        if(!onlineUser.hasOwnProperty(obj.userid)){
            onlineUser[obj.userid]=obj.username;
            //在線人數+1
            onlineCount++;
        }
        //廣播消息
        io.emit('login',{onlineUser:onlineUser,onlineCount:onlineCount,user:obj});
        console.log(obj.username+"加入了聊天室");
    })

    //監聽用戶退出
    socket.on('disconnect',function () {
        //將退出用戶在在線列表刪除
        if(onlineUser.hasOwnProperty(socket.name)){
            //退出用戶信息
            var obj={userid:socket.name, username:onlineUser[socket.name]};
            //刪除
            delete onlineUser[socket.name];
            //在線人數-1
            onlineCount--;
            //廣播消息
            io.emit('logout',{onlineUser:onlineUser,onlineCount:onlineCount,user:obj});
            console.log(obj.username+"退出了聊天室");
        }
    })

    //監聽用戶發佈聊天內容
    socket.on('message', function(obj){
        //向全部客戶端廣播發布的消息
        io.emit('message', obj);
        console.log(obj.username+'說:'+obj.content);
    });
})
http.listen(4000, function(){
    console.log('listening on *:4000');
});

代碼所有貼上來

源碼地址:https://github.com/zimuqi/socketChat

 下載後安裝好socket.io  express後進入到server 目錄下 直接node app.js。而後打開項目主頁就能夠看到效了。能夠多打開幾個窗口互動一下。

 有興趣的能夠再去研究一下WebIM系統,實現相似微信,qq的功能,客戶端能夠看到好友在線狀態,在線列表,添加好友,刪除好友,新建羣組等,消息的發送除了支持基本的文字外,還能支持表情、圖片和文件。

相關文章
相關標籤/搜索