python websockets 网络聊天室V1

  • 效果:

    python websockets 网络聊天室V1

程序打包链接:https://pan.baidu.com/s/1L0Cwur-VnD-BLjkf5n1K1w
提取码:gsqt

  • 新增
  1. 将程序打包成exe文件
  2. 可以在同一网络下通信,不用像v0一样傻傻的在同一台电脑自娱自乐。(若想放在服务器上,请配置好flask的生产环境,否则据说会有漏洞)
  3. 聊天记录保存在chat.log里

注:由于编程能力有限,退出务必是按ctrl+c,直接关闭窗口会生成孤儿进程

  • 我都写了啥
  1. py里有p_web 和 p_chat_server 两个进程,分别负责处理HTML的路由和聊天的后端
  2. 主要看html 里的ws.onmessage 和 py里的async def chat(websocket, path) 函数。他们以json方式进行信息交互
  • 下次迭代
  1. 更加好看的UI
  2. 更加丰富的管理员功能
  • 代码:
# -*- coding:utf8 -*-

import json
import socket
import asyncio
import logging
import websockets
import multiprocessing
from multiprocessing import Process
from flask import Flask, render_template, request

IP = '127.0.0.1'
PORT_WEB = 800
PORT_CHAT = 1234

# 此方法利用UDP协议,生成一个UDP包,将自己的IP放入UDP协议头中,然后再从中获取本机的IP。此方法虽然不会真实向外发包,但仍然会申请一个UDP的端口
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(('8.8.8.8', 80))
    IP = s.getsockname()[0]
finally:
    s.close()


# 保存字典 名字:websockets
USERS = {}

#提供html
app = Flask(__name__)
@app.route('/')
def index_chat():
    return render_template("index.html", ip=IP, port=PORT_CHAT)
def web():
    app.run(host='0.0.0.0', port=PORT_WEB)


#提供聊天的后台
async def chat(websocket, path):
    logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',
                        filename="chat.log",
                        level=logging.INFO)
    # 握手
    await websocket.send(json.dumps({"type": "handshake"}))
    async for message in websocket:
        data = json.loads(message)
        message = ''
        # 用户发信息
        if data["type"] == 'send':
            name = '404'
            for k, v in USERS.items():
                if v == websocket:
                    name = k
            data["from"] = name
            if len(USERS) != 0:  # asyncio.wait doesn't accept an empty list
                message = json.dumps(
                    {"type": "user", "content": data["content"], "from": name})
        # 用户登录
        elif data["type"] == 'login':
            USERS[data["content"]] = websocket
            if len(USERS) != 0:  # asyncio.wait doesn't accept an empty list
                message = json.dumps(
                    {"type": "login", "content": data["content"], "user_list": list(USERS.keys())})
        # 用户退出
        elif data["type"] == 'logout':
            del USERS[data["content"]]
            if len(USERS) != 0:  # asyncio.wait doesn't accept an empty list
                message = json.dumps(
                    {"type": "logout", "content": data["content"], "user_list": list(USERS.keys())})
        #打印聊天信息到日志
        logging.info(data)
        # 群发
        await asyncio.wait([user.send(message) for user in USERS.values()])

def chat_server():
    start_server = websockets.serve(chat, '0.0.0.0', PORT_CHAT)
    asyncio.get_event_loop().run_until_complete(start_server)
    asyncio.get_event_loop().run_forever()


if __name__ == "__main__":
    multiprocessing.freeze_support()
    p_web = Process(target=web, daemon = True)
    p_web.start()
    p_chat_server = Process(target=chat_server, daemon = True)
    p_chat_server.start()

    print("按下ctrl + c 结束程序。聊天记录将保存在chat.log")
    print("聊天室地址" + IP + ':' + str(PORT_WEB))
    p_web.join()
    p_chat_server.terminate()
<!DOCTYPE html>
<html>

<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <style>
        p {
            text-align: left;
            padding-left: 20px;
        }
    </style>
</head>

<body>
    <div style="width: 800px;height: 600px;margin: 30px auto;text-align: center">
        <h1>websocket聊天室</h1>
        <div id='ipp' ip={{ip}} port={{port}}></div>
        <div style="width: 800px;border: 1px solid gray;height: 300px;">
            <div style="width: 200px;height: 300px;float: left;text-align: left;">
                <p><span>当前在线:</span><span id="user_num">0</span></p>
                <div id="user_list" style="overflow: auto;">

                </div>
            </div>
            <div id="msg_list" style="width: 598px;border:  1px solid gray; height: 300px;overflow: scroll;float: left;">
            </div>
        </div>
        <br>
        <textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
        <input type="button" value="发送" onclick="send()">
    </div>
</body>

</html>

<script type="text/javascript">
    // 存储用户名到全局变量,握手成功后发送给服务器
    var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
    var url = "ws://" + document.getElementById("ipp").getAttribute("ip") + ':' + document.getElementById("ipp").getAttribute("port");
    console.log(url);
    var ws = new WebSocket(url);
    ws.onopen = function() {
        var data = "系统消息:建立连接成功";
        listMsg(data);
    };

    /**
     * 分析服务器返回信息
     *
     * msg.type : user 普通信息;system 系统信息;handshake 握手信息;login 登陆信息; logout 退出信息;
     * msg.from : 消息来源
     * msg.content: 消息内容
     */
    ws.onmessage = function(e) {
        var msg = JSON.parse(e.data);
        var sender, user_name, name_list, change_type;

        switch (msg.type) {
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                var user_info = {
                    'type': 'login',
                    'content': uname
                };
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.user_list;
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }

        var data = sender + msg.content;
        listMsg(data);
    };

    ws.onerror = function() {
        var data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };

    //窗口关闭时,发信息给服务器,说明下线了
    window.onbeforeunload = function() {  
        var user_info = {
            'type': 'logout',
            'content': uname
        };
        sendMsg(user_info);
        ws.close();
    }

    /**
     * 在输入框内按下回车键时发送消息
     *
     * @param event
     *
     * @returns {boolean}
     */
    function confirm(event) {
        var key_num = event.keyCode;
        if (13 == key_num) {
            send();
        } else {
            return false;
        }
    }

    /**
     * 发送并清空消息输入框内的消息
     */
    function send() {
        var msg_box = document.getElementById("msg_box");
        var content = msg_box.value;
        var reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        var msg = {
            'content': content.trim(),
            'type': 'send'
        };
        sendMsg(msg);
        msg_box.value = '';
        // todo 清除换行符
    }

    /**
     * 将消息内容添加到输出框中,并将滚动条滚动到最下方
     */
    function listMsg(data) {
        var msg_list = document.getElementById("msg_list");
        var msg = document.createElement("p");

        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }

    /**
     * 处理用户登陆消息
     *
     * @param user_name 用户名
     * @param type  login/logout
     * @param name_list 用户列表
     */
    function dealUser(user_name, type, name_list) {
        var user_list = document.getElementById("user_list");
        var user_num = document.getElementById("user_num");
        while (user_list.hasChildNodes()) {
            user_list.removeChild(user_list.firstChild);
        }

        for (var index in name_list) {
            var user = document.createElement("p");
            user.innerHTML = name_list[index];
            user_list.appendChild(user);
        }
        user_num.innerHTML = name_list.length;
        user_list.scrollTop = user_list.scrollHeight;

        var change = type == 'login' ? '上线' : '下线';

        var data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }

    /**
     * 将数据转为json并发送
     * @param msg
     */
    function sendMsg(msg) {
        var data = JSON.stringify(msg);
        ws.send(data);
    }

    /**
     * 生产一个全局唯一ID作为用户名的默认值;
     *
     * @param len
     * @param radix
     * @returns {string}
     */
    function uuid(len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        var uuid = [],
            i;
        radix = radix || chars.length;

        if (len) {
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
            var r;

            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';

            for (i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | Math.random() * 16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                }
            }
        }

        return uuid.join('');
    }
</script>
  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值