服务器端
import wx
from socket import socket
from socket import AF_INET#AF_INET 用于internet之间的进程通信
from socket import SOCK_STREAM#SOCK_STREAM 表示使用TCP协议
import threading
import time
class Server(wx.Frame):
def __init__(self):
#界面绘制
# 调用父类初始化方法
wx.Frame.__init__(self, None, id=1001, title='多人聊天室服务器端界面', pos=wx.DefaultPosition,size=(450, 450)) # None 表没有父级窗口 id 表当前窗口编号 pos 表窗口打开位置 size 表窗体大小,单位是像素
panel = wx.Panel(self) # 创建面板对象
box = wx.BoxSizer(wx.VERTICAL) # 在面板中放入盒子 wx.VERTICAL 表垂直方向布局
gridding1 = wx.FlexGridSizer(wx.HSCROLL) # 创建可伸缩的网格布局1 wx.HSCROLL 表水平方向布局
start_button = wx.Button(panel, size=(150, 40), label='启动服务器') # 创建按钮对象于面板上,并标记上文字
save_button = wx.Button(panel, size=(150, 40), label='保存聊天记录')
stop_button = wx.Button(panel, size=(150, 40), label='关断服务器')
gridding1.Add(start_button, 1, wx.TOP) # 把按钮顶行放到可伸缩的网格中
gridding1.Add(save_button, 1, wx.TOP)
gridding1.Add(stop_button, 1, wx.TOP)
box.Add(gridding1, 1, wx.ALIGN_CENTRE) # 网格布局1添加至box中 wx.ALIGN_CENTR表居中对齐
self.show_text = wx.TextCtrl(panel, size=(450, 410),style=wx.TE_MULTILINE | wx.TE_READONLY) # 创建只读文本框,用于显示聊天内容 wx.TE_MULTILINE 表多行文本框 wx.TE_READONLY 表只读
box.Add(self.show_text, 1, wx.ALIGN_CENTRE) # 添加文本框于box中
panel.SetSizer(box) # 放置box于面板中
#功能属性
self.ison=False#存储服务器启动状态,默认为未启动状态(False)
self.ip_port=('',8888)#设置服务器端绑定的ip地址和端口(元组类型) '' 空字符串表本机所有ip地址
self.server_socket=socket(AF_INET,SOCK_STREAM)#创建socket对象
self.server_socket.bind(self.ip_port)#绑定ip地址和端口
self.server_socket.listen(5)#监听
self.chat_thread_dictionary={}#创建字典,用于存储客户端对话线程 key(客户端名称)-value(会话线程)
#按钮绑定方法
self.Bind(wx.EVT_BUTTON, self.start, start_button)
self.Bind(wx.EVT_BUTTON, self.save, save_button)
self.Bind(wx.EVT_BUTTON, self.stop, stop_button)
#方法
def start(self, event):#event 表按钮被按动这一事件
if not self.ison:#判断服务器启动状态是否为未启动状态
self.ison=True
main_thread=threading.Thread(target=self.turn_on)#创建主线程对象
main_thread.daemon=True#main_thread设置为守护线程,父线程(界面)执行结束,子线程跟着执行结束
main_thread.start()
print("服务器启动")
def turn_on(self):
while self.ison:#服务器是启动状态时运行
chat_socket,client_address=self.server_socket.accept()#等待客户的连接 系列解包赋值客户端的套接字和地址信息
#客户发送连接请求后,客户发送的第一挑数据作客户端名称,客户端名称作字典中的key
client_name=chat_socket.recv(1024).decode('utf-8')#接收解码客户端数据
chat_thread=Chatthread(chat_socket,client_name,self)#创建会话线程对象
self.chat_thread_dictionary[client_name]=chat_thread#存储到字典中
chat_thread.start()#启动会话线程
self.show_chat('聊天室通知',f'{client_name}进入聊天室',time.strftime('%Y-%m-%d\t%H:%M:%S',time.localtime()))#输出服务器提示信息
self.server_socket.close()#服务器是关闭状态时,关闭服务器端socket
def show_chat(self,chat_source,chat,chattime):#聊天信息源 聊天信息 聊天时间
send_chat=f'{chat_source}:{chat}\n时间:{chattime}'
self.show_text.AppendText('-'*40+'\n'+send_chat+'\n')#AppendText()格式化显示聊天信息于服务器端界面只读文本框
for client in self.chat_thread_dictionary.values():#将聊天信息发送至每一位在线客户会话线程
if client.ison:#需连接,才能发送聊天信息
client.chat_socket.send(send_chat.encode('utf-8'))
def save(self,event):#event 表按钮被按动这一事件
save_chat=self.show_text.GetValue()#GetValue()保存信息
with open('save_chat.log','w',encoding='utf-8')as file:
file.write(save_chat)
def stop(self,event):#event 表按钮被按动这一事件
print("聊天室关闭")
self.ison=False
class Chatthread(threading.Thread):#创建会话线程类
def __init__(self,chat_socket,client_name,server):#客户端的套接字 客户端名称 服务器
#调用父类初始化方法
threading.Thread.__init__(self)
self.chat_socket=chat_socket
self.client_name=client_name
self.server=server
self.ison=True#创建会话线程对象时,服务器是启动状态
def run(self):#重写run方法
print(f'{self.client_name}进入聊天室')
while self.ison:#服务器是启动状态时,从客户端接收数据
chat=self.chat_socket.recv(1024).decode('utf-8')#接收解码数据,存储到chat中
if chat==f'{self.client_name}退出聊天室':#自定义关键词判断客户端是否选择退出聊天室
self.ison=False
self.server.show_chat('聊天室通知',f'{self.client_name}退出聊天室',time.strftime('%Y-%m-%d\t%H:%M:%S',time.localtime()))#发送用户退出通知
else:#正常聊天内容,可读于所有客户端与服务器端
self.server.show_chat(self.client_name,chat,time.strftime('%Y-%m-%d\t%H:%M:%S',time.localtime()))
self.chat_socket.close()#客户端选择退出聊天室,关闭客户端socket
if __name__ == '__main__':
app=wx.App()
Server().Show()#创建服务器端界面对象 打点调用Show方法
app.MainLoop()#循环刷新显示
客户端
import wx#图形化界面
from socket import socket
from socket import AF_INET#AF_INET 用于internet之间的进程通信
from socket import SOCK_STREAM#SOCK_STREAM 表示使用TCP协议
import threading
class Client(wx.Frame):
def __init__(self,client_name):
# 界面绘制
#调用父类初始化方法
wx.Frame.__init__(self,None,id=1001,title=client_name+' 客户端界面',pos=wx.DefaultPosition,size=(400,450))#None 表没有父级窗口 id 表当前窗口编号 pos 表窗口打开位置 size 表窗体大小,单位是像素
panel=wx.Panel(self)#创建面板对象
box=wx.BoxSizer(wx.VERTICAL)#在面板中放入盒子 wx.VERTICAL 表垂直方向布局
gridding1=wx.FlexGridSizer(wx.HSCROLL)#创建可伸缩的网格布局1 wx.HSCROLL 表水平方向布局
connect_button=wx.Button(panel,size=(200,40),label='连接')#创建按钮对象于面板上,并标记上文字
disconnect_button = wx.Button(panel, size=(200, 40), label='断开')
gridding1.Add(connect_button,1,wx.TOP | wx.LEFT)#把两按钮放到可伸缩的网格中 放置顶行左
gridding1.Add(disconnect_button, 1, wx.TOP | wx.RIGHT)#放置顶行右
box.Add(gridding1,1,wx.ALIGN_CENTRE)#网格布局1添加至box中 wx.ALIGN_CENTR表居中对齐
self.show_text=wx.TextCtrl(panel,size=(400,210),style=wx.TE_MULTILINE | wx.TE_READONLY)#创建只读文本框,用于显示聊天内容 wx.TE_MULTILINE 表多行文本框 wx.TE_READONLY 表只读
box.Add(self.show_text,1,wx.ALIGN_CENTRE)#添加文本框于box中
self.chat_text=wx.TextCtrl(panel,size=(400,120),style=wx.TE_MULTILINE)#创建写入文本框,用于写入聊天内容
box.Add(self.chat_text, 1, wx.ALIGN_CENTRE)
gridding2 = wx.FlexGridSizer(wx.HSCROLL)#创建可伸缩的网格布局2
reset_button = wx.Button(panel, size=(200, 40), label='清空') # 创建按钮对象于面板上,并标记上文字
send_button = wx.Button(panel, size=(200, 40), label='发送')
gridding2.Add(reset_button, 1, wx.TOP | wx.LEFT) # 把两按钮放到可伸缩的网格中 放置顶行左边
gridding2.Add(send_button, 1, wx.TOP | wx.RIGHT) # 放置顶行右边
box.Add(gridding2,1,wx.ALIGN_CENTRE)#网格布局2添加至box中
panel.SetSizer(box)#放置box于面板中
# 功能属性
self.client_name=client_name
self.isconnect=False#存储连接状态,默认为未连接状态(False)
self.client_socket=None#设置客户端的socket对象为空
# 按钮绑定方法
self.Bind(wx.EVT_BUTTON, self.connect, connect_button)
self.Bind(wx.EVT_BUTTON, self.disconnect, disconnect_button)
self.Bind(wx.EVT_BUTTON, self.send_chat, send_button)
self.Bind(wx.EVT_BUTTON, self.reset_chat, reset_button)
# 方法
def connect(self, event):#event 表按钮被按动这一事件
if not self.isconnect:#判断是否是为未连接状态
serve_id_port=('127.0.0.1',8888)
self.client_socket=socket(AF_INET,SOCK_STREAM)#创建socket对象
self.client_socket.connect(serve_id_port)#请求连接
self.isconnect=True
self.client_socket.send(self.client_name.encode('utf-8'))
chat_thread=threading.Thread(target=self.receive_data)##创建chat线程对象
chat_thread.daemon=True#chat_thread设置为守护线程,父线程(界面)执行结束,子线程跟着执行结束
chat_thread.start()
print(f'{self.client_name} 连接服务器')
def receive_data(self):
while self.isconnect:#需连接,才能接收服务器传来信息
chat=self.client_socket.recv(1024).decode('utf-8')
self.show_text.AppendText('-'*40+'\n'+chat+'\n')#AppendText()格式化显示聊天信息于只读文本框
def disconnect(self,event):#event 表按钮被按动这一事件
self.client_socket.send(f'{self.client_name}退出聊天室'.encode('utf-8'))#发送关键词退出
self.isconnect=False#改变连接状态
def send_chat(self, event):#event 表按钮被按动这一事件
if self.isconnect:#需连接,才能发送信息
input_chat=self.chat_text.GetValue()#GetValue()从文本框读取数据
if input_chat!='':
self.client_socket.send(input_chat.encode('utf-8'))#发送信息
self.chat_text.SetValue('')#SetValue()清空文本框
def reset_chat(self,event):#event 表按钮被按动这一事件
self.chat_text.Clear()#Clear()清空文本框
if __name__ == '__main__':
app=wx.App()
client_name=input("用户名:")
client=Client(client_name)#创建客户端界面对象
client.Show()#打点调用Show方法
app.MainLoop()#循环刷新显示