计算机网络实训之简单文件传输系统器


只能说计算机网络实训那一周我是真的难受,不仅仅有我自己的实训任务还有别人的,自己这个任务一实际上是第一天就写完了的,大部分的时间是在做网络的一些内容,以及同组其他成员的可视化。


[TOC]

简单文件传输系统器

简介

本文件传输系统为一款实现内网文件传输的类FTP协议软件,由Python语言开发。客户端程序与服务器端程序分别运行局域网内不同主机上,实现客户端与服务器端的文件传输功能。

服务器界面:

图1-1 文件传输系统服务端
该界面包括开始等待下载按钮和选择共享文件夹按钮。

本程序是一款通过服务器客户端的方式实现一个类FTP协议下载功能的应用软件。

图1-2 共享文件夹选择页面
点击选择共享文件夹按钮后,路径选择界面,选择文件夹作为共享文件夹,客户端可以自由获取该文件夹下的文件。同时服务器端会读取当前共享文件夹路径下的所有文件名,写入文件菜单.txt中。

在选择好共享文件后,点击开始等待下载,程序通过点击,服务器进入被动等待,同时服务器会进行弹窗展示自己的IP地址(内网地址)

图1-3 弹窗显示服务器内网IP地址
在此之后,服务器端将等待客户端与其建立连接,连接建立后将持续等待客户端发送文件下载请求,但同一时间只能与一个客户端建立连接。

客户端界面

首先运行客户端后,会进入客户端页面,如图2-1所示。

图2-1 客户端页面
该界面包括对应的服务器IP地址输入栏,文件输入栏。还有下载按钮和文件存放地址设置按钮。在服务器开启后依次输入输入服务器IP地址,与需要下载的文件,设置文件存放地址,点击下载程序将自动链接服务器,下载需下载的文件。

执行程序时,应先下载文件菜单.txt文件,以获悉共享文件夹下的内容。

当文件完成下载时会进行弹窗提示,用户可继续通过当前客户端下载服务器共享文件夹下的其他文件。

图2-2文件下载完成后的相应提示
### 开发环境

本项目使用Python语言开发,使用 PyCharm编译器进行开发。使用WxPython库进行可视化界面制作,同时以socket,Json,struct等支持库进行服务器与客户端的链接。

在开发过程中,客户端程序与服务器端程序分开进行开发。

使用注意事项

  1. 在使用时请在服务器地址下放置需要客户端下载的内容

  2. 由于使用支持库socket的限制,本程序只能实现在同一子网下不同主机间的通信,无法实现对内网的穿透实现外网通信。

  3. 由于windows计算机防火墙的存在,在设计传输程序的过程中,一直选用8000端口作为通信端口,同时手动开启服务器该端口号,只有服务器固定端口号打开时程序才能顺利运行。

  4. 客户端输入IP地址应为局域网内的内网地址,即程序运行过程中服务器端提示地址(原因见注意事项1)。

  5. 在实际应用时,进行了一些简单的过滤,避免客户端通过简单注入即可危害服务器信息安全(读取到不应读取的内容)。

服务器代码:

# -*- coding:utf-8 -*-
import struct  # 制作报头的模块
import json   # 转换数据格式(序列化)
import wx.lib.agw.hyperlink as lib_hyperlink
import wx
import os
import socket


def getIP():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    ip=s.getsockname()[0]
    return ip


class Frame(wx.Frame):
    def __init__(self):
        self.ip=getIP()
        print(self.ip)
        wx.Frame.__init__(self, None, title='简单文件传输器(服务器)', size=(360, 300),name='frame',style=541072384)
        self.qdck = wx.Panel(self)
        self.Centre()
        self.an1 = wx.Button(self.qdck,size=(100, 46),pos=(190, 115),label='开始等待下载',name='button')
        self.an1.SetAuthNeeded(True)
        self.an1.Bind(wx.EVT_BUTTON,self.an1_anbdj)
        self.bq1 = wx.StaticText(self.qdck, size=(150, 30), pos=(100, 30), label='简单文件传输器', name='staticText',
                                 style=2321)
        bq1_字体 = wx.Font(16, 74, 90, 700, False, 'Microsoft YaHei UI', 28)
        self.bq1.SetFont(bq1_字体)
        self.port=8000
        # 设置默认端口号为8848 钛合金手机
        self.cjljkL1 = lib_hyperlink.HyperLinkCtrl(self.qdck,size=(50, 28),pos=(150, 200),name='staticText',
                                                   label='使用说明',URL='https://im-so-scared-2.gitee.io/shier_jinghuang/')
        self.cjljkL1.SetToolTip(wx.ToolTip('使用说明'))
        cjljkL1_字体 = wx.Font(9,70,90,400,True,'Microsoft YaHei UI',-1)
        self.cjljkL1.SetFont(cjljkL1_字体)
        self.cjljkL1.SetForegroundColour((0, 0, 255, 255))
        self.an2 = wx.Button(self.qdck, size=(120, 46), pos=(50,115), label='选择共享文件夹', name='button')
        self.an2.Bind(wx.EVT_BUTTON, self.an2_anbdj)
        self.an2.SetAuthNeeded(True)
        self.share_dir=""

    def an1_anbdj(self,event):
        toastone = wx.MessageDialog(None, "服务器的IP地址为:"+self.ip+"!", "IP地址显示", wx.YES_DEFAULT | wx.ICON_QUESTION)
        if toastone.ShowModal() == wx.ID_YES:
            # 如果点击了提示框的确定按钮
            toastone.Destroy()
            # 则关闭提示框
        phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        phone.bind((self.ip, self.port))
        phone.listen(5)
        while True:
            conn, client = phone.accept()
            while True:
                    # 收命令
                    res = conn.recv(8096)
                    # 解析命令、提取相应的命令参数
                    cmds = res.decode('gbk').split()  # ['get','a.txt']  split变列表格式
                    filename = cmds[1]
                    # 已读的方式打开文件,读取文件内容发送给客户端
                    # 第一步:制作固定长度的报头
                    header_dic = {
                        'filename': filename,
                        'file_size': os.path.getsize(r'%s/%s' % (self.share_dir, filename))
                        # 这里把文件的名字和地址结合在一起得到文件长度
                    }  # 字典方便储存数据
                    header_json = json.dumps(header_dic)  # 把字典转换成js格式(字符串类型)
                    header_bytes = header_json.encode('gbk')
                    # 第二步:先发送报头的长度
                    conn.send(struct.pack('i', len(header_bytes)))
                    # 第三步:再发报头
                    conn.send(header_bytes)
                    # 第四步:发送真实数据
                    with open('%s/%s' % (self.share_dir, filename), 'rb') as f:
                        for line in f:
                            # 这样一行一行发比直接发送f.read节省内存空间
                            conn.send(line)
                    conn.close()
                    # except ConnectionResetError as err:
                    # break
                    break
        phone.close()

    def bjk1_axEnterj(self,event):
        # print('bjk1,按下Enter键')
        # print(self.share_dir)
        pass

    def an2_anbdj(self,event):
        # print('an2,按钮被单击')
        dialog = wx.DirDialog(self.qdck, message="打开文件夹", style=wx.DD_CHANGE_DIR)
        if dialog.ShowModal() == wx.ID_OK:
            filename = dialog.GetPath()
            print(filename)
            self.share_dir=str(filename)
            img_list = os.listdir(self.share_dir)
            print(img_list)
            file = open('文件菜单.txt', 'w')  # 创建文件,权限为写入
            for img_name in img_list:
                file.write(img_name + '\n')
            toastone = wx.MessageDialog(None, "文件菜单已创建", "软件提示", wx.YES_DEFAULT | wx.ICON_QUESTION)
            if toastone.ShowModal() == wx.ID_YES:
                # 如果点击了提示框的确定按钮
                toastone.Destroy()
                # 则关闭提示框
            dialog.Destroy()

class myApp(wx.App):
    def OnInit(self):
        self.frame = Frame()
        self.frame.Show(True)
        return True

if __name__ == '__main__':
    app = myApp()
    app.MainLoop()

客户端:

# -*- coding:utf-8 -*-
import wx.lib.agw.hyperlink as lib_hyperlink
import wx
import socket
import struct  # 制作报头的模块
import json   # 转换数据格式(序列化)


class Frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title='简单文件传输器(客户端)', size=(800, 300),name='frame',style=541072384)
        self.qdck = wx.Panel(self)
        self.Centre()
        self.an1 = wx.Button(self.qdck,size=(100, 46),pos=(220, 135),label='下载',name='button')
        self.an1.SetAuthNeeded(True)
        self.an1.Bind(wx.EVT_BUTTON,self.an1_anbdj)
        self.bjk1 = wx.TextCtrl(self.qdck,size=(400, 30),pos=(300, 30),value='',name='text',style=4096)
        self.bjk1.SetMaxLength(50)
        self.bq1 = wx.StaticText(self.qdck,size=(265, 30),pos=(30, 30),label='请输入服务器IP地址:',name='staticText',style=2321)
        bq1_字体 = wx.Font(16,74,90,700,False,'Microsoft YaHei UI',28)
        self.bq1.SetFont(bq1_字体)
        self.bq2 = wx.StaticText(self.qdck, size=(265, 30), pos=(30, 70), label='请输入需要下载的文件:', name='staticText',
                                 style=2321)
        self.bq2.SetFont(bq1_字体)
        self.bjk2 = wx.TextCtrl(self.qdck, size=(400, 30), pos=(300, 70), value='', name='text', style=4096)
        self.bjk2.SetMaxLength(50)
        self.cjljkL1 = lib_hyperlink.HyperLinkCtrl(self.qdck,size=(100, 28),pos=(380, 200),name='staticText',
                                                   label='使用说明',URL='https://im-so-scared-2.gitee.io/shier_jinghuang/')
        self.cjljkL1.SetToolTip(wx.ToolTip('使用说明'))
        cjljkL1_字体 = wx.Font(9,70,90,400,True,'Microsoft YaHei UI',-1)
        self.cjljkL1.SetFont(cjljkL1_字体)
        self.cjljkL1.SetForegroundColour((0, 0, 255, 255))
        self.an2 = wx.Button(self.qdck, size=(120, 46), pos=(450, 135), label='选择文件存放地址', name='button')
        self.an2.Bind(wx.EVT_BUTTON, self.an2_anbdj)
        self.an2.SetAuthNeeded(True)
        self.share_dir = ""

    def an2_anbdj(self,event):
        # print('an2,按钮被单击')
        dialog = wx.DirDialog(self.qdck, message="请选择文件存放地址", style=wx.DD_CHANGE_DIR)
        if dialog.ShowModal() == wx.ID_OK:
            filename = dialog.GetPath()
            print(filename)
            self.share_dir = str(filename)
            dialog.Destroy()

    def an1_anbdj(self,event):
        self.ip=self.bjk1.GetValue()
        self.filename=self.bjk2.GetValue()
        print(self.filename)
        phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        print(self.ip)
        cmd = "get " + self.filename
        print(cmd)
        phone.connect((self.ip, 8000))
        print("------开始咨询客服---------")
        while True:
            # 发命令
            # get a.txt
            if cmd=='a':
                break
            phone.send(cmd.encode("gbk"))
            # 已写的方式打开一个新文件,接收服务器发来的文件的内容写入客户端的新文件
            # 第一步:先收取报头的长度
            obj = phone.recv(4)
            header_size = struct.unpack('i', obj)[0]
            # 第二步:再收报头
            header_bytes = phone.recv(header_size)
            # 第三步:从报头中间解析出对真是数据的描述信息
            header_json = header_bytes.decode('gbk')
            header_dic = json.loads(header_json)
            print(header_dic)
            total_size = header_dic['file_size']
            filename = header_dic['filename']
            # 第三步:接受真实的数据
            with open('%s/%s' % (self.share_dir, filename), 'wb') as f:  # 在自己的电脑中找一个地址打开一个同类型的文件准备接收数据
                recv_size = 0
                while recv_size < total_size:
                    line = phone.recv(1024)
                    f.write(line)
                    recv_size += len(line)
                    print('总大小:%s   已下载大小:%s' % (total_size, recv_size))  # 显示下载进度
            cmd='a'
            toastone = wx.MessageDialog(None, "文件下载完成!", "信息提示", wx.YES_DEFAULT | wx.ICON_QUESTION)
            if toastone.ShowModal() == wx.ID_YES:
                # 如果点击了提示框的确定按钮
                toastone.Destroy()
                # 则关闭提示框
        phone.close()


class myApp(wx.App):
    def OnInit(self):
        self.frame = Frame()
        self.frame.Show(True)
        return True


if __name__ == '__main__':
    app = myApp()
    app.MainLoop()

写在最后:

这个简单文件传输器是真的简单,基本上还是点对点的下载,只是我想尝试模拟FTP协议,但是发现如果用py开启这个协议,实验完全就是协议的工作了,这个文件传输器就变成了协议开启器。

在权衡之后,我采用了在服务器端读取设置的共享文件夹的文件名,并将其写成一个文件,客户端通过下载该文件可以实现对其他文件的可知。

当我在第二天一开始,把这个代码写完时,我以为我接下来的任务会很轻松然后…


文章作者: 十二惊惶
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 十二惊惶 !
  目录