GUI聊天室
聊天室服务器
- 单独通过控制台开启一个服务器,输入聊天室名称
- 此服务器会定时(每2秒)往局域网所有设备的
8000
发送UDP广播。 - 通知内容是:自己开启的TCP服务器地址:
192.168.38.149:8001
- 接收所有客户端通过TCP的socket接入
Input
接收客户端发来的消息(存到历史记录)Output
发送群通知、群成员列表给新进来的客户端Output
把客户端发来的消息群发给其他所有客户端Output
群成员列表变化时,把新的成员列表发给客户端
聊天室客户端
聊天室列表
- 接收局域网UDP广播:监听
8000
- 将收到的服务器地址信息,根据发送者的
ip:port
进行去重复 - 将去重复之后的数据显示在列表中
点击条目可以加入聊天室
Input
在聊天室:接收群通知、接收群成员列表Input
接受并展示发来的聊天消息Output
获取用户输入的数据,发送给服务器
聊天室列表
获取列表
循环监听接收8000
端口的UDP广播,服务器会不断往外发送自己聊天室的信息:
{
'name': '物联网数据平台',
'ip': '192.168.38.149',
'port': 8001
}
聊天室对话框
聊天记录
每次收到聊天记录时,直接把原来的 edit_recv
清空,一次性加入到edit_recv
{
"code": 0,
"msg": "消息记录",
"data": [
{
"nickname": "你是谁啊",
"from": [
"192.168.38.203",
51937
],
"message": "1111111111111111111111111111111111111111111111111111111111111111111111111",
"time": "2023-08-10 15:55:40"
},
{
"nickname": "系统消息",
"from": [
"192.168.38.149",
8001
],
"message": "来自 ('192.168.38.203', 51937) 的客户端【你是谁啊】下线了",
"time": "2023-08-10 15:55:43"
},
{
"nickname": "系统消息",
"from": [
"192.168.38.149",
8001
],
"message": "来自 ('192.168.38.203', 52877) 的客户端【1111111111111111111111111111111111111111111111111111111111111111111111111】下线了",
"time": "2023-08-10 15:55:53"
},
{
"nickname": "系统消息",
"from": [
"192.168.38.149",
8001
],
"message": "欢迎来自 ('192.168.38.203', 52878) 的【1111111111111111111111111111111111111111111111111111111111111111111111111】加入聊天室!",
"time": "2023-08-10 15:56:01"
},
{
"nickname": "1111111111111111111111111111111111111111111111111111111111111111111111111",
"from": [
"192.168.38.203",
52878
],
"message": "1111111111111111111111111111111111111111111111111111111111111111111111111",
"time": "2023-08-10 15:56:12"
}
]
}
新的消息
每收到一条新的消息,不需要清空edit_recv
,把新的数据追加到末尾
{
"code": 1,
"msg": "msg",
"data": [
{
"nickname": "系统消息",
"from": [
"192.168.38.149",
8001
],
"message": "欢迎来自 ('192.168.38.149', 7788) 的【小糖豆】加入聊天室!",
"time": "2023-08-10 16:02:43"
}
]
}
客户端列表
ListView:每次收到客户端列表,把lv_clients
列表内容清空,把新的数一次性加入到lv_clients
{
"code": 2,
"msg": "客户端列表",
"data": [
[
"1111111111111111111111111111111111111111111111111111111111111111111111111",
[
"192.168.38.203",
52878
]
],
[
"小糖豆",
[
"192.168.38.149",
7788
]
]
]
}
群公告
收到群公告后,直接替换到右上角edit_notify
{
"code": 4,
"msg": "群公告",
"data": "聊天室地址:192.168.38.149:8001 \r\n这里是你温馨的港湾,欢迎来到这里!"
}
完整版代码:
实战开发04-多功能工具箱.zip
Terminal版聊天室(了解)
【服务器】创建聊天室
准备好TCP服务器
在本地的8001
端口开启TCP服务器,等待别人的接入(通过线程能够处理多个客户端同时接入)
发广播通知所有人
- 在一开始就开启子线程专门循环发广播。
- 通过UDP广播(
192.168.23.255
)所有电脑的8000
端口(每隔2秒) 广播内容是:自己聊天室的名称、IP地址、端口号:
{
"name": "00聊天室",
"ip": "192.168.23.88",
"port": 8001
}
通过如下形式将python类型的数据转成字符串:
json.dumps(message)
:::warning 服务器端口号可以任意指定,广播的目标端口号统一使用
8000
:::处理连进来的客户端
一旦有客户单连进来,做以下事情:
接收客户端发来的第一条消息,把消息内容作为其昵称
nickname
将这个客户端保存到一个列表里
client_list
class Client:
def __init__(self, nickname: str, client_socket: socket.socket, ip_port: tuple):
self.client_nickname: str = nickname
self.client_socket: socket.socket = client_socket
self.ip_port: tuple = ip_port
def __str__(self):
return f"{self.client_nickname} {self.ip_port}"
def __eq__(self, other):
# 直接通过==比较两个Client是否是同一个时,只比较ip和port
return self.ip_port == other.ip_port
把最近N条(10)聊天记录发给这个新的客户端,格式如下:
[
{
"nickname": "服务器",
"from": ["192.168.23.88", 8001],
"message": "欢迎来自 (\u0027192.168.23.88\u0027, 11769) 的【刘看山】加入聊天室!",
"time": "2023-06-15 09: 12: 35"
},
{
"nickname": "刘看山",
"from": ["192.168.23.88", 11769],
"message": "1",
"time": "2023-06-15 09: 12: 43"
},
{
"nickname": "服务器",
"from": ["192.168.23.88", 8001],
"message": "来自 (\u0027192.168.23.88\u0027, 11769) 的客户端【刘看山】下线了",
"time": "2023-06-15 09: 12: 53"
},
{
"nickname": "李建国",
"from": ["192.168.23.88", 2312],
"message": "欢迎来自 (\u0027192.168.23.88\u0027, 11791) 的【刘看山】加入聊天室!",
"time": "2023-06-15 09: 13: 16"
},
{
"nickname": "刘看山",
"from": ["192.168.23.88", 3212],
"message": "你好李建国",
"time": "2023-06-15 09: 13: 31"
}
...
]
如果有人发消息,则服务器负责把消息群发给所有的客户端client_list
【扫描器】扫描局域网服务器
使用UDP广播接收器,循环监听8000
端口。
建议使用字典dict接收保存所有的聊天室(key:192.168.23.88:8001
以发送者的IP和port组合为Key),以广播的内容解析出的字段对象作为value。(value的内容应包含name,ip,port信息)
【客户端】加入聊天室
将自己的昵称发给服务器
self.client_socket.send("我的昵称".encode())
解析服务器发来的聊天记录
解析服务器的聊天记录,将解码后的字符串recv_msgs
进行解析:
json.loads(recv_msgs)
客户端清屏代码:
def clear_console():
# 使用ANSI转义序列清空控制台内容
os.system('cls' if os.name == 'nt' else 'clear')
聊天室示例
其他小技巧
获取所有本机IP:
import socket
# 获取所有本机ip
hosts = socket.gethostbyname_ex(socket.gethostname())[2]
# 按指定要求过滤
hosts_filter = [host for host in hosts if host.startswith("192.168") or host.startswith("172.16")]
print(hosts_filter)
好用小工具:
import os
import time
import ipaddress
class Color:
"""
颜色枚举类
"""
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
PURPLE = 35
CYAN = 36
def wrap_color(msg, color):
"""
包装颜色
:param msg: 要包装的字符串
:param color: 颜色
:return:
"""
return f"\033[{color}m{msg}\033[0m"
def calculate_broadcast_address(host, mask_bits):
# 将主机IP地址和掩码位数转换为网络对象
network = ipaddress.ip_network(f"{host}/{mask_bits}", strict=False)
# 获取广播地址
broadcast_address = network.broadcast_address
# 返回广播地址的字符串表示形式
return str(broadcast_address)
def current_time_str():
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
def decode_data(recv_data: bytes):
try:
recv_msg = recv_data.decode("utf-8")
except:
recv_msg = recv_data.decode("gbk")
return recv_msg
def clear_console():
# 使用ANSI转义序列清空控制台内容
os.system('cls' if os.name == 'nt' else 'clear')