本節目錄:
(一)筆記總結;
(二)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-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 '''
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-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 '''
(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()
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>
(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()
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>
版本二:通過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()
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>
(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()
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>