20191113 2019-2020-2 《Python程序设计》实验三报告

课程:《Python程序设计》
班级:1911
姓名:林紫欣
学号:20191113
实验教师:王志强
实验日期:2020年5月16日
必修/选修: 公选课

1.实验内容

  • 创建服务端和客户端,服务端在特定端口监听多个客户请求。
  • 客户端和服务端通过Socket套接字TCP进行通信。
  • 包含文件的基本操作,例如打开和读写操作。
  • 发送方从文件读取内容,加密后并传输;接收方收到密文并解密,保存在文件中。

2. 实验过程及结果

实验要素过多,故而采取分块编程的方式,将任务分解。

完整代码码云链接

TPC_Client
TPC_Server

1)监听多个客户端

为了多个客户端多线并行从而采取了线程处理的方式,将监听放入主线程,将处理放入子线程。

SerSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
SerSock.bind(('127.0.0.1',8001))
SerSock.listen(5)
socks = []  # 存放每个客户端的socket

t = threading.Thread(target=handle)
if __name__ == '__main__':
    t.start()
    print(r'我在%s线程中' % threading.current_thread().name)
    print('waiting for connecting ...')
    while True:
        CliSock,addr = SerSock.accept()
        print('connected from:',addr)
        socks.append(CliSock)

2)客户端连接服务器

CliSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serve_ip = input("Please enter the server IP:")
serve_port = int(input("Please enter the corresponding Port:"))  # 端口要是int类型,所有要转换
CliSock.connect((serve_ip, serve_port))  # 连接服务器,建立连接,参数是元组形式

3)通讯循环

为了使通讯可以不断进行,并在需要的时候停止,进行了如下设置。

while True:
    send_data = input("Enter your message:")
    CliSock.send(send_data.encode('utf-8'))
    time.sleep(1)
    if send_data == 'quit':
        print('\ncommunication ended.')
        break
    elif send_data == 'encrypt file':
        filename = input("Please enter the file name:")
        print("If the file is large, the run time will be a little long, please be patient.")
        af_filename = 'Client encrypted.txt'
        time.sleep(1)
        send_out(filename)
        take_in(af_filename)
        decrypt(af_filename)
        print('')
    re = CliSock.recv(1024)
    print("Server is sending:",re.decode('utf-8'))

4)文件发送与接收

为了提高将二者都变为函数,因为传输前后顺序不同,相关的代码也会有所不同。

客户端相关代码

在本实验中的传输文件代码只能传输本目录下的相关文件。

传输代码
def send_out(filename):
    with open(filename, 'rb') as file:
        for i in file:
            CliSock.send(i)
            data = CliSock.recv(1024)
            if data != b'success':
                break
    time.sleep(1)
    CliSock.send('quit'.encode())
    print("... File sent successfully ...")
接收代码
def take_in(af_filename):
    while True:
        with open(af_filename, 'ab') as file:
            data = CliSock.recv(1024)
            if data == b'quit':
                break
            file.write(data)
        time.sleep(1)
        CliSock.sendall('success'.encode())
    print("... File reception completed ...")

服务端相关代码

传输代码
def send_out(filename,s):
    with open(filename, 'rb') as file:
        i = file.read()
        s.send(i)
    time.sleep(1)
    s.send('quit'.encode())
    print("... File sent successfully ...")
接收代码
def take_in(filename,s):
    while True:
        with open(filename, 'ab') as file:
            data = s.recv(1024)
            if data == b'quit':
                break
            file.write(data)
        time.sleep(1)
        s.sendall('success'.encode())
    print("... File reception completed ...")

5)文件的加密以及解密

我密码方面的知识就从今天开始了。
上网搜索了一些资料,安装了pycrypto模块。发现了一个特别容易的安装方式安装pycrypto,不需要下载第三方的软件创造环境。
最后选择了AES加密的方法。

基本思路:用pyCryptodome模块带的AES先将秘钥,以及要加密的文本填充为16位,随后对AES产生的字节码进行base64位编码,转为字符串的形式即可。
解密思想逆过来即可。先逆向解密base64成bytes,执行解密密并转码返回str,将多余位数的’\0’替换为空

加密代码

def encrypt(filename,af_filename):#加密方法
    key = input("Please set a key:")
    text = open(filename,'rb').read()
    open(filename,'rb').close()
    text = str(text)
    aes = AES.new(add_to_16(key), AES.MODE_ECB)
    encrypt_aes = aes.encrypt(add_to_16(text))
    encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8')
    logbat = open(af_filename, 'w')
    logbat.write(encrypted_text)
    logbat.close()
    print('... File encrypted successfully ...')

解密代码

def decrypt(af_filename):
    key = input('Please enter the corresponding key:')
    text = str(open(af_filename, 'r').read())  # 密文文件
    open(af_filename, 'r').close()
    aes = AES.new(add_to_16(key), AES.MODE_ECB)  # 初始化加密器
    base64_decrypted = base64.decodebytes(text.encode(encoding='utf-8'))  # 优先逆向解密base64成bytes
    decrypted_text = str(aes.decrypt(base64_decrypted), errors='ignore').replace('\0', '')  # 执行解密
    decrypted_text2 = eval(decrypted_text)
    decrypted_text3 = decrypted_text2.decode('utf-8')
    print("Successfully decrypted, the contents of the file are:")
    print(decrypted_text3)

补足key位数的相关函数代码

在内部实现上,AES只是提供一个接收固定长度密钥和16字节大小的分组,然后生成另外一个不同的16字节大小的分组的数学函数。

def add_to_16(value):# str不是16的倍数那就补足为16的倍数
    while len(value) % 16 != 0:
        value += '\0'
    return str.encode(value)  # 返回bytes

以上皆为为了符合通讯而调整过后的函数形式。
功能更加全面,可以任意选择而非只本目录下文件,并且可以全新命名加密文件的代码详见AES加密

  • 在解密过程中发现输出的文字不够美观,是下图示例的文字字符串,几经思考过后利用了eval()函数。
decrypted_text2 = eval(decrypted_text)
    decrypted_text3 = decrypted_text2.decode('utf-8')
    print("Successfully decrypted, the contents of the file are:")
    print(decrypted_text3)

实验结果

在结果测试中再对代码不断进行细微修改,同时增加部分提示文字,使得运行结果更为美观。

  • 客户端传输文件

  • 客户端循环聊天

  • 服务端相关应答

  • 产生的相关文件

3. 实验过程中遇到的问题和解决过程

  • 问题1:码云操作这回真异常惨烈,先是commit的时候文件信息没有更改,想要更改但操作半天被雷声吓得脑袋一抽清空了本地仓库,把所有的绿色文件都给删掉了,难以恢复。重新写完想要push的时候,发现之前的版本和现在的有冲突,push不了。上网查攻略按部就班一无所获。
    问题1解决方案:暴力破解,重新建了一个分支,大家不要学我。以后细心一点,别熬夜熬到头昏眼花了。

  • 问题2:客户端提示文字输出有误,但运行结果正确。出现打印问题。或者是解密过程中出现错误提示。

    问题2解决方案:关闭相关端口,再重新测试。

  • 问题3:密文或者其他产生文件中被输入相关提示字眼产生的错误

    问题3的解决方案:此种问题出现的原因是服务端和客户端在文件传输的过程中有略微的时间差。只要在传送提示字眼之前加上time.sleep(1)即可。

  • 问题4:解密过程中出现如图相关问题。

    问题4的解决方案:在open中加入errors='ignore',忽略错误。

其他(感悟、思考等)

  • 一个程序的产生要经历不断地修改。
  • 在开始编写程序之前需要把基础知识掌握牢固,在进行一个函数的使用时,要把相关要求规则铭记于心,不然很容易出现各种各样的错误并且难以达到自己理想的程序状态。
  • 编写程序的时候可以采用自顶向下、逐步求精,在逐步求精时可以不断的自底向上进行修正。
  • 编写程序就是不断地起起落落,需要耐心、细心。

参考资料

 posted on 2020-05-17 14:31  20191113林紫欣  阅读(326)  评论(0编辑  收藏  举报