FTP上传下载文件的代码(简易版)
import socket import struct import json sk = socket.socket() # buffer = 4096 # 当双方的这个接收发送的大小比较大的时候,就像这个4096,就会丢数据,这个等我查一下再告诉大家,改小了就ok的,在linux上也是ok的。 buffer = 1024 #每次接收数据的大小 sk.bind(('127.0.0.1',8090)) sk.listen() conn,addr = sk.accept() #接收 head_len = conn.recv(4) head_len = struct.unpack('i',head_len)[0] #解包 json_head = conn.recv(head_len).decode('utf-8') #反序列化 head = json.loads(json_head) filesize = head['filesize'] with open(head['filename'],'wb') as f: while filesize: if filesize >= buffer: #>=是因为如果刚好等于的情况出现也是可以的。 content = conn.recv(buffer) f.write(content) filesize -= buffer else: content = conn.recv(buffer) f.write(content) break conn.close() sk.close() tcp_server.py
import os import json import socket import struct sk = socket.socket() sk.connect(('127.0.0.1',8090)) buffer = 1024 #读取文件的时候,每次读取的大小 head = { 'filepath':r'D:打包程序', #需要下载的文件路径,也就是文件所在的文件夹 'filename':'xxx.mp4', #改成上面filepath下的一个文件 'filesize':None, } file_path = os.path.join(head['filepath'],head['filename']) filesize = os.path.getsize(file_path) head['filesize'] = filesize # json_head = json.dumps(head,ensure_ascii=False) #字典转换成字符串 json_head = json.dumps(head) #字典转换成字符串 bytes_head = json_head.encode('utf-8') #字符串转换成bytes类型 print(json_head) print(bytes_head) #计算head的长度,因为接收端先接收我们自己定制的报头,对吧 head_len = len(bytes_head) #报头长度 pack_len = struct.pack('i',head_len) print(head_len) print(pack_len) sk.send(pack_len) #先发送报头长度 sk.send(bytes_head) #再发送bytes类型的报头 #即便是视频文件,也是可以按行来读取的,也可以readline,也可以for循环,但是读取出来的数据大小就不固定了,影响效率,有可能读的比较小,也可能很大,像视频文件一般都是一行的二进制字节流。 #所有我们可以用read,设定一个一次读取内容的大小,一边读一边发,一边收一边写 with open(file_path,'rb') as f: while filesize: if filesize >= buffer: #>=是因为如果刚好等于的情况出现也是可以的。 content = f.read(buffer) #每次读取出来的内容 sk.send(content) filesize -= buffer #每次减去读取的大小 else: #那么说明剩余的不够一次读取的大小了,那么只要把剩下的读取出来发送过去就行了 content = f.read(filesize) sk.send(content) break sk.close() tcp_client.py
FTP上传下载文件的代码(升级版)(注:咱们学完网络编程就留FTP作业,这个代码可以参考,当你用函数的方式写完之后,再用面向对象进行改版却没有思路的时候再来看,别骗自己昂~~)
import socket import struct import json import subprocess import os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 server_dir='file_upload' def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print('from client ',self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack('i', head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={'cmd':'put','filename':'a.txt','filesize':123123} cmd=head_dic['cmd'] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break def put(self,args): file_path=os.path.normpath(os.path.join( self.server_dir, args['filename'] )) filesize=args['filesize'] recv_size=0 print('----->',file_path) with open(file_path,'wb') as f: while recv_size < filesize: recv_data=self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print('recvsize:%s filesize:%s' %(recv_size,filesize)) tcpserver1=MYTCPServer(('127.0.0.1',8080)) tcpserver1.run() server.py
import socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run() client.py
ok~今天的内容就到这里,大家别着急,稳扎稳打,把上面学习的这些内容在好好理解理解,写写代码练习练习~~~
====================================================================================================================
十一 验证客户端的链接合法性
首先,我们来探讨一下,什么叫验证合法性, 举个例子:有一天,我开了一个socket服务端,只想让咱们这个班的同学使用,但是有一天,隔壁班的同学过来问了一下我开的这个服务端的ip和端口,然后他是不是就可以去连接我了啊,那怎么办,我是不是不想让他连接我啊,我需要验证一下你的身份,这就是验证连接的合法性,再举个例子,就像我们上面说的你的windows系统是不是连接微软的时间服务器来获取时间的啊,你的mac能到人家微软去获取时间吗,你愿意,人家微软还不愿意呢,对吧,那这时候,你每次连接我来获取时间的时候,我是不是就要验证你的身份啊,也就是你要带着你的系统信息,我要判断你是不是我微软的windows,对吧,如果是mac,我是不是不让你连啊,这就是连接合法性。如果验证你的连接是合法的,那么如果我还要对你的身份进行验证的需求,也就是要验证用户名和密码,那么我们还需要进行身份认证。连接认证>>身份认证>>ok你可以玩了。
好大致描述相信大家基本理解了,如果这还没有理解,那么同学,我要哭晕在厕所了。
如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现,直接看代码!(SSL,我们都)
from socket import * import hmac,os secret_key=b'Jedan has a big key!' def conn_auth(conn): ''' 认证客户端链接 :param conn: :return: ''' print('开始验证新链接的合法性') msg=os.urandom(32)#生成一个32字节的随机字符串 conn.sendall(msg) h=hmac.new(secret_key,msg) digest=h.digest() respone=conn.recv(len(digest)) return hmac.compare_digest(respone,digest) def data_handler(conn,bufsize=1024): if not conn_auth(conn): print('该链接不合法,关闭') conn.close() return print('链接合法,开始通信') while True: data=conn.recv(bufsize) if not data:break conn.sendall(data.upper()) def server_handler(ip_port,bufsize,backlog=5): ''' 只处理链接 :param ip_port: :return: ''' tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(backlog) while True: conn,addr=tcp_socket_server.accept() print('新连接[%s:%s]' %(addr[0],addr[1])) data_handler(conn,bufsize) if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 server_handler(ip_port,bufsize) 服务端
from socket import * import hmac,os secret_key=b'Jedan has a big key!' def conn_auth(conn): ''' 验证客户端到服务器的链接 :param conn: :return: ''' msg=conn.recv(32) h=hmac.new(secret_key,msg) digest=h.digest() conn.sendall(digest) def client_handler(ip_port,bufsize=1024): tcp_socket_client=socket(AF_INET,SOCK_STREAM) tcp_socket_client.connect(ip_port) conn_auth(tcp_socket_client) while True: data=input('>>: ').strip() if not data:continue if data == 'quit':break tcp_socket_client.sendall(data.encode('utf-8')) respone=tcp_socket_client.recv(bufsize) print(respone.decode('utf-8')) tcp_socket_client.close() if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 client_handler(ip_port,bufsize) 客户端
介绍代码中使用的两个方法:
1、os.urandom(n)
其中os.urandom(n) 是一种bytes类型的随机生成n个字节字符串的方法,而且每次生成的值都不相同。再加上md5等加密的处理,就能够成内容不同长度相同的字符串了。
os.urandom(n)函数在python官方文档中做出了这样的解释 函数定位: Return a string of n random bytes suitable for cryptographic use. 意思就是,返回一个有n个byte那么长的一个string,然后很适合用于加密。 然后这个函数,在文档中,被归结于os这个库的Miscellaneous Functions,意思是不同种类的函数(也可以说是混种函数) 原因是: This function returns random bytes from an OS-specific randomness source. (函数返回的随机字节是根据不同的操作系统特定的随机函数资源。即,这个函数是调用OS内部自带的随机函数的。有特异性) os.urandom官方解释
使用方法:
import os from hashlib import md5 for i in range(10): print md5(os.urandom(24)).hexdigest()
2、hmac: 我们完全可以用hashlib来实现,但是学个新的吗,没什么不好的,这个操作更方便一些。
Python自带的hmac模块实现了标准的Hmac算法,我们首先需要准备待计算的原始消息message,随机key,哈希算法,这里采用MD5,使用hmac的代码如下:
import hmac message = b'Hello world' key = b'secret' h = hmac.new(key,message,digestmod='MD5') print(h.hexdigest())
比较两个密文是否相同,可以用hmac.compare_digest(密文、密文),然会True或者False。
可见使用hmac和普通hash算法非常类似。hmac输出的长度和原始哈希算法的长度一致。需要注意传入的key和message都是bytes
类型,str
类型需要首先编码为bytes
。
def hmac_md5(key, s): return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest() class User(object): def __init__(self, username, password): self.username = username self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)]) self.password = hmac_md5(self.key, password)
为什么要讲socketserver?我们之前写的tcp协议的socket是不是一次只能和一个客户端通信,如果用socketserver可以实现和多个客户端通信。它是在socket的基础上进行了一层封装,也就是说底层还是调用的socket,在py2.7里面叫做SocketServer也就是大写了两个S,在py3里面就小写了。后面我们要写的FTP作业,需要用它来实现并发,也就是同时可以和多个客户端进行通信,多个人可以同时进行上传下载等。
那么我们先看socketserver怎么用呢,然后在分析,先看下面的代码
import socketserver #1、引入模块 class MyServer(socketserver.BaseRequestHandler): #2、自己写一个类,类名自己随便定义,然后继承socketserver这个模块里面的BaseRequestHandler这个类 def handle(self): #3、写一个handle方法,必须叫这个名字 #self.request #6、self.request 相当于一个conn self.request.recv(1024) #7、收消息 msg = '亲,学会了吗' self.request.send(bytes(msg,encoding='utf-8')) #8、发消息 self.request.close() #9、关闭连接
# 拿到了我们对每个客户端的管道,那么我们自己在这个方法里面的就写我们接收消息发送消息的逻辑就可以了 pass if __name__ == '__mian__': #thread 线程,现在只需要简单理解线程,别着急,后面很快就会讲到啦,看下面的图 server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer)#4、使用socketserver的ThreadingTCPServer这个类,将IP和端口的元祖传进去,还需要将上面咱们自己定义的类传进去,得到一个对象,相当于我们通过它进行了bind、listen server.serve_forever() #5、使用我们上面这个类的对象来执行serve_forever()方法,他的作用就是说,我的服务一直开启着,就像京东一样,不能关闭网站,对吧,并且serve_forever()帮我们进行了accept #注意: #有socketserver 那么有socketclient的吗? #当然不会有,我要作为客户去访问京东的时候,京东帮我也客户端了吗,客户端是不是在我们自己的电脑啊,并且socketserver对客户端没有太高的要求,只需要自己写一些socket就行了。
ThreadingTCPServer,多线程,简单解释:看图
通过上面的代码,我们来分析socket的源码:(大家还记得面向对象的继承吗,来,实战的时候来啦)
在整个socketserver这个模块中,其实就干了两件事情:1、一个是循环建立链接的部分,每个客户链接都可以连接成功 2、一个通讯循环的部分,就是每个客户端链接成功之后,要循环的和客户端进行通信。
看代码中的:server=socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer)
还记得面向对象的继承吗?来,大家自己尝试着看看源码:
查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer 实例化得到server,先找ThreadMinxIn中的__init__方法,发现没有init方法,然后找类ThreadingTCPServer的__init__,在TCPServer中找到,在里面创建了socket对象,进而执行server_bind(相当于bind),server_active(点进去看执行了listen) 找server下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address) 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address) 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找.... 源码分析总结: 基于tcp的socketserver我们自己定义的类中的 self.server即套接字对象 self.request即一个链接 self.client_address即客户端地址 基于udp的socketserver我们自己定义的类中的 self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>) self.client_address即客户端地址
一个完整的sockeserver代码示例:
服务端代码示例:
import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "127.0.0.1", 9999 # 设置allow_reuse_address允许服务器重用地址 socketserver.TCPServer.allow_reuse_address = True # 创建一个server, 将服务地址绑定到127.0.0.1:9999 #server = socketserver.TCPServer((HOST, PORT),Myserver) server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver) # 让server永远运行下去,除非强制停止程序 server.serve_forever() tcp_server.py
客户端代码示例:
import socket HOST, PORT = "127.0.0.1", 9999 data = "hello" # 创建一个socket链接,SOCK_STREAM代表使用TCP协议 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((HOST, PORT)) # 链接到客户端 sock.sendall(bytes(data + " ", "utf-8")) # 向服务端发送数据 received = str(sock.recv(1024), "utf-8")# 从服务端接收数据 print("Sent: {}".format(data)) print("Received: {}".format(received)) tcp_client.py
十三 网络编程的作业
好了同学们,到了这儿,我们的网络编程socket就讲完了,大致就是这些内容,给大家留个作业:(你的努力的成果你自己是看的到的~!)
加粗的是必须要做的,倾斜的是比较有难度的,大家别放松呀。
1. 多用户同时登陆
2. 用户登陆,加密认证
3. 上传/下载文件,保证文件一致性
4. 传输过程中现实进度条
5. 不同用户家目录不同,且只能访问自己的家目录
6. 对用户进行磁盘配额、不同用户配额可不同
7. 用户登陆server后,可在家目录权限下切换子目录
8. 查看当前目录下文件,新建文件夹
9. 删除文件和空文件夹
10. 充分使用面向对象知识
11. 支持断点续传
View Code
2. 用户登陆,加密认证
3. 上传/下载文件,保证文件一致性
4. 传输过程中现实进度条
5. 不同用户家目录不同,且只能访问自己的家目录
6. 对用户进行磁盘配额、不同用户配额可不同
7. 用户登陆server后,可在家目录权限下切换子目录
8. 查看当前目录下文件,新建文件夹
9. 删除文件和空文件夹
10. 充分使用面向对象知识
11. 支持断点续传
简单分析一下实现方式:
1.字符串操作以及打印 —— 实现上传下载的进度条功能
一、 import sys import time for i in range(50): sys.stdout.write('>') sys.stdout.flush() time.sleep(0.2) 二、 #总共接收到的大小和总文件大小的比值: #all_size_len表示当前总共接受的多长的数据,是累计的 #file_size表示文件的总大小 per_cent = round(all_size_len/file_size,2) #将比值做成两位数的小数 #通过 来实现同一行打印,每次打印都回到行首打印 print(' '+ '%s%%'%(str(int(per_cent*100))) + '*'*(int(per_cent*100)),end='') #由于float类型的数据没法通过%s来进行字符串格式化,所以我在这里通过int来转换了一下,并用str转换了一下,后面再拼接上*,这个*的数量根据现在计算出来的比值来确定,就能够出来%3***这样的效果。自行使用上面的sys.stdout来实现一下这个直接print的效果。 打印进度条
2.socketserver —— 实现ftp server端和client端的交互
3.struct模块 —— 自定制报头解决文件上传下载过程中的粘包问题
4.hashlib或者hmac模块 —— 实现文件的一致性校验和用户密文登录
5.os模块 —— 实现目录的切换及查看文件文件夹等功能
6.文件操作 —— 完成上传下载文件及断点续传等功能
看一下流程图:
01 ftp上传简单示例客户端
01 ftp上传简单示例服务端
03 验证合法性连接的客户端
03 验证合法性连接的服务端