网络编程之UDP编程与FTP作业

1> 基本通信

        不需要像TCP一样先建立连接,直接创建socket对象,绑定IP和端口,就能收发数据了,客户端就更简单,
    创建对象就行,如下

    为什么是recvfrom,sendto?
        因为通讯不基于连接,服务端需要知道是谁发来的消息,才能回复,所以recvfrom得到的对象包含 (数据和 
    客户端IP与端口) ,同理,sendto里面也包含数据和 对端IP和端口,因为没有连接,所以需要指明这个包是发往
    哪里    而是之前的TCP协议是建立好了通道再进行数据传送,所以不必IP和端口信息,因为通过通道就是发往对
    端的,很明确的知道对端是谁。

2>数据包协议,为何没有粘包现象

    可以看到,客户端发了5 个字节的hello,但是服务端收取上限为2,windows下报错了,说缓冲区比数据包小,
linux下直接收取2个字节,其他的丢弃UDP没有通道的概念,自然不有存放残留数据的机制,他每个包都是独立
封装并发送,所以不会有粘包现象,所以它也叫数据报协议。
    所以它的收发 也是 一对一的。

3>并且你可以发送空,因为就算你内容为空,UDP自带头部信息(对端IP和端口等),实际上这条消息(这个数据报)并
    不为空(因为包含头部大小),所以操作系统是会帮你发送的。

4>应用场景

    绝大多数的应用都是基于TCP的,但也有些UDP通信的场景或应用,如QQ,DNS域名解析(输入网址,DNS服务器
    会转换成IP+端口再访问,这个时候对数据的安全性啥的要求并不太高,  请求断就断了,我再发一次就是,但是
    追求速度,速度正是UDP的长处)等类似一些这样的场景。


二>基于TCP实现文件上传和下载(面向对象版)

服务端

class Ftp_server():
    '''
    服务端的文件操作类,包含上传,下载,运行服务等功能
    '''
    def __init__(self, addr, port):

        self.addr = addr
        self.port = port
        self.socket_obj = socket.socket()
        self.serfile_dir = conf['server']['file_dir']
        self.socket_obj.bind((self.addr, self.port))
        self.socket_obj.listen(5)

    def wait_client_conn(self):
        '''
        服务端启动函数,这里建立连接,并且等待客户端的指令,反射至对应的函数
        '''

        while True:
            print('文件服务器已经启动,等待客户端接入...')
            conn, data = self.socket_obj.accept()
            while True:
                try:
                    cmd = conn.recv(1024)
                    if not cmd:continue
                    operation = cmd.decode().split()[0]
                    file_name = cmd.decode().split()[1]
                    if hasattr(self, operation):
                        func = getattr(self, operation)
                        func(conn, file_name)
                except ConnectionResetError:
                    print('客户端已关闭连接!')
                    break
            conn.close()

    def download(self, obj, file_name):

        # 制作消息头
        file = self.serfile_dir+file_name
        file_size = os.path.getsize(file)
        with open(file, 'rb') as f1:
            file_data = f1.read()
        sec_value = hashlib.sha1(file_data).hexdigest()
        head_dic = {'sec_value':sec_value, 'file_size':file_size}
        head_str = json.dumps(head_dic)
        head_size = len(head_str)
        head_size_bytes = struct.pack('i', head_size)
        print('头消息制作完毕')

        # 发送消息头大小
        obj.send(head_size_bytes)
        print('头消息大小:%s'%head_size)

        # 发送消息头数据
        obj.send(head_str.encode())
        print('头消息:%s'%head_str)

        # 发送真实数据
        with open(file, 'rb') as f:
            for line in f:
                obj.send(line)
        print('文件发送完毕')

    def upload(self, obj, b=0):

        # 接收数据头大小
        head_size_bytes = obj.recv(4)
        head_size = struct.unpack('i', head_size_bytes)
        print('头部数据大小:%s'%head_size)

        # 根据头大小接收头数据
        head_data_bytes = obj.recv(head_size[0])
        head_data_str = head_data_bytes.decode()
        print('头部文件内容:%s'%head_data_str)
        head_data_dic = json.loads(head_data_str)

        # 根据头数据中的文件大小接收文件
        file_size = head_data_dic['file_size']
        file_name = head_data_dic['file_name']
        file = self.serfile_dir+file_name
        recieved_size = 0
        with open(file, 'wb') as f:
            while recieved_size < file_size:
                res_data = obj.recv(8096)
                f.write(res_data)
                recieved_size += len(res_data)

        print('文件下载完成!')

server = Ftp_server('127.0.0.1', 8080)
server.wait_client_conn()
客户端

class Ftp_client():
    '''
    客户端的文件操作类,包含上传,下载,查看文件,查看空间等功能
    '''
    def __init__(self, addr, port, username):

        self.addr = addr
        self.port = port
        self.max_space = int(conf[username]['max_space'])
        self.home_dir = conf[username]['home_dir']
        self.serfile_dir = conf['server']['file_dir']
        self.socket_obj = socket.socket()
        self.socket_obj.connect((self.addr, self.port))

    def upload_to_server(self, file_name):
        '''
        上传功能,1、制作消息头,2、发送头大小,3、发送头信息,4、发送真实数据
        '''

        if file_name in os.listdir(self.home_dir):  # 判断家目录是否有该文件

            # 制作上传指令并发送
            download_cmd = 'upload %s'%file_name
            self.socket_obj.send(download_cmd.encode())
            print('正请求服务端接收文件,等待其响应。。。')
            time.sleep(1)   # 因为这里基于数据包还要多一条指令,直接休眠1秒避免粘包

            # 制作消息头
            file = self.home_dir+file_name
            file_size = os.path.getsize(file)
            head_dic = {'file_size':file_size, 'file_name':file_name}
            head_str = json.dumps(head_dic)
            head_size = len(head_str)
            head_size_bytes = struct.pack('i', head_size)
            print('头消息制作完毕')

            # 发送消息头大小
            self.socket_obj.send(head_size_bytes)
            print('头消息大小:%s'%head_size)

            # 发送消息头数据
            self.socket_obj.send(head_str.encode())
            print('头消息:%s'%head_str)

            # 发送真实数据
            with open(file, 'rb') as f:
                for line in f:
                    self.socket_obj.send(line)
            print('文件上传完毕')

        else:
            print('文件名错误!')

    def download_from_server(self, file_name):
        '''
        下载功能,1、发送下载指令,2、接收头大小,3、接收头信息,4、接收真实数据,5、验证文件一致性
        '''

        if file_name in os.listdir(self.serfile_dir):    # 判断服务端是否有该文件
            file_size = os.path.getsize(self.serfile_dir+file_name)
            if file_size <= (int(self.max_space)-self.used_size):   # 判断空间是否足够

                # 制作下载指令并发送
                download_cmd = 'download %s'%file_name
                self.socket_obj.send(download_cmd.encode())
                # print('发送下载指令')

                # 接收数据头大小
                head_size_bytes = self.socket_obj.recv(4)
                head_size = struct.unpack('i', head_size_bytes)
                # print('头部数据大小:%s'%head_size)

                # 根据头大小接收头数据
                head_data_bytes = self.socket_obj.recv(head_size[0])
                head_data_str = head_data_bytes.decode()
                # print('头部文件内容:%s'%head_data_str)
                head_data_dic = json.loads(head_data_str)

                # 根据头数据中的文件大小接收文件
                file_size = head_data_dic['file_size']
                file = self.home_dir+file_name
                recieved_size = 0
                with open(file, 'wb') as f:
                    while recieved_size < file_size:
                        res_data = self.socket_obj.recv(8096)
                        f.write(res_data)
                        recieved_size += len(res_data)
                        self.progressbar(recieved_size*10/file_size,10)
                # print('文件下载完成!')

                # 根据头数据中的加密串验证文件是否一致
                sec_value = head_data_dic['sec_value']
                with open(file, 'rb') as f1:
                    file_data = f1.read()
                file_sec = hashlib.sha1(file_data).hexdigest()
                # print(os.path.getsize(file))
                # print(file_sec)

                if sec_value == file_sec:
                    print('文件对比完成,两文件一致!')

            else:
                print('家目录存储空间不足。')
        else:
            print('文件名错误!')
@staticmethod
def progressbar(cur,total):
    '''
    进度条函数
    '''

    percent = '{:.2%}'.format(cur / total)
    sys.stdout.write('\r')
    sys.stdout.write('[%-50s] %s' % ( '#' * int(math.floor(cur * 50 /total)),percent))
    sys.stdout.flush()
    if cur == total:
        sys.stdout.write('\n')
程序还有很大的可扩展性,如 客户端这边需要查看服务端的文件,也可以send一个'check XXX'的命令过去,服务端定义
相应的 check()函数就行,其他的地方都基本不必改动,


最后一个进度条函数,直接从网上找的,没具体研究怎么用,只是发现刚好能套进去,就引入了(实现下载文件有进度条

显示功能)。


猜你喜欢

转载自blog.csdn.net/huangql517/article/details/79576675