20192213 《Python程序设计》实验三报告
课程:《Python程序设计》
班级: 1922
姓名: 刘子谦
学号:20192213
实验教师:王志强
实验日期:2020年5月17日
必修/选修: 公选课
1.实验内容
创建服务端和客户端,服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
2. 实验过程及结果
-
构思
本实验要求实现文件加密传输,所以关键是“文件发送”和“数据加解密”。程序设计流程如下:
①编写简单的服务器和客户端,实现数据传输。
②编写文件读写模块。
③设计加密程序。 -
附加功能
为提高程序实用性,客户端采用了GUI界面,并支持文件传输和消息发送。 -
下面是程序效果:
信息发送功能如下图:
文件传输效果如下图:
-
代码
服务器:
from socket import *
import base64
import re
def jiami(content):
# 转成bytes string
bytesString = content.encode(encoding="utf-8")
# base64 编码
encodestr = base64.b64encode(bytesString)
return encodestr.decode()
def jiemi(content):
# 解码
decodestr = base64.b64decode(content)
return decodestr.decode()
IP = '0.0.0.0'#全0表示本机所有可用的IP地址
# 端口
PORT = 8000
BUFLEN = 512
KEY = '文件标识秘钥hello'
#实例化一个socket对象
listenSocket = socket(AF_INET,SOCK_STREAM) #参数说明:AF_INET是“网络层”使用IP协议;SOCK_STREAM是传输层使用tcp协议
#socket绑定地址和端口
listenSocket.bind((IP,PORT))
# 使socket处于监听状态;等待客户端连接请求
# 参数5表示:最多接受多少个等待连接的客户端
listenSocket.listen(5)
print(f'服务器已启动,端口:{PORT};等待客户端连接')
dataSocket,addr = listenSocket.accept() #返回的 是一个元组,用两个变量接收;①一个用于数据传输的socket对象;②一个地址&端口号
print('接受一个客户端连接:',addr)
while True:
#尝试读取对方发送的消息
#BUFLEN:指定从接受缓冲里最多读取多少个字节
recved = dataSocket.recv(BUFLEN)
#如果返回为空bytes,表明对方关闭了链接
#退出循环
if not recved:
break
#读取的数据是bytes类型;解码
info = recved.decode()
info = jiemi(info)
if KEY in info:
info = info.replace(KEY,'')
filename = re.split(':', info, 1)[0]
info = re.split(':', info, 1)[1]
with open(filename,'w') as f:
f.write(info)
f.close()
print("已保存文件:"+filename)
else:
print(">>",info)
#发送的数据类型必须是bytes;编码
dataSocket.send(f'服务器接收到消息'.encode())
# 服务端也调用close()关闭socket
dataSocket.close()
listenSocket.close()
客户端(GUI版)
from socket import *
import base64
import re
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import time
def jiami(content):
# 转成bytes string
bytesString = content.encode(encoding="utf-8")
# base64 编码
encodestr = base64.b64encode(bytesString)
return encodestr.decode()
def jiemi(content):
# 解码
decodestr = base64.b64decode(content)
return decodestr.decode()
def AddThread(fun,*args):
import threading
th = threading.Thread(target=fun, args=(*args,))
th.setDaemon(True) # 守护线程
th.start()
class client():
def __init__(self):
self.targetIP = '127.0.0.1'
self.messages = '' # 发生信息
self.sendKey = '**send**' # 定义规则;发送秘钥
self.contents = '' # 文件内容
self.name = '' # 文件名
self.SendFileOrNot = False # 记录文件是否发送,防止发生两次
#读取文件
def readFile(self,path,name):
with open(path,encoding='utf-8') as f:
self.contents = f.read()
print(self.contents)
self.messages = self.sendKey
self.name = name
def getMessage(self,message):
self.messages = message
def sendMessage(self):
SERVER_PORT = 8000
BUFLEN = 512
#实例化一个socket对象,指明协议
dataSocket = socket(AF_INET,SOCK_STREAM)
#连接到服务端socket
dataSocket.connect((self.targetIP,SERVER_PORT))
while True:
'''
定义规则:
self.message从另一个线程读取
如果是空,就continue;
如果和key变量相同,就发送self.contents的内容;
如果是exit就退出;
其他情况直接发送
文件发送格式如下:
文件标识KEY + 文件名 + : + 文件内容
(服务器会根据这个格式判断区分文件和普通消息,并提取文件内容和文件名)
'''
toSend = self.messages
if toSend == '':
continue
if toSend == self.sendKey:
toSend = '文件标识秘钥hello'+self.name+':'+self.contents
if toSend == 'exit':
break
dataSocket.send(jiami(toSend).encode())
#等待接收服务端消息
recved = dataSocket.recv(BUFLEN)
#如果返回为空bytes,表明对方关闭了链接
if not recved:
break
print(recved.decode())
self.messages = ''
dataSocket.close()
class mainWindow():
def __init__(self):
#用户设置
self.name = '' #等待开发
self.pwd = '' #等待开发
#功能核心
self.main = client()
#加载主窗口
def window(self):
self.window = tk.Tk()
self.window.title('哈巴狗1.0')
self.window.geometry('530x400')
self.window.resizable(0, 0) # 禁止修改尺寸
# self.window.iconphoto(False, tk.PhotoImage(file='image/方块.png')) # 设置图标
# self.window.iconbitmap("image/方块.ico")
self.window.config(bg='white') # 背景色
self.textBox()
self.buttons()
#开始实时发送消息(当message不为空时就发送)
AddThread(self.main.sendMessage)
self.window.mainloop()
#加载文本框
def textBox(self):
self.output = tk.Text(self.window,font=('',13))
self.output.configure(state=tk.DISABLED) #只读
self.output.config(bg='white',foreground='black')
self.output.place(x=0,y=0,width=530,height=300)
#定义格式
self.output.tag_config("time", foreground='green',font=('',10))
self.output.tag_config("content", foreground='black',font=('',13))
self.input = tk.Text(self.window)
self.input.config(bg='white')
self.input.place(x=0,y=300,height=100)
#加载按钮
def buttons(self):
self.sendButton = tk.Button(self.window,text='发 送')
self.sendButton.config(relief='flat',bg='DeepSkyBlue',foreground='white',command=self.send,font=('黑体',11))
self.sendButton.place(x=400,y=360,width=100,height=30)
self.sendButton = tk.Button(self.window, text='传文件')
self.sendButton.config(relief='flat', bg='Silver', foreground='white', command=self.sendFile, font=('黑体', 11))
self.sendButton.place(x=270, y=360, width=100, height=30)
#发送信息函数
def send(self):
# content = self.input.get("0.0", index2="end")
content = self.input.get("0.0", 'end')
content = content.strip()
if content == '':
self.input.delete(0.0, 'end')
print('发送内容不允许为空')
return
#清空内容
self.input.delete(0.0, 'end')
#发送信息
self.main.getMessage(content)
xy = self.output.index(tk.INSERT) # 获取当前光标位置
y = xy.split('.',1)[0]
y = int(y)
Sendtime = time.strftime("\n%Y-%m-%d %H:%M:%S", time.localtime())
self.output.configure(state='normal')
self.output.insert('insert',Sendtime)
self.output.tag_add('time', str(y+1) + '.0', str(y+1) + '.25') # 使用格式1
self.output.insert('insert', '\n'+content+'\n')
self.output.tag_add('content', str(y + 2) + '.0',str(y + 2)+'.100' ) # 使用格式1
self.output.configure(state=tk.DISABLED)
def sendFile(self):
AddThread(self.sendFile1)
def sendFile1(self):
filepath = filedialog.askopenfilename()
print(filepath)
filename = re.findall(r'[^\\/:*?"<>|\r\n]+$', filepath)[0]
print(filename)
self.main.readFile(filepath,filename)
print('ok')
xy = self.output.index(tk.INSERT) # 获取当前光标位置
y = xy.split('.', 1)[0]
y = int(y)
Sendtime = time.strftime("\n%Y-%m-%d %H:%M:%S", time.localtime())
self.output.configure(state='normal')
self.output.insert('insert', Sendtime)
self.output.tag_add('time', str(y + 1) + '.0', str(y + 1) + '.25') # 使用格式1
self.output.insert('end', '\n文件上传成功:'+filename+'\n')
self.output.tag_add('content', str(y + 2) + '.0', str(y + 2) + '.100') # 使用格式1
self.output.configure(state=tk.DISABLED)
a = mainWindow()
a.window()
- 代码具体功能已放在代码注释中。
3. 实验过程中遇到的问题和解决过程
-
问题1:程序如何通过一个接口发送并识别信息是“文件”还是“普通消息”
-
问题1解决方案:为文件前面加上一个标识字符串,标识文件。通过定义的字符串规则,让服务器提取文件名和文件内容。
-
问题2:socket不熟练,很多代码需要现查资料
-
问题2解决方案:多加练习,掌握socket编程技巧
其他(感悟、思考等)
熟能生巧!对于一个新Python库,除了多查资料,就要多练习,多使用。
Python博大精深,希望以后能学习更多