python websockets 网络聊天室V1
程序打包链接:https://pan.baidu.com/s/1L0Cwur-VnD-BLjkf5n1K1w
提取码:gsqt
注:由于编程能力有限,退出务必是按ctrl+c,直接关闭窗口会生成孤儿进程
# -*- 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>