CH14. 项目案例《多人聊天室》

- wxPython:是Python的第三方库,代码实现基于C++的wxWidgets库封装,呈现的界面风格和系统本地风格一致
使用wxpython绘制客户端布局

import wx
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))
pl = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
fgz1 = wx.FlexGridSizer(wx.HSCROLL)
conn_btn = wx.Button(pl, size=(200,40), label='连接')
dis_conn_btn = wx.Button(pl, size=(200,40), label='断开')
fgz1.Add(conn_btn, 1, wx.TOP | wx.LEFT)
fgz1.Add(dis_conn_btn, 1, wx.TOP | wx.RIGHT)
box.Add(fgz1, 1, wx.ALIGN_CENTER)
self.show_text = wx.TextCtrl(pl, size=(400,210),
style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.show_text, 1, wx.ALIGN_CENTER)
self.chat_text = wx.TextCtrl(pl, size=(400,120),
style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.chat_text, 1, wx.ALIGN_CENTER)
fgz2 = wx.FlexGridSizer(wx.HSCROLL)
reset_btn = wx.Button(pl, size=(200,40), label='重新输入')
send_btn = wx.Button(pl, size=(200,40), label='发送')
fgz2.Add(reset_btn, 1, wx.TOP | wx.LEFT)
fgz2.Add(send_btn, 1, wx.TOP | wx.RIGHT)
box.Add(fgz2, 1, wx.ALIGN_CENTER)
pl.SetSizer(box)
if __name__ == "__main__":
app = wx.App()
client = Client('客户端App')
client.Show()
app.MainLoop()

import wx
class Server(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, id=1002, title='服务端App',
pos=wx.DefaultPosition, size=(400,500))
pl = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
fgz1 = wx.FlexGridSizer(wx.HSCROLL)
start_server_btn = wx.Button(pl,size=(133,40),label='启动服务')
record_btn = wx.Button(pl,size=(144,40),label='保存聊天记录')
stop_server_btn = wx.Button(pl,size=(133,40),label='停止服务')
fgz1.Add(start_server_btn, 1, wx.TOP)
fgz1.Add(record_btn, 1, wx.TOP)
fgz1.Add(stop_server_btn, 1, wx.TOP)
box.Add(fgz1, 1, wx.ALIGN_CENTRE)
self.show_text = wx.TextCtrl(pl, size=(400,410),
style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.show_text, 1, wx.ALIGN_CENTER)
pl.SetSizer(box)
if __name__ == '__main__':
app = wx.App()
server = Server()
server.Show()
app.MainLoop()
会话通信
- 代码功能实现
- 启动服务器功能实现
- 客户端连接服务器
- 显示聊天信息
- 发送聊天信息到聊天室
- 客户端断开连接
- 客户端重置
- 保存聊天记录
- 停止服务
客户端代码实现
import threading
from socket import AF_INET, SOCK_STREAM, socket
import wx
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))
pl = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
fgz1 = wx.FlexGridSizer(wx.HSCROLL)
conn_btn = wx.Button(pl, size=(200,40), label='连接')
dis_conn_btn = wx.Button(pl, size=(200,40), label='断开')
fgz1.Add(conn_btn, 1, wx.TOP | wx.LEFT)
fgz1.Add(dis_conn_btn, 1, wx.TOP | wx.RIGHT)
box.Add(fgz1, 1, wx.ALIGN_CENTER)
self.show_text = wx.TextCtrl(pl, size=(400,210),
style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.show_text, 1, wx.ALIGN_CENTER)
self.chat_text = wx.TextCtrl(pl, size=(400,120),
style=wx.TE_MULTILINE)
box.Add(self.chat_text, 1, wx.ALIGN_CENTER)
fgz2 = wx.FlexGridSizer(wx.HSCROLL)
reset_btn = wx.Button(pl, size=(200,40), label='重新输入')
send_btn = wx.Button(pl, size=(200,40), label='发送')
fgz2.Add(reset_btn, 1, wx.TOP | wx.LEFT)
fgz2.Add(send_btn, 1, wx.TOP | wx.RIGHT)
box.Add(fgz2, 1, wx.ALIGN_CENTER)
pl.SetSizer(box)
self.Bind(wx.EVT_BUTTON, self.connect_to_server, conn_btn)
self.client_name = client_name
self.isConnected = False
self.client_socket = None
self.Bind(wx.EVT_BUTTON, self.send_data_to_server, send_btn)
self.Bind(wx.EVT_BUTTON, self.disconnect_with_server, dis_conn_btn)
self.Bind(wx.EVT_BUTTON, self.reset_chat_text, reset_btn)
def connect_to_server(self, event):
if not self.isConnected:
server_host_port = ('127.0.0.1', 8888)
self.client_socket = socket(AF_INET, SOCK_STREAM)
self.client_socket.connect(server_host_port)
self.client_socket.send(self.client_name.encode('utf-8'))
client_thread = threading.Thread(target=self.recv_data)
client_thread.daemon = True
self.isConnected = True
client_thread.start()
print(f'客户端{self.client_name}连接服务器成功')
def recv_data(self):
while self.isConnected:
try:
data = self.client_socket.recv(1024).decode('utf-8')
except:
self.isConnected = False
continue
self.show_text.AppendText('-'*40+'\n'+data+'\n')
def send_data_to_server(self, event):
if self.isConnected:
input_data = self.chat_text.GetValue()
if input_data !='':
self.client_socket.send(input_data.encode('utf-8'))
self.chat_text.SetValue('')
def disconnect_with_server(self, event):
self.client_socket.send('Folin-disconnet'.encode('utf-8'))
self.isConnected = False
def reset_chat_text(self, event):
self.chat_text.Clear()
if __name__ == "__main__":
app = wx.App()
client = Client('Folin')
client.Show()
app.MainLoop()
服务端代码实现
import threading
import time
from socket import AF_INET, SOCK_STREAM, socket
import wx
class SessionThread(threading.Thread):
def __init__(self, client_socket, user_name, server):
threading.Thread.__init__(self)
self.client_socket = client_socket
self.user_name = user_name
self.server = server
self.isConnected = True
def run(self) -> None:
print(f'客户端:{self.user_name}已经和服务器连接成功,服务器启动一个会话线程')
while self.isConnected:
data = self.client_socket.recv(1024).decode('utf-8')
if data == 'Folin-disconnet':
self.isConnected = False
self.server.show_info_and_send_to_client('服务器通知:', f'{self.user_name}离开聊天室', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
else:
self.server.show_info_and_send_to_client(self.user_name, data, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
self.client_socket.close()
print(f'{self.user_name} close...')
class Server(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, id=1002, title='服务端App',
pos=wx.DefaultPosition, size=(400,500))
pl = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
fgz1 = wx.FlexGridSizer(wx.HSCROLL)
start_server_btn = wx.Button(pl,size=(133,40),label='启动服务')
record_btn = wx.Button(pl,size=(144,40),label='保存聊天记录')
stop_server_btn = wx.Button(pl,size=(133,40),label='停止服务')
fgz1.Add(start_server_btn, 1, wx.TOP)
fgz1.Add(record_btn, 1, wx.TOP)
fgz1.Add(stop_server_btn, 1, wx.TOP)
box.Add(fgz1, 1, wx.ALIGN_CENTRE)
self.show_text = wx.TextCtrl(pl, size=(400,410),
style=wx.TE_MULTILINE | wx.TE_READONLY)
box.Add(self.show_text, 1, wx.ALIGN_CENTER)
pl.SetSizer(box)
self.isOn = False
self.host_port = ('', 8888)
self.server_socket = None
self.session_thread_dict = {}
self.Bind(wx.EVT_BUTTON, self.start_server, start_server_btn)
self.Bind(wx.EVT_BUTTON, self.save_record, record_btn)
self.Bind(wx.EVT_BUTTON, self.stop_server, stop_server_btn)
def save_record(self, event):
record_data = self.show_text.GetValue()
with open('record.log', 'w', encoding='utf-8') as file:
file.write(record_data)
def stop_server(self, event):
print('服务器已停止服务')
self.isOn = False
self.server_socket.close()
def start_server(self, event):
if not self.isOn:
self.isOn = True
self.server_socket = socket(AF_INET, SOCK_STREAM)
self.server_socket.bind(self.host_port)
self.server_socket.listen(5)
main_thread = threading.Thread(target=self.do_work)
main_thread.daemon = True
main_thread.start()
print('start server')
def do_work(self):
while self.isOn:
try:
client_socket, client_addr = self.server_socket.accept()
except:
print('has error')
continue
user_name = client_socket.recv(1024).decode('utf-8')
session_thread = SessionThread(client_socket, user_name, self)
self.session_thread_dict[user_name] = session_thread
session_thread.start()
self.show_info_and_send_to_client('服务器通知:', f'欢迎{user_name}进入聊天室!', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
self.server_socket.close()
print('server_socket close...')
def show_info_and_send_to_client(self, data_src, data, datetime):
if self.isOn:
send_data = f'{data_src}: {data}\n时间: {datetime}'
self.show_text.AppendText('-'*40+'\n'+send_data+'\n')
for client in self.session_thread_dict.values():
if client.isConnected:
client.client_socket.send(send_data.encode('utf-8'))
if __name__ == '__main__':
app = wx.App()
server = Server()
server.Show()
app.MainLoop()