封装,多态,反射,异常处理,网络编程

接口与归一化设计(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'))
View Code

基于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'))
原文地址:https://www.cnblogs.com/Ryans-World/p/7405362.html