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_listclass Client:def __init__(self, nickname: str, client_socket: socket.socket, ip_port: tuple):self.client_nickname: str = nicknameself.client_socket: socket.socket = client_socketself.ip_port: tuple = ip_portdef __str__(self):return f"{self.client_nickname} {self.ip_port}"def __eq__(self, other):# 直接通过==比较两个Client是否是同一个时,只比较ip和portreturn 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# 获取所有本机iphosts = 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 osimport timeimport ipaddressclass Color:"""颜色枚举类"""RED = 31GREEN = 32YELLOW = 33BLUE = 34PURPLE = 35CYAN = 36def 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_msgdef clear_console():# 使用ANSI转义序列清空控制台内容os.system('cls' if os.name == 'nt' else 'clear')
