GUI聊天室

聊天室服务器

  • 单独通过控制台开启一个服务器,输入聊天室名称
  • 此服务器会定时(每2秒)往局域网所有设备的8000发送UDP广播。
  • 通知内容是:自己开启的TCP服务器地址:192.168.38.149:8001
  • 接收所有客户端通过TCP的socket接入
    • Input 接收客户端发来的消息(存到历史记录)
    • Output发送群通知、群成员列表给新进来的客户端
    • Output把客户端发来的消息群发给其他所有客户端
    • Output群成员列表变化时,把新的成员列表发给客户端

聊天室客户端
聊天室列表

  • 接收局域网UDP广播:监听8000
  • 将收到的服务器地址信息,根据发送者的ip:port进行去重复
  • 将去重复之后的数据显示在列表中

点击条目可以加入聊天室

  • Input 在聊天室:接收群通知、接收群成员列表
  • Input 接受并展示发来的聊天消息
  • Output获取用户输入的数据,发送给服务器

聊天室列表

image.png

获取列表

循环监听接收8000端口的UDP广播,服务器会不断往外发送自己聊天室的信息:

  1. {
  2. 'name': '物联网数据平台',
  3. 'ip': '192.168.38.149',
  4. 'port': 8001
  5. }

聊天室对话框

image.png

聊天记录

每次收到聊天记录时,直接把原来的 edit_recv 清空,一次性加入到edit_recv

  1. {
  2. "code": 0,
  3. "msg": "消息记录",
  4. "data": [
  5. {
  6. "nickname": "你是谁啊",
  7. "from": [
  8. "192.168.38.203",
  9. 51937
  10. ],
  11. "message": "1111111111111111111111111111111111111111111111111111111111111111111111111",
  12. "time": "2023-08-10 15:55:40"
  13. },
  14. {
  15. "nickname": "系统消息",
  16. "from": [
  17. "192.168.38.149",
  18. 8001
  19. ],
  20. "message": "来自 ('192.168.38.203', 51937) 的客户端【你是谁啊】下线了",
  21. "time": "2023-08-10 15:55:43"
  22. },
  23. {
  24. "nickname": "系统消息",
  25. "from": [
  26. "192.168.38.149",
  27. 8001
  28. ],
  29. "message": "来自 ('192.168.38.203', 52877) 的客户端【1111111111111111111111111111111111111111111111111111111111111111111111111】下线了",
  30. "time": "2023-08-10 15:55:53"
  31. },
  32. {
  33. "nickname": "系统消息",
  34. "from": [
  35. "192.168.38.149",
  36. 8001
  37. ],
  38. "message": "欢迎来自 ('192.168.38.203', 52878) 的【1111111111111111111111111111111111111111111111111111111111111111111111111】加入聊天室!",
  39. "time": "2023-08-10 15:56:01"
  40. },
  41. {
  42. "nickname": "1111111111111111111111111111111111111111111111111111111111111111111111111",
  43. "from": [
  44. "192.168.38.203",
  45. 52878
  46. ],
  47. "message": "1111111111111111111111111111111111111111111111111111111111111111111111111",
  48. "time": "2023-08-10 15:56:12"
  49. }
  50. ]
  51. }

新的消息

每收到一条新的消息,不需要清空edit_recv,把新的数据追加到末尾

  1. {
  2. "code": 1,
  3. "msg": "msg",
  4. "data": [
  5. {
  6. "nickname": "系统消息",
  7. "from": [
  8. "192.168.38.149",
  9. 8001
  10. ],
  11. "message": "欢迎来自 ('192.168.38.149', 7788) 的【小糖豆】加入聊天室!",
  12. "time": "2023-08-10 16:02:43"
  13. }
  14. ]
  15. }

客户端列表

ListView:每次收到客户端列表,把lv_clients列表内容清空,把新的数一次性加入到lv_clients

  1. {
  2. "code": 2,
  3. "msg": "客户端列表",
  4. "data": [
  5. [
  6. "1111111111111111111111111111111111111111111111111111111111111111111111111",
  7. [
  8. "192.168.38.203",
  9. 52878
  10. ]
  11. ],
  12. [
  13. "小糖豆",
  14. [
  15. "192.168.38.149",
  16. 7788
  17. ]
  18. ]
  19. ]
  20. }

群公告

收到群公告后,直接替换到右上角edit_notify

  1. {
  2. "code": 4,
  3. "msg": "群公告",
  4. "data": "聊天室地址:192.168.38.149:8001 \r\n这里是你温馨的港湾,欢迎来到这里!"
  5. }

完整版代码:
实战开发04-多功能工具箱.zip

Terminal版聊天室(了解)

image.png

【服务器】创建聊天室

准备好TCP服务器

在本地的8001端口开启TCP服务器,等待别人的接入(通过线程能够处理多个客户端同时接入)

发广播通知所有人

  1. 在一开始就开启子线程专门循环发广播。
  2. 通过UDP广播(192.168.23.255)所有电脑的8000端口(每隔2秒)
  3. 广播内容是:自己聊天室的名称、IP地址、端口号:

    1. {
    2. "name": "00聊天室",
    3. "ip": "192.168.23.88",
    4. "port": 8001
    5. }
  4. 通过如下形式将python类型的数据转成字符串:

    1. json.dumps(message)

    :::warning 服务器端口号可以任意指定,广播的目标端口号统一使用8000 :::

    处理连进来的客户端

    一旦有客户单连进来,做以下事情:

  5. 接收客户端发来的第一条消息,把消息内容作为其昵称nickname

  6. 将这个客户端保存到一个列表里client_list

    1. class Client:
    2. def __init__(self, nickname: str, client_socket: socket.socket, ip_port: tuple):
    3. self.client_nickname: str = nickname
    4. self.client_socket: socket.socket = client_socket
    5. self.ip_port: tuple = ip_port
    6. def __str__(self):
    7. return f"{self.client_nickname} {self.ip_port}"
    8. def __eq__(self, other):
    9. # 直接通过==比较两个Client是否是同一个时,只比较ip和port
    10. return self.ip_port == other.ip_port
  7. 把最近N条(10)聊天记录发给这个新的客户端,格式如下:

    1. [
    2. {
    3. "nickname": "服务器",
    4. "from": ["192.168.23.88", 8001],
    5. "message": "欢迎来自 (\u0027192.168.23.88\u0027, 11769) 的【刘看山】加入聊天室!",
    6. "time": "2023-06-15 09: 12: 35"
    7. },
    8. {
    9. "nickname": "刘看山",
    10. "from": ["192.168.23.88", 11769],
    11. "message": "1",
    12. "time": "2023-06-15 09: 12: 43"
    13. },
    14. {
    15. "nickname": "服务器",
    16. "from": ["192.168.23.88", 8001],
    17. "message": "来自 (\u0027192.168.23.88\u0027, 11769) 的客户端【刘看山】下线了",
    18. "time": "2023-06-15 09: 12: 53"
    19. },
    20. {
    21. "nickname": "李建国",
    22. "from": ["192.168.23.88", 2312],
    23. "message": "欢迎来自 (\u0027192.168.23.88\u0027, 11791) 的【刘看山】加入聊天室!",
    24. "time": "2023-06-15 09: 13: 16"
    25. },
    26. {
    27. "nickname": "刘看山",
    28. "from": ["192.168.23.88", 3212],
    29. "message": "你好李建国",
    30. "time": "2023-06-15 09: 13: 31"
    31. }
    32. ...
    33. ]

如果有人发消息,则服务器负责把消息群发给所有的客户端client_list

image.png

【扫描器】扫描局域网服务器

使用UDP广播接收器,循环监听8000端口。
image.png
建议使用字典dict接收保存所有的聊天室(key:192.168.23.88:8001以发送者的IP和port组合为Key),以广播的内容解析出的字段对象作为value。(value的内容应包含name,ip,port信息)

【客户端】加入聊天室

将自己的昵称发给服务器

  1. self.client_socket.send("我的昵称".encode())

解析服务器发来的聊天记录

解析服务器的聊天记录,将解码后的字符串recv_msgs进行解析:

  1. json.loads(recv_msgs)

image.png
客户端清屏代码:

  1. def clear_console():
  2. # 使用ANSI转义序列清空控制台内容
  3. os.system('cls' if os.name == 'nt' else 'clear')

聊天室示例

image.png

其他小技巧

获取所有本机IP:

  1. import socket
  2. # 获取所有本机ip
  3. hosts = socket.gethostbyname_ex(socket.gethostname())[2]
  4. # 按指定要求过滤
  5. hosts_filter = [host for host in hosts if host.startswith("192.168") or host.startswith("172.16")]
  6. print(hosts_filter)

好用小工具:

  1. import os
  2. import time
  3. import ipaddress
  4. class Color:
  5. """
  6. 颜色枚举类
  7. """
  8. RED = 31
  9. GREEN = 32
  10. YELLOW = 33
  11. BLUE = 34
  12. PURPLE = 35
  13. CYAN = 36
  14. def wrap_color(msg, color):
  15. """
  16. 包装颜色
  17. :param msg: 要包装的字符串
  18. :param color: 颜色
  19. :return:
  20. """
  21. return f"\033[{color}m{msg}\033[0m"
  22. def calculate_broadcast_address(host, mask_bits):
  23. # 将主机IP地址和掩码位数转换为网络对象
  24. network = ipaddress.ip_network(f"{host}/{mask_bits}", strict=False)
  25. # 获取广播地址
  26. broadcast_address = network.broadcast_address
  27. # 返回广播地址的字符串表示形式
  28. return str(broadcast_address)
  29. def current_time_str():
  30. return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  31. def decode_data(recv_data: bytes):
  32. try:
  33. recv_msg = recv_data.decode("utf-8")
  34. except:
  35. recv_msg = recv_data.decode("gbk")
  36. return recv_msg
  37. def clear_console():
  38. # 使用ANSI转义序列清空控制台内容
  39. os.system('cls' if os.name == 'nt' else 'clear')