当前位置:   article > 正文

基于Python的多线程多人聊天室(命令行形式、Tkinter界面形式)_tkinter聊天室

tkinter聊天室

命令行形式:

服务端

  1. import socket # 导入socket网络通讯套接字
  2. from threading import Thread # 导入线程操作包
  3. s = socket.socket() # 实例化socket套接字,唯一,服务端需要保持套接字常开,不能轻易close(),debug教训
  4. host = socket.gethostname() # 指定要连接到的IP地址
  5. port = 3 # TCP端口
  6. Max_Client = 8 # 服务端所能维持的最大连接数
  7. s.bind((host, port)) # 绑定本地服务端的IP地址及端口
  8. s.listen(Max_Client) # 设置最大连接数
  9. class CommunicateHandle: # 服务端的socket通讯处理类
  10. def __init__(self): # 类初始化函数,绑定类属性以用于全局调用
  11. self.con = None # 连接句柄
  12. self.addr = None # 实例化socket套接字时会返回一个socket操作句柄,即上方的con所要存储的变量,还会返回一个客户机的 “IP地址”
  13. self.name = None # 用户名
  14. self.c_n = 0 # 当前的已连入客户数
  15. self.connections = list() # 用于存储客户机的连接句柄
  16. self.addrs = list() # 用于存储客户机的IP地址
  17. self.login_name = list() # 用于存储客户机的用户名
  18. def link_accept(self): # 服务端的连接受理函数,用于接收连接及启动连接子线程
  19. self.con, self.addr = s.accept() # 调用socket套接字实例的accept函数会返回两个对象,接收这两个对象
  20. if self.c_n >= Max_Client: # 如果已到达服务端所能承载的最大客户连接数
  21. print("已到达服务端最大允许连入数,新的连入请求已被拒绝")
  22. self.con.send("聊天室已满,无法加入".encode(encoding='utf-8'))
  23. self.con.close()
  24. else: # 服务端未满员情况下
  25. self.name = self.con.recv(1024).decode(encoding='utf-8')
  26. self.c_n += 1
  27. self.connections.append(self.con) # 将新的客户操作连接置入连接堆
  28. self.addrs.append(self.addr)
  29. self.login_name.append(self.name) # 将客户的用户名置入用户名堆
  30. print("用户:{0}加入聊天室".format(self.name))
  31. self.broadcast("(用户编号: {0} ): 用户{1} 加入聊天室".format(self.addrs.index(self.addr), self.name))
  32. print("目前客户机数:{0}".format(self.c_n))
  33. th = Thread(target=self.message_recv, args=(self.con, self.addr, self.name)) # 为对应的连接分配单独的处理子线程
  34. th.start()
  35. def message_recv(self, con, addr, name): # 服务端的接收消息函数
  36. while True:
  37. position = self.addrs.index(addr)
  38. msg = con.recv(1024).decode(encoding='utf-8')
  39. if msg == "exit": # 若用户登出则关闭此条连接,释放线程的持续占用,线程执行完成并被回收
  40. print("{0}退出了聊天室".format(name))
  41. self.broadcast("{0}退出了聊天室".format(name))
  42. self.connections[position].send("exit".encode(encoding="utf-8")) # 接收到客户端发来的exit请求,给予回应
  43. print("已为其发送exit指令")
  44. self.connections.remove(con) # 将推出的用户级从服务端存储列中移除
  45. self.addrs.remove(addr)
  46. self.login_name.remove(name)
  47. self.c_n -= 1
  48. break
  49. else:
  50. try:
  51. if msg[0:2] == "tk" and msg != '':
  52. # 服务端显示聊天室信息
  53. print("来自: {0} 的用户 {1} {2}:{3}".format(addr, position, self.login_name[position], msg))
  54. self.broadcast("(ID:{0}) {1}说: {2}".format(position, self.login_name[position], msg[3:]))
  55. if msg[0:2] != "tk" and msg != '':
  56. if type(eval(msg[0:2])) == int:
  57. print("检测到私聊信息")
  58. IDF = position # 发送方id
  59. IDT = eval(msg[0:2]) # 接收方id
  60. # 服务端检视私聊信息
  61. print("{0} 对 {1} 说: {2}".format(self.login_name[IDF], self.login_name[IDT], msg[3:]))
  62. self.private_chat(idf=IDF, idt=IDT, mg=msg[2:])
  63. except NameError:
  64. print("接收到的消息格式错误NameError")
  65. self.connections[position].send("您的消息格式错误".encode(encoding='utf-8'))
  66. except IndexError:
  67. print("接收到的消息格式错误IndexError")
  68. self.connections[position].send("指定ID不存在".encode(encoding='utf-8'))
  69. con.close()
  70. def broadcast(self, msg): # 广播函数,服务端使用此函数来向所有的客户机群发信息
  71. for i in range(0, len(self.connections)):
  72. self.connections[i].send(msg.encode(encoding='utf-8'))
  73. def private_chat(self, idf, idt, mg): # 私聊函数,idF:发送方ID;idT:接收方ID;msg:信息
  74. self.connections[idf].send("[Private] 你对 {0} 说: {1}".format(self.login_name[idt], mg).encode(encoding='utf-8'))
  75. try:
  76. self.connections[idt].send("[Private] {0}对你说: {1}".format(self.login_name[idf], mg).encode(encoding='utf-8'))
  77. except IndexError:
  78. print("私聊发送方的指定ID不存在")
  79. if __name__ == '__main__':
  80. Instance = CommunicateHandle() # 实例化服务端的网络通讯类
  81. while True: # 服务端持续探询是否有新的客户连接接入,若有则为其分配一个线程进行处理
  82. Instance.link_accept() # 实例化socket网络接口,打开接入子线程接口

客户端

  1. import socket
  2. from threading import Thread
  3. s = socket.socket() # 实例化socket套接字
  4. host = socket.gethostname() # 指定要连接到的IP地址
  5. port = 3 # TCP端口
  6. login = input("请输入你的用户名:")
  7. s.connect((host, port)) # 连接指定IP地址的指定端口
  8. s.send(login.encode(encoding='utf-8')) # 向主机发送本机的用户名
  9. print("已成功连接到聊天室服务端!")
  10. class CommunicateHandle: # 客户端的socket通讯处理类
  11. def __init__(self, connection): # 初始化函数,用于绑定类中的全局属性
  12. self.connection = connection # 承载socket套接字的连接句柄
  13. self.login = True # 连接状态判断位
  14. def message_recv(self): # 客户端的信息接收函数
  15. while self.login:
  16. mi = self.connection.recv(1024).decode(encoding='utf-8')
  17. print(mi)
  18. s.close()
  19. def message_send(self): # 客户端的信息 发送函数
  20. t = Thread(target=self.message_recv, args=()) # 发送前,先启动信息接收线程
  21. t.start()
  22. print("提示:直接输入想要发送的信息即可")
  23. print("退出聊天室:exit;\n公聊:tk 您要发送的信息;\n私聊:ID(1~99) 您要发送的信息;")
  24. while self.login: # 不断探询用户的输入
  25. ms = input()
  26. if ms == "exit":
  27. s.send(ms.encode(encoding='utf-8'))
  28. self.login = False
  29. else:
  30. s.send(ms.encode(encoding='utf-8'))
  31. Instance = CommunicateHandle(connection=s) # 实例化客户端的网络通讯类
  32. t1 = Thread(target=Instance.message_send, args=()) # 启用发送线程
  33. t1.start()

图形化界面形式:

服务端

  1. import socket # 导入socket网络通讯套接字
  2. from threading import Thread # 导入线程操作包
  3. s = socket.socket() # 实例化socket套接字,唯一,服务端需要保持套接字常开,不能轻易close(),debug教训
  4. host = socket.gethostname() # 指定要连接到的IP地址
  5. port = 3 # TCP端口,要与客户端保持一致
  6. Max_Client = 99 # 服务端所能维持的最大连接数
  7. s.bind((host, port)) # 绑定本地服务端的IP地址及端口
  8. s.listen(Max_Client) # 设置最大连接数
  9. class CommunicateHandle: # 服务端的socket通讯处理类
  10. def __init__(self): # 类初始化函数,绑定类属性以用于全局调用
  11. self.con = None # 连接句柄
  12. self.addr = None # 实例化socket套接字时会返回一个socket操作句柄,即上方的con所要存储的变量,还会返回一个客户机的 “IP地址”
  13. self.name = None # 用户名
  14. self.c_n = 0 # 当前的已连入客户数
  15. self.connections = list() # 用于存储客户机的连接句柄
  16. self.addrs = list() # 用于存储客户机的IP地址
  17. self.login_name = list() # 用于存储客户机的用户名
  18. def link_accept(self): # 服务端的连接受理函数,用于接收连接及启动连接子线程
  19. self.con, self.addr = s.accept() # 调用socket套接字实例的accept函数会返回两个对象,接收这两个对象
  20. if self.c_n >= Max_Client: # 如果已到达服务端所能承载的最大客户连接数
  21. print("已到达服务端最大允许连入数,新的连入请求已被拒绝")
  22. self.con.send("聊天室已满,无法加入".encode(encoding='utf-8'))
  23. self.con.close()
  24. else: # 服务端未满员情况下
  25. self.name = self.con.recv(1024).decode(encoding='utf-8')
  26. self.c_n += 1
  27. self.connections.append(self.con) # 将新的客户操作连接置入连接堆
  28. self.addrs.append(self.addr)
  29. self.login_name.append(self.name) # 将客户的用户名置入用户名堆
  30. print("用户:{0}加入聊天室".format(self.name))
  31. self.broadcast("user_list:{0}".format(self.login_name)) # 传递当前在线用户列表
  32. print("目前客户机数:{0}".format(self.c_n))
  33. th = Thread(target=self.message_recv, args=(self.con, self.addr, self.name)) # 为对应的连接分配单独的处理子线程
  34. th.start()
  35. def message_recv(self, con, addr, name): # 服务端的接收消息函数
  36. while True:
  37. msg = con.recv(1024).decode(encoding='utf-8')
  38. if msg == "exit": # 若用户登出则关闭此条连接,释放线程的持续占用,线程执行完成并被回收
  39. print("{0}退出了聊天室".format(name))
  40. self.broadcast("{0}退出了聊天室".format(name))
  41. self.connections.remove(con) # 将推出的用户级从服务端存储列中移除
  42. self.addrs.remove(addr)
  43. self.login_name.remove(name)
  44. self.c_n -= 1
  45. self.broadcast("user_list:{0}".format(self.login_name)) # 传递当前在线用户列表
  46. break
  47. else:
  48. position = self.addrs.index(addr)
  49. try:
  50. if msg[0:2] == "tk" and msg != '':
  51. # 服务端显示聊天室信息
  52. print("来自: {0} 的用户 {1} {2}:{3}".format(addr, position, self.login_name[position], msg))
  53. # 广播聊天信息
  54. self.broadcast("(ID:{0}) {1}说: {2}".format(position, self.login_name[position], msg[3:]))
  55. if msg[0:2] != "tk" and msg != '':
  56. if type(eval(msg[0:2])) == int:
  57. print("检测到私聊信息")
  58. IDF = position # 发送方id
  59. IDT = eval(msg[0:2]) # 接收方id
  60. # 服务端检视私聊信息
  61. print("{0} 对 {1} 说: {2}".format(self.login_name[IDF], self.login_name[IDT], msg[3:]))
  62. self.private_chat(idf=IDF, idt=IDT, mg=msg[2:])
  63. except NameError:
  64. print("接收到的消息格式错误NameError")
  65. self.connections[position].send("您的消息格式错误".encode(encoding='utf-8'))
  66. except IndexError:
  67. print("接收到的消息格式错误IndexError")
  68. self.connections[position].send("指定ID不存在".encode(encoding='utf-8'))
  69. except SyntaxError:
  70. print("接收到的消息格式错误SyntaxError")
  71. self.connections[position].send("您的消息格式错误".encode(encoding='utf-8'))
  72. con.close()
  73. def broadcast(self, msg): # 广播函数,服务端使用此函数来向所有的客户机群发信息
  74. for i in range(0, len(self.connections)):
  75. self.connections[i].send(msg.encode(encoding='utf-8'))
  76. def private_chat(self, idf, idt, mg): # 私聊函数,idF:发送方ID;idT:接收方ID;msg:信息
  77. if idf != idt:
  78. self.connections[idf].send("[Private] 你对 (ID:{0})用户 {1} 说: {2}".format(idt, self.login_name[idt], mg).encode(encoding='utf-8'))
  79. try:
  80. self.connections[idt].send("[Private] (ID:{0})用户 {1} 对你说: {2}".format(idf, self.login_name[idf], mg).encode(encoding='utf-8'))
  81. except IndexError:
  82. print("私聊发送方的指定ID不存在")
  83. else:
  84. self.connections[idf].send("请勿私聊自己!".encode(encoding="utf-8"))
  85. if __name__ == '__main__':
  86. Instance = CommunicateHandle() # 实例化服务端的网络通讯类
  87. while True: # 服务端持续探询是否有新的客户连接接入,若有则为其分配一个线程进行处理
  88. Instance.link_accept() # 实例化socket网络接口,打开接入子线程接口

客户端

  1. import socket
  2. from threading import Thread
  3. from tkinter import *
  4. from tkinter import messagebox
  5. import sys # 用于退出程序
  6. '''
  7. 类: LoginGUI
  8. 作用: 用于搭建客户端的初始化登入界面
  9. '''
  10. class LoginGUI(Frame):
  11. '''
  12. 函数: __init__
  13. 参数: master:用于绑定Tkinter的根窗口
  14. 作用: 初始化Tkinter的必备类,放置正式界面的GUI组件
  15. 返回值: 无
  16. '''
  17. def __init__(self, master):
  18. super().__init__()
  19. self.master = master
  20. self.pack()
  21. self.s = None
  22. self.name = None
  23. self.GUI_set()
  24. '''
  25. 函数: GUI_set
  26. 参数: 无
  27. 作用: 放置GUI组件
  28. 返回值: 无
  29. '''
  30. def GUI_set(self):
  31. self.v1, self.v2, self.v3 = StringVar(), StringVar(), StringVar()
  32. label1 = Label(self, text="用户名:(必填)")
  33. label1.pack()
  34. entry1 = Entry(self, textvariable=self.v1)
  35. entry1.pack()
  36. label2 = Label(self, text="聊天室IP地址:")
  37. label2.pack()
  38. entry2 = Entry(self, textvariable=self.v2)
  39. entry2.pack()
  40. label3 = Label(self, text="端口:")
  41. label3.pack()
  42. entry3 = Entry(self, textvariable=self.v3)
  43. entry3.pack()
  44. button = Button(self, text="进入", command=self.log_in)
  45. button.pack()
  46. label4 = Label(self, text="(若为本机测试则上方除用户名外全留空)")
  47. label4.pack()
  48. '''
  49. 函数: log_in
  50. 参数: 无
  51. 作用: 登入界面 “进入” 按钮所对应的响应事件,接收并传递用户输入的用户名、IP、端口等值
  52. 返回值: 无
  53. '''
  54. def log_in(self):
  55. self.s = socket.socket() # 实例化socket套接字
  56. self.name = self.v1.get() # 获取本机的用户名
  57. if self.name == '':
  58. messagebox.showwarning(title="用户名为空", message="请输入一个用户名!")
  59. else:
  60. try:
  61. IP = self.v2.get() # 收取IP值
  62. pt = self.v3.get() # 收取端口值
  63. if IP == '' and pt == '':
  64. host = socket.gethostname() # 指定要连接到的IP地址
  65. port = 3 # TCP端口,默认置3,服务端应与客户端的端口置同
  66. elif IP != '' and pt != '': # 若用户输入了指定要连接的IP与端口号,则
  67. host = IP
  68. port = eval(pt)
  69. else:
  70. raise socket.gaierror # IP及端口号要么全输入,要么全不输入,否则抛出异常
  71. self.s.connect((host, port)) # 连接指定IP地址的指定端口
  72. self.s.send(self.name.encode(encoding='utf-8')) # 向主机发送本机的用户名
  73. self.master.destroy()
  74. # 下方创建正式聊天室界面的GUI界面根窗口
  75. root2 = Tk()
  76. root2.title("多人聊天室(局域网)")
  77. root2.geometry("850x500+500+125")
  78. Instance = CommunicateHandleAndGUI(connection=self.s, name=self.name, master=root2)
  79. # 实例化客户端的网络通讯类
  80. t1 = Thread(target=Instance.message_send, args=()) # 启用发送线程
  81. t1.start()
  82. root2.mainloop()
  83. except (NameError, SyntaxError, socket.gaierror):
  84. messagebox.showerror(title="输入错误!", message="请输入正确的IP地址及端口!")
  85. '''
  86. 类: CommunicateHandleAndGUI
  87. 作用: 正式聊天室界面的GUI组件放置、C/S通讯的架构
  88. '''
  89. class CommunicateHandleAndGUI(Frame): # 客户端的socket通讯处理类
  90. '''
  91. 函数: __init__
  92. 参数:
  93. connection: 用于臣在socket套接字连接句柄
  94. name: 本客户端所对应的的用户名
  95. master: 正式聊天室界面的根窗口
  96. 作用: 绑定输入、输出缓存区全局属性、绑定本客户端用户名、放置正式聊天室界面的GUI组件
  97. 返回值: 无
  98. '''
  99. def __init__(self, connection, name, master): # 初始化函数,用于绑定类中的全局属性
  100. self.connection = connection # 承载socket套接字的连接句柄
  101. self.nm = name # 本客户端的ID值
  102. self.login = True # 连接状态判断位
  103. self.MS_temp = None
  104. self.MR_temp = None
  105. super().__init__() # Frame类的超类初始化函数引用,将Tkinter控件类Frame的初始化各属性映射到本类中来
  106. self.master = master # 将类外传入的根窗口传递进来
  107. self.pack(expand=1, fill="both") # 将本类的Frame放置上指定的根窗口上
  108. self.GUI_set()
  109. '''
  110. 函数: GUI_set
  111. 参数: 无
  112. 作用: 放置正式聊天室GUI界面的组件
  113. 返回值: 无
  114. '''
  115. def GUI_set(self): # GUI控件放置函数
  116. self.private_to_user_id = StringVar()
  117. button1 = Button(self, text="公屏发送", command=self.broadcast, bg="Coral")
  118. button1.place(relx=0.61, rely=0, width=175, height=120)
  119. button2 = Button(self, text="私聊发送\n(下方输入私聊对象的ID)", command=self.private_chat, bg="Coral")
  120. button2.place(relx=0.61, rely=0.2, width=175, height=120)
  121. private_char_information = Frame(bg="Coral") # 存放私聊ID的区域
  122. private_char_information.place(relx=0.615, rely=0.44, width=170, height=120)
  123. lb1 = Label(master=private_char_information, text="私聊对象ID:", bg="Coral")
  124. lb1.pack()
  125. entry = Entry(master=private_char_information, textvariable=self.private_to_user_id, border=3, bg="Wheat")
  126. entry.pack()
  127. button3 = Button(self, text="退出聊天室", command=self.exit, bg="Coral")
  128. button3.place(relx=0.61, rely=0.66, width=175, height=120)
  129. lb2 = Label(self, text="made by: lys")
  130. lb2.place(relx=0.67, rely=0.92)
  131. self.message_window = Text(self, bg="BlanchedAlmond")
  132. self.message_window["state"] = "disabled"
  133. self.message_window.place(relx=0, rely=0, width=525, height=350)
  134. self.send_area = Text(self, bg="LightSalmon")
  135. self.send_area.place(relx=0, rely=0.7, width=525, height=150)
  136. self.user_online = Text(self, bg="BlanchedAlmond")
  137. self.user_online["state"] = "disabled"
  138. self.user_online.place(relx=0.815, rely=0, width=155, height=500)
  139. '''
  140. 函数: broadcast
  141. 参数: 无
  142. 作用: 客户端的广播发送函数
  143. 返回值: 无
  144. '''
  145. def broadcast(self): # 广播按钮对应的事件函数
  146. if self.MS_temp != '\n': # 服了,对空白情况下的Text组件使用get方法返回的初始无状态空白值是一个换行符
  147. send_data = "tk " + self.MS_temp # 置上广播标志前缀 “tk” 再将信息打包发出去
  148. self.connection.send(send_data.encode(encoding='utf-8'))
  149. self.send_area.delete(1.0, END)
  150. '''
  151. 函数: private_chat
  152. 参数: 无
  153. 作用: 客户端的私聊发送函数
  154. 返回值: 无
  155. '''
  156. def private_chat(self): # 私聊按钮对应的事件函数
  157. if self.private_to_user_id.get() == '':
  158. messagebox.showwarning(title="错误", message="请输入私聊对象的用户ID!")
  159. else:
  160. try:
  161. ID_Value = eval(self.private_to_user_id.get())
  162. if len(self.private_to_user_id.get()) == 1:
  163. send_data = "{0} ".format(ID_Value) + self.MS_temp
  164. self.connection.send(send_data.encode(encoding='utf-8'))
  165. if len(self.private_to_user_id.get()) == 2: # 可换成else,不过这样写更明了一点
  166. send_data = "{0}".format(ID_Value) + self.MS_temp
  167. self.connection.send(send_data.encode(encoding='utf-8'))
  168. self.send_area.delete(1.0, END)
  169. except (NameError, SyntaxError):
  170. messagebox.showwarning(title="错误", message="请输入正确形式的ID值号!")
  171. '''
  172. 函数: exit
  173. 参数: 无
  174. 作用: 客户端的退出函数
  175. 返回值: 无
  176. '''
  177. def exit(self):
  178. self.login = False
  179. self.connection.send("exit".encode(encoding="utf-8"))
  180. self.message_window["state"] = "normal"
  181. self.message_window.insert(END, '\n')
  182. self.message_window.insert(END, "您已退出聊天室!")
  183. self.message_window["state"] = "disabled"
  184. messagebox.showerror(title="已退出!", message="您已退出聊天室!")
  185. sys.exit()
  186. '''
  187. 函数: message_recv
  188. 参数: 无
  189. 作用: 客户端的信息接收函数
  190. 返回值: 无
  191. '''
  192. def message_recv(self): # 客户端的信息接收函数
  193. while self.login:
  194. self.MR_temp = self.connection.recv(1024).decode(encoding='utf-8')
  195. if self.MR_temp != '':
  196. if self.MR_temp[0:10] == "user_list:": # 若服务端传来的是用户列表信息
  197. lst = eval(self.MR_temp[10:]) # 将服务端传来的在线用户字符串转化为一个列表
  198. self.user_online["state"] = "normal"
  199. self.user_online.delete(1.0, END)
  200. self.user_online.insert(1.0, "目前在线的用户有:\n")
  201. for i in lst:
  202. if i == self.nm:
  203. self.user_online.insert(END, "你-> (ID:{0}) {1}".format(lst.index(i), i))
  204. self.user_online.insert(END, '\n')
  205. else:
  206. self.user_online.insert(END, "(ID:{0}) {1}".format(lst.index(i), i))
  207. self.user_online.insert(END, '\n')
  208. self.user_online["state"] = "disabled"
  209. else:
  210. print(self.MR_temp)
  211. self.message_window["state"] = "normal"
  212. self.message_window.insert(END, '\n')
  213. self.message_window.insert(END, self.MR_temp)
  214. self.message_window["state"] = "disabled"
  215. self.connection.close()
  216. '''
  217. 函数: message_send
  218. 参数: 无
  219. 作用: 客户端的信息发送函数
  220. 返回值: 无
  221. '''
  222. def message_send(self): # 客户端的信息发送函数
  223. t = Thread(target=self.message_recv, args=()) # 发送前,先启动信息接收线程
  224. t.start()
  225. while self.login: # 不断探询用户的输入
  226. self.MS_temp = self.send_area.get(1.0, END).strip() # 获取输入框全部文本,结果字符串结尾会自带一个换行符,使用strip函数删去
  227. root1 = Tk()
  228. root1.title("聊天室连入")
  229. LOG = LoginGUI(master=root1)
  230. root1.geometry("400x200+520+250")
  231. root1.mainloop()

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/892393
推荐阅读
相关标签
  

闽ICP备14008679号