客户端与服务端聊天窗口
服务端
导入
wxPython 用于创建图形界面。
socket 用于网络通信,AF_INET 是 IPv4 地址族,SOCK_STREAM 表示流式套接字(TCP)。
利用wxPython 创建图形界面,并通过 socket 与服务器通信。
主要功能:
- 连接服务器。
- 发送和接收消息。
- 断开连接。
- 界面上有文本框和按钮来操作。
- wxPython 处理 UI 部分,socket 处理网络通信。
import threading
from threading import main_thread, Thread
#coding UTF-8
import self
import wx
import time
from socket import socket,AF_INET,SOCK_STREAM
class YsjServer(wx.Frame):
//通过继承 wx.Frame,我们可以创建一个带有窗口的应用程序。
def __init__(self):
wx.Frame.__init__(self,None,id=1002,title='某工作室界面')
# 创建面板对象
pl = wx.Panel(self)
# 面板中放盒子
box = wx.BoxSizer(wx.VERTICAL)
# 可伸缩的网格布局
fgz1 = wx.FlexGridSizer(wx.HSCROLL)
start_server_btn = wx.Button(parent=pl, size=(133,40), label='启动')
record_btn = wx.Button(parent=pl, size=(133, 40), label='保存聊天记录')
stop_server_btn = wx.Button(parent=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中
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_CENTRE)
#盒子放面板
pl.SetSizer(box)
'--------界面绘制--------'
'-----------服务器功能------'
self.isOn=False
self.host_port=('',8888)
# 创建socket对象
self.server_socket = socket(AF_INET, SOCK_STREAM)
# 绑定IP地址和端口
self.server_socket.bind(self.host_port)
# 监听
self.server_socket.listen(5)
# 创建一个字典,存储与客户端对话的会话线程
self.session_thread_dict = {} # key-value {客户端的名称key:会话线程value}
# 当鼠标点击“启动服务”按钮时,要执行的操作
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 stop_server(self, event):
print('服务器已停止服务')
self.isOn = False
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 start_server(self,event):
#判断是否启动
if not self.isOn:
#启动
self.isOn=True
#创建主线程对象
main_thread=threading.Thread(target=self.do_work())
#设置守护线程,父线程执行结束,子线程也自动关闭
main_thread.demon=True
#启动
main_thread.start()
def do_work(self):
while self.isOn:
#接收客户端的连接请求
session_socket,client_addr=self.server_socket.accept()
#客户端发送连接请求之后,发送过来的第一条数据为客户端的名称,作为键
user_name=session_socket.recv(1024).decode('utf-8')
#创建绘画线程对象
session_thread=SessionThread(session_socket,user_name,self)
self.session_thread_dict[user_name]=session_thread
session_thread.start()
self.show_info_and_send_client('服务器通知', f'欢迎{user_name}进入聊天室!',time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
)
self.server_socket.close()
def show_info_and_send_client(self, data_source, data, date_time):
# 字符串操作
send_data = f'{data_source}:{data}\n时间:{date_time}'
# 只读文本框
self.show_text.AppendText('-' * 40 + '\n' + send_data + '\n')
# 给每一个客户端都发送一次
for client in self.session_thread_dict.values():
# 如果当前的会话处于开启状态
if client.isOn:
client.client_socket.send(send_data.encode('utf-8'))
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.isOn = True # 会话线程是否启动,当创建SessionThread对象时会话线程即启动
def run(self) -> None:
print(f'客户端:{self.user_name}已经和服务器连接成功,服务器启动一个会话线程')
while self.isOn:
# 从客户端接收数据 存储到data中
data = self.client_socket.recv(1024).decode('utf-8')
# 如果客户端点击断开按钮,先给服务器发送一句话, 消息自定义 Y-disconnect-SJ 自定义的结束词儿
if data == 'Y-disconnect-SJ':
self.isOn = False
self.server.show_info_and_send_client('服务器通知',f'{self.user_name}离开聊天室',
time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
else:
# 其它聊天信息显示给所有的客户端,包含服务器也显示
# 调用刚才编写的方法
self.server.show_info_and_send_client(self.user_name, data,time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
# 关闭 socket
self.client_socket.close()
if __name__ == '__main__':
app=wx.App()
server =YsjServer()
server.Show()
app.MainLoop()
客户端
import wx
#coding UTF-8
from socket import socket,AF_INET,SOCK_STREAM
class YsJClient(wx.Frame):
def __init__(self, client_name):
#调用父类的初始化方法
#None:表示没有父级窗口
#id 表示窗口编号
#pos 窗体打开的位置
#size 窗体的大小,单位像素
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(parent=pl, size=(200, 40), label='连接')
dis_conn_btn = wx.Button(parent=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中
box.Add(fgz1, 1, wx.ALIGN_CENTRE)
#只读文本框
self.show_text=wx.TextCtrl(pl,size=(400,210),style=wx.TE_MULTILINE|wx.TE_READONLY)
box.Add(self.show_text,1,wx.ALIGN_CENTRE)
#创建聊天文本框
self.chat_text = wx.TextCtrl(pl, size=(400, 120), style=wx.TE_MULTILINE)
box.Add(self.chat_text, 1, wx.ALIGN_CENTRE)
#可绳索网格布局
fgz1 = wx.FlexGridSizer(wx.HSCROLL)
#两个按钮
reset_btn = wx.Button(parent=pl, size=(200, 40), label='重置')
send_btn = wx.Button(parent=pl, size=(200, 40), label='发送')
fgz1.Add(reset_btn, 1, wx.TOP | wx.LEFT)
fgz1.Add(send_btn, 1, wx.TOP | wx.RIGHT)
# (可伸缩的网络布局)添加到box中
box.Add(fgz1, 1, wx.ALIGN_CENTRE)
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_to_server, send_btn)
#断开绑定
self.Bind(wx.EVT_BUTTON, self.dis_conn_to_server, dis_conn_btn)
# 重置绑定
self.Bind(wx.EVT_BUTTON, self.reset, reset_btn)
def reset(self,event):
self.chat_text.Clear()
def dis_conn_to_server(self,event):
self.client_socket.send('Y-disconnect-SJ'.encode('utf-8'))
self.isconnected=False
def send_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 connect_to_server(self, event):
print(f'客户端{self.client_name}连接服务器成功')
if not self.isConnected:
server_host_port=('127.0.0.1',8888)
#创建socket对象
self.client_socket=socket(AF_INET,SOCK_STREAM)
#发送请求
self.client_socket.connect(server_host_port)
import threading
# 只要连接成功,发送一条数据
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()
def recv_data(self):
while self.isConnected:
data=self.client_socket.recv(1024).decode('utf-8')
# 只读文本框
self.show_text.AppendText('-' * 40 + '\n' + data + '\n')
'-----------服务器功能------'
if __name__ == '__main__':
app=wx.App()
name=input('请输入客户端名称')
client =YsJClient(name)
client.Show()
app.MainLoop()