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>

代碼文件傳送門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM