zoukankan      html  css  js  c++  java
  • 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=" 500px;height: 300px;border: 1px red solid ;position: relative">
        </div>
    
        <div style=" 500px;height: 50px;border: 1px red solid ;position: relative" id="send">
            <label for="send_msg"></label>
            <textarea style=" 448px;height: 100%;border: 0;padding: 0 ;" id="send_msg"></textarea>
            <input style=" 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>
    
    

    代码文件传送门

  • 相关阅读:
    Error[e46]: Undefined external "?V1" referred in AF
    总是遇到奇怪问题一
    BrokenPipeError: [Errno 32] Broken pipe
    Segment BANKED_CODE must be defined in a segment definition option (-Z, -b or -P)
    使用jupyter打开已存在的ipynb文件
    时钟控制命令
    中断系统以及外部中断
    pytorch上的循环层和全连接层操作
    02池化层
    距离毕业还有---100天
  • 原文地址:https://www.cnblogs.com/sunxiuwen/p/10254753.html
Copyright © 2011-2022 走看看