面向对象三大特性及socket-day7
通过上篇博客的记录了Python剩余的一些内置模块用法,在Python内置模块中我们以后常用的有os,sys,time,json,logging,subprocess,这些模块不需要记住他们所有的用法,包括在后面学习的时候有很多第三方的模块,其中有很多在后面的开发中都会用到。不过幸运的是这些模块在网上都有非常详细的讲解。将内置的模块过完后我们又记录了面向对象的基本格式和使用方法,比如我们定义一个函数必须是使用class开头,类名第一个字母大写,还有函数的实例化也就是def __init__(self)函数,实例是实例化后的对象。函数的三大特性:封装,继承,多态(这里多态没有说)。
本节类容
- 面向对象三大特性的Python示例
- 面向对象的进阶
- socket通信
#绑定到对象的方法的特殊之处
class LeonYan(object): con = "CN" def __init__(self,name,age,grader): self.name = name self.age = age self.grader = grader def go_to_school(self): print("%s 正在班级上课,请不要打扰他..."%self.name) l1 = LeonYan('yan','18',"F") l2 = LeonYan('CHEN','20','M') LeonYan.go_to_school(l1) LeonYan.go_to_school(l2)
类中定义的函数(没有被任何装饰器修饰)是类的函数属性,类是可以直接使用,但必须遵循函数参数规则,有几个参数需传几个参数。类中定义的函数,主要是给对象(实例)使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不通的对象就是不通的绑定方法
PS:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用就会将“谁”本身当做第一个参数传递给方法,即自动传填。
# 对象之间的交互(实例之间的交互)
class Basketball(object): def __init__(self,time,chapter,score): self.time = time self.chapter = chapter self.score = score def kb(self,player_score): return self.score + player_score class Basketball_player(object): def __init__(self,name,high,power): self.name = name self.high = high self.power = power def shoot(self,P_score): print("%s basketball player score is %s"%(self.name,P_score)) b = Basketball('7min','3s',50) b_p = Basketball_player("CHEN",'170cm',25) print(b.score) print(b.kb(b_p.power))
上面的示例中其实我们实例化了两个实例一个是Basketball(篮球比赛中的目前得分),和Basketball_player(篮球运动员得分)。其实我写的两个实例只是想完成一个工作就是让场上的总比分加上篮球运动员在场上的得分,告诉大家一个实例是可以被另外一个实例犯法调用的。
关于对象交互练习
class Kai(object): def __init__(self, nick_name, attack, life_value): self.nick_name = nick_name self.attack = attack self.life_value = life_value def attack_other(self,attack_value): value = attack_value - self.attack return value class LanLingwang(object): def __init__(self, nick_name, attack, life_value): self.nick_name = nick_name self.attack = attack self.life_value = life_value def attack_other(self,attack_value): value = attack_value - self.attack return value k = Kai("Leonyan", 80, 1000) l = LanLingwang("CHEN", 100, 800) value = k.attack_other(l.life_value) print(id(value)) while value > 0: value = k.attack_other(value) print("%s is dath"%(l.nick_name))
# 继承的顺序:
类的继承和函数的作用域非常相似。比方需要调函数的一个方法会首先在函数内部找,如果没有找到这个属性,会在全局找。类在继承时调用类的属性也是如此,比如说我在父类中定义了一个eat的函数,在子类中也定义了一个eat的函数。将子类实例化之后调用eat方法首先也是从自己的内部中找,如果没有找到才会到父类中去找。
# 类的组合
class Teacher: def __init__(self,name,bitrh,course): self.name=name self.birth=bitrh self.course=course class Course: def __init__(self,name,price,period): self.name=name self.price=price self.period=period class Date: def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day t=Teacher('egon',Date(1999,1,25),Course('python',11000,'4mons')) print(t.birth.year)
# 抽象类
- 定义:子类必须实现父类的方法。详情如下示例
import abc class Linuxfile(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): """ 该方法在子类中实现 """ pass @abc.abstractmethod def write(self): """ 该方法在子类中实现 """ pass class systemfile(Linuxfile): def read(self): print("系统读文件") def write(self): print("系统写文件")
在抽象类中父类定义的是实现一个功能的模板,而子类是实现模板的方法,而且子类是必须实现父类中加了装饰器的函数。另外抽象的父类是不可以被实例化的
# 多态示例,对接口的调用
import abc class SuperHero(metaclass=abc.ABCMeta): def __init__(self,hq,mw,gander): self.hq = hq self.mw = mw self.gander = gander @abc.abstractmethod def flat(self): pass class HeiGuaFu(SuperHero): def __init__(self,hq,mw,gander): SuperHero.__init__(self,hq,mw,gander) def flat(self): print("zuo ai") class Amra(SuperHero): def __init__(self,hq,mw,gander): SuperHero.__init__(self,hq,mw,gander) def flat(self): print("xxljsdf") a = Amra('dc','ac',"F") h = HeiGuaFu('dc','ac','M') def func(obj): obj.flat() func(a)
1 class People(object): 2 def talk(self): 3 print("aaaaa") 4 5 class Dog(object): 6 def talk(self): 7 print("wawawa") 8 9 def func(obj): 10 obj.talk() 11 12 p = People() 13 d = Dog() 14 15 func(p) 16 func(d) 17 18 #在上面代码中多态是我们定义的一个个类,而多态性就是我们写的func杉树,同样也是我们提供给用户的接口。 19 #1.增加了程序的灵活性 20 21 #2.增加了程序额可扩展性
# 特性(property)
1. 什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
import math #math -- > 圆周率 3.1415926 class Circle(object): def __init__(self,radius): """ :param radius: 圆的半径 """ self.radius = radius def area(self): return math.pi * self.radius ** 2 #圆的面积 def perimeter(self): return 2 * math.pi * self.radius #圆的周长 perimeter = property(perimeter) c = Circle(10) print(c.area()) print(c.perimeter)
执行上面的代码我们会惊讶的发现print(c.area()) 和print(c.perimeter)都触发了函数的执行,但是我们是知道后面的perimeter并没有加双括号,在Python中按理说是不会执行函数中的代码的,而这里却得到了之后后的结果;但是我们仔细看完代码后看见了perimeter = property(perimeter),这个公式我们看的好像有一点熟悉,原来这个是装饰器。而这个装饰器的作用就是帮助我们执行下面的代码,方便我们在调用的时候不需要加上双括号。如下版本
import math #math -- > 圆周率 3.1415926 class Circle(object): def __init__(self,radius): """ :param radius: 圆的半径 """ self.radius = radius @property def area(self): return math.pi * self.radius ** 2 #圆的面积 @property def perimeter(self): return 2 * math.pi * self.radius #圆的周长 c = Circle(10) print(c.area) print(c.perimeter)
#扩展知识(触发方法)
class A(object): def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.setter def name(self,value): self.__name = value @name.deleter def name(self): print(">>>>N") a = A("Leonyan") print(a.name) a.name = 123 #触发name.seter方法 print(a.name) del a.name #触发name.delter方法
通过上面的连线我们可以模糊的看书来类之间的调用,已经触发方法的时间。
# 静态方法和类方法(staticmethod,classmethod)
静态方法示例:另类的实例化方式
import time class Date(object): def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day @staticmethod def now(): t = time.localtime() return Date(t.tm_year,t.tm_mon,t.tm_mday) #实例化的两种方法 t = time.localtime() d = Date(t.tm_year,t.tm_mon,t.tm_mday) #常规实例化 d1 = Date.now() #直接调用类下面的方法 #一般在实际应用中静态方法我们需要活学活用
类方法:
加入我们需要对上面的代码加上一个欧洲时间的功能,并且保证调用发放的一致性,这个时候我们就可使用到类方法了
import time class Date(object): def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day @classmethod def now(cls): t = time.localtime() return cls(t.tm_year,t.tm_mon,t.tm_mday) class Europe(Date): def __init__(self,year,mon,day): super().__init__(year,mon,day) E = Europe.now() print(E.year,E.day,E.mon) #类方法,调用的时候讲类当做第一个参数传给函数(cls)
#面向对象的高级用法
class Foo: def __init__(self,name): self.name=name def func(self): print('--------------.func') print(hasattr(Foo,'func')) f=Foo('egon') print(hasattr(f,'x')) f.x=1 print(getattr(f,'x')) print(getattr(f,'func')) if hasattr(f,'func'): aa=getattr(f,'func') aa() print(getattr(f,'y',None)) # f.y=1 #f y 1 setattr(f,'y',1) print(f.__dict__) delattr(f,'y') print(f.__dict__) # print(Foo.__dict__) # print(f.__dict__)
#Socket编程
什么是Socket:
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。(大白话就是socket可以理解成为一个别人已经把方法写好的接口,我们直接可以拿过来用)
--》发送一次Socket连接
Socket建立连接我们可以理解成发送短信的过程,如下面代码
import socket #封装好的Socket模块 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #给手机开机 server.bind(('127.0.0.1',8099)) #给手机插卡 server.listen(5) #最大接听(连接)数 conn,addr = server.accept() #等待连接(客户端连接) client_date = conn.recv(1024) #客户端给你发送的消息 print(client_date) conn.send(client_date.upper()) #给客户端回消息 conn.close() #拔卡 server.close() #关机
import socket client = socket.socket() client.connect(('127.0.0.1',8099)) #连接客户端 client.send('Hello'.encode('utf-8')) #给服务端发送信息 server_date = client.recv(1024) #接手服务端发送过来的消息 print(server_date) client.close() #关闭连接
通过实例其实知道从客户端给服务端发送一条消息是非常容易的,首先就是建立一个server端口通信,其实就是以哪种方式建立连接,对应的就是“server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)”,然后在本地监听一个端口接受消息,发送消息,关闭连接之类的操作。当我们“仔细”看了一遍代码后发现很多问题,比如我们客户端只能给服务端发送一次消息便自动断开了,这貌似不是我们想要的,还有就是比方说客户端给服务端发送一条空的字符串,客户端首先会无法执行任何东西,并且链接还不会断开。。。。
补充:
参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务
参数三:协议
0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
小练习,使用Socket编程,在远程主机执行命令,并将结果返回
import socket import subprocess phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind(('127.0.0.1',8099)) phone.listen(5) while True: #连接循环 conn,addr = phone.accept() while True: #回话循环 date = conn.recv(1024) if not date :break print("收取到数据%s"%date) res = subprocess.Popen(date.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE) err = res.stderr.read() if err: conn.send(err) else: res_date = res.stdout.read() print(res_date) conn.send(res_date) conn.close() phone.close()
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8099)) while True: cmd = input("commond>>>:").strip() if not cmd:continue client.send(cmd.encode('utf-8')) date = client.recv(1024) print(date.decode("gbk")) client.close()
通过运行上述代码我们可能会发现一个问题,就是当我们在执行完一条命令后执行下一条命令的时候返回的还是上一条命令的结果。这个是怎么回事呢?首先我们需要连接,我们发送一条socket字符串,是从用户态(用户界面)发送到内核态(操作系统),也就是我们在挑用socket这个模块的时候,这个模块发送我们所需要的指令是需要通过调用操作系统留给我们得接口的,而在内核中有一块缓存区。
作为客户端我们将制定的命令发送到server端的缓存区,而server端在回我们命令消息的时候也同样把结果放到了client端pc机的缓存区了。当我们从客户端读出缓存区中的数据的时候可能没有一次性读完,而tcp 又是一个流式连接,当我们发送下一条指令的时候客户端也会从上次未读完的地方开始读取数据,这个就是导致粘包的原因了。
解决粘包的两种方式:
第一种:
思路:通过上面对粘包的基本介绍其实不难得出导致其主要的原因还是在conn.recv(num)上,由于不知道服务端发送过来的长度到底有多少,使得客户端无法一次性全部读完。所以对粘包的主要改进也就是在解决如何得知服务端发送过来的套接字长度了。
import socket import subprocess import struct server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('127.0.0.1',8099)) #给手机插卡 server.listen(5) #最大接听(连接)数 while True: #连接循环 conn,addr = server.accept() #等待连接(客户端连接) while True: #会话循环 try: client_date = conn.recv(1024).decode() #客户端给你发送的消息 print(client_date) cmd = subprocess.Popen(client_date,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE) err_cmd = cmd.stderr.read() if err_cmd: back_message = err_cmd else: back_message = cmd.stdout.read() date = struct.pack('i',len(back_message)) #封装固定长度的信息, conn.send(date) #发送将真实内容所需要的大小封装发送给客户端 conn.sendall(back_message) #发送真实数据,sendall是优化当数据过大的时候客户端可以循环调用send函数将数据发送给客户端 except Exception : break conn.close() server.close()
import socket import struct client = socket.socket() client.connect(('127.0.0.1',8099)) #连接客户端 while True: msg = input("shell>>>:") if not msg : continue client.send(msg.encode('utf-8')) #给服务端发送信息 server_date = client.recv(4) #接手服务端发送过来的消息,固定大小为4字节 date_lens = struct.unpack('i',server_date)[0] recv_size = 0 #下面为循环接收服务端发送过来的数据 recv_bytes = b'' while recv_size < date_lens: res = client.recv(1024) recv_bytes += res recv_size += len(res) # real_date = client.recv(date_lens) #如果数据不是很大可以直接接收数据,个人比较喜欢这一种,,因为上面从理解的角度不是很好理解 print(recv_bytes.decode("gbk")) client.close()
第二种
在写第二种方法欠我们需要了解下struct模块pack(打包)可输入的字符串是有字节限制的,比方之前使用的“i” 他的范围就是 -2147483648 <= number <= 2147483647 。这个模块的具体限制如下图
一般遇到大小溢出的这个问题的时候我们就需要在原有的基础上再加上一个头部封装,将数据字典发送给客户端,让客户端分析发送过来的数据,从而获取到真实的数据。好吧说到这肯定有很多的疑惑,下面直接通过一个示例来看看消息传送到底是一个怎样的过程
import socket import subprocess import struct,json server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('127.0.0.1',8099)) #给手机插卡 server.listen(5) #最大接听(连接)数 while True: #连接循环 conn,addr = server.accept() #等待连接(客户端连接) while True: #会话循环 try: client_date = conn.recv(1024).decode() #客户端给你发送的消息 print(client_date) cmd = subprocess.Popen(client_date,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE) err_cmd = cmd.stderr.read() if err_cmd: back_message = err_cmd else: back_message = cmd.stdout.read() # date = struct.pack('i',len(back_message)) #封装固定长度的信息, head_dict = { "date_size":len(back_message) } head_str = json.dumps(head_dict) head_bytes = head_str.encode('utf-8') print(head_bytes) head_date = struct.pack('i',len(head_bytes)) conn.send(head_date) #发送将真实内容所需要的大小封装发送给客户端 conn.send(head_bytes) #发送真实数据 conn.sendall(back_message) #发送真实数据,sendall是优化当数据过大的时候客户端可以循环调用send函数将数据发送给客户端 except Exception : break conn.close() server.close()
import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8099)) #连接客户端 while True: msg = input("shell>>>:") if not msg : continue client.send(msg.encode('utf-8')) #给服务端发送信息 server_date = client.recv(4) #接收到的是这个字典的长度 date_recv = struct.unpack('i',server_date)[0] #反解除长度 real_date = client.recv(date_recv) #接收真是的字典 date_str = real_date.decode('utf-8') #将字典解码成utf8 json_dict = json.loads(date_str) date_lens = json_dict['date_size'] recv_size = 0 #下面为循环接收服务端发送过来的数据 recv_bytes = b'' while recv_size < date_lens: res = client.recv(1024) recv_bytes += res recv_size += len(res) # real_date = client.recv(date_lens) #如果数据不是很大可以直接接收数据,个人比较喜欢这一种,,因为上面从理解的角度不是很好理解 print(recv_bytes.decode("gbk")) client.close()
#SocketServer使用
SocketServer是一个为了实现socket多线程的一个模块,通过上面的很多示例我们不难发现。当一个客户端连接到服务端的时候,如果再开一个客户端想连接到服务端的时候回无法正常通信的,除非是最早的客户端自己断开,下一个客户端才能连接。而SocketServer模块就是为了实现回话连接多并发的。
SocketServer模块在Python3中的使用方法也不是特别难,一般我们只要正常导入 import socketserver 模块然后基本使用写其模块的基本方法就可以完成并发服务端了,具体如下:(建立一个SocketServer服务端)
import socketserver class Server_FTP(socketserver.BaseRequestHandler): def handle(self): #相当于回话循环-->conn,addr = phone.accept() # self.request # self.request == conn # self.client_address # self.client_address == client_ip while True: #回话循环 date = self.request.recv(1024) if not date :break self.request.sendall(date.upper()) if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8099),Server_FTP) #相当于创建的一个连接 -》socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.serve_forever() #相当于连接循环
----》SocketServer使用补充:在上面示例中 server = socketserver.ThreadingTCPServer(('127.0.0.1',8099),Server_FTP)其实就是绑定了一个连接。而 server.serve_forever()就是保证连接循环,当然在这个方法里我们需要定义一个会话循环也就是后面的类Server_FTP。这里就需要注意了,Server_FTP中的handle方法就是会话循环,这个是固定写法。