接口与归一化设计(python中使用继承的方式)
抽象类
import abc class Interface(metaclass=abc.ABCMeta): #定义接口Interface类来模仿接口的概念,python中#没有interface关键字来定义一个接口 all_type='file' #只定义功能集合而不实现具体的功能 @abc.abcstractmethod def read(self): # 定义接口函数read pass @abc.abcstractmethod def write(self): # 定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的写方法') t=Txt() print(t.all_type)
多态
一种接口,多种实现(接口重用)
class Animal: def __init__(self,name): self.name=name def talk(self): pass @staticmethod def animal_talk(obj): obj.talk() class Cat(Animal): def talk(self): print('Meow!') class Dog(Animal): def talk(self): print('Woof!Woof!') d = Dog('chen') #d.talk() c = Cat('xu') #c.talk() 使用统一的接口 Animal.animal_talk(c) Animal.animal_talk(d)
封装
1.如何隐藏属性(类的属性和对象的属性)
class Foo: __N=1111 # 类的属性被隐藏 _Foo__N:1111 def __init__(self,name): self.__Name=name # 给对象属性隐藏 self._Foo__Name=name def __f1(self): #_Foo__f1 print('f1') def f2(self): self.__f1() # self._Foo__f1() f=Foo('egon') #print(f.__N) # 报错,无法访问 #f.__f1() # 也无法访问 #f.__Name #也无法访问 f.f2() # f1 内部调用,可以访问
#以上隐藏需要注意的问题:
#1.这种隐藏只是一种语法上变形操作,并不会将属性真正隐藏起来
#print(Foo.__dict__)类的名称空间 => '-Foo__N':1111 #print(f.__dict__)对象的名称空间 => {'_foo_Name':'egon'} #print(f._foo_Name) # egon #print(f._Foo__N) # 1111
#2.这种语法级别的变形是在类定义阶段发生,并且只在类定义阶段发生
Foo.__x=2222 print(Foo.__dict__) # '__x':2222 print(Foo.__x) # 2222 f.__x=3333 print(f.__dict__) # '__x':3333 并没有发生变形 print(f.__x) # 3333
#3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,
#而父类中变形成了:_父类名__x,即双下划线开头的属性在继承给子类时,子类是无法覆盖的
class Foo: __N=1111 # _Foo__N def __init__(self,name): self.__Name=name # self._Foo__name=name def __f1(self): # _Foo__f1 print('f1') def f2(self): self.__f1() # self._Foo__f1() f=Foo('egon') #print(f.__N) #f.__f1() #f.__Name #f.f2()
封装的真实目的
#封装不是单纯意义上的隐藏
#1.封装数据属性:将属性隐藏起来,然后对外提供访问属性的接口,关键是我们在接口内定制一些控制逻辑
#从而严格控制使用对数据属性的使用
class People: def __init__(self,name,age): self.__Name=name self.__Age=age def tell_info(self): print('<名字:%s 年龄:%s>' % (self.__Name,self.__Age)) def set_info(self,x,y): if not isinstance(x,str): raise TypeError('%s must be str' %x) if not isinstance(y,int): raise TypeError('%s must be int' %y) self.__Name=x self.__Age=y p=People('egon',18) p.tell_info() #p.set_info('Egon','19') p.set_info('Egon',19) p.tell_info()
#2.封装函数属性:为了隔离复杂度
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
# bmi 体脂指数 class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p=People('egon',75,1.80) #print(p.bmi()) #bmi是一个名词,像访问数据属性一样访问bmi就可以了 print(p.bmi) #访问,设置,删除 class Foo: def __init__(self,x): self.__Name=x @property def name(self): return self.__Name @name.setter # 前提是name已经被property修饰过一次了,才能调用.setter def name(self,val): if not isinstance(val,str): raise TypeError self.__Name=val @name.deleter def name(self): #print('===') #del self.__Name raise PermissionError # 不允许删除 f=Foo('egon') print(f.name) f.name='Egon' print(f.name) del f.name print(f.name)
反射
#通过字符串找到属性对应的值
class Foo: x=1 def __init__(self,name): self.name=name def f1(self): print('from f1') f=Foo('egon') #hasattr print(hasattr(f,'name')) # f.name print(hasattr(f,'f1')) # f.f1 print(hasattr(f,'x')) # f.x #setattr setattr(f,'age',18) # f.age=18 #getattr print(getattr(f,'name')) # f.name print(getattr(f,'abc',None)) # f.abc print(getattr(f,'name',None)) # f.abc func=getattr(f,'f1') # f.f1 print(func) func() #delattr delattr(f,'name') # del f.name print(f.__dict__)
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): func=getattr(self,cmd) func() def get(self): print('get func') def put(self): print('put func') f=FtpServer('192.168.1.2',21) f.run()
item系列
#把对象变成类似字典对象,可以像字典一样进行增删改查
#也是一种归一化的思想
#__setitem__,__getitem__,__delitem__ class Foo: def __getitem__(self,item): print('getitem--->',self,item) return self.__dict__[item] def __setitem__(self,key,value): print('setitem--->',self,key,value) self.__dict__[key]=value # setattr(self,key,value) 效果一样 def __delitem__(self,key): print('delitem--->',self,key) self.__dict__.pop(key) f=Foo() f['x']=1111 del f['x'] print(f['x'])
打印对象信息__str__
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def __str__(self): #在对象被打印时触发执行 return '<name:%s age:%s sex:%s>' % (self.name,self.age,self.sex) #只能返回字符串类型 p=People('egon',18,'male') print(p)
析构方法__del__
class Foo: def __init__(self,x): self.x=x def __del__(self): # 在对象资源被释放时出发 print('---del---') f=Foo() print('===>')
异常处理
#什么时候用try...except
#错误一定会发生,但是无法预知错误发生条件
#CS结构的软件能够用到,客户的操作无法控制,客户单方面把软件终止掉,
#因为服务端客户端都是基于TCP链接的,但客户端单方面把链接断开,服务端也会受影响
#这种错误有可能发生,服务端不能受影响,这就用到了异常处理
class EgonException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return '<%s>' % self.msg raise EgonException('egon的异常')
基于tcp协议的socket实现简单通信
简单的套接字
1.服务端
# socket.AF_INET 用什么类型的套接字,地址家族
# socket.SOCK_STREAM 流式套接字,就是调用tcp
import socket #买手机 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 基于tcp的套接字 #插卡 phone.bind(('127.0.0.1',8080)) #开机 phone.listen(5) #最大挂起的链接数 #等电话链接 print('server start...') conn,client_addr=phone.accept() # (建立好的tcp链接,客户端的ip和端口) print('链接',conn) print(client_addr)
#基于建立的链接,收发消息
client_data=conn.recv(1024)
print('客户端的消息',client_data)
conn.send(client_data.upper())
#挂电话
conn.close()
#关机
phone.close()
2.客户端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) phone.send('hello'.encode('utf-8')) # 需要二进制类型,转为bytes类型 server_data=phone.recv(1024) #服务端回复的消息 print('服务端回应的消息',server_data) phone.close()
加上通信循环
#服务端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 用来解决Address already in use 问题 phone.bind(('127.0.0.1',8080)) phone.listen(5) print('server start...') conn,client_addr=phone.accept() #基于建立的链接,收发消息 while True: #通讯循环 client_data=conn.recv(1024) conn.send(client_data.upper()) conn.close() phone.close()
#客户端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
加上链接循环的套接字
服务端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) print('server start...') #链接循环 while True: # 这个循环是用来解决当有Exception异常时,是客户端断了,把这个断掉的资源回收掉 # 重新等待其他链接链接进来 conn,client_addr=phone.accept() while True: #此处用try..except是由于和多个客户端建立了链接,一个客户端断掉链接后会服务端抛出异常, #这样就导致其余的客户端也会发生错误,抛异常,为了使服务端能够继续和没有主动断掉链接的客户端 #继续通信,不崩溃,需要用到这个try..except..,但是只能解决不抛出异常的问题,不能解决让其他客户端 #进来和服务端建立通信 try: # 用于解决windows方面的问题 client_data=conn.recv(1024) if not client_data:break #主要用于linux系统和mac,客户端单方面断掉后,recv不会阻塞掉, #而是不断的收空,一直send,对方没有收,一直死循环这个try内的 #代码,所以需要加上 if not client_data:break ,如果收到的消息 #为空,肯定是客户端断掉了链接 conn.send(client_data.upper()) except Exception: break # 把这个通信循环直接断掉 conn.close() phone.close()
#客户端1
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
#客户端2
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
#客户端3
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
模拟ssh远程执行命令
#客户端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: cmd=input('>>: ').strip() if not cmd:continue #发命令 phone.send(cmd.encode('utf-8')) #收命令的执行结果 cmd_res=phone.recv(1024) #打印结果 print(cmd_res.decode('gbk')) #linux系统的话改为utf-8 phone.close()
#服务端
import socket import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) print('server start...') while True: conn,client_addr=phone.accept() while True: try: cmd=conn.recv(1024) #bytes格式 if not cmd:break #执行命令,拿到结果(subprocess命令的执行结果是bytes类型) res=subprocess.Popen(cmd.decode('utf-8'), # 需要字符串格式, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=res.stdout.read() #从正确的管道里面读,但是gbk编码 stderr=res.stderr.read() #从错误管道里面读,但是gbk编码 conn.send(stdout+stderr) except Exception: break conn.close() phone.close()
解决粘包问题
只有TCP有粘包现象,UDP永远不会粘包
由于TCP是流式的,没有开头没有结尾,就会发生粘包现象
UDP是数据报协议,它不是基于流的
粘包现象
服务端
from socket import * s=socket(AF_INET,SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.listen(5) conn,addr=s.accept() #收发消息 data1=conn.recv(1024) print('data1',data1) data2=conn.recv(1024) print('data2',data2) conn.close() s.close() #data1: b'helloworld' #data2 b'' 上面的结果不一定会发生,取决于算法要不要合并到一起发送过去, 多尝试几次,就会发生
客户端
from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) c.send('hello'.encode('utf-8')) c.send('world'.encode('utf-8')) c.close()
ssh远程执行命令+定制报头
客户端
import socket import struct phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: cmd=input('>>: ').strip() if not cmd:continue #发命令 phone.send(cmd.encode('utf-8')) #先收报头 header=phone.recv(4) total_size=struct.unpack('i',header)[0] #再收命令的执行结果 recv_size=0 data=b'' #结果 while recv_size < total_size: recv_data=phone.recv(1024) recv_size+=len(recv_data) data+=recv_data print(data.decode('gbk')) phone.close()
服务端
import socket import struct import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) print('server start...') while True: conn,client_addr=phone.accept() print(conn,client_addr) while True: try: cmd=conn.recv(1024) if not cmd:break res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=res.stdout.read() stderr=res.stderr.read() #制作报头 header=struct.pack('i',len(stdout)+len(stderr)) #先发报头(固定长度,这里一直是4) conn.send(header) #再发真实数据 conn.send(stdout) conn.send(stderr) except Exception: break conn.close() phone.close() #此方法的问题是最大只能发小于2G的内容
定制报头的正确方式
客户端
import socket import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: cmd=input('>>: ').strip() if not cmd:continue #发命令 phone.send(cmd.encode('utf-8')) #先收报头的长度 struct_res=phone.recv(4) header_size=struct.unpack('i',struct_res)[0] #再收报头 header_bytes=phone.recv(header_size) head_json=header_bytes.decode('utf-8') head_dic=json.loads(head_json) total_size=head_dic['total_size'] #再收命令的执行结果 recv_size=0 data=b'' #结果 while recv_size < total_size: recv_data=phone.recv(1024) recv_size+=len(recv_data) data+=recv_data print(data.decode('gbk')) phone.close()
服务端
import socket import struct import subprocess import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) print('server start...') while True: conn,client_addr=phone.accept() print(conn,client_addr) while True: try: cmd=conn.recv(1024) if not cmd:break res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=res.stdout.read() stderr=res.stderr.read() #制作报头 header_dic={'total_size':len(stdout)+len(stderr),'md5':None} header_json=json.dumps(header_dic) header_bytes=header_json.encode('utf-8') #先发报头的长度(固定4个bytes) conn.send(struct.pack('i',len(header_bytes))) #再发报头 conn.send(header_bytes) #再发真实数据 conn.send(stdout) conn.send(stderr) except Exception: break conn.close() phone.close()
socketserver实现并发
服务端
import socketserver class MyTcphandler(socketserver.BaseRequestHandler): def handle(self): while True: #通信循环 data=self.request.recv(1024) # 这个self.request就是conn self.request.send(data.upper()) if __name__ == '__main__': #取代链接循环 server=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyTcphandler) server.serve_forever()
客户端1
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
客户端2
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
客户端3
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data=phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
基于UDP的套接字
UDP是无连接的,UDP发送的数据本身就自带报头
udp服务端
s=socket() s.bind() #这一步tcp需要listen,listen()里面需要指定挂起的连接数,而udp没有连接, #所以没有listen #对于tcp来讲,这一步应该是链接循环,但是udp没有 #udp的通信循环 inf_loop: cs=s.recvfrom()/s.sendto() s.close()
udp客户端
c=socket() comm_loop: #通信循环 c.sendto()/cs.recvfrom() c.close()
完整实例
1 udp服务端 2 3 from socket import * 4 5 udp_server=socket(AF_INET, SOCK_DGRAM) 6 udp_server.bind(('127.0.0.1',8080)) 7 8 while True: 9 data, client_addr=udp_server.recvfrom(1024) 10 udp_server.sendto(data.upper(),client_addr) 11 12 udp客户端 13 14 from socket import * 15 16 udp_client=socket(AF_INET, SOCK_DGRAM) 17 18 while True: 19 msg=input('>>: ').strip() 20 udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) 21 data,server_addr=udp_client.recvfrom(1024) 22 print(data.decode('utf-8'))
基于socketserver实现并发的udp套接字
udp服务端
import socketserver class MyUDPhandler(socketserver.BaseRequestsHandler): def handle(self): print(self.request) #(b'hello',<socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1',8080>) #(客户端发来的消息,用这个可以给客户端回消息) self.request[1].sendto(self.request[0].upper(),self.client_addr) #self.request[1] 套接字 #self.client_addr 要回复的客户端地址 if __name__ == '__main__': s=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyUDPhandler) s.serve_forever()
udp客户端
from socket import * udp_client=socket(AF_INET, SOCK_DGRAM) while True: msg=input('>>: ').strip() udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) data,server_addr=udp_client.recvfrom(1024) print(data.decode('utf-8'))