python123查找指定字符输入m_README.md · chengzi666/python_study3 - Gitee.com

1.网络编程

网络功能:数据的传输

OSI七层模型 --> 网络通信标准化流程

应用层 : 提供用户服务,具体功能有程序体现

表示层 : 数据的压缩优化和加密

会话层 : 建立应用连接,选择合适的传输服务

传输层 : 提供传输服务,进行流量控制

网络层 : 路由选择,网络互连

链路层 : 进行数据交换,控制具体消息收发,链路连接

物理层 : 提供物理硬件传输,网卡,接口设置,传输介质

OSI七层模型优势:

1.建立了统一的网络工作流程

2.每个层次功能清晰,各司其职

3.降低了耦合度,方便了开发流程

cookie:

高内聚:模块功能尽可能单一,不要相互掺杂

低耦合:模块之间的关联影响尽可能少

四层模型(TCP/IP模型)

应用层 传输层 网络层 物理链路层

将应用层,表示层,会话层统一为应用层,便于开发实践

五层模型

应用层 传输层 网络层 链路层 物理层

数据的传输流程

1.发送端由应用层逐层根据协议添加首部信息,最终在物理层实现发送

2.发送的消息经过中间多个节点转发到达目标主机

3.目标主机根据协议逐层解析首部,最终达到应用层获取数据

网络协议:网络通信双方都遵守的规定,包括建立什么数据结构,消息结构,首部内容等

网络编程基本概念

网络主机:在网络中标识一台主机的标志

本地使用:'localhost'

'127.0.0.1'

网络使用 : '192.168.56.131'

'0.0.0.0'

ifconfig:查看本地网络消息

IP地址: 在网络上查找一台主机的网络位置

IPv4 : 点分十进制 192.168.1.2 0-255

IPv6 : 扩充地址的范围

ping IP:网络连接测试

特殊IP

127.0.0.1 本机测试IP

0.0.0.0 本机自动获取能够使用的网卡IP

192.168.1.0 表达一个网段

192.168.1.1 表示网关地址

域名:网络服务器的别名

作用:方便记忆,表达一定的含义

端口号:port

端口号是地址的一部分,用于区分主机上不同的网络应用

*在一个系统中应用监听的端口不重复

取值范围:1--65535

1--255 一些众所周知的公共程序端口

256--1023 系统应用端口

1024--65535 自用端口

网络字节序:网络上数据传输的排列方式

传输层服务

面向连接的传输服务(基于TCP协议的数据传输)

传输特征:提供可靠的数据传输,可靠性指的是传输过程中无丢失,无失序,无差错,无重复

实现手段:在通信前需要建立通信连接,通信结束需要断开连接

连接过程(三次握手)

1.客户端向服务端发起连接请求

2.服务端收到客户端请求报文(消息序列号),回复报文消息表示可以连接

3.客户端收到服务端回复,再重发送报文最终建立连接

连接断开(四次挥手)

1.主动方发送报文提出断开连接

2.被动方收到断开请求,立即返回消息表示开始准备断开

3.被动方处理消息完毕,完成断开准备,再次发送报文表示可以断开

4.主动方收到断开指令,发送报文最终确认断开

适用情况:对数据传输有准确性的要求,传输文件较大,需要确保传输可靠性

比如:网页获取,文件下载,邮件收发

面向无连接的传输服务(基于UDP协议的传输)

传输特征:不保证传输的可靠性,数据传输不需要提前建立连接

适用情况:网络情况较差,对传输可靠性要求不高.

比如:网络视频,群聊,广播

要求:

1.OSI七层模型介绍一下,tcp/ip模型是什么

2.tcp服务和udp服务有什么区别,tcp,udp是那层协议

3.简单描述下三次握手和四次挥手过程

socket 模块

功能:python 的标准库模块,提供网络编程的一系列接口功能

简单函数示例:

socket.gethostname() #'tedu' 查看主机地址

socket.gethostbyname('www.baidu.com') #'119.75.217.109' 通过域名获取主机地址

socket.gethostbyaddr('localhost') # ('localhost', [], ['127.0.0.1']) 查看指定服务器信息(服务器名,别名,ip)

socket.inet_aton('192.168.1.2') # b'\xc0\xa8\x01\x02' 将点分十进制地址转换为二进制

socket.inet_ntoa(b'\xc0\xa8\x01\x02') #'192.168.1.2' 将二进制地址转换为点分十进制

套接字:实现网络编程,进行数据传输的一种编程方案,通过socket模块提供的接口函数进行组合搭配实现

套接字分类:

流式套接字(SOCK_STREAM)

特征:面向连接的传输服务,能够保证传输可靠性,是基于tcp请求的一种套接字,数据传输使用字节流传输

数据报套接字(SOCK_DGRAM)

特征:面向无连接的传输服务,不保证传输可靠,是基于udp请求的一种套接字,使用数据报进行传输

tcp流式套接字编程

服务端流程

1.创建套接字

sockfd = socket(socket_family=AF_INET,socket_type=SOCK_STERAM,proto=0)

功能:创建套接字

参数:socket_family:地址族类型 AF_INET -> IPv4

socket_type:套接字类型

SOCK_STREAM 流式

SOCK_DGRAM 数据报

proto:通常为0

返回值:套接字对象

2.绑定服务器地址

sockfd.bind(addr)

功能:绑定服务器网络地址

参数:元组(ip,port) --> ('127.0.0.1',8888)

3.设置套接字监听

sockfd.listen(n)

功能:将套接字设置为监听套接字并创建监听队列

参数:监听队列大小

*一个监听套接字能够连接多个客户端套接字

4.等待处理客户端连接

connfd,addr = sockfd.accept()

功能:阻塞等待处理客户端连接请求

返回值:connfd 生成的新的客户端连接套接字

addr 连接的客户端的地址

*阻塞函数:程序运行中遇到阻塞函数则暂停执行,直到阻塞条件满足后再继续执行

5.收发消息

data = connfd.recv(buffersize)

功能:接收tcp消息

参数:每次最多接收消息大小(字节)

返回值:接收到的内容

n = connfd.send(data)

功能:发送tcp消息

参数:要发送的内容(bytes格式)

返回值:发送的字节数

str --> bytes encode()

bytes --> str decode()

6.关闭套接字

connfd.close()

sockfd.close()

功能:关闭套接字

客户端

1.创建套接字(必须相同类型套接字才能通信)

2.请求连接

sockfd.connect(addr)

功能:连接服务端套接字

参数:服务端地址

3.消息收发

4.关闭套接字

tcp传输特征

1.当一端退出时,如果连接端阻塞在recv,此时recv会立即结束阻塞返回空字符串

2.如果连接端关闭,再调用send企图发送时,会出现BrokenPipe异常

网络收发缓冲区

1.减少和磁盘的交互次数

*send 和 recv实际都是向缓冲区发送,从缓冲区接收

tcp粘包

1.tcp套接字是以字节流的方式传输消息,没有消息边界

2.多次发送的内容被一次接收

影响:如果每次发送的内容是一个独立含义的个体,此时如果粘包会产生影响

处理粘包:

1.人为添加消息边界

2.将消息结构化

3.控制消息发送速度

基于UDP套接字服务端

1.创建数据报套接字

sockfd = socket(AF_INET,SOCK_DGRAM)

2.绑定地址

sockfd.bind(addr)

3.消息收发

data,addr = sockfd.recvfrom(buffersize)

功能:接收UDP消息

参数:每次最多接收消息的大小

返回值:data 接收到的消息

addr 消息发送方地址

n = sockfd.sendto(data,addr)

功能:发送UDP消息

参数:data 要发送的内容

addr 目标地址

返回值:发送字节数

4.关闭套接字

sockfd.close()

UDP客户端:

1.创建udp套接字

2.消息收发

3.关闭套接字

cookie

import sys

sys.argv属性:用于获取命令行传入参数

tcp套接字和udp套接字区别

1.tcp套接字以字节流方式传输,数据报套接字以数据形式传输

2.tcp传输会有粘包,udp不会(有消息边界)

3.tcp传输保证传输可靠性,udp则会有部分消息丢失的可能

4.tcp需要listen accept 保证连接,udp不需要

5.tcp需要send,recv收发消息,udp使用sendto,recvfrom

补充函数:

sendall(data)

功能:发送完整tcp消息

参数:发送的内容bytes

返回值:发送成功返回None,失败产生异常

套接字对象属性

sockfd.family 获取套接字地址族类型

sockfd.type 获取套接字类型

sockfd.getsockname() 获取套接字绑定地址

sockfd.fileno() 获取套接字的文件描述符

文件描述符:系统中给每一个IO操作分配的唯一的整数,用于管理IO,这个整数即这个IO的文件描述符

sockfd.getpeername() 获取连接端地址信息

sockfd.setsockopt(level,option,value)

功能:设置套接字选项,丰富或者修改套接字属性的功能

参数:level 要设置的套接字选项类别

option 选择每个类别中具体的选项

value 要设置的值

eg:

s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #设置端口重用

sockfd.setsockopt(level,option)

功能:获取套接字选项值

参数:level 要获取的套接字选项类别

option 选择每个类别中具体的选项

返回值:获取的值

udp通信--->广播

广播:一点发送,多点接收

广播地址:192.168.56.255(通常为每个网段的最大地址)

设置可以发送接收广播

s.setsockopt(SOL_SOCKET,SO_BROADCAST,1)

广播风暴:一个网络中存在大量广播会占用较多宽带,对网络产生影响

TCP通信-->http协议通信

http协议(超文本传输协议)应用层协议

用途:网页的获取

数据的传输

特点:

1.应用层协议,传输层采用tcp方式收发消息

2.简单,灵活,很多语言都有http协议接口

3.无状态的协议,协议本身不要求记录传输数据

4.http1.1支持持久连接

网页请求过程:

1.客户端(浏览器)通过tcp传输,发送http请求给服务器

2.服务器收到http请求后进行解析

3.服务端处理具体请求内容,整理需要的数据

4.将数据以http响应的格式回发给客户端(浏览器)

5.浏览器接收响应,显示内容

http请求(request)

请求格式

请求行: 具体的请求类别和请求内容

格式: GET / HTTP/1.1

请求类别 请求内容 协议版本

请求类别 每种类别代表不同的事情

GET 获取网络资源

POST 提交一定的信息,得到反馈

HEAD 获取网络资源的响应头

PUT 更新服务器资源

DELETE 删除服务器资源

CONNECT 预留

TRACE 测试

OPTIONS 获取服务器信息

请求头: 对请求内容的基本描述(键值对)

空行

请求体: 请求参数和提交内容

HTTP响应(response)

响应格式

响应行: 反馈响应的基本情况

格式: HTTP/1.1 200 OK

协议版本 响应码 附加信息

响应码: 1xx 提示信息,请求被接收

2xx 响应成功

3xx 响应需要进一步操作,重定向

4xx 客户端错误

5xx 服务器错误

响应头: 对响应内容的描述信息(键值对)

空行

响应体:回复给客户端的具体内容

要求:

1.知道http协议的作用

2.了解网页访问的基本流程

3.掌握http协议 请求和响应的格式

4.知道http请求的基本类型和响应码的含义

IO (input output)

在内存中存在数据交互的操作认为是IO操作

和终端交互:input print

和磁盘交互:read write

和网络交互:recv send

IO密集型程序:在程序执行中有大量的IO操作,而较少的cpu运算,消耗cpu少,效率低,耗时长

计算密集型程序:在程序运行中,IO操作较少,cpu计算较多,cpu消耗大,运行速度快

IO模型:

阻塞IO 非阻塞IO IO多路复用 事件IO 异步IO...

阻塞IO:

阻塞IO是IO的默认形态,是效率很低的一种IO

阻塞情况:

因为某种条件没有达成造成的函数阻塞

e.g. accept input recv

处理IO的时间较长产生的阻塞行为

e.g. 网络延迟,大文件的读写

非阻塞IO

将原本阻塞的函数通过属性的设置改变阻塞行为,变为非阻塞

sockfd.setblocking(bool)

功能:设置套接字为非阻塞IO

参数:默认True 表示套接字调用阻塞函数时为阻塞状态

设置为False则表示非阻塞状态

超时检测:即设置一个最长阻塞等待时间,超时后不再阻塞

sockfd.settimeout(sec)

功能:设置套接字超时时间

参数:设置的时间,秒

超时检测不能和非阻塞同用,否则超时没有意义

IO多路复用

定义:同时监控多个IO事件,选择其中能够执行的IO进行IO事件处理,以此形成可以同时操作多个IO的行为模式,避免一个IO阻塞造成其他IO均无法执行的情况

IO事件就绪:IO已经发生,内核需要交给应用程序处理

具体方法:

import select

select:windows linux unix

poll: linux unix

apoll: linux

select 方法:

rs,ws,xs=select(rlist,wlist,xlist[,timeout])

功能:监控IO事件,阻塞等待IO事件发生

参数:rlist 列表 存放需要等待条件发生的IO

wlist 列表 存放需要主动处理的IO事件

xlist 列表 当发生异常你需要处理的IO事件

timeout 超时时间

返回值:rs 列表 rlist中准备就绪的IO

ws 列表 wlist中准备就绪的IO

xs 列表 xlist中准备就绪的IO

注意: 1.IO多路复用占用计算机资源较少,效率较高

2.wlist中如果有IO则select立即返回处理

3.在IO处理过程中不要出现死循环,影响IO监控

位运算

& 按位与

| 按位或

^ 按位异或

<< 左移

右移

eg:

11 1011

14 1110

11 & 14 1010 一0则0

11 | 14 1111 一1则1

11 ^ 14 0101 相同为0不同为1

11 << 2 101100 向左移动右侧补0

14 >> 2 11 向右移动去掉低位

poll方法

p = select.poll()

功能:创建poll对象

返回值:poll对象

p.register(fd,event)

功能:注册要关注的IO

参数:fd 要关注的IO对象

event 要关注的事件

常用事件类型: POLLIN 读IO rlist

POLLOUT 写IO wlist

POLLERR 出错IO xlist

POLLHUP 断开连接事件

e.g. p.register(sockfd,POLLIN|POLLERR)

p.unregister(fd)

功能:取消关注的IO

参数:IO对象或者文件描述符

events = p.poll()

功能:阻塞等待IO事件发生

返回值:events 是一个列表

[(fileno,event),()...]

每个就绪IO对应一个元组,元组中为该IO的fileno和就绪事件

*返回值中没有IO对象,所以通过fileno配合IO对象字典查找

{fileno:io_obj}

poll_server步骤

1.创建套接字

2.设置套接字为关注

3.建立fileno查找字典

4.循环监控IO

5.处理发生的IO

epoll方法

使用方法:基本同poll相同

*将生成对象函数改为epoll

*将所有关注IO时间类型改为EPOLL类型

epoll特点:

*epoll是linux的专属多路复用方法

*epoll效率比select和poll要高

*epoll可以监控更多的IO(select最多1024)

*epoll支持更多的触发事件类型(EPOLLET边缘触发)

结构化数据

import struct

原理:将部分数据类型放在一起,转换成bytes格式数据包,并且可以按照指定格式解析bytes数据包

struct.Struct(fmt)

功能:生成struct格式包对象

参数:fmt 定制的数据结构组成

e.g. 1 b'JAME' 1.75 #要发送的一组数

st = Struct('i4sf') #生成对应数据格式对象

常用fmt int i

bytes ns(n代表几个字符)

float f

st.pack(v1,v2,v3...)

功能:将数据按照指定格式打包为bytes

参数:要打包的数据

返回值:打包后的bytes子节串

st.unpack(bytes)

功能:将bytes格式数据包解析

参数:要解析的数据包

返回值:数据元组

pack()和unpack()可以通过struct模块直接调用

struct.pack(fmt,v1,v2...)

struct.unpack(fmt,data)

本地套接字

功能:本地两个程序之间利用套接字进行通信的一种方法

cookie:

Linux 文件类型: b(块设备文件)

c(字符设备文件)

d(目录)

-(普通文件)

l(链接文件)

s(套接字文件)

p(管道文件)

1.创建本地套接字

sockfd = socket(AF_UNIX,SOCK_STREAM)

2.绑定本地套接字文件

sockfd.bind(path)

3.监听

sockfd.listen()

4.连接

sockfd.accept()

5.消息收发

recv send

多任务编程

意义:充分利用计算机的多核资源,同时运行多个任务,以提高程序的执行效率

实现方法:多进程 多线程

并发:同时处理多个任务,内核在任务不间断的切换达到好像多个任务被同时执行的效果,实际每一时刻只有一个任务占有cpu

并行:多个任务利用计算机多核资源在同时执行,此时多个任务间是并行关系

进程(process)

定义:程序在计算机中的一次运行过程

程序:是一个可执行的文件,是静态的,占有磁盘

进程:进程是一个动态过程,占有计算机资源,有生命周期

进程的诞生

1.用户空间通过启动执行程序,或者调用进程创建接口发起进程创建请求

2.操作系统接收用户请求,开始创建进程

3.操作系统分配计算机资源,确定进程状态,开辟空间等

4.操作系统将进程提供给用户使用

CPU时间片:如果一个进程占有cpu内核则称这个进程在CPU时间片上

PCB(进程控制快):在内存中开辟的一块空间,存放进程的基本信息,也是操作系统调用进程的重要标志

进程ID(PID):系统为每个进程分配一个不重复的ID号,是该进程在系统中的标识

查看进程信息:ps -aux

父子进程:系统中每一个进程都有唯一的父进程,可以有0个或者多个子进程

查看进程树:pstree

进程状态:

三态:

就绪态:进程具备执行条件,等待系统分配cpu

运行态:进程占有cpu时间片运行

等待态:进程暂时阻塞,不具备执行条件

五态:

新建态:创建一个新的进程,获取资源的过程

终止态:进程结束,释放资源的过程

查看进程状态: ps -aux STAT列

S 等待态

D 等待态

T 暂停态

R 运行态

Z 僵尸

进程优先级:

作用:决定进程的优先权限和占有资源的优先程度

Linux 优先级范围:-20---19 数字越小优先级越高

查看优先级: top

以某个优先值运行:nice -num process

eg: 以9运行进程 nice -9 python3 while.py

< 有较高优先级的

N 有较低优先级的

前台进程

s 会话组组长

l 有进程链接

后台运行进程:运行命令后加 &

eg python3 while.py &

杀死一个进程:kill -9 PID

要求:

1.什么是进程,进程和程序的区别

2.进程有哪些状态,各种状态如何转换

进程运行特征

1.进程可以使用计算机多核资源

2.进程是计算机分配资源的最小单位

3.进程之间的运行互不干扰,相互独立

4.每个进程空间独立,有自己的空间资源

进程创建

import os

pid = os.fork()

功能:创建新的进程

返回值:失败 返回一个负数

成功 原进程中返回新进程的PID号

新进程中返回0

关于 fork

*子进程从fork的下一句开始执行

*子父进程各自独立运行,运行顺序不一定

*if结构几乎是fork的固定搭配,通过父子进程返回值的区别,使其执行不同的代码

*子进程会复制父进程的全部代码段和内存空间,包括fork前所有开辟的空间

*父子进程空间互不影响,各自修改各自空间内容

*子进程也有自己独立的内容,比如PID指令集PCB等

进程函数

os.getpid()

功能:获取当前的pid号

返回值:返回pid

os.getppid()

功能:获取父进程的pid号

返回值:返回pid

os._exit(status)

功能:退出进程

参数:整数 表示进程的退出状态

sys.exit([status])

功能:退出进程

参数:整数 表示进程的退出状态 默认为0

字符串 表示进程退出时打印该字符串

孤儿进程:父进程先于子进程退出,此时子进程就成为孤儿进程

*孤儿进程会被系统进程收养,即该系统进程成为孤儿进程新的父进程

僵尸进程:子进程先于父进程退出,且父进程没有处理子进程退出行为,此时子进程就会成为僵尸进程

*僵尸进程虽然结束但是会存留部分进程信息在内存中,大量的僵尸进程会消耗系统资源,因此应该避免僵尸进程产生

如何处理僵尸进程:

*二级子进程

pid,status=os.wait()

功能:父进程中阻塞等待处理子进程的退出

返回值:pid 退出的子进程的PID号

status 子进程退出状态

pid,status=os.waitpid(pid,option)

功能:父进程中等待处理子进程的退出

参数:pid -1表示等待任意子进程退出

>0表示等待对应PID的子进程退出

option 0 表示阻塞等待

WNOHANG 表示非阻塞

返回值:pid 退出的子进程的PID号

status 子进程退出状态

注意:

如果子进程从父进程继承对象,则父子进程使用此对象可能会互相影响

但是如果对象是子进程创建后,在各自进程中生成的则一定没有影响

群聊聊天室

功能:类似QQ群聊

1.进入聊天室需要输入姓名,姓名不能重复

2.进入聊天室会向其他成员发送通知

xxx进入了聊天室

3.一个人发消息,其他人都会收到

xxx 说:xxxxxxxxxx

4.某人退出了聊天室,其他人也会收到通知

xxx 退出了聊天室

5.管理员消息可以从后台发送消息,此时群里人都收到

管理员 说 xxxxxxxxxx

技术分析

发消息:udp套接字

成员存储 服务端 {name:addr} [(name,addr),()]

消息发送模式:转发,消息先发给服务器,服务器转发给其他成员

管理员:服务端 发送内容,其他人接收

保证收发消息互不影响:使用多进程

整体设计

1.封装方式

2.编写流程 先确保通信实现 再逐个功能实现

3.测试 实现一个功能测试一个功能

具体功能实现

网络通信

服务端:创建udp套接字,绑定地址

客户端:创建套接字

进入聊天室

客户端:输入用户名

将用户名发送给服务端

得到一个反馈

如果不允许进入则重新输入姓名

如果允许进入聊天室则进入聊天

创建新的进程一个用于接收,一个用于发送

服务端:接收姓名

判断姓名是否存在

如果存在返回不允许进入

如果不存在则将用户加入存储结构

通知其他人登录信息

聊天:

服务端:接收消息

转发消息

客户端:接收消息

退出:

服务端:接收退出请求

发送通知给其他人

给退出者发送EXIT

从user删除用户

客户端:发送提出信息

父子进程退出

multiprocessing 模块创建进程

1.将需要执行的进程事件封装为函数

2.使用模块中的Process 类生成进程对象,并关联相关函数

3.可以通过对象属性设置进程信息

4.通过进程对象启动进程,此时自动运行进程函数

5.回收进程

Process()

功能:创建进程对象

参数:target 绑定的目标函数

name 进程名 默认Process-1

args 元组,按位置给target函数传参

kwargs 字典,按照键值对给target函数传参

p.start()

功能:启动进程

*target绑定的函数会自动执行,此时进程真正被创建

p.join([timeout])

功能:阻塞等待回收进程

参数:超时时间

使用multiprocess创建进程,同样子进程复制父进程全部代码空间,父子进程互不影响,各自运行,子进程相当于只执行绑定函数

*join可以处理僵尸进程

*multiprocessing 中父进程可以更方便创建多个进程执行多个文件,父进程往往将事件交由子进程完成

进程对象属性

p.name 进程名称

p.pid 进程号

p.is_alive() 进程状态(查看是否在生命周期)

p.daemon

默认是False 表示主进程退出不会影响子进程继续运行

如果设置为True,此时主进程退出,子进程也会结束

*要求在start()前设置

自定义进程类

适用情况:使用类将一系列功能进行封装,通过调用类中的方法完成较复杂的功能

步骤:

1.继承Process类

2.编写自己的__init__添加属性,使用super重写加载父类__init__方法

3.重写run方法,完成功能的逻辑调用

使用:

1.使用自定义类实例化对象

2.使用实例化对象调用start()则创建新的进程,自动运行run方法

3.使用对象调用join()则回收进程

多进程

优点:可以使用计算机多核资源,同时运作多个任务,提高运行效率

缺点:进程的创建和删除过程需要消耗较多的系统资源,大量进程频繁的创建删除会给系统带来压力

进程池技术

产生原因:应对大量任务需要多进程完成,需要频繁创建删除进程的情况

原理:创建一定量的进程作为进程池,用来处理事件,事件处理完毕后,进程不退出,而是继续等待处理其他事件,直到所有待处理事件处理完毕后统一销毁进程,

增加了进程的重复利用,降低资源消耗

使用方法:

1.创建进程池,放入适当的进程

2.将要做的事件封装为函数,放入进程池等待队列

3.进程池中的进程会不断执行队列中的事件直到全部被执行

4.关闭进程池,回收进程

from multiprocessing import Pool

Pool(processes)

功能:创建进程池对象

参数:指定进程池中进程数据量,默认根据系统自动判定

pool.apply_async(func,args,kwds) #异步执行,非阻塞式

功能:放入要执行的事件函数

参数:func 事件函数

args 元组 给函数按位置传参

kwds 字典 给函数按键值传参

返回值:返回函数事件对象,调用get()可以获取func返回值

pool.apply(func,args,kwds) #同步执行,阻塞式

功能:放入要执行的事件函数

参数:func 事件函数

args 元组 给函数按位置传参

kwds 字典 给函数按键值传参

pool.map(func,iter)

功能:将函数事件放入进程池执行

参数: func 要执行的函数

iter 可迭代对象,用于func传参

返回值:函数的返回值列表

pool.close()

功能:关闭进程池,无法再添加新的事件

pool.join()

功能:阻塞等待回收进程池

进程间通信(IPC)

原因:进程空间相对独立,资源无法直接获取,此时在不同进程间需要消息传输,即进程间通信

进程间通信方式:管道 消息队列 内存共享 信号 信号量 套接字

管道通信(pipe)

通信原理:在内存中开辟管道空间,生成管道操作对象,进程间使用同一个管道对象进行读写实现通信

from multiprocessing import Pipe

fd1,fd2 = Pipe(duplex = True)

功能:创建管道

参数:默认表示双向管道

False 表示单向管道

返回值:表示管道两端的读写对象

如果是双向管道,则fd1,fd2均可读写

如果是单向管道,则fd1只读,fd2只写

fd.recv()

功能:从管道读取内容

返回值:读到的内容

*如果管道为空则阻塞

fd.send(data)

功能:向管道写入内容

参数:要写入的内容

*可以写入python格式数据

消息队列(queue)

队列:先进先出

通信原理:在内存中建立队列模型,进程通过队列对象将消息存入到队列,或者从队列取出消息,完成进程间通信,被先放入的消息一定先取出

from multiprocessing import Queue

q = Queue(maxsize=0)

功能:创建消息队列

参数:表示队列中最多存放多少消息

返回值:队列对象

q.put(data,[block,timeout])

功能:向队列存入消息

参数:data 要存入的内容 python数据

block 默认队列满会阻塞,设置为False则为非阻塞

timeout 超时检测

q.get([block,timeout])

功能:从队列中获取消息

参数:block 默认队列空阻塞,设置False为非阻塞

timeout 超时检测

返回值:获取到的数据

q.full() 判断队列是否为满

q.empty() 判断队列是否为空

q.qsize() 获取队列中消息个数

q.close() 关闭队列

共享内存

通信原理:只在内存中开辟一个区域,对多进程可见,进程可以写入内容或读取内容,但是每次写入内容都会覆盖之前的

from multiprocessing import Value,Array

obj = Value(ctype,data)

功能:开辟共享内存

参数:ctype 字符串 表示共享内存中的数据类型

常用格式:int-->'i'

float-->'f'

char(bytes)--->'c'

data :初始化数据

obj.value 属性 即共享内存中的值,对该属性的读取和修改即修改共享内存

obj.Array(ctype,obj)

功能:创建共享内存

参数:ctype 要存储的数据结构

obj 列表(字符串) 表示共享内存中初始数据

数字 空间结构的大小

返回值:共享内存对象

*可以通过遍历或者[]序列号方式获取共享内存值,或改写内存共享值

*如果共享内存中是字节串,可以通过obj.value获取该字符串

管道 消息队列 共享内存

开辟空间 内存 内存 内存

读写方式 两端读写 先进先出 覆盖之前内容

效率 一般 一般 较高

使用特点 多用于父子进程 第三方库较多 操作需要注意争夺内存资源

信号量(信号灯)

原理:给定一个数量,对多个进程可见,多个进程可以通过方法操作这个数量,达到协同工作的目的

from multiprocessing import Semaphore

sem = Semaphore(num)

功能:创建信号量

参数:初始化信号量

返回值:信号量对象

sem.acquire()

功能:将消耗一个信号量,当信号量为0会阻塞

sem.release()

功能:增加一个信号资源

sem.get_value()

功能:获取信号量资源数量

多任务编程之 线程(Thread)

什么是线程?

1.线程也是多任务编程方法

2.线程也可以使用计算机多核资源

3.线程被称为轻量级的进程,也是运行状态的概念

4.一个进程中可以包含多个线程,线程是进程的一部分

5.线程是系统分配内核的最小单位

线程特征:

1.线程也是运行状态,有生命周期,消耗计算机资源

2.多个线程之间独立运行,互不干扰

3.一个进程内的线程共享进程资源

4.线程的创建,删除消耗的系统资源远远小于进程

5.线程也有自己独立的资源,栈空间,命令集,ID等

threading 模块创建线程

from threading import Thread

t = Thread()

功能:创建线程对象

参数:target 绑定线程函数

args 元组 给线程函数传参

kwargs 字典 给线程函数传参

name 线程名 默认Thread-1

t.start() 启动线程,自动运行线程函数

t.join([timeout]) 阻塞等待回收线程

线程对象属性

t.name 线程名称

t.setName() 设置名称

t.getName() 获取名称

t.is_alive() 线程状态

threading.currentThread() 获取当前线程对象

t.daemon 默认为False此时主线程退出分支线程继续执行,如果设置为True则主线程退出分支线程也结束执行

t.setDaemon(True) 设置daemon属性

t.isDaemon() 查看daemon属性

*在start前设置,通常不和join()同用

自定义线程类

步骤:

1.继承Thread类

2.添加自己的属性__init__,加载父类__init__

3.重写run

4.使用自己的类生成线程对象,调用start()会自动以一个线程执行run

线程的通信

通信方法:使用进程空间中的全局变量通信

注意事项:共享资源争夺,往往需要同步互斥机制协调

线程同步互斥

共享资源(临界资源):多个线程都可以操作的资源称为共享资源

临界区:指一段代码,对临界资源操作的代码段

同步:同步是一种合作关系,为完成任务,多个进程或者线程之间形成一种协调调度,按照必要的步骤有序执行一系列操作

互斥:互斥是一种制约关系,当一个进程或者线程使用临界资源时会进行加锁处理,此时另一个进程或者线程就无法操作,直到解锁后才能操作

线程同步互斥方法

线程 event

from threading import Event

e = Event()

功能:创建事件对象(事件对象初始为未设置状态)

e.wait([timeout])

功能:如果e是未设置的状态则阻塞

如果e是被设置的状态则不阻塞

参数:timeout 超时时间

e.set() 将e变为被设置状态

e.clear() 清除e的设置

e.is_set() 判断当前e的状态 设置-->True

未设置-->False

线程锁 Lock

from threading import Lock

lock = Lock() #创建锁对象

lock.acquire() #上锁,如果已经上锁调用该函数阻塞

lock.release() #解锁

with lock: 上锁

...

...

with代码段结束则解锁

python线程的GIL问题(全局计时器锁)

GIL : 由于python解释器中加入了全局解释器锁,导致python解释器同一时刻只能解释一个线程,所以大大降低了多线程的执行效率

后果: python线程一般只能用在大量IO阻塞存在,或者高延迟的IO程序中,遇到阻塞,线程会自动让出解释器,而在cpu密集型程序中,python线程效率低下

GIL问题建议:

*尽量使用多进程完成并发

*不使用c作为解释器情况没有GIL问题 java c#

*使用多种组合方案完成并发

进程和线程的区别和联系:

1.两者都是多任务编程方式,都能够使用计算机内核,都是动态运行的过程,占有计算机资源

2.进程的创建,删除消耗资源要高于线程

3.一个进程可以包含多个线程

4.进程空间独立,数据互不干扰,有专门的IPC,线程使用全局变量通信

5.多个线程共享进程的全局资源,资源操作时往往需要同步互斥方法

6.进程线程在系统中都有特有属性,如ID代码段,命令集等

使用场景

*一个任务包含多个分支任务,且需要消耗资源少时用线程

*不同的独立任务,需要空间独立(方便资源的使用管理)用多进程

*IO多时,可能选择 进程+IO多路复用,或者看通信中编码逻辑复杂程度

要求:

1.进程线程区别有哪些

2.你在什么情况下使用线程或者进程

3.什么是同步互斥,你在什么情况下使用,如何使用

4.进程间的通信方法之道哪些,有什么特点

5.你是如何处理僵尸进程的

6.进程池原理是什么,怎么用

网络通信模型

什么是服务器?

硬件服务器:主机 集群

软件服务器:编写的后端服务程序,在硬件服务器系统上运行,提供一定的后端服务

httpserver-->处理http请求

webserver-->处理网站后端服务

邮箱服务器-->邮件收发

文件服务器-->文件下载上传

架构模型: C/S 客户端服务器模型

B/S 浏览器服务器模型

网络通信模型:

循环模型:循环接收客户端请求,处理请求,同一时刻只处理一个请求,处理完毕后再处理下一个

优点:实现简单,占用资源少

缺点:无法同时处理多个客户端请求

适用情况:处理任务可以很快完成的情况,不需要建立并发,UDP比TCP更适合循环

IO并发模型:IO多路复用 协程

优点:能同时处理多个IO,资源消耗少

缺点:只能监控IO事件,当多任务是CPU运算时无法同时处理

多进程多线程并发模型:每当有一个客户端连接则创建一个新的进程或者线程处理客户端请求

优点:每个客户端可以长期占有服务器,使用多核进行IO处理或者CPU运算

缺点:资源消耗较多

多进程并发

基于fork完成多进程并发

1.创建套接字,绑定,监听

2.等待接收客户端请求 accept

3.当新的客户端连接后,创建新的进程处理客户端请求

4.原有进程继续等待其他客户端连接

5.如果客户端退出,则销毁对应连接

ftp文件服务器

功能:1.服务器分为客户端和服务端两部分,要求启动服务端可以同时有多个客户端操作

2.客户端可以查看服务器文件库中的文件(客户端只能查看普通文件,不包含隐藏文件)

3.客户端可以选择文件库中的文件进行下载

4.客户端也可以上传本地文件到文件库

5.使用print打印移动的命令提示界面

技术分析:fork并发 tcp套接字

判断普通文件:os.path.isfile()

查看文件列表:os.listdir()

结构设计:将2,3,4三个功能封装在一个类中

工作步骤:1.搭建网络

2.封装类

3.功能函数实现并测试

网络TCP并发

服务端:fork tcp 并发服务器模型

客户端:连接服务端

基于threading的多线程并发

步骤:

1.创建套接字,绑定,监听

2.接收客户端连接

3.创建新的线程处理客户端请求

4.主线程继续等待其他客户端连接

5.当客户端退出时处理对应的线程

集成模块完成多进程/线程socket并发

python2 SocketServer

python3 socketserver

功能:通过模块提供的不同类的组合完成多进程或者多线程tcp/udp并发程序

使用步骤:

1.创建服务器类,通过选择继承模块中的TCPServer或者UDPServer确定服务器类型,继承多进程或者多线程类确定并发类型

2.创建请求处理类,根据服务器类型选择继承流式套接字处理还是数据报套接字处理

3.通过服务器类创建服务器对象,并绑定处理类

4.通过服务器对象调用server_forever()启动服务

5.当客户端发起请求后,会自动调用请求处理类中handle()方法处理

HTTPServer V2.0

功能:

1.接收客户端请求

2.解析客户端请求

3.组织数据,形成http响应格式

4.将响应内容回发给客户端

升级:

1.基本的请求解析,根据具体请求返回具体内容

2.采用多线程并发,可以满足多个客户端的同时访问

3.除了网页也可以获取一些简单数据

4.使用类进行httpserver封装

技术点:

*使用tcp套接字 socket多线程并发

*类的封装:使用类实例化对象,对象调用启动接口

*http请求和响应格式

请求:

请求行 GET /abc.html HTTP/1.1

请求头

空行

请求体

响应:

响应行 HTTP/1.1 200 OK

响应头

空行

响应体 具体内容

协程

定义:纤程,微纤程.是为非抢占式多任务产生子程序的计算机程序组件

协程允许不同入口点,在不同位置暂停或者开始执行,简单来说,协程就是可以暂停执行的函数

*yield是实现协程的基本关键字

协程原理:记录一个函数栈的上下文,进行协程的切换调度,当一个携程函数暂停执行时会将上下文栈帧保存起来,在切换回来时恢复到原来的执行位置,从而继续执行

协程优点:

1.协程可以同时处理多个任务

2.协程本质是单线程,资源消耗少

3.协程无需切换的开销,无需同步互斥

协程缺点:

1.无法利用计算机多核

greenlet

安装: sudo pip3 install greenlet

greenlet.greenlet(func)

功能:创建协程对象

参数:协程函数

g.switch()

功能:启动协程函数

gevent

安装: sudo pip3 install gevent

gevent.spawn(func,argv)

功能:生成协程对象

参数:func 协程函数

argv 给协程函数传参

返回值:返回协程对象

*当函数中遇到gevent类型阻塞则会跳出

gevent.joinall(list,[timeout])

功能:阻塞等待回收协程

参数:list 协程对象列表

timeout 超时时间

gevent.sleep(sec):

功能:提供协程阻塞

from gevnet import monkey

monkey.patch_all()

功能:修改原有的IO阻塞行为,可以触发协程事件跳转

*必须在模块导入前设置

cookie

import getpass

getpass.getpass()

功能:隐藏密码输入

正则表达式

动机:

1.文本处理已经成为计算机的常见工作

2.对文本内容的搜索,定位,提取是逻辑比较复杂的工作

3.为了快速方便的解决上述问题,产生了正则表达式

定义:

即文本的高级匹配模式,提供搜索替换等功能,其本质是一系列由字符和特殊符号组成的字符串,这个字符串即正则表达式

匹配原理:

由普通的字符和特殊符号构成,通过描述字符串的重复,位置,种类等行为达到匹配某一类字符串的目的

正则特点:

*方便处理文本

*支持语言众多

*使用灵活多样

目标:

1.熟练掌握正则表达式符号

2.读懂正则表达式,编写简单的正则表达式

3.能够使用python re操作正则表达式

python-->re模块

re.findall(pattern,string)

功能:使用正则表达式匹配字符串

参数:pattern 正则表达式

string 目标字符串

返回值:返回匹配内容列表

元字符的使用

1.普通字符

元字符:a B c

匹配规则:每个字符匹配对应的自身字符

2.或

元字符: |

匹配规则:匹配 | 两边任意一个正则表达式

re.findall('ab|cd','abfsdghiugscd')

['ab', 'cd']

3.匹配单个字符

元字符:.

匹配规则:匹配除换行外任意一个字符

f.o --> foo fao

re.findall('f.o','foo fao')

['foo', 'fao']

4.匹配字符串开始位置

元字符:^

匹配规则:匹配目标字符串的开始位置

re.findall('jame','hi,jame')

['jame']

re.findall('^jame','hi,jame')

[]

5.匹配字符串结束位置

元字符:$

匹配规则:匹配目标字符串的结束位置

>>> re.findall('jame$','hi,jame')

['jame']

6.匹配重复

元字符:*

匹配规则:匹配前面的字符出现0次或多次

>>> re.findall('fo*','foooooaabcdeffo')

['fooooo', 'f', 'fo']

7.匹配重复

元字符: +

匹配规则:匹配前面的字符出现1次或多次

>>> re.findall('fo+','foooo0')

['foooo']

>>> re.findall('A.','A')

['A']

>>> re.findall('A.+','A')

[]

8.匹配重复

元字符:?

匹配规则:匹配前面的字符出现0次或1次

>>> re.findall('fo?','foooooaabcdeffo')

['fo', 'f', 'fo']

9.匹配重复

无字符:{n}

匹配规则:匹配前面的字符重复指定的次数

fo{3}--->fooo

>>> re.findall('fo{3}','foooofdsgkl')

['fooo']

10.匹配重复

元字符:{m,n}

匹配规则:匹配前面的字符出现m--n次

fo{2,4}-->foo fooo foooo

>>> re.findall('fo{2,4}','fooooffoodsgkl')

['foooo', 'foo']

11.匹配字符集

元字符:[字符集]

匹配规则:匹配字符集中任意一个字符

[abc123]--> a b c 1 2 3

[a-z] [A-F] [0-9]

>>> re.findall('[A-Z][_a-z]','Hi,This is Lua')

['Hi', 'Th', 'Lu']

>>> re.findall('[A-Z][-_a-z]','Hi,This is Lua')

['Hi', 'Th', 'Lu']

>>> re.findall('[A-Z][-_a-z]','Hi,This is Lua')

['Hi', 'This', 'Lua']

12.匹配字符集

元字符:[^...]

匹配规则:匹配除指定字符外的任意一个字符

[^abc]-->除了a b c 外任意一个字符

[^a-z]

>>> re.findall('[^ ]+','Hi,This is Lua')

['Hi,This', 'is', 'Lua']

13.匹配任意(非)数字字符

元字符: \d \D

匹配规则: \d 匹配任意一个数字字符 [0-9]

\D 匹配任意一个非数字字符 [^0-9]

>>> re.findall('\d+','2018年就快过去了,2019马上到来')

['2018', '2019']

>>> re.findall('\D+','2018年就快过去了,2019马上到来')

['年就快过去了,', '马上到来']

14.匹配任意(非)普通字符

元字符: \w \W

匹配规则: \w 匹配普通字符(数字字母下划线,utf-8字符)

\W 匹配特殊字符

>>> re.findall('\w+','2018年就快过去了,2019马上到来_adjafk21323213')

['2018年就快过去了', '2019马上到来_adjafk21323213']

>>> re.findall('\w+',' 2018年就快过去了,2019马上到来_adjafk@+-=/?')

['2018年就快过去了', '2019马上到来_adjafk']

>>> re.findall('\W+',' 2018年就快过去了,2019马上到来_adjafk@+-=/?')

[' ', ',', '@+-=\/?']

15.匹配任意(非)空字符

元字符: \s \S

匹配规则:\s 匹配任意空字符 [ \r\n\t\v\f]

\S 匹配任意非空字符

>>> re.findall('\s+','h\fello w\no\tr\vld\r')

['\x0c', ' ', '\n', '\t', '\x0b', '\r']

>>> re.findall('\S+','h\fello w\no\tr\vld\r')

['h', 'ello', 'w', 'o', 'r', 'ld']

16.匹配字符串开头结尾位置

元字符: \A \Z

匹配规则:\A 匹配字符串开头位置

\Z 匹配字符串结尾位置

绝对匹配(完全匹配):保证正则表达式匹配目标字符串的全部内容

>>> re.findall('\A\d+-\d+\Z','1000-15000')

['1000-15000']

>>> re.findall('\A\d+\Z','1000-15000')

[]

17.匹配(非)单词边界

元字符:\b \B

匹配规则:\b 匹配单词边界(普通字符和其它字符的交界)

\B 匹配非单词边界

>>> re.findall('\bis\b','This is a boy')

[]

>>> re.findall(r'\bis\b','This is a boy')

['is']

>>> re.findall(r'\Bis\b','This is a boy')

['is']

>>> re.findall(r'\Bis\B','This is a boy')

[]

元字符总结

匹配单个字符:. [...] [^...] \d \D \w \W \s \S

匹配重复:* + ? {n} {m,n}

匹配位置:^ $ \A \Z \b \B

其它:| () \

正则表达式的转义

正则特殊符号:. * + ? ^ $ | [] ()

正则表达式如果匹配特殊字符则需加

e.g. 匹配字符. 用.

目标字符串 正则表达式 字符串

$10 $\d+ "\$\d+"

raw字串:对字符串不进行转义解析

r'$\d+' ==>'\$\d+'

贪婪和非贪婪

贪婪模式:正则表达式的重复匹配默认总是尽可能多的向后匹配内容

? {m,n}

非贪婪(懒惰)模式:尽可能少的匹配内容

贪婪-->非贪婪 *? +? ?? {m,n}?

re.findall(r'ab+?','abbbbbbbbbbb')

['ab']

正则表达式分组

使用()可以为正则表达式内部建立内部分组.通过分组构建正则表达式的内部整体处理部分

*子组是正则表达式的一部分,子组需要在整体能够匹配内容的前提下发挥作用

子组的作用:

1.作为一个内部整体,改变某些元字符的操作对象

re.search(r'\w+@\w+.(com|cn)','abc@123.cn').group()

'abc@123.cn'

2.在匹配到内容的前提下,子组对应内容可以单独提取

re.search(r'(http|https|ftp)://\S+','http://www.baidu.com').group(1)

'http

re.search(r'(http|https|ftp)://\S+','http://www.baidu.com').group(0)

'http://www.baidu.com'

捕获组和非捕获组

捕获组:被命名的子组

格式:(?Ppattern)

作用:名字可以表达一定的含义,也可以通过名字来提取子组对应的内容

>>> re.search(r'(?P[A-Z])\w*','Hello world').group()

'Hello'

*一个正则表达式中可以有多个子组.多个子组尽量不要重叠或者过多嵌套,通常由内到外,从左到右分为第一第二子组

((ab)cd)(ef))

re.search(r'((ab)cd)(ef)','abcdefgh').group(0)

'abcdef'

re.search(r'((ab)cd)(ef)','abcdefgh').group(1)

'abcd'

re.search(r'((ab)cd)(ef)','abcdefgh').group(2)

'ab'

re.search(r'((ab)cd)(ef)','abcdefgh').group(3)

'ef'

正则表达式的设计原则:

1.正确性:能够正确的匹配目标字符串

2.排他性:除了要匹配的内容,尽可能不会匹配到其它多余内容

3.全面性:尽可能将目标字符串的全部情况考虑全面,不遗漏

re模块

regex = compile(pattern,flags=0)

功能:生成正则表达式对象

参数:pattern 正则表达式

flags 功能标识,用来丰富正则表达式内容

返回值:正则表达式对象

re.findall(pattern,string,flags=0)

功能:使用正则表达式匹配目标字符串

参数:pattern 正则表达式

string 目标字符串

flags 标志位

返回值:列表,匹配到的内容

如果正则表达式有子组则只获取子组对应内容

regex.findall(string,pos,endpos)

功能:使用正则表达式匹配目标字符串

参数:string 目标字符串

pos 匹配目标的起始位置

endpos 匹配目标的结束位置

返回值:列表,匹配到的内容

如果正则表达式有子组则只获取子组对应内容

re.split(pattern,string,flags=0)

功能:使用正则匹配到的部分切割目标字符串

参数:pattern 正则表达式

string 目标字符串

flags 标志位

返回值 : 返回列表,为切割后内容

re.sub(pattern,replaceStr,string,max,flags)

功能: 使用字符串替换匹配到的内容

参数: replaceStr 替换字串

max 最多替换几处 默认全部替换

返回值 : 返回替换后的字符串

re.subn(pattern,replaceStr,string,max,flags)

功能参数同sub

返回值 : 返回替换后的字符串和替换数量

re.finditer(pattern,string,flags)

功能 : 使用正则表达式匹配目标字符串

参数 : pattern 正则

string 目标字串

flags 标志位

返回值 : 返回一个包含匹配内容的迭代对象

re.fullmatch(pattern,string,flags)

功能 : 完全匹配一个目标字符串

参数 : pattern 正则

string 目标字串

flags 标志位

返回值 : match对象

re.match(pattern,string,flags)

功能 : 匹配一个目标字符串开始位置

参数 : pattern 正则

string 目标字串

flags 标志位

返回值 : match对象

search(pattern,string,flags)

功能 : 匹配目标字符串第一处匹配内容

参数 : pattern 正则

string 目标字串

flags 标志位

返回值 : match对象

compile对象属性变量

pattern : 正则表达式

flags : 标志位值

groupindex : 获取捕获组名和对应序列号的字典

groups: 子组数量

match对象的属性和函数

属性

pos #目标子串开始位置

endpos #目标子串结束位置

re #正则表达式

string #目标字符串

lastgroup #最后一组组名

lastindex #最后一组序列号

方法

start() #匹配内容的开始位置

end() #匹配内容的结束位置

span() #匹配内容的起止位置

group(n)

功能 : 获取match对象的对应内容

参数 : 默认为0表示获取match对象所有对应内容

如果为>0整数则表示某个子组对应内容

如果为子组名称则表示对应捕获组匹配内容

返回值: 返回对应字符串

groupdict()

功能: 获取捕获组和对应值的字典

groups()

功能: 获取所有子组对应内容

flags参数使用

使用参数:re.compile re.findall re,search...

作用:辅助正则表达式,丰富匹配效果

I == IGNORECASE 匹配时忽略字母大小写

S == DOTALL 作用于元字符 . 使其可以匹配换行

M == MULTILINE 作用于元字符 ^ $ 使其可以匹配每一行的开头结尾位置

A == ASCII 使\w\W\d\D\b\B 匹配ASCII字符

L == LOCAL 是\w\W\b\B匹配本地字符

U == UNICODE 使\w\W\d\D\b\B 匹配UNICODE字符

X == VERBOSE 详细模式,忽略空白可以加入模式

同时使用多个flags:

e.g. re.I|re.X

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值