简单的网络文件传输示例,多线程传一个目录!(pickle很实用) « Xiaoxia[PG]
简单的网络文件传输示例,多线程传一个目录!(pickle很实用)
写了很多年C/C++代码了,有时候换了一种语言去写程序,还是发觉自己的代码始终没有摆脱C的风格和思想。
正当我考虑是否用Python里struct的pack和unpack函数来实现C里的结构功能的时候,我想起来有个东西叫做pickle,可以把一个对象编译成字符串然后保存到外部文件。既然可以这样做,那么我想可不可以用它来把一些数据都用pickle来打包,然后把生成的字符串通过网络传输到另外一个程序,然后得到这些数据内容呢?经过了一些测试,我发现是可行的。
在Python的官网文档上说,有个叫cPickle的东西,和pickle实现了一样的功能,只不过cPickle是用C语言实现的,工作起来效率高很多。另外还了解到Python的marshal也可以实现一样的功能,只不过它没有文档化,不是一个公共的库,会随着版本变化而改变,所以不推荐使用。
对pickle使用方法的详细文档在http://docs.python.org/library/pickle.html。
用pickle把一个复杂对象'all'保存到文件,然后重新加载到内存的'all2'>>> import pickle
>>> list_ = [1, 2, 3, 4, 5]
>>> dict_ = {'a':1, 'b':2, 'c':3}
>>> all = [list_, dict_]
>>> pickle.dump(all, file("temp", "wb"))
>>> all2 = pickle.load(file("temp", "rb"))
>>> print all
[[1, 2, 3, 4, 5], {'a': 1, 'c': 3, 'b': 2}]
>>> print all2
[[1, 2, 3, 4, 5], {'a': 1, 'c': 3, 'b': 2}]下面写一个例子,实现一个目录传输功能,即能够把一个目录下的所有文件(包含子目录)传送到网络的另一端。通常可以作为备份或者转移文件来用。该例子包含两个部分,一个是服务器部分,监听一个端口2011,等待客户端发起文件传输的请求。另一个是客户端部分,连接服务器传输用户指定的目录文件。
服务器server.py的代码。每当有一个连接请求,就产生一个新的线程处理文件接收工作。
- # -*- coding: utf8 -*-
- import socket, cPickle, os, threading, struct
- def receive_process(clientfd):
- clientReader = clientfd.makefile("rb")
- while True:
- # 接收数据包的大小
- data = clientReader.read(4)
- if len(data)!=4: break
- dataLength = struct.unpack("I", data)[0]
- data = clientReader.read(dataLength)
- packet = cPickle.loads(data)
- path = packet["path"]
- # 递归创建目录
- parent = os.path.dirname(path)
- if not os.path.exists(parent):
- os.makedirs(parent)
- file(path, "wb").write(packet["data"])
- print "Received file", path
- clientfd.send('\xff')
- def server_process():
- fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # 设置重用标记,这样重启程序的时候不会提示端口被占用。
- fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- fd.bind(("", 2011))
- fd.listen(5)
- while True:
- # 等待客户端连接
- clientfd, addr = fd.accept()
- thread = threading.Thread(target = receive_process, args = (clientfd, ))
- # 设置Daemon属性可以让server结束,则所有子线程必须也退出
- thread.setDaemon(True)
- thread.start()
- if __name__ == '__main__':
- try:
- server_process()
- except KeyboardInterrupt:
- exit()
# -*- coding: utf8 -*- import socket, cPickle, os, threading, struct def receive_process(clientfd): clientReader = clientfd.makefile("rb") while True: # 接收数据包的大小 data = clientReader.read(4) if len(data)!=4: break dataLength = struct.unpack("I", data)[0] data = clientReader.read(dataLength) packet = cPickle.loads(data) path = packet["path"] # 递归创建目录 parent = os.path.dirname(path) if not os.path.exists(parent): os.makedirs(parent) file(path, "wb").write(packet["data"]) print "Received file", path clientfd.send('\xff') def server_process(): fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置重用标记,这样重启程序的时候不会提示端口被占用。 fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) fd.bind(("", 2011)) fd.listen(5) while True: # 等待客户端连接 clientfd, addr = fd.accept() thread = threading.Thread(target = receive_process, args = (clientfd, )) # 设置Daemon属性可以让server结束,则所有子线程必须也退出 thread.setDaemon(True) thread.start() if __name__ == '__main__': try: server_process() except KeyboardInterrupt: exit()
客户端程序client.py的代码。首先遍历一遍要传输的目录文件,添加到队列中,然后启动5个线程,获取队列中的路径进行文件传输。
- # -*- coding: utf8 -*-
- import socket, os, struct, threading, sys, cPickle
- import Queue
- # 使用5个线程发送
- ThreadCount = 5
- sendQueue = Queue.Queue()
- remoteHost = ""
- localPath = ""
- targetPath = ""
- def searchPath():
- fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- fd.connect((remoteHost, 2011))
- while True:
- try:
- path = sendQueue.get_nowait()
- except:
- print "Thread", threading.currentThread().name, "exited"
- return
- print threading.currentThread().name, ":", "Sending", path
- target = os.path.join(targetPath, path[len(localPath):].lstrip("/"))
- packet = {"path" : target, "data" : file(path, "rb").read() }
- data = cPickle.dumps(packet)
- fd.send(struct.pack("I", len(data)))
- fd.send(data)
- replyCode = fd.recv(1)
- if replyCode[0] != '\xff':
- print "Failed to send file", path
- if __name__ == "__main__":
- if len(sys.argv)!=4:
- print "Usage: senddir SourcePath TargetPath IP"
- exit()
- localPath, targetPath, remoteHost = sys.argv[1:]
- if os.path.exists(localPath):
- # 枚举目录文件,包含子目录
- for parent,dirs,files in os.walk(localPath):
- for f in files: #把每个文件放入发送队列
- sendQueue.put(os.path.join(parent, f))
- print "Found", sendQueue.qsize(), "files!"
- for i in range(ThreadCount):
- threading.Thread(target = searchPath).start()
- else:
- print "File not found:", path
# -*- coding: utf8 -*- import socket, os, struct, threading, sys, cPickle import Queue # 使用5个线程发送 ThreadCount = 5 sendQueue = Queue.Queue() remoteHost = "" localPath = "" targetPath = "" def searchPath(): fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) fd.connect((remoteHost, 2011)) while True: try: path = sendQueue.get_nowait() except: print "Thread", threading.currentThread().name, "exited" return print threading.currentThread().name, ":", "Sending", path target = os.path.join(targetPath, path[len(localPath):].lstrip("/")) packet = {"path" : target, "data" : file(path, "rb").read() } data = cPickle.dumps(packet) fd.send(struct.pack("I", len(data))) fd.send(data) replyCode = fd.recv(1) if replyCode[0] != '\xff': print "Failed to send file", path if __name__ == "__main__": if len(sys.argv)!=4: print "Usage: senddir SourcePath TargetPath IP" exit() localPath, targetPath, remoteHost = sys.argv[1:] if os.path.exists(localPath): # 枚举目录文件,包含子目录 for parent,dirs,files in os.walk(localPath): for f in files: #把每个文件放入发送队列 sendQueue.put(os.path.join(parent, f)) print "Found", sendQueue.qsize(), "files!" for i in range(ThreadCount): threading.Thread(target = searchPath).start() else: print "File not found:", path
测试效果
发送方效果图:
接收方效果图: