Flask框架-通过websocket实现IM通讯
需求
实现登录用户的单聊和群聊功能,一旦有消息,服务器就主动推给所有人或某个人
实现加好友/离线消息处理(还未完成)
设计思路
群聊
- 前端
- 用户发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>
人生苦短,我用python!