websocket
- 使用webscoket實現類似web 微信的一個即時通訊工具
- 流程
- 做前端
- 建立webserver django / flask
- 制作聊天功能
1. 輪詢和長鏈接
1. 輪詢
# A、B client:無限循環和服務器對話,有 xx 消息嗎?
# A、B client:client 發起請求至 server,等待15s(默認http超時時間) --> 等待消息時間
# -->主動斷開連接
# -->收到消息主動返回
2. 長鏈接
- 短連接:通訊雙方有數據交互時,就建立一個連接,數據發送完成后,則斷開此連接,即每次連接只完成一項業務的發送。
- 長連接,指在一個連接上可以連續發送多個數據包,在連接保持期間,如果沒有數據包發送,需要雙方發鏈路檢測包。
長連接特性:
- A、B client --> server 建立連接並保持連接不斷開
- A to B --> server 消息轉發 -->B 建立連接的情況下,可以及時准確收到消息
- 客戶端和服務器保持永久性連接
- 除非有一方主動發起斷開
- 消息轉發
2. Websocket
- 實現的組件:werkzeug、gevent-websocket
1. 示例
- 長連接
- web + socket
- Flask + Websocket 模塊 + gevent-websocket
# 下載 gevent-websocket,Websocket
# 請求處理 WSGI 處理 HTTP 請求,WebSocketHandler處理socket請求
# 使用 WSGIServer 替換flask的 Werkzueg
# 語法提示
from flask import Flask
from geventwebsocket.hander import WebSocketHandler
from geventwebsocket.server import WSGIServer
from geventwebsocket.websocket import WebSocket
app = Flask(__name__)
@app.route('/ws')
def ichat():
print(request.environ)
ws_socket = request.environ.get('wsgi.websocket') # type:WebSocket
try:
while True:
msg = ws_socket.receive()
print(msg)
ws_socket.send(b'xxx')
except:pass
# return '200 ok!'
if __name__ == '__main__':
# handler_class=WSGIhandler(not sure),只支持http請求
http_server = WSGIServer(('0.0.0.0', 9527), app, handler_class=WebSocketHandler)
http_server.server_forever()
2. websocket的狀態碼
- 0:連接創建失敗,
- 1:當前link激活,處於可用狀態
- 2:客戶端主動斷開連接,看不到其狀態碼
- 3:服務器主動發起斷開
<script type='application/javascript'>
var ws = new WebSocket('ws://127.0.0.1:5000/chat');
ws.onmessage = function (messageEvent) {
console.log(messageEvent.data);
var ptag = document.createElement('p');
ptag.innerText = messageEvent.data;
document.getElementById('content_list').appendChild(ptag);
};
function send_message() {
var msg = document.getElementById('content').value;
ws.send(msg);
}
</script>
// ws.close
3. websocket
1. 單聊示例
- 群聊時使用 socket_list = [],記錄每個連接用戶socket_obj
- 單聊時使用 socket_dict = {},記錄sender 、reciver 和 msg
socket_list = []
@app.route('/chat/<username>')
def chat(username):
# print(request.environ)
websocket_obj = request.environ.get('wsgi.websocket') # type:WebSocket
websocket_dict[username] = websocket_obj
while True:
msg = websocket_obj.receive()
msg_dict = json.loads(msg)
receiver = msg_dict.get('receiver')
try:
receiver_socket = websocket_dict.get(receiver)
receiver_socket.send(msg)
except:
msg = {'sender': '系統',
'receiver': username,
'data':'對方不在線',
}
websocket_obj.send(json.dumps(msg))
@app.route('/ws')
def ws():
return render_template('ptop.html')
<script type="application/javascript">
var ws;
function send_message() {
var msg = {
sender:document.getElementById('username').value,
receiver:document.getElementById('receiver').value,
data:document.getElementById('content').value,
};
var data = JSON.stringify(msg);
ws.send(data);
var ptag = document.createElement('p');
ptag.innerText = msg.data + ':' + msg.sender;
ptag.style.cssText = "text-align:right";
document.getElementById('content_list').appendChild(ptag);
}
function login() {
var username = document.getElementById('username').value;
ws = new WebSocket('ws://127.0.0.1:5000/chat/' + username);
ws.onmessage = function (messageEvent) {
// 收到信息后先反序列化,在創建 p 標簽並加入到div中
var msg = JSON.parse(messageEvent.data);
var ptag = document.createElement('p');
ptag.innerText = msg.sender + ':' + msg.data;
document.getElementById('content_list').appendChild(ptag);
};
}
</script>
2. 基於websocket實現群聊
- 建立websocket 服務 + Flask web 框架 + Gevent-WebSocket
- requst.environ.get('wsgi.websocket')獲取鏈接,並保存到服務器中
- 基於長連接socket 接收用戶傳遞的消息
- 將消息轉發給其他用戶
3. 基於javascirpt 實現websocket客戶端
- 服務器保存的連接方式變化,以dict形式儲存
- 存儲結構:{uid: websocket連接, user1:websocket}
- 消息發送時,receiver, data = {'sender':發送方, 'receiver':接收發, data:數據}
- 從data中找到接收方的key
- 去存儲結構中找到 key 對應的websocket連接
- websocket.send(data). socket傳輸, bytes類型
- js 中常用的事件
var ws = new WebSocket('ws://ip:port/路徑')
// ws.onmessage 當ws客戶端收到消息時執行回調函數
// ws.onopen 當ws客戶端建立完成連接時, status == 1 時執行
// ws.onclose 當ws客戶端關閉中后關閉,執行的回調函數,status 2 或 3
// ws.onerror 當ws客戶端出現錯誤時
ws.onmessage = func(messageEvent){
...
}
4. 示例
import json
from flask import Flask, request
from geventwebsocket.handler import WebSocketHandler
from geventwebsocket.server import WSGIServer
from geventwebsocket.websocket import WebSocket
ws = Flask(__name__)
web_socket = {}
@ws.route('/app/<user_id>')
def app(user_id):
app_socket = request.environ.get('wsgi.websocket') # type:WebSocket
web_socket[user_id] = app_socket
print('建立app_socket連接。。。', app_socket, user_id)
while True:
try:
# 收發數據
msg = app_socket.receive()
msg_info = json.loads(msg)
receiver = msg_info.get('to_user')
receiver_socket = web_socket.get(receiver)
try:
receiver_socket.send(msg)
except:
web_socket.pop(receiver)
except:
pass
@ws.route('/toy/<toy_id>')
def toy(toy_id):
toy_socket = request.environ.get('wsgi.websocket') # type:WebSocket
web_socket[toy_id] = toy_socket
print('保持toy_socket連接。。。', toy_socket, toy_id)
while True:
try:
msg = toy_socket.receive()
msg_info = json.loads(msg)
receiver = msg_info.get('to_user')
receiver_socket = web_socket.get(receiver)
receiver_socket.send(msg)
except:
pass
if __name__ == '__main__':
http_server = WSGIServer(('0.0.0.0', 9528), ws, handler_class=WebSocketHandler)
http_server.serve_forever()