第八(类进阶及网络编程)
一、抽象类与接口
1.1.java有interface关键字来实现接口
在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
/* 文件名 : Animal.java */ interface Animal { public void eat(); public void travel(); }
/* 文件名 : MammalInt.java */ public class MammalInt implements Animal{ public void eat(){ System.out.println("Mammal eats"); } public void travel(){ System.out.println("Mammal travels"); } public int noOfLegs(){ return 0; } public static void main(String args[]){ MammalInt m = new MammalInt(); m.eat(); m.travel(); } }
interface定义好eat和trave,当类实现接口,就必须实现接口的所有方法,也就是这两个方法,那么python如何实现?
class Interface: def read(self): pass def write(self): pass class Sata(Interface): def read(self): print("read file type: sata") def write(self): print("write file type: sata") class Files(Interface): def read(self): print("read file type: filesß") def write(self): print("write file type: files") class Process(Interface): def read(self): print("read file type: process") def write(self): print("write file type: process")
这样是实现了,但是好像没多大意义,父类完全没必要存在,也没有起到限制作用,这里就要用到抽象类。python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。
import abc #导入模块 class Interface(metaclass=abc.ABCMeta): @abc.abstractmethod #定义抽象类方法,无须实现 def read(self): pass @abc.abstractmethod #定义抽象类方法,无须实现 def write(self): pass class Sata(Interface): def duqu(self): pass def write(self): print("write file type: sata")
实例化报错:
不能实例化,因为缺少抽象方法,正确如下:
import abc #导入模块 class Interface(metaclass=abc.ABCMeta): @abc.abstractmethod #定义抽象类方法,无须实现 def read(self): pass @abc.abstractmethod #定义抽象类方法,无须实现 def write(self): pass class Sata(Interface): def read(self): print("read file type: sata") def write(self): print("write file type: sata") class Files(Interface): def read(self): print("read file type: files") def write(self): print("write file type: files") class Process(Interface): def read(self): print("read file type: process") def write(self): print("write file type: process") s=Sata() f=Files() p=Process()
二、多态及多态性
多态就是事物具有多种形态
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class Mankind(Animal): #动物形态一:人类 def talk(self): print("Man is saying") class Pigeon(Animal): #动物形态二:鸽子 def talk(self): print("Pigeon is singing") class Rabbit(Animal): #动物形态三:兔子 def talk(self): print("Rabbit is huhu")
多态性
有时称多态绑定(继承前提下使用)
多态性是不考虑实例类型而使用实例
在面向对象方法中一般是这样表述多态性: 向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。 也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。 比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
如下例子:
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class Mankind(Animal): #动物形态一:人类 def talk(self): print("Man is saying") class Pigeon(Animal): #动物形态二:鸽子 def talk(self): print("Pigeon is singing") class Rabbit(Animal): #动物形态三:兔子 def talk(self): print("Rabbit is huhu") def func(obj): obj.talk() M1=Mankind() P1=Pigeon() R1=Rabbit() func(M1) func(P1) func(R1)
运行结果:
多态增加了程序的灵活性和扩展性。
三、封装
隐藏:
class Foo: __N=888 def __init__(self,name): self.__Name=name def __f1(self): print('is f1') def f2(self): self.__f1() f=Foo('egon') # print(f.__N) #访问不到 # f.__f1() #访问不到 # print(f.__Name) #访问不到 f.f2() #可以访问到f2,f2可以访问到__f1,所以结果是'is f1'
#这种隐藏需要注意的问题
- 这种隐藏只是一种语法上变形操作,并不会将属性真正隐藏起来
print(Foo.__dict__) #{'__module__': '__main__', '_Foo__N': 888, '__init__': <function Foo.__init__ at 0x107aad1e0>, '_Foo__f1': <function Foo.__f1 at 0x107aad378>, 'f2': <function Foo.f2 at 0x107aad400>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None} #刚才隐藏的属性变形了,如:_Foo__N,但是外部还可以通过这种方式访问 print(f._Foo__Name) #egon
- 这种语法级别的变形,是在类定义阶段发生的,并且只在类定义阶段发生
class Foo: __N=888 #_Foo__N def __init__(self,name): self.__Name=name #_Foo__Name def __f1(self): #_Foo__f1 print('is f1') def f2(self): self.__f1() #_Foo__f1()
Foo.__x=1234567 print(Foo.__x) #没有发生变形,可以直接访问 # 1234567
示例看隐藏的使用
class Foo: def f1(self): print("this is Foo's f1") def f2(self): self.f1() class Bar(Foo): def f1(self): print("this is bar's f1") b1=Bar() b1.f2()
调用结果:
这是之前的结果,如果想调用Foo的f1呢,这里就需要用到隐藏了,如下:
class Foo: def __f1(self): #_Foo__f1 print("this is Foo's f1") def f2(self): self.__f1() #_Foo_f1() class Bar(Foo): def __f1(self): #_Bar__f1 print("this is bar's f1") b1=Bar() b1.f2()
结果:
这是因为隐藏在定义阶段以及变形,所以访问的f1不会再覆盖困扰了。
封装数据属性
class People: def __init__(self,name,age): self.__Name = name self.__Age = age def tell_info(self): print("name is :%s \nage is: %s" %(self.__Name,self.__Age)) p1=People('ckl',22) p1.tell_info()
结果:
可以通过一个tell_info接口来访问内部隐藏属性的内容,但是如果要修改呢?
class People: def __init__(self,name,age): self.__Name = name self.__Age = age def tell_info(self): print("name is :%s \nage is: %s" %(self.__Name,self.__Age)) def set_info(self,x,y): if not isinstance(x,str): raise TypeError("%s must be a str type" %x) if not isinstance(y,int): raise TypeError("%s must be a int type" %y) self.__Name = x self.__Name = y p1=People('ckl',22) p2=People('ckl',24) p1.tell_info() p2.tell_info()
结果:
属性隐藏起来后,内部可以用,外部不可以,但是通过增加一个接口的方式,外部也可以调用。并且在接口上可以增加任何控制逻辑来控制接口运行
例子中,控制姓名为字符串,年龄为数字类型。
封装函数属性
隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 #隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
四、静态属性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
BMI指数成人的BMI数值:
class Man: def __init__(self,name,weight,hight): self.name = name self.weight = weight self.hight = hight @property def bmi(self): print(self.weight/(self.hight*self.hight)) m1=Man('ckl',73,1.76) m1.bmi
结果:
通过property属性,可以使访问函数方法像访问属性一样,注意,我体脂指标正常。
property属性的修改和删除
class Foo: def __init__(self,x): self.__Name = x @property def name(self): return self.__Name @name.setter #修改,必须name是property def name(self,val): if not isinstance(val,str): raise TypeError self.__Name = val @name.deleter #删除,必须name是property def name(self): print("Start to delete....") del self.__Name f=Foo('ckl') print(f.name) #ckl f.name = 'kkk' print(f.name) #kkk del f.name print(f.name)
五、反射
反射就是通过字符串的形式获取方法或属性
反射有4个内置函数分别为:getattr、hasattr、setattr、delattr,获取、检查、设置、删除
class Foo: x=1 def __init__(self,name): self.name = name def f1(self): print('from f1') f=Foo('ckl') # print(f.__dict__) #{'name': 'ckl'} #检查name是否存在 print(hasattr(f,'name')) #True print(hasattr(f,'f1')) #True print(hasattr(f,'x')) #True #获取name print(getattr(f,'name')) #ckl print(getattr(f,'abc',None)) #没有则返回None,否则会报错 fc=getattr(f,'f1') fc() # from f1 #设置name的值 setattr(f,'name','wukaka') print(f.name) #wukaka #删除属性 delattr(f,'name') print(f.__dict__) #{}
反射ftp小示例,通过用户输入,运行相关方法
class FtpServer: def __init__(self,host,port): self.host = host self.port = port def run(self): while True: cmd = input(':>> ').strip() if not cmd: continue if hasattr(self,cmd): fc1=getattr(self,cmd) fc1() def get(self): print("get func ...") def put(self): print("put func ...") f1=FtpServer('11.1.1.1','21') f1.run()
运行结果:
六、item
通过字典的形式去访问对象属性
class Mankind: def __init__(self,name,age): self.name = name self.age = age def __getitem__(self, item): print('getitem ----->>>') return getattr(self,item) def __setitem__(self, key, value): print('setitem ----->>>') setattr(self,key,value) def __delitem__(self, key): print('delitem ----->>>') delattr(self,key) m=Mankind('wukaka',22) print(m['name']) # getitem ----->>> # wukaka m['name'] = 'nianhui' #setitem ----->>> print(m['name']) # getitem ----->>> # nianhui del m['name'] #getitem ----->>> print(m.__dict__) #{'age': 22}
只有字典形式访问属性,才能触发定义的方法。
__str__
class Mankind: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def __str__(self): #在对象被打印时触发 return ("name :%s,age :%s,gender: %s"%(self.name,self.age,self.gender)) M1=Mankind('ckl',22,'male') M2=Mankind('wz',19,'Female') print(M1) print(M2)
结果:
“__del__”就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间。
__del__()也是可选的,如果不提供,则Python 会在后台提供默认析构函数
class Foo: def __init__(self,name): self.name = name print("%s begin to ...." %self.name) def __del__(self): print("When run this,it's mean that you go die") f=Foo('wukaka') del f print("has been deleted.....")
结果:
七、网络编程
TCP通信模型
模拟tcp通信示例
服务端:
import socket #准备服务端套接字 cklServer=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定地址和端口 cklServer.bind(('127.0.0.1',9090)) #建立半链接,5为等待半链接的队列 cklServer.listen(5) #等待建立链接 conn,client_addr=cklServer.accept() #tcp链接;客户端地址端口等 print("tcpserver",conn) print("tcpserver",client_addr) #建立链接,接收消息 client_data = conn.recv(1024) print("from client msg: ",client_data) #发送消息 conn.send(client_data.upper()) #关闭链接 conn.close() #关闭server端 cklServer.close()
客户端:
import socket #准备客户端套接字 cklClient = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #与服务的建立链接 cklClient.connect(('127.0.0.1',9090)) #向服务端发送消息 cklClient.send('kitty'.encode('utf-8')) #接收服务端的消息 server_data = cklClient.recv(1024) print('from server msg:',server_data) #关闭客户端 cklClient.close()
服务端结果:
客户端结果:
上面问题:
1.只能接收一次消息。
2.运行服务端有时候会出现地址被占用(因为socket还未完全被释放)
改良如下:
服务端:
#服务端 import socket cklServer=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #增加对地址的重用,防止socket还未被释放的情况 cklServer.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) cklServer.bind(('127.0.0.1',9090)) cklServer.listen(5) conn,client_addr=cklServer.accept() #循环接收与发送文件,通讯循环 while True: client_data = conn.recv(1024) print(client_data) conn.send(client_data.upper()) conn.close() cklServer.close()
客户端:
import socket cklClient = socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklClient.connect(('127.0.0.1',9090)) #循环接收发送文件 while True: msg = input('>> ').strip() #内容为空则继续 if not msg: continue cklClient.send(msg.encode('utf-8')) server_data = cklClient.recv(1024) print(server_data.decode('utf-8')) #对于接收的内容进行解码 cklClient.close()
服务端运行:(结果基于客户端已经发送)
客户端运行:
新问题:
1.客户端如果断开,服务端默认接收空,所以进入死循环,不停发送空
2.如果有多个客户端,其它客户端无法进来
服务端:
import socket cklServer=socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklServer.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) cklServer.bind(('127.0.0.1',9090)) cklServer.listen(5) while True: #循环链接 conn,client_addr=cklServer.accept() print(conn) while True: #循环通信 client_data = conn.recv(1024) if not client_data: #如果内容为空,则终止,mac和linux break print(client_data) conn.send(client_data.upper()) conn.close() cklServer.close()
客户端不变,运行两个相同的客户端:
运行服务器:
...
运行客户端1:
服务器端返回:
建立链接,并且返回客户端1内容
运行客户端2:(处于等待状态)
关闭客户端1
再看客户端2:
服务端返回:
与客户端2建立连接,并返回内容。
模拟ssh
服务端:
import socket import subprocess cklServer=socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklServer.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) cklServer.bind(('127.0.0.1',9090)) cklServer.listen(5) while True: conn,client_addr=cklServer.accept() while True: client_cmd = conn.recv(1024) if not client_cmd: break print(client_cmd) #执行命令 cmd_result = subprocess.Popen(client_cmd,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #得到命令正常输出 stdout = cmd_result.stdout.read() #得到命令错误输出 stderr = cmd_result.stderr.read() #发送命令结果到客户端 conn.send(stdout+stderr) conn.close() cklServer.close()
客户端:
import socket cklClient = socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklClient.connect(('127.0.0.1',9090)) while True: cmd = input('>> ').strip() if not cmd: continue cklClient.send(cmd.encode('utf-8')) server_data = cklClient.recv(1024) print(server_data.decode('utf-8')) cklClient.close()
运行服务端:
...
运行客户端:
tcp粘包
如上实现,当我继续输入命令?输入结果如下:
ls命令结果竟然是上条命令的结尾,这是为什么?因为上调命令的结果没有接受完毕,第二次继续接受上调命令的结果,这就是粘包现象,如何解决?
这里用到python另外一个模块,struct,可以把一个类型,转成固定长度的bytes
使用示例:
优化的服务端:
import socket import subprocess import struct cklServer=socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklServer.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) cklServer.bind(('127.0.0.1',9090)) cklServer.listen(5) while True: conn,client_addr=cklServer.accept() while True: client_cmd = conn.recv(1024) if not client_cmd: break print(client_cmd) cmd_result = subprocess.Popen(client_cmd,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = cmd_result.stdout.read() stderr = cmd_result.stderr.read() #设定发送头部,内容为实际内容,长度转为固定长度4 header=struct.pack('i',len(stdout+stderr)) #发送头部 conn.send(header) #发送实际内容 conn.send(stdout+stderr) # conn.send(stdout+stderr) conn.close() cklServer.close()
优化客户端:
import socket import struct cklClient = socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklClient.connect(('127.0.0.1',9090)) while True: cmd = input('>> ').strip() if not cmd: continue cklClient.send(cmd.encode('utf-8')) #设定接受头部长度为4 header = cklClient.recv(4) #反解头部 head_data = struct.unpack('i',header) #获取头部的实际长度 data_length = head_data[0] #设定接受内容为空 recv_data = '' #循环接受内容,每次1024,直到全部接受完毕 while len(recv_data) < data_length: every_recv = cklClient.recv(1024).decode('utf-8') recv_data += every_recv print(recv_data) # print(server_data.decode('utf-8')) cklClient.close()
运行服务端:
运行客户端:
没有出现粘包现象。
socketserver 多并发
之前的程序都是单个客户端,通过socketserver可以实现多客户端并发
服务端:
import socketserver class CklTcphandler(socketserver.BaseRequestHandler): def handle(self): #通信循环 print(self) while True: data=self.request.recv(1024) self.request.send(data.upper()) if __name__ == '__main__': #取代连接循环 server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),CklTcphandler) server.serve_forever()
客户端:
import socket import struct cklClient = socket.socket(socket.AF_INET,socket.SOCK_STREAM) cklClient.connect(('127.0.0.1',8090)) while True: msg = input('>> ').strip() if not msg: continue cklClient.send(msg.encode('utf-8')) server_data = cklClient.recv(1024) print(server_data.decode('utf-8')) cklClient.close()
运行服务端:
...
运行客户端1:
运行客户端2:
服务端:
模拟ftp
一个失败的服务端:
import socketserver import hashlib import struct import json import os current_user = {'user': None} class ftpTcphandler(socketserver.BaseRequestHandler): def handle(self): # print(self) # while True: # data=self.request.recv(1024) # self.request.send(data.upper()) #self.auth() self.run() def auth(self): if current_user['user']: print("need not to login") # return self() with open('db.txt', encoding='utf-8') as f: user_dic = eval(f.read()) count=0 while count < 3: #接受用户名和密码 name_pass = self.request.recv(1024).decode('utf-8') #将json格式内容解为字典 name_pass_dict = json.loads(name_pass) name = name_pass_dict['user'] password = name_pass_dict['pass'] #加密存储的密码并加盐 dic_pass = hashlib.sha256('ckl168'.encode('utf8')) dic_pass.update(user_dic[name].encode('utf8')) store_pass = dic_pass.hexdigest() if name in user_dic and password == store_pass: # res = self() current_user['user'] = name # return res login_msg= "login successful." self.request.send(login_msg.encode('utf-8')) break else: if count == 2: logf_msg1="you have tried too many times.you will quit" self.request.send(logf_msg1.encode('utf-8')) else: logf_msg2="username or password is wrong" self.request.send(logf_msg2.encode('utf-8')) count+=1 print(count) def put(self,fmsg): #获取总文件大小 all_recv_length = fmsg['filesize'] print("start to receiving....") recv_fsize = 0 while recv_fsize < all_recv_length: with open(fmsg['filename'], 'ab') as fwrite: each_recv = self.request.recv(200) print(each_recv) fwrite.write(each_recv) #获取当前文件大小 recv_fsize = os.path.getsize(fmsg['filename']) print(recv_fsize) print(all_recv_length) print('-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x') def run(self): while True: msg_recv = self.request.recv(1024).decode('utf-8') if not msg_recv: break msg_dict = json.loads(msg_recv) print(msg_dict) client_cmd = msg_dict['act'] print(client_cmd) if client_cmd == 'put': self.put(msg_dict) if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8022),ftpTcphandler) server.serve_forever()
一个失败的客户端:
import socket import struct import json import re import sys import hashlib import os cmd_list = ['put','get','ls'] class CklFtpClient: 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) self.socket.settimeout(3) 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 auth(self): print("Welcome login ckl ftp.") ask_tag=1 user_dict = {} while ask_tag: username = input("please input your login name: ") password = input("please input your password: ") #用户名密码加密并加盐 codepass = hashlib.sha256('ckl168'.encode('utf-8')) codepass.update(password.encode('utf-8')) password = codepass.hexdigest() #用户名和密码加入到字典 user_dict['user'] = username user_dict['pass'] = password print(user_dict) #将字典转为json格式 js_user_dict = json.dumps(user_dict) self.socket.send(js_user_dict.encode('utf-8')) data=self.socket.recv(1024).decode('utf-8') print(data) suc_str = 'successful' fail_str = 'quit' if suc_str in data: ask_tag = 0 if fail_str in data: self.socket.close() sys.exit(1) def put(self,fjson,flist): #发送文件信息json self.socket.send(fjson.encode('utf-8')) print("start to upload file ...") with open(flist[1],'rb') as fread: for line in fread: self.socket.send(line) else: print("upload success.") def run(self): while True: cmd = input('>> ').strip() if not cmd: continue #命令列表,第一个是命令,第二个是文件路径 cmd_input_list = cmd.split(' ') #生成空文件信息字典 cmd_input_dict = {} try: if cmd_input_list[0] not in cmd_list: print('wrong: command not found') if cmd_input_list[0] == 'put': if not os.path.exists(cmd_input_list[1]): print('file is not found') else: print('--------------------') fsize = os.path.getsize(cmd_input_list[1]) #获取文件md5值 put_md5 = hashlib.md5() with open(cmd_input_list[1],'rb') as fread: for line in fread: put_md5.update(line) f_md5 = put_md5.hexdigest() #获取文件名 file_name = os.path.basename(cmd_input_list[1]) cmd_input_dict['act'] = cmd_input_list[0] cmd_input_dict['filename'] = file_name cmd_input_dict['filesize'] = fsize cmd_input_dict['md5'] = f_md5 #将字段转成json cmd_put = json.dumps(cmd_input_dict) print(cmd_put) self.put(cmd_put,cmd_input_list) if cmd_input_list[0] == 'get': pass except IndexError: print("Wrong: please choose a file.") ftpclient=CklFtpClient(('127.0.0.1',8022)) #ftpclient.auth() ftpclient.run()
都失败了,就没运行,哈哈