需求
實現登錄用戶的單聊和群聊功能,一旦有消息,服務器就主動推給所有人或某個人
實現加好友/離線消息處理(還未完成)
設計思路
群聊
- 前端
- 用戶發http請求獲取聊天頁面,獲取頁面dom渲染自動發起websocket連接請求,建立連接
- 通過jq獲取要發送的人,和消息,發送ajax請求
- 通過jq將要發送的信息顯示在頁面,將websocket收到的信息也顯示在頁面上
注:本次項目開始,未進行登錄保存用戶信息,是將socket連接保存,后端將收到的消息發送給已連接的所有人,實現群聊
- 后端
- 創建2個接口,一個提供聊天頁面,一個提供websocket連接收發消息
單聊
- 前端
- 當用戶獲取聊天頁面后,發起websocket連接
- 用戶勾選好友后,通過jq將要發的信息發給后端
- 后端
- 創建4個接口,一個提供聊天頁面,一個提供websocket連接收發消息,2個用戶注冊/登錄接口
- 用戶登錄驗證后將用戶信息保存到session中,以便標識每個websocket連接
- 通過session中的用戶信息將其與用戶的socket連接保存在一個大字典中【優化:可以放到redis中】
- 獲取用戶發給好友的信息和好友的名字,然后去保存有標識每個用戶的大字典中獲取對應的socket連接,將相應消息返回即可
安裝gevent-websocket模塊
pip3 install gevent-websocket
后端代碼
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import json
from flask import Flask
from flask import request
from flask import session
from flask import render_template, redirect
from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
from geventwebsocket.websocket import WebSocket # 語法提示
from utils.app_dbutils import POOL # 導入自制dbutil組件提供的線程級別的連接池
app = Flask(__name__)
app.secret_key = "welcome to my site"
# 將所有的socket連接放到列表中
user_socket_list = [
# 'webSocket_obj'
]
# 單聊需要保存用戶標識
user_socket_dict = {
# 'nick_name': 'webSocket_obj'
}
@app.route('/ql')
def ws():
"""群聊"""
# print(request.environ)
user_socket = request.environ.get("wsgi.websocket", '') # type:WebSocket
user_socket_list.append(user_socket)
print(len(user_socket_list), user_socket_list)
while 1:
try:
msg = user_socket.receive() # 接收消息
print(msg, type(msg))
msg = json.loads(msg)
for uscoket in user_socket_list:
if uscoket != user_socket: # 除開自己發給其他人
uscoket.send(json.dumps({'msg': msg.get('msg')})) # 發送消息給所有人
except:
user_socket_list.remove(user_socket) # 如果
@app.route('/single_ws')
def single_ws():
print(request.environ)
user_socket = request.environ.get("wsgi.websocket", '')
username = session.get('user_name') # 用戶名
print(username)
if username not in user_socket_dict and username:
user_socket_dict[username] = user_socket
while 1:
try:
msg = user_socket.receive() # socket獲取數據
msg = json.loads(msg)
to_friends = msg.get('to_friend') # 是一個列表,一個或多個朋友的用戶名
print(to_friends)
msg = msg.get('msg') # 要發的信息
send_msgs = f'來自{username}:{msg}'
print(send_msgs)
for i in to_friends:
print(i)
# 從socket連接表中獲取朋友的socket連接
print(user_socket_dict)
friend_socket = user_socket_dict[i] # type:WebSocket
friend_socket.send(send_msgs)
except:
user_socket_dict.pop(username)
@app.route('/single')
def single_chat():
"""單聊頁面"""
return render_template('chat_single.html')
@app.route('/chat')
def chats():
"""群聊頁面"""
return render_template('chat_mutil.html')
@app.route('/login', methods=['get', 'post'])
def login():
msg = ""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
conn = POOL.connection()
cursor = conn.cursor()
sql = 'select name from userinfo where name ="%s"and password="%s"' % (username, password)
cursor.execute(sql)
res = cursor.fetchall()
cursor.close()
conn.close()
if res:
session['user_name'] = username
session['is_login'] = True
return redirect('/single')
else:
msg = "用戶名米亞錯誤"
return render_template('login.html', msg_dic={'msg': msg})
@app.route('/register', methods=['GET', 'POST'])
def register():
msg = ''
if request.method == 'POST':
username = request.form.get('username', '')
password = request.form.get('password', '')
print(username, password)
try:
if username and password:
# 寫入數據庫
conn = POOL.connection()
cursor = conn.cursor()
sql = 'insert into userinfo (name,password) values ("%s","%s")' % (username, password)
cursor.execute(sql)
conn.commit()
cursor.close()
conn.close()
return redirect('/login')
else:
msg = "注冊不合格"
except:
msg = "注冊不合格"
return render_template('register.html', msg_dic={"msg": msg})
@app.route('/add_friends')
def add_friends():
"""加好友"""
pass
@app.route('/friends')
def friends():
"""加載聊天頁面時,通過ajax訪問此接口提供該用戶的朋友列表
pass
if __name__ == '__main__':
# 表示如果是http請求就直接app處理,如果是websocket就交給handler再給app
http_serv = WSGIServer(('0.0.0.0', 5000), app, handler_class=WebSocketHandler)
http_serv.serve_forever()
DButils數據池連接
- 安裝
pip search DBUtils
pip install DBUtils
- 創建數據池
import pymysql
from DBUtils.PooledDB import PooledDB
POOL = PooledDB(
creator=pymysql, # 使用鏈接數據庫的模塊
maxconnections=6, # 連接池允許的最大連接數,0和None表示不限制連接數
mincached=2, # 初始化時,鏈接池中至少創建的空閑的鏈接,0表示不創建
maxcached=5, # 鏈接池中最多閑置的鏈接,0和None不限制
maxshared=3,
# 鏈接池中最多共享的鏈接數量,0和None表示全部共享。PS: 無用,因為pymysql和MySQLdb等模塊的 threadsafety都為1,所有值無論設置為多少,_maxcached永遠為0,所以永遠是所有鏈接都共享。
blocking=True, # 連接池中如果沒有可用連接后,是否阻塞等待。True,等待;False,不等待然后報錯
maxusage=None, # 一個鏈接最多被重復使用的次數,None表示無限制
setsession=[], # 開始會話前執行的命令列表。如:["set datestyle to ...", "set time zone ..."]
ping=0,
# ping MySQL服務端,檢查是否服務可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
host='127.0.0.1',
port=3306,
user='root',
password='123456',
database='ball',
charset='utf8'
)
前端代碼
- 群聊頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top: 70px;">
<p>基於websocket實現的簡單聊天頁面</p>
<dl>實現思路:
<dt>前端:</dt>
<dd>1.首先后端設計一個路由提供聊天頁面</dd>
<dd>2.獲取到頁面后,js發起websocket連接,時刻等待着獲取后端傳過來的信息,展示到頁面上</dd>
<dd>3.前端用戶在輸入框中輸入聊天信息,然后通過websocket,發送給后端,后端處理后發給全部用戶,或指定的用戶</dd>
<dt>后端:</dt>
<dd>1.通過geventwebsocket建立websocket服務</dd>
<dd>2.接收前端用戶的請求,將其保存到一個列表中</dd>
<dd>3.當接到前端用戶傳過來的消息時,遍歷用戶列表,將消息發給所有人,或指定的人</dd>
<dd>4.發給所有人,記得要將自己排除外(因為自己也在那個列表中)</dd>
<dd>5.發給指定人,則要處理怎么知道發給誰,怎么確認</dd>
</dl>
<div class="clearfix" id="content"
style="width: 500px;height: 300px;border: 1px red solid ;position: relative">
</div>
<div style="width: 500px;height: 50px;border: 1px red solid ;position: relative" id="send">
<label for="send_msg"></label>
<textarea style="width: 448px;height: 100%;border: 0;padding: 0 ;" id="send_msg"></textarea>
<input style="width: 46px;height: 100%;position: absolute" type="button" value="發送" onclick="sendMsg();">
</div>
</div>
</body>
<script type="text/javascript">
var ws = new WebSocket("ws://127.0.0.1:5000/ql");
ws.onmessage = function (data) {
// 收到消息后
{#console.log(data, typeof (data.data));#}
var msg = JSON.parse(data.data);
console.log(msg);
var ptag = document.createElement('p');
ptag.style.textAlign = 'left';
ptag.innerText = msg.msg;
document.getElementById('content').appendChild(ptag);
};
function sendMsg() {
var msg = document.getElementById('send_msg').value;
console.log(msg);
// 將自己輸入的信息放到右邊
var my_msg = document.createElement('p');
my_msg.innerText = msg;
my_msg.style.textAlign = 'right';
$('#content').append(my_msg);
// 發送消息
// 先序列化為json
msg = JSON.stringify({'msg': msg});
ws.send(msg);
//清除輸入框信息
document.getElementById('send_msg').value = ""
}
</script>
</html>
- 單聊頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>chat</title>
<link rel="stylesheet" href="../static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="../static/js/jquery-3.3.1.min.js"></script>
<script src="../static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top: 50px">
<div class="row">
<div class="col-lg-3">
<ul id="friend_list" style="background-color: #3c3c3c;color: white">
<li><input type="checkbox" name="friend_name" value="zhangshan">張三</li>
<li><input type="checkbox" name="friend_name" value="lisi">李四</li>
<li><input type="checkbox" name="friend_name" value="python">派神</li>
<li><input type="checkbox" name="friend_name" value="java">加瓦</li>
</ul>
</div>
<div class="col-lg-6">
<div id="content" style="height: 300px;background-color: #1b6d85">
展示聊天信息
</div>
<div style="height: 50px;background-color: greenyellow;">
<p>Msg:<input style="height: 33%;" type="text" name="msg"/></p>
<input type="button" value="發送" onclick="sendMsg();">
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
$(function () {
// 一帶開網頁就自動發送ajax,獲取用戶朋友信息,動態創建
})
var ws = new WebSocket('ws://127.0.0.1:5000/single_ws');
ws.onmessage = function (result) {
var ptag = $('<p>');
ptag.css({'text-align':'left','color':'red'});
ptag.text(result.data);
$('#content').append(ptag);
{#console.log('接收到的消息:',result.data)#}
};
function sendMsg() {
var msgs = $('input[name="msg"]').val();
var ptag = $('<p>');
ptag.css({'text-align':'right','color':'red'});
ptag.text(msgs);
$('#content').append(ptag);
{#console.log('要發送的信息:',msgs);#}
var friend_name = new Array()
$('input[name="friend_name"]:checked').each(function (i) {
friend_name[i] = $(this).val()
});
console.log('想發給的用戶朋友:',friend_name);
msg_json = {'to_friend':friend_name, 'msg':msgs};
{#console.log(msg_json);#}
ws.send(JSON.stringify(msg_json))
}
</script>
</html>
- 注冊/登錄頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
<form action="/login" method="post">
{% if msg_dic.get('msg') %}
<p>{{ msg_dic['msg'] }}</p>
{% endif %}
<label for="username">用戶名</label>
<input type="text" name="username" id="username">
<label for="password">密碼</label>
<input type="password" name="password" id="password">
<input type="submit" value="提交">
</form>
</body>
</html>