Tornado 之 WebSocket


7.3 WebSocket

WebSocket是HTML5規范中新提出的客戶端-服務器通訊協議,協議本身使用新的ws://URL格式。

WebSocket 是獨立的、創建在 TCP 上的協議,和 HTTP 的唯一關聯是使用 HTTP 協議的101狀態碼進行協議切換,使用的 TCP 端口是80,可以用於繞過大多數防火牆的限制。

WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端直接向客戶端推送數據而不需要客戶端進行請求,兩者之間可以創建持久性的連接,並允許數據進行雙向傳送。

目前常見的瀏覽器如 Chrome、IE、Firefox、Safari、Opera 等都支持 WebSocket,同時需要服務端程序支持 WebSocket。

1. Tornado的WebSocket模塊

Tornado提供支持WebSocket的模塊是tornado.websocket,其中提供了一個WebSocketHandler類用來處理通訊。

WebSocketHandler.open()

當一個WebSocket連接建立后被調用。

WebSocketHandler.on_message(message)

當客戶端發送消息message過來時被調用,注意此方法必須被重寫。

WebSocketHandler.on_close()

當WebSocket連接關閉后被調用。

WebSocketHandler.write_message(message, binary=False)

向客戶端發送消息messagea,message可以是字符串或字典(字典會被轉為json字符串)。若binary為False,則message以utf8編碼發送;二進制模式(binary=True)時,可發送任何字節碼。

WebSocketHandler.close()

關閉WebSocket連接。

WebSocketHandler.check_origin(origin)

判斷源origin,對於符合條件(返回判斷結果為True)的請求源origin允許其連接,否則返回403。可以重寫此方法來解決WebSocket的跨域請求(如始終return True)

2. 前端JavaScript編寫

在前端JS中使用WebSocket與服務器通訊的常用方法如下:

var ws = new WebSocket("ws://127.0.0.1:8888/websocket"); // 新建一個ws連接
ws.onopen = function() {  // 連接建立好后的回調
   ws.send("Hello, world");  // 向建立的連接發送消息
};
ws.onmessage = function (evt) {  // 收到服務器發送的消息后執行的回調
   alert(evt.data);  // 接收的消息內容在事件參數evt的data屬性中
};

  

3. 在線聊天室的小Demo

后端代碼 server.py

import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
import os,json
import datetime

from tornado.web import RequestHandler
from tornado.options import define, options
from tornado.websocket import WebSocketHandler

class IndexHandler(RequestHandler):
    def get_current_user(self):
        '''
        重寫RequestHandler類中的get_current_user方法,用來判斷當前是否是登錄狀態,請求中所有被@tornado.web.authenticated 裝飾的方法,都需要此方法返回值不為None,否則給與403拒絕
        :return: 用戶名或者None . 為None判斷為非法請求,POST 時Tornado進行403禁止訪問 ;GET 時 302 重定向到/login
        '''
        user = self.get_argument(name='username',default='None')
        if user and user != 'None':
            print('IndexHandler類 get_current_user獲取到用戶:',user)
            return user

    @tornado.web.authenticated   #確認請求合法 依賴於get_current_user(self):函數的返回值作為判斷請求是否合法
    def get(self):
        print("IndexHandler 收到GET請求")
        self.render("online_index.html",current_user=self.current_user)

    @tornado.web.authenticated
    def post(self, *args, **kwargs):
        print('IndexHandler 收到POST請求')
        self.render("online_index.html", current_user=self.current_user)

class LoginHandler(RequestHandler):
    def get(self, *args, **kwargs):
        '''
        處理輸入昵稱界面get請求
        :param args:
        :param kwargs:
        :return:
        '''
        cookie_value = self.get_secure_cookie('count')
        print('cookie_value :', cookie_value)
        count = int(cookie_value) + 1 if cookie_value else 1
        self.set_secure_cookie("count", str(count))  # 設置一個帶簽名和時間戳的cookie,防止cookie被偽造。

        #使用ajax方法做的前端
        # self.render('login_use_ajax.html')
        #使用form表單提交數據 的前端
        self.render('login_use_form.html')
    def post(self, *args, **kwargs):
        '''
        暫時用不到
        :param args:
        :param kwargs:
        :return:
        '''
        pass

 # 繼承tornado.websocket.WebSocketHandler,只處理WS協議的請求
class ChatHandler(WebSocketHandler):
    def get_current_user(self):
        user = self.get_argument(name='username',default='None')
        if user and user != 'None':
            return user

    users = set()  # 用來存放在線用戶的容器
    @tornado.web.authenticated
    def open(self):
        print('收到新的WebSocket連接')
        self.users.add(self)  # 建立連接后添加用戶到容器中
        for u in self.users:  # 向已在線用戶發送消息
            u    .write_message(u"[%s]-[%s]-%s 進入聊天室" % (
    self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), self.current_user))

    def on_message(self, message):
        message = json.loads(message)
        print(type(message),message)
        for u in self.users:  # 向在線用戶廣播消息
            u.write_message(u"[%s]-[%s]-說:<br> &nbsp&nbsp&nbsp&nbsp%s" % ( datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), self.current_user,message.get('msg')))

    def on_close(self):
        self.users.remove(self) # 用戶關閉連接后從容器中移除用戶
        for u in self.users:
            u.write_message(u"[%s]-[%s]-%s 離開聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),self.current_user))

    def check_origin(self, origin):
        return True  # 允許WebSocket的跨域請求

if __name__ == '__main__':
    tornado.options.parse_command_line()    #允許命令行啟動程序

    app = tornado.web.Application([         #定義處理web請求的應用程序
            (r"/", IndexHandler),
            (r"/login", LoginHandler),
            (r"/chat", ChatHandler),        # 處理WebSocket協議傳輸的數據
        ],
     websocket_ping_interval = 5,     # WebSocket ping探活包發送間隔秒數 static_path
= os.path.join(os.path.dirname(__file__), "statics"), #配置應用程序前端所需靜態文件目錄 template_path = os.path.join(os.path.dirname(__file__), "templates"), #配置html文件路徑 login_url='/login', #配置登錄url xsrf_cookies=True, #,防止跨站請求攻擊,在post請求中起效, cookie_secret="2hcicVu+TqShDpfsjMWQLZ0Mkq5NPEWSk9fi0zsSt3A=", # 安全cookie用預置秘鑰 base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes) debug = True #配置調試級別 ) http_server = tornado.httpserver.HTTPServer(app) #將應用處理邏輯 傳遞給HTTPServer 服務 define("port", default=8000, type=int) #設置一個監聽地址 http_server.listen(options.port) #配置監聽地址到 HTTPServe tornado.ioloop.IOLoop.current().start() #啟動應用

前端代碼online_index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>聊天室</title>
</head>
<body>
    <div id="contents" style="height:500px;overflow:auto; border:green solid 2px"></div>
    <div>
        <textarea id="msg" style="width: 260px;height: 120px" placeholder="輸入要發送的內容"></textarea>
        <input id="send_msg" type="button" href="javascript:;" onclick="sendMsg()" value="發送">
    </div>
    <script src="{{static_url('js/jquery.min.js')}}"></script>
    <script type="text/javascript">

        //創建ws協議連接,創建出來后就已經和服務端建立連接
        var ws = new WebSocket("ws://127.0.0.1:8000/chat?username={{current_user}}");

        //收到ws消息的時候在div對話框里面增加一行文字
        ws.onmessage = function(recv) {
            $("#contents").append("<p>" + recv.data + "</p>");
        };

        var username = "{{current_user}}";
/////////////////////////監控按下回車鍵動作//////////////////////////
//         $("#send_msg").keypress(
//             function () {
//                 console.log('發送消息');
//                 sendMsg();
//             }
//         )
///////////////////////////////////////////////////////////////////////
        function sendMsg() {
            $('#msg').val($('#msg').val().trim());//去除輸入前后的空格
            var msg = $("#msg").val();
            if ( msg && msg.length >0) {
                data = {
                    // username:username,
                    msg:msg
                };
                ws.send(JSON.stringify(data));
                $("#msg").val("");
            }else {
                alert('內容不許為空')
            }
        }
    </script>
</body>
</html>

前端login_use_form登錄界面代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登錄聊天室</title>
    <script src="{{static_url('js/jquery.min.js')}}"></script>
</head>
<body>
    <!--使用form表單獲取登錄頁-->
    <form action="/" method="post" onsubmit="return check_user()">
        {% module xsrf_form_html() %}
    <input id="username" name="username" placeholder="配置昵稱,即可聊天">
    <input id="conform_username" type="submit" value="確認昵稱"  >
    </form>

    <script>
        function check_user(){
            $('#username').val($('#username').val().trim());//去除輸入前后的空格
            var username = $('#username').val();
            if ( !username ){
                alert('昵稱不能為空');
                return false;
            }else {
                alert('昵稱OK');
                return true
            }
        }
    </script>

</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM