一.文件上传
学习了socket套接字,我们现在可以写一个文件上传的程序,如下示例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import struct server = socket.socket() #创建socket server.bind(('127.0.0.1', 8000)) #绑定IP端口 server.listen(5) #开始监听,设置client最大的能等待接数 while True: print("服务器等待连接...") conn, addr = server.accept() #conn代表客户端socket对象;addr是客户端Ip地址 while True: try: # 接收文件名长度: header_pack = conn.recv(4) # 接收压缩的4个字节(文件名的长度) data_length = struct.unpack('i', header_pack)[0] # 解压接收到的4个字节得到文件名的长度 # print("文件名长度",data_length) # 取出文件名: ret = conn.recv(data_length) # 根据文件名长度取文件名 name = ret.decode('utf-8') # 接收文件大小: size = conn.recv(4) # 接收压缩的4个字节(文件的大小) file_size = struct.unpack('i', size)[0] # 解压接收到的4个字节得到文件的大小 print("接收文件:%s,大小为%s字节" % (name, file_size)) recv_data_length = 0 # 计数,初始为0 已接收数据的长度 #循环接收 while recv_data_length < file_size: data = conn.recv(1024) # with open(name,mode='a',encoding='utf-8') as f1: # f1.write(data.decode('utf-8')) with open(name, mode='ab') as f1: f1.write(data) recv_data_length += len(data) # 计数每次加1024 print("接收完成") except ConnectionResetError as e: break conn.close()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import os import struct client = socket.socket() client.connect(("127.0.0.1",8000)) while 1: file_name = input("要上传的文件名") if file_name == "exit": break if os.path.exists(file_name): client.send(struct.pack('i',len(file_name))) #发送文件名的长度 client.send(file_name.encode('utf8')) #发送文件名 file_size = os.path.getsize(file_name) #发送文件的大小 client.send(struct.pack("i",file_size)) with open(file_name,"rb") as f: #发送文件 for line in f: client.send(line) print(client.recv(1024).decode('utf8')) else: print("文件不存在,请重新输入!") continue break client.close()
*直接发送文件数据且避免黏包现象发生该怎么写?(用socket模块解决)
服务端代码
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'xxxxxxxx{"":"","":"","":"","":""}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()
客户端代码
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()(:创建套接字对象),bind(),accpet()等等,学习了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方法;
创建线程,执行方法process_request_thread()
(4)执行ThreadingMixIn 类中的process_request_thread(self, request, client_address)方法:
看try中self.finish_request(request,client_address)
(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方法。
附录:以下是各类的继承关系以及类中成员方法: