????????本项目基于wxpython图形开发库,socket库,threading库的多人聊天室系统,通俗的将就是一个可供多人加入聊天的群聊。
? ? ? ? 运行服务器端代码,点击启动按钮启动服务器端等待用户连接
????????运行客户端代码输入用户名创建客户端窗口
?
????????点击连接连接服务端进入聊天室,服务器通知张三进入聊天室并将消息发送到所有已连接的客户端。
? ? ? ? 再次运行客户端代码创建第二个用户,并连接客户端可以开始聊天了。并且聊天记录会同时同步到服务器端,点击聊天记录保存可以将聊天记录保存到文件中。?
? ? ? ? 点击断开按钮退出聊天室,仍然在线的客户端和服务端会受到用户退出的消息。
?
? ? ? ? 点击服务器端的停止会发送一条服务器停止的消息并强制客户端下线,再次点击连接会显示连接失败。
?
????????至此,一个多线程多人聊天室系统大致完成。至于是否可以同时用于两台电脑的连接我还没有试过,具体操作好像还需要内网ip映射,有兴趣的可以研究一下。代码端只需要更改服务器端绑定的ip地址和端口号以及客户端对应的ip地址和端口号。?
import wx
from socket import *
import threading
import time
class HwbServer(wx.Frame):
def __init__(self):
'''创建窗口'''
wx.Frame.__init__(self,None,id=102,title='HWB的服务器界面',
pos=wx.DefaultPosition,size=(400,470))
pl=wx.Panel(self) #在窗口中初始化一个面板
#在面板里放按钮,文本输入框
box = wx.BoxSizer(wx.VERTICAL) #在盒子里面垂直方向自动排版
grid1 = wx.FlexGridSizer(wx.HORIZONTAL) #可伸缩的水平网格布局
#创建两个按钮
start_server_button = wx.Button(pl,size=(133,40),label="启动")
record_save_button = wx.Button(pl,size=(133,40),label="聊天记录保存")
stop_server_button = wx.Button(pl,size=(133,40),label="停止")
grid1.Add(start_server_button,1,wx.TOP)
grid1.Add(record_save_button,1,wx.TOP)
grid1.Add(stop_server_button,1,wx.TOP)
box.Add(grid1,1,wx.ALIGN_CENTER) #ALIGN_CENTER 联合居中
#创建只读的文本框,显示聊天记录
self.show_chat = wx.TextCtrl(pl,size=(400,400),style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.show_chat,1,wx.ALIGN_CENTER)
pl.SetSizer(box)
'''以上窗口创建代码结束'''
'''服务器准备执行的一些属性'''
self.isOn = False #服务器没有启动
self.host_port=('',8888) #服务器绑定的地址和端口号
self.server_socket = None #TCP协议的服务器端套接字
self.session_thread = None
self.session_thread_map={} #存放所有的服务器会话线程,key=客户端名字,value=服务器线程
'''给所有的按钮绑定相应的动作'''
self.Bind(wx.EVT_BUTTON,self.Start_Server,start_server_button) #给启动按钮绑定一个事件,事件触发自动调用一个函数
self.Bind(wx.EVT_BUTTON,self.Save_Record,record_save_button)
self.Bind(wx.EVT_BUTTON,self.Stop_Server,stop_server_button)
#服务器开始启动的函数
def Start_Server(self,event):
self.server_socket = socket(AF_INET,SOCK_STREAM) #TCP协议的服务器端套接字
self.server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #端口复用
self.server_socket.bind(self.host_port)
self.server_socket.listen(5) #服务器监听数量
print('服务器开始启动')
if not self.isOn:
#启动服务器的主线程
self.isOn = True
main_thread = threading.Thread(target=self.Do_Work)
main_thread.daemon = True #设置为守护线程
main_thread.start()
#服务器运行之后的函数
def Do_Work(self):
print("服务器开始工作")
try:
while self.isOn:
session_socket,client_address = self.server_socket.accept()
#服务器首先接受客户端发过来的第一天消息,规定第一条消息为客户端的名字
username = session_socket.recv(1024).decode('UTF-8') #接受客户端的名字
#创建一个会话线程
self.session_thread = Session_Thread(session_socket,username,self)
self.session_thread_map[username] = self.session_thread
self.session_thread.start()
#表示有客户端进入聊天室
self.Show_Information_and_Send_Client("服务器通知","欢迎{}进入聊天室!".format(username),
time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
except:
self.show_chat.AppendText('\n服务器已停止\n')
#在文本中显示聊天信息,同时发送消息给所有的客户端 source:信息源 data:信息
def Show_Information_and_Send_Client(self,source,data,data_time):
send_data = '{}:{}\n时间:{}\n'.format(source,data,data_time)
#在服务器文本框显示信息
self.show_chat.AppendText('------------------------------------\n{}'.format(send_data))
for client in self.session_thread_map.values():
if(client.isOn): #当前客户端是运行状态
client.user_socket.send(send_data.encode('UTF-8'))
#服务器保存聊天记录
def Save_Record(self,event):
record = self.show_chat.GetValue()
with open("record.log",'a') as f:
f.write(record)
#关闭服务器
def Stop_Server(self,event):
self.isOn = False
for client in self.session_thread_map.values():
if client.isOn:
client.user_socket.send('服务器已关闭'.encode('UTF-8'))
client.isOn = False
client.user_socket.close()
self.server_socket.close()
#服务器端会话线程的类
class Session_Thread(threading.Thread):
def __init__(self,user_socket,username,server):
threading.Thread.__init__(self)
self.user_socket = user_socket
self.username = username
self.server = server
self.isOn = True
def run(self): #会话线程的运行
print('客户端{},已经和客户端连接成功,服务器启动一个会话线程'.format(self.username))
try:
while self.isOn:
data = self.user_socket.recv(1024).decode('UTF-8') #接受客户端的聊天信息
if data == 'A^disconnect^B': #如果客户端点击断开按钮,先发一条消息给服务器,消息内容规定为:A^disconnect^B
self.isOn = False
#有用户离开,需要在聊天室通知其他人
self.server.Show_Information_and_Send_Client("服务器通知","{}离开聊天室!".format(self.username),
time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
else:
#其他聊天信息,我们应该显示给所有客户端,包括服务器
self.server.Show_Information_and_Send_Client(self.username,data,
time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
except:
return
if __name__ == '__main__':
app = wx.App()
HwbServer().Show()
app.MainLoop() #循环刷新显示
import wx
from socket import *
import threading
#客户端继承wx.Frame 窗口界面
class HwbClient(wx.Frame):
def __init__(self,c_name):
#调用父类的初始化函数
wx.Frame.__init__(self,None,id=101,title='{}的客户端界面'.format(c_name),
pos=wx.DefaultPosition,size=(400,470))
pl=wx.Panel(self) #在窗口中初始化一个面板
#在面板里放按钮,文本输入框
box = wx.BoxSizer(wx.VERTICAL) #在盒子里面垂直方向自动排版
grid1 = wx.FlexGridSizer(wx.HORIZONTAL) #可伸缩的水平网格布局
#创建两个按钮
connect_button = wx.Button(pl,size=(200,40),label="连接")
dis_connect_button = wx.Button(pl,size=(200,40),label="断开")
grid1.Add(connect_button,1,wx.TOP | wx.LEFT) #连接按钮在左边
grid1.Add(dis_connect_button,1,wx.TOP | wx.RIGHT) #断开按钮在右边
box.Add(grid1,1,wx.ALIGN_CENTER) #ALIGN_CENTER 联合居中
#创建聊天内容的文本框,不能写消息:TE_MULTILINE -->多行 TE_READONLY-->只读
self.show_chat = wx.TextCtrl(pl,size=(400,250),style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.show_chat,1,wx.ALIGN_CENTER)
#创建聊天的输入文本框,可以写
self.input_chat = wx.TextCtrl(pl,size=(400,100),style=wx.TE_MULTILINE)
box.Add(self.input_chat,1,wx.ALIGN_CENTER)
#最后创建两个按钮,分别是发送和重置
grid2 = wx.FlexGridSizer(wx.HORIZONTAL) #可伸缩的水平网格布局
#创建两个按钮
clear_button = wx.Button(pl,size=(200,40),label="重置")
send_button = wx.Button(pl,size=(200,40),label="发送")
grid2.Add(clear_button,1,wx.TOP | wx.LEFT) #连接按钮在左边
grid2.Add(send_button,1,wx.TOP | wx.RIGHT) #断开按钮在右边
box.Add(grid2,1,wx.ALIGN_CENTER)
pl.SetSizer(box) #把盒子放入面板中
'''以上代码完成了客户端窗口'''
'''给所有按钮绑定点击事件'''
self.Bind(wx.EVT_BUTTON,self.Connect_To_Server,connect_button)
self.Bind(wx.EVT_BUTTON,self.Send_To_Server,send_button)
self.Bind(wx.EVT_BUTTON,self.Disconnect_To_Server,dis_connect_button)
self.Bind(wx.EVT_BUTTON,self.Reset,clear_button)
'''客户端属性'''
self.name = c_name
self.isConnected = False #客户端是否已经连上服务器
self.client_socket = None
#连接服务器
def Connect_To_Server(self,event):
try:
print("客户端{},开始连接服务器……".format(self.name))
if not self.isConnected:
server_host_port = ('192.168.1.101',8888)
self.client_socket = socket(AF_INET,SOCK_STREAM)
self.client_socket.connect(server_host_port)
# 之前规定,客户端连接成功,马上发送名字给服务器
self.client_socket.send(self.name.encode('UTF-8'))
client_thread = threading.Thread(target=self.Receive_Data)
client_thread.daemon = True #客户端UI界面如果关闭,当前守护线程也自动关闭
self.isConnected = True
client_thread.start()
except:
self.show_chat.AppendText("\n未找到服务器,连接失败\n")
#接受服务器发送过来的聊天数据
def Receive_Data(self):
try:
while self.isConnected:
data = self.client_socket.recv(1024).decode('UTF-8')
if data == "服务器已关闭":
self.isConnected = False
# 从服务器接受到的数据,需要显示
self.show_chat.AppendText('{}\n'.format(data))
except:
return
def Send_To_Server(self,event):
#客户端发送消息到聊天室
if self.isConnected:
information = self.input_chat.GetValue()
if information != '':
self.client_socket.send(information.encode('UTF-8'))
#若输入框中的数据已经发送,输入框清空
self.input_chat.SetValue('')
#客户端离开聊天室
def Disconnect_To_Server(self,event):
if self.isConnected:
self.client_socket.send('A^disconnect^B'.encode('UTF-8'))
self.isConnected = False
#客户端主线程也要关闭
self.client_socket.close()
#客户端输入框的信息重置
def Reset(self,event):
self.input_chat.Clear()
if __name__ == '__main__':
app=wx.App()
name = input("请输入客户端名字:")
HwbClient(name).Show()
app.MainLoop() #循环刷新显示