操作系统基础
操作系统是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行
操作系统应该分为两部分功能:
- 隐藏了丑陋的硬件调用接口(负责管理键盘、鼠标、音响等),为应用程序员提供调用硬件资源更好、更简单、更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作系统的细节,专心开发自己的应用程序即可
- 将应用程序对硬件资源的静态请求变得有序化
例如:很多应用软件其实是共享一套计算机硬件,比方说有可能有三个应用程序同时需要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,然后可能是b竞争到了打印机资源,也可能是c这就导致了无序,打印机可能打印一段a的内容然后去打印c,操作系统的一个功能就是将这种无需变得有序。
SOCKET
首先回顾五层通讯流程
从传输层开始及以下都是操作系统完成的,下面的各种报头封装过程
关于socket简介
Socket又称为套接字,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。再设计模式中Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对于用户来说,一组简单 的接口就是全部,让Socket去组织数据,以符合指定的协议。
当我们使用不同的协议进行通信的时候就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难-度,软件也不易于扩展,于是UNIX BSD就发明了Socket,Socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用Socket提供的接口来进行互联网之间的不同协议之间的通信。
这就好比操作系统给我们提供了使用底层硬件系统的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己进去磁盘进行读写、内存管理,Socket其实也是一样的东西,就是提供了TCP/IP协议的抽象,对外提供了一套接口,通过这个接口就可以统一方便的使用TCP/IP协议的功能了。
套接字家族
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此, 有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个 应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于 文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一 机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名称:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已 经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一 个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET )
实际的案例的程序
qq客户端(UDP)
from socket import AF_INET, socket, SOCK_DGRAM
BUFSIZE = 1024
#初始化格式:
#获取tcp/ip套接字
udp_client_socket = socket(AF_INET, SOCK_DGRAM)
udp_client_socket.bind(('127.0.0.1', 8082))# 绑定端口号到套接字
qq_name_dic={
'taibai':('127.0.0.1',8081),
'Jedan':('127.0.0.1',8081),
'Jack':('127.0.0.1',8081),
'John':('127.0.0.1',8081),
}
while True:
while True:
for name in qq_name_dic:
print(name)
qq_name = input("请选择聊天对象").strip()
if qq_name not in qq_name_dic.keys():
print('没有这个聊天对象')
continue
break
while True:
msg = input('请输入消息,回车发送,输入q结束和他的聊天:').strip()
if msg == 'q':break
if not msg:continue
msg_send ='发给'+ qq_name + ': ' +msg
udp_client_socket.sendto(msg_send.encode('utf-8'), qq_name_dic[qq_name])
back_msg, addr = udp_client_socket.recvfrom(BUFSIZE)
print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' %(addr[0], addr[1], back_msg.decode('utf-8')))
qq服务端(UDP)
import socket
ip_port = ('127.0.0.1', 8081)
udp_server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)
while True:
qq_msg, addr = udp_server_sock.recvfrom(1024)#获取的信息以元组的形式返回,(message, address)
print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' %
(addr[0], addr[1], qq_msg.decode('utf-8')))
back_msg = input('回复消息:').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'), addr)
套接字的工作流程
关于TCP和UDP之间的对比
- TCP
可靠的、面向连接的协议(eg:打电话)、效率比较低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:web浏览器;文件传输程序,需要在发送数据之前经过都次的连接与握手
- UDP
不可靠的、无连接的服务,传输效率高(发送时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统(DNS);视频流;IP语音
在python代码实现上的区别
- 在创建套接字的时候使用的socket函数中的参数不同,
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
UDPclient = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
TCP
tcp使用recv而udp使用的是recvfromdata = client.recv(1024)
data = client.recvfrom(1024)
tcp 和 udp的区别:
1. tcp传输数据使用字节流的方式传输,udp是数据报
2. tcp会产生粘包现象,udp不会
3. tcp对网络条件要求高,udp更适合实时传输
4. tcp编程可以保证传输的可靠性,udp则不保证
5. tcp使用listen accpet, udp不需要
6. tcp使用recv send
udp使用recvfrom sendto
对于站保现象的理解和解决方法
产生粘包现象的原因
- 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
UDP协议下的socket
udp是无衔接的,先启动哪一端都不会报错
会话的主要工作流程
关于面向对象编程
封装
- 封装是面向对象编程的一大特点
- 面向对象编程的第一步——将属性和方法封装到一个抽象的类中
- 外界使用类创建对象,然后让对象调用方法
-
案例一,摆放家具
```python class HouseItem: def init(self,name,area):
'''
:param name:家具名称
:param area: 家具面积
'''
self.name = name
self.area = area
def str(self):
return f"{self.name}的占地面积为{self.area}"
class House: def init(self, house_type, area): ‘’’
:param house_type: 房子类型
:param area: 总面积
'''
self.area = area
self.house_type = house_type
self.free_area = area
self.item_list = []
def __str__(self):
return f"{self.house_type}总面积:{self.area},剩余{self.free_area},家具{self.item_list}"
def add_item(self, item):
print(f'要添加{item}')
if item.area > self.free_area:
print(f"{item.area}的面积太大,不能添加到房子中")
return
self.item_list.append(item.name)
self.free_area =self.free_area - item.area
bed = HouseItem(“席梦思”, 4) chest = HouseItem(“衣柜”, 2) table = HouseItem(“餐桌”, 1.5)
my_home = House(“两室一厅”, 60) my_home.add_item(bed) my_home.add_item(chest) my_home.add_item(table) print(my_home)
<a name="qJhqn"></a>
### 案例二,士兵突击
```python
import time
class Gun:
def __init__(self, model):
'''
:param model: 枪的型号
'''
self.model = model
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
if self.bullet_count <= 0:
print("没有子弹了")
return
self.bullet_count -= 1
print(f"{self.model}发射子弹[{self.bullet_count}].....突突突")
class Soldier:
def __init__(self, name):
'''
:param name: 士兵名字
'''
self.name = name
self.gun = None
def equip(self,gun):
self.gun = gun
def fire(self):
if self.gun is None:
print(f"{self.name}还没有枪")
return
print(f'冲啊!!!![{self.name}]')
self.gun.add_bullet(50)
for i in range(50):
time.sleep(0.1)
self.gun.shoot()
ak47 = Gun('ak47')
# ak47.add_bullet(50)
# for i in range(50):
# time.sleep(0.5)
# ak47.shoot()
xusanduo = Soldier('xusanduo')
xusanduo.equip(ak47)
xusanduo.fire()
多态
多态的不同子类对象调用父类方法,产生不同的执行结果
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计 ```python class Dog(object): def init(self, name):
self.name = name
def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)
class XiaoTianDog(Dog):
在子类中重新定义game方法
def game(self):
print("%s 飞到天上去玩耍..." % self.name)
class Person(object): def init(self, name): self.name = name def game_with_dog(self,dog): print(“%s 和 %s 快乐的玩耍…” % (self.name, dog.name)) dog.game()
wangcai = Dog(‘旺财’) xiaotianquan = XiaoTianDog(‘飞天旺财’)
xiaoming = Person(‘小明’)
xiaoming.game_with_dog(wangcai) xiaoming.game_with_dog(xiaotianquan)
<a name="aoVn1"></a>
## 鸭子类型
python中有一句谚语说的号,你看起来像鸭子,那么你就是鸭子<br />对于代码上的解释其实很简单<br />一个对象的特征不是由父类决定的,而是由方法决定的<br />**我们不关心对象是什么类型,到底是不是鸭子,只关心行为**
```python
class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class B:
def f1(self):
print('in B f1')
def f2(self):
print('in B f2')
obj = A()
obj.f1()
obj.f2()
obj2 = B()
obj2.f1()
obj2.f2()
反射
使用hassttr(obj,‘str’)的方式来观察某一对象是否有某一方法
类的约束
对于不同类的同一个方法,不同的程序员写出来的代码会完全的不同,甚至无法用统一的方法来对对象的方法进行调用
就像这样子
class QQpay:
def pay(self,money):
print('使用qq支付%s元'%money)
class AliPay:
def pay(self,money):
print('使用支付宝支付%s元'%money)
class WechatPay:
def fuqian(self,money):
print('使用微信支付%s元'%money)
def pay(obj,money):
print('='*20)
obj.pay(money)
a = AliPay()
b = QQpay()
pay(a,100)
所以此时我们需要用到对于类的约束,对类的约束有两种:
- 提取父类,然后再父类中定义好方法,在这个方法中什么都不用干,就抛一个异常就可以了,这样所有的子类都必须重写这个方法,否则,访问的时候会出现报错
- 使用元类来描述父类,在元类中给出一个抽象的方法,这样子类就不得不给出抽象方法的具体实现,也可以起到约束的效果
- 首先使用第一种方法
```python
class Payment:
“””
这种方式在子类没有定义pay方法的时候会继承父类的该方法,所以会报错
如果在子类中有重新定义,则不会继承到父类的该方法,所以不会报错
“””
def pay(self,money):
raise Exception('你没有实现pay的方法')
class QQpay: def pay(self, money): print(‘使用qq支付%s元’ % money) class AliPay: def pay(self, money): print(‘使用支付宝支付%s元’ % money) class WechatPay: def fuqian(self, money): print(‘使用微信支付%s元’ % money) def pay(obj, money): print(‘=’ * 20) obj.pay(money) a = AliPay() b = QQpay() c = WechatPay() pay(a, 100) pay(b, 200) pay(c, 200)
- 引入抽象类的概念处理
```python
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):# 抽象类 接口类,规范约束,metaclass指定的是一个元类
@abstractmethod
def pay(self):
pass
class QQpay(Payment):
def pay(self, money):
print('使用qq支付%s元' % money)
class AliPay(Payment):
def pay(self, money):
print('使用支付宝支付%s元' % money)
class WechatPay(Payment):
def fuqian(self, money):
print('使用微信支付%s元' % money)
def pay(obj, money):
print('=' * 20)
obj.pay(money)
a = AliPay()
a.pay(100)
pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
个人的理解
这种方式的主要作用就是为子类提供一个规范,在上面的示例中,使用Payment类作为父类的类必须要写pay方法才可以被成功初始化
总结: 约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:
- 使⽤抽象类和抽象⽅法, 由于该⽅案来源是java和c#. 所以使⽤频率还是很少的
- 使⽤⼈为抛出异常的⽅案. 并且尽量抛出的是NotImplementError. 这样比较专业, ⽽且错误比较明
确.(推荐)对于super()函数的深入理解
个人的理解就是使用调用父类的方法class A: def f1(self): print('in A f1') def f2(self): print('in A f2') class Foo(A): def f1(self): super().f2() print('in A Foo') obj = Foo() obj.f1()
类的成员
类的成员分为成员变量和成员函数,分别称为属性和方法细分类的组成成员
在之前的课程中讲过
类主要分为
- 第一部分:静态字段(静态)部分
- 第二部分:方法部分
每个区域的详细划分
class A:
company_name = '陈松'#静态变量(静态字段)
__iphone = '512673'#私有静态变量
def __init__(self, name, age):#特殊方法
self.name = name#对象属性
self.age = age#私有对象属性
def func1(self):#普通方法
pass
def __func(self):#私有方法
print(666)
@classmethod#类方法
def class_func(cls):
"""定义类方法,至少有一个cls参数"""
print('类方法')
@staticmethod#静态方法
def static_func():
'''定义静态方法,无默认参数'''
print('静态方法')
@property#属性
def prop(self):
pass
类的私有成员
杜宇每一个类的成员而言都有两种形式
公有静态字段访问范围示例
class C:
name = '公有静态字段'
def func(self):
print(C.name)
class D(C):
def show(self):
print(C.name)
print(C.name)
obj = C()
obj.func()
obj_son = D()
obj_son.show()
私有静态字段访问示例
静态字段只可以在类的内部进行调用,在类的外部、生成的对象、生成的子类、子对象中都不可以进行访问。
class C:
__name = '公有静态字段'
def func(self):
print(C.__name)
class D(C):
def show(self):
print(C.__name)
print(C.__name)
obj = C()
print(C.__name)
obj.func()
obj_son = D()
obj_son.show()
普通字段
公有普通字段:对象可以访问;类的内部可以访问;派生类中可以访问
私有普通字段:仅类的内部可以访问;
公有普通字段示例
class C:
def __init__(self):
self.foo = '公有字段'
def func(self):
print(self.foo)#类的内部访问
class D(C):
def show(self):
print(self.foo)#派生类中访问
obj = C()
obj.foo#通过对象访问
obj.func()#类的内部访问
obj_son = D()
obj_son.show()#派生对象访问
私有普通字段示例
class C:
def __init__(self):
self.__foo = '私有字段'
def func(self):
print(self.foo)#类的内部访问
class D(C):
def show(self):
print(self.foo)#派生类中访问
obj = C()
obj.__foo#通过对象访问
obj.func()#类的内部访问
obj_son = D()
obj_son.show()#派生对象访问
方法
公有方法:对象可以访问;类的内部可以访问;派生类中可以访问
私有方法:仅类的内部可以访问
私有方法示例
class C:
def __init__(self):
pass
def __add(self):
print('in C')
class D(C):
def __show(self):
print('in D')
def func(self):
self.__show()
C.__add()#访问不到
obj = D()
# obj.__show()#访问不到
obj.func()
# obj.__add()
总结:
对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用
非要访问私有成员的话,可以通过对象.类属性名,但是绝对不允许!!!
为什么可以通过类私有成员名访问呢?是因为类在创建的时候,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上类名
类的其他成员
这里的成员主要就是类方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同
实例方法:
定义:第一个参数必须是实例对象,该参数名一般约定为’self‘通过它来传递实例的属性和方法(也可以传类的属性) 调用:只能由实例对象调用
类方法:
定义:使用装饰器 @classmethod 。第一个参数必须是当前类的对象,该参数名一般约定为’cls‘ 通过他来传递实例的属性和方法(也可以传类的属性和方法) 调用:实例对象和类对象都可以调用
静态方法:
定义:使用装饰器 @classmethod 参数随意, 没有“self”和“cls”参数, 但是方法体中不能使用类或实例的任何属性和方法 调用:实例对象和类对象都可以调用
双下方法
定义:双下方法是特殊的方法,他是解释器提供的由双下划线加方法名加双下划线方法名的具有特殊意义的方法,双下方法主要是python源码程序员使用的
我们在开发的过程中尽量不要使用双下方法,但是深入研究双下方法更有益于我们阅读源码
调用:不同的双下方法由不停的触发方式,就好比盗墓时触发机关一样,不知不觉就触发了双下方法,例如:init
类方法
使用装饰器@classmethod。
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象类调用更合理,那么这个方法就可以定义为类方法。另外如果需要继承,也可以定义为类方法
如下场景: 假设我有一个学生类和一个班级类,想要实现的功能为:
执行班级人数增加的操作、获得班级的总人数;
学生类继承自班级类,每实例化一个学生,班级人数都能增加;
最后,我想定义一些学生,获得班级中的总人数。
class Student:
__num = 0
def __init__(self, name, age):
self.name = name
self.age = age
Student.addNum()
@classmethod
def addNum(cls):
cls.__num += 1
@classmethod
def getNum(cls):
return cls.__num
Student('陈松', 18)
Student('阿松', 36)
Student('松松', 73)
print(Student.getNum())
静态方法
就是定义一个和当前类没有任何关系的方法,将其放在已经定义好的类中,便于管理
使用装饰器@staticmethod。
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管与某个类的名称和空间中,便于使用和维护
譬如,我想定义一个关于时间操作的类,其中有一个获取当权时间的操作
import time
class TimeTest(object):
def __init__(self, hour, minute, second):
self.hour = hour
self.minute = minute
self.second = self
@staticmethod
def showTime():
return time.strftime('%H:%M:%S', time.localtime())
print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)
方法综合案例
需求
- 设计一个Game类
- 属性:
- 定义一个类属性top_score记录游戏的历史最高分
- 定义一个实例属性player_name记录当前游戏的玩家姓名
- 方法
- 静态方法
class Game(object):
top_score = 0
@staticmethod
def show_help():
print('帮助信息,让僵尸走进房间')
@classmethod
def show_top_score(cls):
print('游戏最高分是%d'%cls.top_score)
def __init__(self, player_name):
self.player_name = player_name
def start_game(self):
print("[%s] 开始游戏..." % self.player_name)
Game.top_score = 999
Game.show_help()
Game.show_top_score()
game = Game("小明")
game.start_game()
Game.show_top_score()
属性
property是一个特殊的属性,访问它时会执行一段功能(函数)然后返回值
实例:
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)
p1 =People('陈松', 75, 1.85)
print(p1.bmi)
将一个类地函数定义成特性之后,对象再去使用地时候obj.name,根本无法察觉自己地name是执行了一个函数然后计算出来地,这种特性地使用方式遵循了统一地访问原则
由于新式类中具有三种访问方式,我们可以根据他们几个属性地访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self, value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
##只有在属性AAA定义property后定义AAA.setter,AAA.deleter
f1 = Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA
另外一种写法
class Foo:
def get_AAA(self):
print('get的时候运行我啊')
def set_AAA(self, value):
print('set的时候运行我啊')
def delete_AAA(self):
print('delete的时候运行我啊')
AAA = property(get_AAA, set_AAA, delete_AAA)
#内置property三个参数与get, set, delete一一对应
f1 = Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA
其实就是定义了一个property方法可以进行属性的get、set、delete。
商品的案例
class Goods(object):
def __init__(self):
self.original_price = 100
self.discount = 0.8
@property
def price(self):
new_price = self.original_price*self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
print(obj.price)
obj.price = 200
print(obj.price)
del obj.price