flask之gevent-websocket的IO多路復用長連接通信


本節目錄:

(一)筆記總結;

(二)gevent-websocket+flask+javascript實現WS即時通信

  (1)無昵稱群聊

  (2)有昵稱群聊

  (3)私聊

 

三種通信模型簡述: 

(1)輪詢:
客戶端周期性不間斷高頻率的給服務器發送請求:
客戶端請求--服務器響應--斷開連接,請求次數QPS比較頻繁,對客戶端和服務器的配置要求比較高
(2)長輪詢:
客戶端周期性不間斷的給服務器發送請求:
客戶端與服務器建立的連接會保持一定時間,因此請求相對不會特別頻繁
(3)長連接:
客戶端與服務端建立連接后,如果不是特殊原因(客戶端主動斷開,服務器故障)連接會一直保持
同時通過多線程進行IO多路復用技術解決並發問題

flask中基於gevent-websocket的IO多路復用技術進行長連接通信:  

(1)基於gevent-websocket的IO多路復用長連接通信,需要導入一下模塊:
#pip install gevent-websocket導入IO多路復用模塊
        from geventwebsocket.handler import WebSocketHandler         #提供WS(websocket)協議處理
        from geventwebsocket.server import WSGIServer                #websocket服務承載
        #WSGIServer導入的就是gevent.pywsgi中的類
        # from gevent.pywsgi import WSGIServer
        from geventwebsocket.websocket import WebSocket              #websocket語法提示
(2)路由視圖函數中的處理必須通過request.environ.get('wsgi.websocket')獲取與客戶端的ws連接client_socket:
 #websocket協議通信如下(http請求正常處理即可)
        @app.route()
        def func():
            client_socket=request.environ.get('wsgi.websocket')
            while 1:
                client_socket.recive()
                    ...
                client_socket.send(str)
                    ...
(3)flask項目啟動如下:
WSGIServer默認處理的是http請求,路由視圖中可以正常使用http,
但是在使用ws協議時務必在視圖函數通過request.environ.get('wsgi.websocket')獲取與客戶端的ws連接client_socket,
通過連接client_socket進行client_socket.recive()/client_socket.send()通信,這連個方法會對字符串自動進行編解碼)
http_server=WSGIServer(('192.168.16.14',8888),application=app,handler_class=WebSocketHandler。
http_server.serve_forever()
(4)前端頁面使用js進行WS(websocket)請求:
瀏覽器提供了websocket客戶端,直接new創建websocket連接ws,
(ws狀態碼0表示已創建未連接,1表示已連接保持中,2表示客戶端主動斷開連接,3表示服務端斷開連接),
通過ws.onmessage=function (MessageEvent){}監聽執行回調函數獲取信息MessageEvent.data,
通過ws.send()發送信息。
<script>
    var ws = new WebSocket('ws://192.168.16.14:8888/websocket');
    ws.onmessage = function (MessageEvent) {
        console.log(MessageEvent);
        console.log(MessageEvent.data);        
    };

    function send() {
        var msg = document.getElementById('msg').value;
        ws.send(msg);
    }
</script>

http請求協議和websocket請求協議的請求原數據request.environ和請求頭部信息request.headers比較:
http-environ:

{
     'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_SOFTWARE': 'gevent/1.4 Python/3.6', 
     'SCRIPT_NAME': '', 'wsgi.version': (1, 0), 
     'wsgi.multithread': False, 'wsgi.multiprocess': False, 
     'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 
     'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 
     'SERVER_NAME': 'PC-Yang', 'SERVER_PORT': '8888', 'REQUEST_METHOD': 'GET', 
    'PATH_INFO': '/websocket', 'QUERY_STRING': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 
    'REMOTE_ADDR': '192.168.16.14', 'REMOTE_PORT': '61539', 'HTTP_HOST': '192.168.16.14:8888', 
    'HTTP_CONNECTION': 'keep-alive', 'HTTP_PRAGMA': 'no-cache', 'HTTP_CACHE_CONTROL': 'no-cache', 
    'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 
    'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36', 
    'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 
    'HTTP_ACCEPT_ENCODING': 'gzip, deflate', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 
    'wsgi.input': <gevent.pywsgi.Input object at 0x03A9DC00>, 
     'wsgi.input_terminated': True, 'werkzeug.request': <Request 'http://192.168.16.14:8888/websocket' [GET]>
    }
http-environ

http-headers:

'''
    Host: 192.168.16.14:8888
    Connection: keep-alive
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
'''
http-headers

websocket-environ: 'wsgi.websocket': <geventwebsocket.websocket.WebSocket object at 0x03A9DC00>,websocket連接

'''
    {
     'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_SOFTWARE': 'gevent/1.4 Python/3.6', 
     'SCRIPT_NAME': '', 'wsgi.version': (1, 0), 'wsgi.multithread': False, 
     'wsgi.multiprocess': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 
     'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 
     'SERVER_NAME': 'PC-Yang', 'SERVER_PORT': '8888', 'REQUEST_METHOD': 'GET', 
    'PATH_INFO': '/websocket', 'QUERY_STRING': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 
    'REMOTE_ADDR': '192.168.16.14', 'REMOTE_PORT': '61591', 'HTTP_HOST': '192.168.16.14:8888', 
    'HTTP_CONNECTION': 'Upgrade', 'HTTP_PRAGMA': 'no-cache', 'HTTP_CACHE_CONTROL': 'no-cache', 
    'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36', 
    'HTTP_UPGRADE': 'websocket', 'HTTP_ORIGIN': 'http://192.168.16.14:8888', 'HTTP_SEC_WEBSOCKET_VERSION': '13', 
    'HTTP_ACCEPT_ENCODING': 'gzip, deflate', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 
    'HTTP_SEC_WEBSOCKET_KEY': 'Oyfq0MCEBnsypKstjjRvYg==', 
    'HTTP_SEC_WEBSOCKET_EXTENSIONS': 'permessage-deflate; client_max_window_bits', 
    'wsgi.input': <gevent.pywsgi.Input object at 0x03A9DCA8>, 
    'wsgi.input_terminated': True, 'wsgi.websocket_version': '13', 
    
    'wsgi.websocket': <geventwebsocket.websocket.WebSocket object at 0x03A9DC00>, 
    
    'werkzeug.request': <Request 'http://192.168.16.14:8888/websocket' [GET]>
    }
'''
websocket-environ

websocket-headers:     Upgrade: websocket   #websocket請求中的標識

'''
    Host: 192.168.16.14:8888
    Connection: Upgrade
    Pragma: no-cache
    Cache-Control: no-cache
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
    
    Upgrade: websocket   #websocket請求中的標識
    
    Origin: http://192.168.16.14:8888
    Sec-Websocket-Version: 13
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Sec-Websocket-Key: Oyfq0MCEBnsypKstjjRvYg==
    Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
'''
websocket-headers

 

(1)基於websocket+flask實現的群聊無昵稱即時通信

  flask_websocket(MUC_Nonick).py  

 1 '''
 2 基於websocket+flask實現的群聊無昵稱即時通信
 3 設計列表client_list = []來存儲客戶端與服務器的連接,
 4 服務收到任意客戶端的信息(信息時),對連接存儲列表進行遍歷獲取每個連接,直接進行轉發
 5 '''
 6 from flask import Flask, render_template, request
 7 
 8 # pip install gevent-websocket導入IO多路復用模塊
 9 from geventwebsocket.handler import WebSocketHandler  # 提供WS(websocket)協議處理
10 from geventwebsocket.server import WSGIServer  # websocket服務承載
11 # WSGIServer導入的就是gevent.pywsgi中的類
12 # from gevent.pywsgi import WSGIServer
13 from geventwebsocket.websocket import WebSocket  # websocket語法提示
14 
15 app = Flask(__name__)
16 
17 # @app.route('/websocket')
18 # 多個客戶端可以同時給falsk服務端發送ws協議的信息
19 # def websocket():
20 #     client_socket=request.environ.get('wsgi.websocket') #type:WebSocket
21 #     while 1:
22 #             msg_from_cli=client_socket.receive()
23 #             print(msg_from_cli)
24 # 多個客戶端可以同時給falsk服務端發送ws協議的信息,同時服務端將信息轉送到每個客戶端頁面,實現多人聊天室即時通信
25 client_list = []
26 
27 
28 @app.route('/websocket')
29 def websocket():
30     client_socket = request.environ.get('wsgi.websocket')  # type:WebSocket
31     client_list.append(client_socket)
32     # print(len(client_list), client_list)
33     while 1:
34         msg_from_cli = client_socket.receive()
35         # print(msg_from_cli)
36         #收到任何一個客戶端的信息都進行全部轉發(注意如果某個客戶端連接斷開,在遍歷發送時連接不存在會報錯,需要異常處理)
37         for client in client_list:
38             try:
39                 client.send(msg_from_cli)
40             except Exception as e:
41                 continue
42 
43 @app.route('/chat')
44 def chat():
45     return render_template('MUC_Nonick.html')
46 
47 
48 if __name__ == '__main__':
49     # app.run('192.168.16.14',8888,debug=True)
50     http_server = WSGIServer(('192.168.16.14', 8888), application=app, handler_class=WebSocketHandler)
51     http_server.serve_forever()
flask_websocket(MUC_Nonick).py

  MUC_Nonick.html  

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>多用戶聊天無昵稱</title>
 6 </head>
 7 <body>
 8 <div id="chat_room">
 9     <p>請輸入聊天內容:<input type="text" id="msg">
10         <button id="send" onclick="send()">發送</button>
11     </p>
12     <div id="chat_content"></div>
13 </div>
14 </body>
15 <script type="application/javascript">
16     var ws = new WebSocket('ws://192.168.16.14:8888/websocket');
17     ws.onmessage = function (MessageEvent) {
18         //console.log(MessageEvent);
19        //console.log(MessageEvent.data);
20         var time=new Date();
21         var t= time.toLocaleString();
22         var p=document.createElement("p");
23         p.innerText="("+t+")"+MessageEvent.data;
24         document.getElementById('chat_content').appendChild(p);
25     };
26 
27     function send() {
28         var msg = document.getElementById('msg').value;
29         ws.send(msg);
30     }
31 </script>
32 </html>
MUC_Nonick.html

 

(2)基於websocket+flask實現的群聊有昵稱即時通信

  版本一:通過動態路有參數獲取客戶端昵稱

  flask_websocket(MUC_nick_route).py  

 1 '''
 2 基於websocket+flask實現的群聊即時通信
 3 設計字典client_dict = {}來存儲{客戶端的名字:客戶端與服務器的連接},客戶端的名字通過動態路由參數獲取到,
 4 服務器接收客戶端發來的信息(經過json序列化后的字典),對存儲連接信息的字典進行遍歷,獲取客戶端的連接,直接轉發
 5 '''
 6 from flask import Flask, render_template, request
 7 from geventwebsocket.handler import WebSocketHandler  # 提供WS(websocket)協議處理
 8 from geventwebsocket.server import WSGIServer  # websocket服務承載
 9 from geventwebsocket.websocket import WebSocket  # websocket語法提示
10 
11 app = Flask(__name__)
12 
13 client_dict = {}
14 
15 
16 @app.route('/websocket/<client_name>')#通過動態路由參數獲取昵稱,必須在視圖函定義同名形參接收
17 def websocket(client_name):
18     client_socket = request.environ.get('wsgi.websocket')  # type:WebSocket
19     client_dict[client_name] = client_socket
20     # print(len(client_dict), client_dict)
21     while 1:
22         msg_from_cli = client_socket.receive()
23         for client in client_dict.values():
24             try:
25                 client.send(msg_from_cli)
26             except Exception as e:
27                 continue
28 
29 
30 @app.route('/chat')
31 def chat():
32     return render_template('MUC_nick_route.html')
33 
34 
35 if __name__ == '__main__':
36     # app.run('192.168.16.14',8888,debug=True)
37     http_server = WSGIServer(('192.168.16.14', 8888), application=app, handler_class=WebSocketHandler)
38     http_server.serve_forever()
flask_websocket(MUC_nick_route).py

  MUC_nick_route.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>多用戶聊天無昵稱</title>
 6 </head>
 7 <body>
 8 <div id="chat_room">
 9     <p>輸入昵稱進入多人聊天室:<input type="text" id="client_name"></input>
10         <button id='login' onclick="login()">登錄</button>
11     </p>
12     <p id='chat_msg' hidden="hidden">請輸入聊天內容:<input type="text" id="msg">
13         <button id="send" onclick="send()">發送</button>
14     </p>
15     <div id="chat_content" hidden="hidden"></div>
16 </div>
17 </body>
18 <script type="application/javascript">
19     var ws = null;
20     var name=null;
21 
22     function login() {
23         document.getElementById('login').setAttribute('hidden', 'hidden');
24         document.getElementById('client_name').setAttribute('disabled', 'disabled');
25         document.getElementById('chat_msg').removeAttribute('hidden');
26         document.getElementById('chat_content').removeAttribute('hidden');
27         name = document.getElementById('client_name').value;
28         //進行WS實例化
29         ws = new WebSocket('ws://192.168.16.14:8888/websocket/' + name);
30 
31         //監聽服務器發來的消息(json數據)
32         ws.onmessage = function (MessageEvent) {
33             //console.log(MessageEvent);
34             //console.log(MessageEvent.data);
35             var content_str = JSON.parse(MessageEvent.data);
36             var time = new Date();
37             var t = time.toLocaleTimeString();
38             var p = document.createElement("p");
39             p.innerText = content_str.name + "(" + t + "):" + content_str.msg;
40             document.getElementById('chat_content').appendChild(p);
41         };
42     };
43 
44 
45     //聊天信息發送(json數據)
46     function send() {
47         var msg = document.getElementById('msg').value;
48         var data = {
49             name: name,
50             msg: msg,
51         };
52         var data_json = JSON.stringify(data);
53         ws.send(data_json);
54     }
55 </script>
56 </html>
MUC_nick_route.html

 

  版本二:通過websocket接收客戶端發來基於websocket發來的昵稱: 

  flask_websocket(MUC_nick).py

 1 '''
 2 基於websocket+flask實現的群聊即時通信
 3 設計字典client_dict = {}來存儲{客戶端的名字:客戶端與服務器的連接},客戶端的名字通過客戶端執行WS請求協議發送獲取,
 4 服務器再持續接收客戶端發來的信息(經過json序列化后的字典),對存儲連接信息的字典進行遍歷,獲取客戶端的連接,直接轉發
 5 '''
 6 from flask import Flask, render_template, request
 7 from geventwebsocket.handler import WebSocketHandler  # 提供WS(websocket)協議處理
 8 from geventwebsocket.server import WSGIServer  # websocket服務承載
 9 from geventwebsocket.websocket import WebSocket  # websocket語法提示
10 
11 
12 app = Flask(__name__)
13 
14 client_dict = {}
15 
16 
17 @app.route('/websocket')
18 def websocket():
19     client_socket = request.environ.get('wsgi.websocket')  # type:WebSocket
20     # print(client_socket)
21     client_name = client_socket.receive()
22     client_dict[client_name] = client_socket
23     # print(len(client_dict), client_dict)
24     while 1:
25         msg_from_cli = client_socket.receive()
26         # msg_from_cli_str=json.loads(msg_from_cli)
27         # print(msg_from_cli_str)
28         for client in client_dict.values():
29             try:
30                 client.send(msg_from_cli)
31             except Exception as e:
32                 continue
33 
34 
35 @app.route('/chat')
36 def chat():
37     return render_template('MUC_nick.html')
38 
39 
40 if __name__ == '__main__':
41     # app.run('192.168.16.14',8888,debug=True)
42     http_server = WSGIServer(('192.168.16.14', 8888), application=app, handler_class=WebSocketHandler)
43     http_server.serve_forever()
flask_websocket(MUC_nick).py

  MUC_nick.html  

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>多用戶聊天無昵稱</title>
 6 </head>
 7 <body>
 8 <div id="chat_room">
 9     <p>輸入昵稱進入多人聊天室:<input type="text" id="client_name"></input>
10         <button id='login' onclick="login()">登錄</button>
11     </p>
12     <p id='chat_msg' hidden="hidden">請輸入聊天內容:<input type="text" id="msg">
13         <button id="send" onclick="send()">發送</button>
14     </p>
15     <div id="chat_content" ></div>
16 </div>
17 </body>
18 <script type="application/javascript">
19     var ws = new WebSocket('ws://192.168.16.14:8888/websocket');
20     var name = null;
21 
22     //向服務端發送本機昵稱
23     function login() {
24         document.getElementById('login').setAttribute('hidden', 'hidden');
25         document.getElementById('client_name').setAttribute('disabled', 'disabled');
26         document.getElementById('chat_msg').removeAttribute('hidden');
27         document.getElementById('chat_content').removeAttribute('hidden');
28         name = document.getElementById('client_name').value;
29         ws.send(name);
30     };
31 
32 
33    //監聽服務器發來的消息(json數據)
34     ws.onmessage = function (MessageEvent) {
35         //console.log(MessageEvent);
36         //console.log(MessageEvent.data);
37         var content_str = JSON.parse(MessageEvent.data);
38         var time = new Date();
39         var t = time.toLocaleTimeString();
40         var p = document.createElement("p");
41         p.innerText = content_str.name + "(" + t + "):" + content_str.msg;
42         document.getElementById('chat_content').appendChild(p);
43     };
44 
45 
46     //聊天信息發送(json數據)
47     function send() {
48         var msg = document.getElementById('msg').value;
49         var data = {
50             name: name,
51             msg: msg
52         };
53         var data_json = JSON.stringify(data);
54         ws.send(data_json);
55     }
56 </script>
57 </html>
MUC_nick.html

 

(3)基於websocket+flask實現的私聊即時通信

  flask_websocket(Private_chat).py

 1 '''
 2 基於websocket+flask實現的私聊即時通信
 3 設計字典client_dict = {}來存儲{客戶端的名字:客戶端與服務器的連接},客戶端的名字通過動態路由參數獲取到,
 4 服務器通過客戶端發來的信息(經過json序列化后的字典)中的目標客戶端名字,在存儲字典中獲取目標客戶端的連接,直接轉發
 5 '''
 6 from flask import Flask, render_template, request
 7 from geventwebsocket.handler import WebSocketHandler  # 提供WS(websocket)協議處理
 8 from geventwebsocket.server import WSGIServer  # websocket服務承載
 9 from geventwebsocket.websocket import WebSocket  # websocket語法提示
10 import json
11 
12 app = Flask(__name__)
13 
14 client_dict = {}
15 
16 
17 @app.route('/websocket/<client_name>')  # 通過動態路由參數獲取昵稱,必須在視圖函定義同名形參接收
18 def websocket(client_name):
19     client_socket = request.environ.get('wsgi.websocket')  # type:WebSocket
20     client_dict[client_name] = client_socket
21     if client_socket:
22         while 1:
23             msg_from_cli = client_socket.receive()
24             to_client = json.loads(msg_from_cli).get('to_client')
25             client = client_dict.get(to_client)
26             try:
27                 client.send(msg_from_cli)
28             except Exception as e:
29                 continue
30 
31 
32 @app.route('/chat')
33 def chat():
34     return render_template('Private_chat.html')
35 
36 
37 if __name__ == '__main__':
38     http_server = WSGIServer(('192.168.16.14', 8888), application=app, handler_class=WebSocketHandler)
39     http_server.serve_forever()
flask_websocket(Private_chat).py

  Private_chat.html  

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>單人聊天室</title>
 6 
 7 </head>
 8 <body>
 9 <div id="chat_room">
10     <p>輸入昵稱進入單人聊天室:<input type="text" id="client_name"></input>
11         <button id='login' onclick="login()">登錄</button>
12     </p>
13     <p hidden id="client_recv">收信人:<input type="text" id="to_client"></p>
14     <p id='chat_msg' hidden="hidden">請輸入聊天內容:<input type="text" id="msg">
15 
16         <button id="send" onclick="send()">發送</button>
17     </p>
18     <div id="chat_content" hidden="hidden"></div>
19 </div>
20 </body>
21 <script type="application/javascript">
22     var ws = null;
23     var name=null;
24 
25     function login() {
26         document.getElementById('login').setAttribute('hidden', 'hidden');
27         document.getElementById('client_name').setAttribute('disabled', 'disabled');
28         document.getElementById('chat_msg').removeAttribute('hidden');
29         document.getElementById('chat_content').removeAttribute('hidden');
30         document.getElementById('client_recv').removeAttribute('hidden');
31 
32         name = document.getElementById('client_name').value;
33         //進行WS實例化
34         ws = new WebSocket('ws://192.168.16.14:8888/websocket/' + name);
35 
36         //監聽服務器發來的消息(json數據)
37         ws.onmessage = function (MessageEvent) {
38             //console.log(MessageEvent);
39             //console.log(MessageEvent.data);
40             var content_str = JSON.parse(MessageEvent.data);
41             var time = new Date();
42             var t = time.toLocaleTimeString();
43             var p = document.createElement("p");
44             p.innerText = content_str.name + "(" + t + "):" + content_str.msg;
45             document.getElementById('chat_content').appendChild(p);
46         };
47     };
48 
49 
50     //聊天信息發送(json數據)
51     function send() {
52         var msg = document.getElementById('msg').value;
53         var to_client=document.getElementById('to_client').value;
54         var data = {
55             name: name,
56             msg: msg,
57             to_client:to_client
58         };
59 
60         var data_json = JSON.stringify(data);
61         ws.send(data_json);
62 
63 
64         var time = new Date();
65             var t = time.toLocaleTimeString();
66             var p = document.createElement("p");
67             p.innerText = name + "(" + t + "):" + msg;
68             document.getElementById('chat_content').appendChild(p);
69 
70 
71 
72 
73 
74     }
75 </script>
76 </html>
Private_chat.html


免責聲明!

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



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