学号 20191304《Python程序设计》实验三报告
- 课程:《Python程序设计》
- 班级: 1913
- 姓名: 商苏赫
- 学号:20191304
- 实验教师:王志强
- 实验日期:2020 年 6 月 27 日
- 必修/选修: 公选课
1.实验内容
实现多人聊天,建立可视化聊天窗口界面。对客户端程序进行打包exe文件运行。
2. 实验过程及结果
**(1)启动服务器,显示建立通信的IP和端口,可以建立可视化窗口进行显示。 **
(2)启动客户端,根据服务器所显示的IP和端口进行登陆。
(3)在不同主机,在局域网下进行聊天的实现。
(4)客户端程序进行打包形成exe文件。
运行截图如下:
前面运行结果是在一台主机上进行操作,后续也是尝试在两台主机连接相同网络下进行通信:
将Python文件打包成exe,一般是用PyInstaller或者是py2exe。首先我是安装好的:
如果是利用py2exe的话,一般是建立一个setup.py:
然后可以通过新建txt文件,输入python setup.py py2exe然后将其改为bat文件进行运行。
然后可以得到dist文件夹,里面会有client.exe
其实我也尝试过将sever的代码打包为exe,但是运行一直没成功也不知道问题所在。其实还有一种更简便的方式,是利用auto-py-to-exe
很方便进行操作,选择相应的Python文件,选择生成目录还是文件,还可以附加文件,设置图标,这里我添加了一个.ico文件,还有很多高级设置,名称等等。生成这样一个exe:
服务端的代码server:
import socket
import threading
import queue
import json # json.dumps(some)打包 json.loads(some)解包
import os
import os.path
import easygui
import sys
hostname = socket.gethostname()
IP = socket.gethostbyname(hostname)
easygui.msgbox("默认端口是9999,当前IP地址为:"+IP)
PORT = 9999 # 端口
messages = queue.Queue()
users = [] # 0:userName 1:connection
lock = threading.Lock()
def onlines(): # 统计当前在线人员
online = []
for i in range(len(users)):
online.append(users[i][0])
return online
class ChatServer(threading.Thread):
global users, que, lock
def __init__(self): # 构造函数
threading.Thread.__init__(self)
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
os.chdir(sys.path[0])
# 接受来自客户端的用户名,如果用户名为空,使用用户的IP与端口作为用户名。如果用户名出现重复,则在出现的用户名依此加上后缀“2”、“3”、“4”……
def receive(self, conn, addr): # 接收消息
user = conn.recv(1024) # 用户名称
user = user.decode()
if user == '用户名不存在':
user = addr[0] + ':' + str(addr[1])
tag = 1
temp = user
for i in range(len(users)): # 检验重名,则在重名用户后加数字
if users[i][0] == user:
tag = tag + 1
user = temp + str(tag)
users.append((user, conn))
USERS = onlines()
self.Load(USERS,addr)
# 在获取用户名后便会不断地接受用户端发来的消息(即聊天内容),结束后关闭连接。
try:
while True:
message = conn.recv(1024) # 发送消息
message = message.decode()
message = user + ':' + message
self.Load(message,addr)
# 如果用户断开连接,将该用户从用户列表中删除,然后更新用户列表。
except:
j = 0 # 用户断开连接
for man in users:
if man[0] == user:
users.pop(j) # 服务器段删除退出的用户
break
j = j+1
USERS = onlines()
self.Load(USERS,addr)
conn.close()
# 将地址与数据(需发送给客户端)存入messages队列。
def Load(self, data, addr):
lock.acquire()
try:
messages.put((addr, data))
finally:
lock.release()
# 服务端在接受到数据后,会对其进行一些处理然后发送给客户端,如下图,对于聊天内容,服务端直接发送给客户端,而对于用户列表,便由json.dumps处理后发送。
def sendData(self): # 发送数据
while True:
if not messages.empty():
message = messages.get()
if isinstance(message[1], str):
for i in range(len(users)):
data = ' ' + message[1]
users[i][1].send(data.encode())
print(data)
print('\n')
if isinstance(message[1], list):
data = json.dumps(message[1])
for i in range(len(users)):
try:
users[i][1].send(data.encode())
except:
pass
def run(self):
self.s.bind((IP,(PORT)))
self.s.listen(5)
q = threading.Thread(target=self.sendData)
q.start()
while True:
conn, addr = self.s.accept()
t = threading.Thread(target=self.receive, args=(conn, addr))
t.start()
if __name__ == '__main__':
cserver = ChatServer()
cserver.start()
客户端client代码:
import socket
import tkinter
import tkinter.messagebox
import threading
import json
import tkinter.filedialog
from tkinter.scrolledtext import ScrolledText
IP = ''
PORT = ''
user = ''
listbox1 = '' # 用于显示在线用户的列表框
show = 1 # 用于判断是开还是关闭列表框
users = [] # 在线用户列表
chat = '------Group chat-------' # 聊天对象
# 登陆窗口
root0 = tkinter.Tk()
root0.geometry("300x150")
root0.title('用户登陆窗口')
root0.resizable(0, 0)
one = tkinter.Label(root0, width=300, height=150, bg="LightBlue")
one.pack()
IP0 = tkinter.StringVar()
IP0.set('')
USER = tkinter.StringVar()
USER.set('')
labelIP = tkinter.Label(root0, text='IP地址', bg="LightBlue")
labelIP.place(x=20, y=20, width=100, height=40)
entryIP = tkinter.Entry(root0, width=60, textvariable=IP0)
entryIP.place(x=120, y=25, width=100, height=30)
labelUSER = tkinter.Label(root0, text='用户名', bg="LightBlue")
labelUSER.place(x=20, y=70, width=100, height=40)
entryUSER = tkinter.Entry(root0, width=60, textvariable=USER)
entryUSER.place(x=120, y=75, width=100, height=30)
def Login(*args):
global IP, PORT, user
IP, PORT = entryIP.get().split(':')
user = entryUSER.get()
if not user:
tkinter.messagebox.showwarning('warning', message='用户名为空!')
else:
root0.destroy()
loginButton = tkinter.Button(root0, text="登录", command=Login, bg="Yellow")
loginButton.place(x=135, y=110, width=40, height=25)
root0.bind('<Return>', Login)
root0.mainloop()
# 建立连接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, int(PORT)))
if user:
s.send(user.encode()) # 发送用户名
else:
s.send('用户名不存在'.encode())
user = IP + ':' + PORT
# 聊天窗口
root1 = tkinter.Tk()
root1.geometry("640x480")
root1.title('群聊')
root1.resizable(0, 0)
# 消息界面
listbox = ScrolledText(root1)
listbox.place(x=5, y=0, width=640, height=320)
listbox.tag_config('tag1', foreground='red', backgroun="yellow")
listbox.insert(tkinter.END, '欢迎进入群聊,大家开始聊天吧!', 'tag1')
INPUT = tkinter.StringVar()
INPUT.set('')
entryIuput = tkinter.Entry(root1, width=120, textvariable=INPUT)
entryIuput.place(x=5, y=320, width=580, height=170)
# 在线用户列表
listbox1 = tkinter.Listbox(root1)
listbox1.place(x=510, y=0, width=130, height=320)
def send(*args):
message = entryIuput.get() + '~' + user + '~' + chat
s.send(message.encode())
INPUT.set('')
sendButton = tkinter.Button(root1, text="\n发\n\n\n送", anchor='n', command=send, font=('Helvetica', 18), bg='white')
sendButton.place(x=585, y=320, width=55, height=300)
root1.bind('<Return>', send)
def receive():
global uses
while True:
data = s.recv(1024)
data = data.decode()
print(data)
try:
uses = json.loads(data)
listbox1.delete(0, tkinter.END)
listbox1.insert(tkinter.END, "当前在线用户")
listbox1.insert(tkinter.END, "------Group chat-------")
for x in range(len(uses)):
listbox1.insert(tkinter.END, uses[x])
users.append('------Group chat-------')
except:
data = data.split('~')
message = data[0]
userName = data[1]
chatty = data[2]
message = '\n' + message
if chatty == '------Group chat-------': # 群聊
if userName == user:
listbox.insert(tkinter.END, message)
else:
listbox.insert(tkinter.END, message)
elif userName == user or chatty == user: # 私聊
if userName == user:
listbox.tag_config('tag2', foreground='red')
listbox.insert(tkinter.END, message, 'tag2')
else:
listbox.tag_config('tag3', foreground='green')
listbox.insert(tkinter.END, message, 'tag3')
listbox.see(tkinter.END)
r = threading.Thread(target=receive)
r.start() # 开始线程接收信息
root1.mainloop()
s.close()
其他(感悟、思考等)
这一次实验其实刚开始有很多很多想法,像爬虫想爬一些数据,然后做出可视化的窗口进行数据整理之类的。还有很多游戏是可以用Python实现,也有很多游戏实现的教程。最后是选择继续实验三的一个Socaket,进行可视化聊天的界面,还有打包的一些操作。整体代码的难度不算难,也没有学习和利用像一些人工智能化的很实用的库。还有很多很多东西没能实现,大二下这学期的时间并不算充裕,时间很紧张,关于这次实验内容的想法也有很多,未来希望能够实现登陆认证的操作,还有消息窗口里面消息的不可修改。(整个实验完成最可笑的地方是,可视化界面里面可以修改别人已经发出过的消息。)还有想继续学习能够不再局限于局域网之间的通信。还有GUI的美化,现在能够在制作的实在太简陋了,功能也很简陋。希望在完成这些功能后,打包成为exe文件能顺利的在别的电脑上运行,即便没有安装python,这次虽然成功打包,但是exe也只能在本机上运行,在未安装Python的主机上可以跑出登陆界面但是无法与服务器连接成功。学习Python学的还是太浅薄,心有余而力不足。
主要讲述遇到的问题,实验三中就涉及到了,多个客户端的运行,当时采用多线程实现,而实验三都只是客户端在于服务器通信,没有客户与客户之间的通信实现,所以这次客户端与服务端均采用多线程模式以便并行的接受与发送消息。这样便可实现多人的线上聊天。最开始运行连界面都加载不出来,客户端与服务器没法连接通,遇到像“[WinError 10038] 在一个非套接字上尝试了一个操作”。[Errno 11001] getaddrinfo failed还有代理错误[WinError 10061]在操作不当的情况下,还会有【WinError 10048】这些问题网上有很多很多的解决方案,一个个尝试,也学习到很多知识,在后面打包EXE的时候还会有更多问题,像python版本的问题等等。因为打包为exe还去将下载的svg图标转变为ico的格式,为exe更改图标。
最后这一学期的学习只能当作对Python的进一步了解吧,因为之前学习过C语言的基础,可以明显感觉到Python在很多功能实现上都简洁方便许多,而C语言则是更加基础偏向于底层设计。这学期同时也学习了Java和web里html语言,让我意识到自己还有很多知识要去学习。学习Python是最有成就感的,上手比较快,实现的功能都很实用。在未来也是希望自己能够更多的学习将这次实验内容拓展,完成自己最初想象的样子。