python 复习——网络编程——文件上传下载socketserver(并发)解读socketserver源码
Posted 胖虎是只mao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 复习——网络编程——文件上传下载socketserver(并发)解读socketserver源码相关的知识,希望对你有一定的参考价值。
一、文件上传/下载
1、文件上传/下载
学习了socket套接字,我们现在可以写一个文件上传/下载的程序,如下示例:
服务端代码:
import socket
import json
server = socket.socket()
server.bind(('127.0.0.1',8899))
server.listen(5)
while 1:
print("server is working....")
conn,addr = server.accept()
while 1:
dic_json = conn.recv(1024).decode('utf8')
dic = json.loads(dic_json)
print(dic)
operate = dic.get('operate')
file_name = dic.get('file_name')
file_size = dic.get('file_size')
conn.send(b'200')
with open(file_name,'wb') as f:
recv_len = 0
while recv_len < file_size:
line = conn.recv(1024)
recv_len += len(line)
f.write(line)
print('接收完成,接收了%s,发送了%s' % (recv_len,file_size))
print('接收完成')
conn.close()
服务端代码
客户端代码
import socket
import os
import json
client = socket.socket()
client.connect(('127.0.0.1',8899))
while 1:
cmd = input('请输入命令>>>') # 'put a.txt'
operate,file_name = cmd.strip().split()
file_size = os.path.getsize(file_name)
print('文件大小是---',file_size)
dic = {
'operate':operate,
'file_name':file_name,
'file_size':file_size
}
dic_json = json.dumps(dic).encode('utf-8')
client.send(dic_json)
# 确认服务端接收到了文件信息
res = client.recv(1024).decode('utf8')
print(res)
if res == '200':
with open(file_name,'rb') as f:
for line in f:
client.send(line)
else:
print('服务器异常')
客户端代码
分析上边代码,我们发现,client发送上传文件相关信息的字典序列化之后,server又给client发送了一个状态码,然后client才开始上传文件数据,思考一下如果不发送状态码,直接发送文件数据且避免黏包现象发生该怎么写?没错!可以用struct
模块解决,如下示例:
服务端代码:
import socket
import json
import struct
import hashlib
server = socket.socket()
server.bind(('127.0.0.1',8899))
server.listen(5)
while 1:
print("server is working....")
conn,addr = server.accept()
while 1:
dic_json_len_pack = conn.recv(4) # 接收字典字节码长度的pack值
dic_json_len = struct.unpack('i',dic_json_len_pack)[0]
dic_json = conn.recv(dic_json_len).decode('utf8')
dic = json.loads(dic_json)
# b'\\xx\\xx\\xx\\xx{"":"","":"","":"","":""}xxxxxxxxxxxxxxxxxxxxxxxxxx'
operate = dic.get('operate')
file_name = dic.get('file_name')
file_size = dic.get('file_size')
md5 = hashlib.md5()
with open(file_name,'wb') as f:
recv_len = 0
while recv_len < file_size:
line = conn.recv(1024)
recv_len += len(line)
f.write(line)
md5.update(line)
print('接收完成,接收了%s,发送了%s' % (recv_len,file_size))
print('接收完成')
conn.send(b'ok')
print(md5.hexdigest())
client_md5_val = conn.recv(1024).decode('utf8')
if client_md5_val == md5.hexdigest():
conn.send(b'200')
else:
conn.send(b'400')
conn.close()
I
表示4字节无符号整数
客户端代码:
import socket
import os
import json
import struct
import hashlib
client = socket.socket()
client.connect(('127.0.0.1',8899))
while 1:
cmd = input('请输入命令>>>') # 'put a.txt'
operate,file_name = cmd.strip().split()
file_size = os.path.getsize(file_name)
print('文件大小是---',file_size)
dic = {
'operate':operate,
'file_name':file_name,
'file_size':file_size
}
dic_json = json.dumps(dic).encode('utf-8') # 字典序列化后转成字节码
dic_json_len_pack = struct.pack('i',len(dic_json)) # 对字典序列化并转成字节码的长度进行pack
client.send(dic_json_len_pack)
client.send(dic_json) # 发送字典序列化后的字节码
md5 = hashlib.md5()
with open(file_name,'rb') as f:
for line in f:
client.send(line)
md5.update(line)
print(md5.hexdigest())
client.recv(1024).decode('utf8')
md5_val = md5.hexdigest()
client.send(md5_val.encode('utf8'))
response = client.recv(1024).decode('utf8')
if response == '200':
print('文件完整')
else:
print('文件上传失败!')
分析上面的代码,看看是如何避免黏包现象的?client先给server发送字典的长度的pack
包,再发送字典,再发送文件数据,server先接收4字节的pack包,进行unpack后得到字典长度,再根据字典长度接收字典,最后再循环接收文件数据。并且该示例还进行了MD5算法来进行文件一致性校验。
二、socketserver(并发)
通过这两天学习socket套接字,我们发现在写服务端和客户端的时候,在建立连接之前总是要写一些固定的重复代码来,比如socket.socket()(创建套接字对象)、bind()、acecept()等等,学习了socketserver
(并发)之后,就可以少写一些代码,并且实现并发,如下示例:
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
"""
到此已经是等待跟客户端连接的状态
所以从这里写代码正常逻辑代码
conn 用self.request替换即可
"""
# 1 创建socket对象 2 self.socket.bind() 3 self.socket.listen(5)
server = socketserver.ThreadingTCPServer(('127.0.0.1',8800),Myserver)
server.serve_forever() # self.accept()
三、解读python中socketserver源码
结合下图中类的继承关系和类中的方法,分析socketserver代码的执行过程:
a、初始化相关过程:server = socketserver.ThreadingTCPServer(('127.0.0.1',8800),Myserver)
(1)TCPServer
类中的__init__
方法:
TCPServer类中主动执行BaseServer类中的__init__ 方法(把自己创建的Myserver类传参);
创建socket.socket()对象;
server_bind() -- 在TCPServer类中执行了socket.bind(self.server_address)
server_activate() -- 在TCPServer类中执行了socket.listen(5)
(2)BaseServer
类中的__init__
方法:
将参数server_address(ip地址和端口号)赋值给了self.server_address;
将形参RequestHandlerClass
(实参是我们自己创建的Myserver类)赋值给了self.RequestHandlerClass;
b、执行serve_forever
的相关代码:
(1)执行BaseServer类中的serve_forever()
方法:
注意看if ready后边的那句self._handle_request_noblock()
,其他先忽略;
(2)执行BaseServer中的_handle_request_noblock(self)
方法:
看第一个try中request,client_address = self.get_request(),
TCPServer中有get_request
()方法,方法中是return self.socket.accept()
,即等待连接;
再看第二个try中的self.process_request(request,client_address)
(3)连接成功之后拿到了request
(即conn
)和client_address(即addr
)再去执行BaseServer类中的.process_request
方法;
5)执行BaseServer中的finish_request(request,client_address)
方法:
此时还记得RequestHandlerClass
这个参数吗?正是我们执行BaseServer中__init__方法时传过来的自己创建的类Myserver,类() 即实例化一个Myserver对象,并且传了两个参数(conn,addr),但是我们自己写的Myserver类中没有__init__方法,所以执行Myserver父类BaseRequestHandler
类中的__init__
方法,并且带了每个连接的线程的conn和addr:
(6)执行BaseRequestHandler
中的__init__方法:
此时self是Myserver类的对象,所以优先去执行Myserver类中handle方法。
附录:以下是各类的继承关系以及类中成员方法
以上是关于python 复习——网络编程——文件上传下载socketserver(并发)解读socketserver源码的主要内容,如果未能解决你的问题,请参考以下文章