Tornado-長輪詢_webSocket

長輪詢

  1. 輪詢(Polling):是指不管服務(wù)器端有沒有更新进陡,客戶端(通常是指瀏覽器)都定時(shí)的發(fā)送請求進(jìn)行查詢滴须,輪詢的結(jié)果可能是服務(wù)器端有新的更新過來,也可能什么也沒有类腮,只是返回個(gè)空的信息做入。不管結(jié)果如何冒晰,客戶端處理完后到下一個(gè)定時(shí)時(shí)間點(diǎn)將繼續(xù)下一輪的輪詢。類似去不斷查詢銀行支付狀態(tài)竟块。
  2. 推送或叫長連接(Long-Polling):這種方式的服務(wù)其客戶端是不做輪詢的壶运,客戶端在發(fā)起一次請求后立即掛起,一直到服務(wù)器端有更新的時(shí)候浪秘,服務(wù)器才會(huì)主動(dòng)推送信息到客戶端蒋情。 在服務(wù)器端有更新并推送信息過來之前這個(gè)周期內(nèi),客戶端不會(huì)有新的多余的請求發(fā)生耸携,服務(wù)器端對此客戶端也啥都不用干棵癣,只保留最基本的連接信息,一旦服務(wù)器有更新將推送給客戶端夺衍,客戶端將相應(yīng)的做出處理狈谊,處理完后再重新發(fā)起下一輪請求。
    優(yōu)點(diǎn):在無消息的情況下不會(huì)頻繁的請求沟沙。
    缺點(diǎn):服務(wù)器hold連接開銷大河劝,浪費(fèi)資源,消耗流量矛紫。
    實(shí)例:WebQQ赎瞎、Hi網(wǎng)頁版、Facebook IM颊咬。

WebSocket

輪詢與長輪詢都是基于HTTP的务甥,兩者本身存在著缺陷:輪詢需要更快的處理速度;長輪詢則更要求處理并發(fā)的能力;兩者都是“被動(dòng)型服務(wù)器”的體現(xiàn):服務(wù)器不會(huì)主動(dòng)推送信息贪染,而是在客戶端發(fā)送ajax請求后進(jìn)行返回的響應(yīng)缓呛。而理想的模型是"在服務(wù)器端數(shù)據(jù)有了變化后,可以主動(dòng)推送給客戶端",這種"主動(dòng)型"服務(wù)器是解決這類問題的很好的方案杭隙。Web Sockets就是這樣的方案哟绊。
由于長輪詢消耗太多資源,主要原因是客戶端和服務(wù)器并沒有連接在一起痰憎,能夠讓客戶端和服務(wù)器一直保持連接票髓,這就需要用到websocket。

應(yīng)用場景:實(shí)現(xiàn)即時(shí)通訊:如股票交易行情分析铣耘、聊天室洽沟、在線游戲等,替代輪詢和長輪詢蜗细。

優(yōu)點(diǎn)

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單裆操,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)怒详。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手踪区,兩者之間就直接可以創(chuàng)建持久性的連接昆烁,并進(jìn)行雙向數(shù)據(jù)傳輸。

介紹:
WebSocket 協(xié)議是基于 TCP 的一種新的 HTML5 網(wǎng)絡(luò)協(xié)議缎岗。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動(dòng)發(fā)送信息給客戶端静尼。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并被RFC7936所補(bǔ)充規(guī)范传泊。在WebSocket API中鼠渺,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作,然后眷细,瀏覽器和服務(wù)器之間就形成了一條快速通道拦盹。兩者之間就直接可以數(shù)據(jù)互相傳送。
簡單說:客戶端和服務(wù)器一直連接在一起溪椎。

WebSocket客戶端編程

瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請求掌敬,連接建立以后,客戶端和服務(wù)器端就可以通過 TCP 連接直接交換數(shù)據(jù)池磁。當(dāng)你獲取 Web Socket 連接后奔害,你可以通過 send() 方法來向服務(wù)器發(fā)送數(shù)據(jù),并通過 onmessage 事件來接收服務(wù)器返回的數(shù)據(jù)地熄。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> WebSocket </title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .box{
            width: 800px;
            margin-left: auto;
            margin-right: auto;
            margin-top: 25px;
        }
        #text{
            width: 685px;
            height: 130px;
            border: 1px solid skyblue;
            border-radius: 10px;
            font-size: 20px;
            text-indent: 1em;
            resize:none;
            outline: none;
        }
        #text::placeholder{
            color: skyblue;
        }
        .btn{
            width: 100px;
            margin: -27px 0 0px 8px;
        }
        #messages{
            padding-left: 10px;
            font-size: 25px;
        }
        #messages li{
            list-style: none;
            color: #000;
            line-height: 30px;
            font-size: 18px;

        }
    </style>
</head>
<body>
    <div class="box">
        <div>
            <textarea id="text" placeholder="請輸入您的內(nèi)容"></textarea>
            <a href="javascript:WebSocketSend();" class="btn btn-primary">發(fā)送</a>
        </div>
        <ul id="messages">
        </ul>
    </div>
    <script type="text/javascript">
        var mes = document.getElementById('messages');

        if('WebSocket' in window){  /*判斷瀏覽器是否支持WebSocket接口*/
            /*創(chuàng)建創(chuàng)建 WebSocket 對象华临,協(xié)議本身使用新的ws://URL格式*/
            var Socket = new WebSocket("ws://127.0.0.1:8000/websocket")
            /*連接建立時(shí)觸發(fā)*/
            Socket.onopen = function () {
                alert("連接已建立,可以進(jìn)行通信")
            };
            /*客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā)*/
            Socket.onmessage = function (ev) {
                var received_msg = ev.data; /*接受消息*/
                var aLi = "<li>" + received_msg + "</li>";
                mes.innerHTML += aLi;
                /*jq方式*/
                // $(mes).append($(aLi));
            };
            /*連接關(guān)閉時(shí)觸發(fā)*/
            Socket.onclose = function () {
                mes.innerHTML += "<br>連接已經(jīng)關(guān)閉...";
            };
        }else{
            /*瀏覽器不支持 WebSocket*/
            alert("您的瀏覽器不支持 WebSocket!");
        }
        function WebSocketSend() {
            /*form 里的Dom元素(input select checkbox textarea radio)都是value*/
            var send_msg = document.getElementById('text').value;
            //或者JQ中獲取
            // var send_msg = $("#text").val();
            /*使用連接發(fā)送消息*/
            Socket.send(send_msg);
        }
    </script>
</body>
</html>

WebSocket服務(wù)端編程

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from data.user_module import User
from tornado.options import define,options

from tornado.web import authenticated  #導(dǎo)入裝飾器
from pycket.session import SessionMixin

import tornado.websocket

import datetime

define('port', default=8080, help='run port', type=int)


#設(shè)置BaseHandler類端考,重寫函數(shù)get_current_user
class BaseHandler(tornado.web.RequestHandler, SessionMixin):
    def get_current_user(self): #前面有綠色小圓圈帶個(gè)o雅潭,再加一個(gè)箭頭表示重寫
        print('********')
        current_user = self.session.get('user') #獲取加密的cookie
        print(current_user)
        if current_user:
            return current_user
        return None


#基類
class BaseWebSocketHandler(tornado.websocket.WebSocketHandler,SessionMixin):
    def get_current_user(self):
        current_user = self.session.get('user')
        if current_user:
            return current_user
        return None
#跳轉(zhuǎn)
class IndexHandler(BaseHandler):
    @authenticated
    def get(self):
        self.render('websocket.html')



#在LoginHandler類中的post函數(shù)里面加上設(shè)置cookie
class LoginHandler(BaseHandler):
    def get(self):
        nextname = self.get_argument('next','') #將原來的路由賦值給nextname
        print(nextname)
        self.render('login.html',nextname=nextname) #跳轉(zhuǎn)頁面帶上獲取的參數(shù)

    def post(self,*args,**kwargs):
        user = self.get_argument('name','')
        username = User.by_name(user)
        passwd = self.get_argument('password','')

        nextname = self.get_argument('next','') #獲取之前頁面的路由

        if username and passwd == username[0].password:
            self.session.set('user', user)  # 設(shè)置加密cookie
            self.redirect(nextname)     #跳轉(zhuǎn)到之前的路由
        else:
            self.render('login.html',nextname=nextname)



'''Tornado 定義了 tornado.websocket.WebSocketHandler 類用于處理 WebSocket 鏈接的請求,
應(yīng)用開發(fā)者應(yīng)該繼承該類并實(shí)現(xiàn)其中的open()却特、on_message()扶供、on_close() 函數(shù)
'''
class MessageHandler(BaseWebSocketHandler):
    users = set()  #避免重復(fù)登錄,利用集合的去重性

    def open(self):   #當(dāng)一個(gè)新的WebSocket連接建立后被調(diào)用裂明。
        MessageHandler.users.add(self)   #類屬性椿浓,用類名直接調(diào)用,
        print('----------open------')

    def on_message(self, message): #當(dāng)客戶端發(fā)送消息message過來時(shí)被調(diào)用闽晦,注意此方法必須被重寫扳碍。
        print(self.request.remote_ip)
        for u in self.users:   #write_message,向客戶端發(fā)送消息messagea,message可以是字符串或字典(字典會(huì)被轉(zhuǎn)為json字符串
            u.write_message('%s-%s-說:%s'%(self.current_user,
                                          datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                                          message
                                          ))

    def on_close(self): #當(dāng)WebSocket連接關(guān)閉后被調(diào)用仙蛉。
        if self in MessageHandler.users:
            MessageHandler.users.remove(self)   #斷開的時(shí)候移除
            print(self)
        print('-----------close--------')





application = tornado.web.Application(
    handlers = [
        (r'/login',LoginHandler),
        (r'/websocket',MessageHandler),
        (r'/',IndexHandler),
    ],
    # cookie_secret='1q2w3e4r',
    #設(shè)置跳轉(zhuǎn)路由笋敞,為了防止在沒有登錄情況下,直接輸入需要登錄才可見的url進(jìn)行訪問荠瘪,做判斷夯巷,如果沒有登錄則跳轉(zhuǎn)到這個(gè)路由下
    login_url='/login',
    pycket={
        'engine': 'redis',
        'storage': {
            'host': 'localhost',
            'port': 6379,
            'db_sessions': 5,
            'db_notifications': 11,
            'max_connections': 2 ** 31,
        },
        'cookies': {
            'expires_days': 30,
            'max_age': 100
        },
    },
    static_path='static',
    template_path = 'templates', #想要Tornado能夠正確的找到html文件赛惩,需要在 Application 中指定文件的位置
    cookie_secret='sgsdf',
    debug = True   #調(diào)試模式,修改后自動(dòng)重啟服務(wù)趁餐,不需要自動(dòng)重啟坊秸,生產(chǎn)情況下切勿開啟,安全性
)

if __name__ == '__main__':
    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

運(yùn)行效果:


image.png

image.png

Tornado 定義了 tornado.websocket.WebSocketHandler 類用于處理 WebSocket 鏈接的請求澎怒,應(yīng)用開發(fā)者應(yīng)該繼承該類并實(shí)現(xiàn)其中的open()、on_message()阶牍、on_close() 函數(shù)喷面,除了這3個(gè) Tornado 框架自動(dòng)調(diào)用的入口函數(shù),WebSocketHandler 還提供了兩個(gè)開發(fā)者主動(dòng)操作 WebSocket的函數(shù):
WebSocketHandler.write_message(message)函數(shù):用于向與本鏈接相對應(yīng)的客戶端寫消息走孽。
WebSocketHandler.close(code=None,reason=None)函數(shù):主動(dòng)關(guān)閉 WebSocket鏈接惧辈。其中的code和reason用于告訴客戶端鏈接被關(guān)閉的原因。參數(shù)code必須是一個(gè)數(shù)值磕瓷,而reason是一個(gè)字符串盒齿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市困食,隨后出現(xiàn)的幾起案子边翁,更是在濱河造成了極大的恐慌,老刑警劉巖硕盹,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件符匾,死亡現(xiàn)場離奇詭異,居然都是意外死亡瘩例,警方通過查閱死者的電腦和手機(jī)啊胶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垛贤,“玉大人焰坪,你說我怎么就攤上這事∑傅耄” “怎么了某饰?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長善绎。 經(jīng)常有香客問我露乏,道長,這世上最難降的妖魔是什么涂邀? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任瘟仿,我火速辦了婚禮,結(jié)果婚禮上比勉,老公的妹妹穿的比我還像新娘劳较。我一直安慰自己驹止,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布观蜗。 她就那樣靜靜地躺著臊恋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪墓捻。 梳的紋絲不亂的頭發(fā)上抖仅,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音砖第,去河邊找鬼抱既。 笑死倦微,一個(gè)胖子當(dāng)著我的面吹牛讥电,可吹牛的內(nèi)容都是我干的御毅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼羽杰,長吁一口氣:“原來是場噩夢啊……” “哼渡紫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起考赛,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤惕澎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后颜骤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體集灌,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年复哆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欣喧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梯找,死狀恐怖唆阿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锈锤,我是刑警寧澤驯鳖,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站久免,受9級特大地震影響浅辙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阎姥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一记舆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呼巴,春花似錦泽腮、人聲如沸御蒲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厚满。三九已至,卻和暖如春碧磅,著一層夾襖步出監(jiān)牢的瞬間碘箍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工鲸郊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丰榴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓严望,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逻恐。 傳聞我的和親對象是個(gè)殘疾皇子像吻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容