Flask框架-通过websocket实现IM通讯

需求

实现登录用户的单聊和群聊功能,一旦有消息,服务器就主动推给所有人或某个人
实现加好友/离线消息处理(还未完成)

设计思路

群聊

  • 前端
  1. 用户发http请求获取聊天页面,获取页面dom渲染自动发起websocket连接请求,建立连接
  2. 通过jq获取要发送的人,和消息,发送ajax请求
  3. 通过jq将要发送的信息显示在页面,将websocket收到的信息也显示在页面上

注:本次项目开始,未进行登录保存用户信息,是将socket连接保存,后端将收到的消息发送给已连接的所有人,实现群聊

  • 后端
  1. 创建2个接口,一个提供聊天页面,一个提供websocket连接收发消息

单聊

  • 前端
  1. 当用户获取聊天页面后,发起websocket连接
  2. 用户勾选好友后,通过jq将要发的信息发给后端
  • 后端
  1. 创建4个接口,一个提供聊天页面,一个提供websocket连接收发消息,2个用户注册/登录接口
  2. 用户登录验证后将用户信息保存到session中,以便标识每个websocket连接
  3. 通过session中的用户信息将其与用户的socket连接保存在一个大字典中【优化:可以放到redis中】
  4. 获取用户发给好友的信息和好友的名字,然后去保存有标识每个用户的大字典中获取对应的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>

代码文件传送门

posted @ 2019-01-11 13:49  Alive_2020  阅读(1879)  评论(3编辑  收藏  举报