python并发与网络编程

4 篇文章 0 订阅
2 篇文章 0 订阅

python并发与网络编程

Linux 操作系统及其组成

1.操作系统的作用

​ 操作系统 : 与硬件交互

​ 应用程序 --> 请求调用操作系统功能 --> 硬件调用(cpu,内存)

​ 操作系统(OS)是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需
要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系
统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。

2.Linux 操作系统组成

一个典型的 Linux 操作系统组成为:Linux 内核,文件系统,命令行 shell,图形界面和桌面环境,并包各种工
具和应用软件。

1:Linux 内核: Linux 操作系统的核心代码

2:文件系统:通常指称管理磁盘数据的系统,可将数据以目录或文件的型式存储。每个文件系统都有自己的特
殊格式与功能

3:shell 命令: 接收用户命令,然后调用相应的应用程序,并根据用户输入的指令来反馈给用户指定的信息。

在这里插入图片描述

shell 命令:文件操作命令

linux 下的目录结构

在这里插入图片描述

Linux常用命令

1、pwd  			  --print wrok directory   查看当前所在文件夹
2、cd 目录名   		 --change directory		  切换文件夹
3、ls,ls -l,ls -a   --list					查看当前文件夹下的内容
4、mkdir 目录名		 --make directory		  创建目录
5、touch	目录名	 	 --touch				  如果文件不存在,新建文件
6、rm 文件名		 --remove 				  删除指定的文件名
7、rm -r liu  	   --remove					删除文件夹liu
8、clear			   --clear					清屏
8、tar 		 	   --
9、cp    	 	   --copy					复制文件或者目录
10、mv		 	   --						移动文件
11、cat			   --						查看文件内容

IO

  1. 定义

在内存中存在数据交换的操作认为是IO操作,比如和终端交互 ,和磁盘交互,和网络交互等

  1. 程序分类
  • IO密集型程序:在程序执行中有大量IO操作,而cpu运算较少。消耗cpu较少,耗时长。
  • 计算密集型程序:程序运行中计算较多,IO操作相对较少。cpu消耗多,执行速度快,几乎没有阻塞。

文件

​ 文件是保存在持久化存储设备(硬盘、U盘、光盘…)上的一段数据。从格式编码角度分为文本文件(打开后会自动解码为字符)、二进制文件(视频、音频等)。在Python里把文件视作一种类型的对象,类似之前学习过的其它类型。

字节串(bytes)

​ 在python3中引入了字节串的概念,与str不同,字节串以字节序列值表达数据,更方便用来处理二进程数据。因此在python3中字节串是常见的二进制数据展现方式。

  • 普通的ascii编码字符串可以在前面加b转换为字节串,例如:b’hello’
  • 字符串转换为字节串方法 :str.encode()
  • 字节串转换为字符串方法 : bytes.decode()

文件读写

对文件实现读写的基本操作步骤为:打开文件,读写文件,关闭文件

#所有文件都可以用二进制方式打开(b)
#但是二进制格式文件则不能用文本方式打开(后续读写出错)
f = open('a.py','r+') # 要求文件存在
f = open('a.py','w') # 文件不存在创建存在清空
f = open('a.py','a') # 文件不存在创建,存在追加
f = open('a.py','rb') # 加 b后续的读写都以字节串操作
f.close() #关闭文件对象
  1. 打开文件
file_object = open(file_name, access_mode='r', buffering=-1)
功能:打开一个文件,返回一个文件对象。
参数:file_name  文件名;
     access_mode  打开文件的方式,如果不写默认为‘r’ 
          文件模式                        操作
              r                    以读方式打开 文件必须存在
              w                    以写方式打开
                                   文件不存在则创建,存在清空原有内容 
              a                    以追加模式打开 
              r+                   以读写模式打开 文件必须存在
              w+                   以读写模式打开文件
                                   不存在则创建,存在清空原有内容
              a+                   以读写模式打开 追加模式
              rb                   以二进制读模式打开 同r
              wb                   以二进制写模式打开 同w
              ab                   以二进制追加模式打开 同a
              rb+                  以二进制读写模式打开 同r+
              wb+                  以二进制读写模式打开 同w+
              ab+                  以二进制读写模式打开 同a+
     buffering  1表示有行缓冲,默认则表示使用系统默认提供的缓冲机制。
返回值:成功返回文件操作对象。
  1. 读取文件

read([size]) 注意:返回结果是字符串 str
功能: 来直接读取文件中字符。
参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size最多读取给定数目个字符(字节)。
返回值: 返回读取到的内容

  • 注意:文件过大时候不建议直接读取到文件结尾,读到文件结尾会返回空字符串。
f = open('dict.txt','r')
data = f.read()# 读取文件到最后遇到空字符
#data = f.read(50)#读取文件中的前50个字符
print(data)
f.close()

readline([size]) 注意:返回结果是字符串:str
功能: 用来读取文件中一行
参数: 如果没有给定size参数(默认值为-1)或者size值为负,表示读取一行,给定size表示最多读取制定的字符(字节)。
返回值: 返回读取到的内容

f = open('dict.txt','r')
data = f.readline(500)#最多读取500个字符(包含空字符)
#data = f.readline()#读取一行
print(data)
f.close()

readlines([sizeint]) 注意:返回结果是列表:list
功能: 读取文件中的每一行作为列表中的一项
参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size表示读取到size字符所在行为止。
返回值: 返回读取到的内容列表

文件对象本身也是一个可迭代对象,在for循环中可以迭代文件的每一行。

f = open('dict.txt','r')
data = f.readlines(200)  # 读取前200个字节所在的所有行的内容形成列表
# data = f.readlines() #读到最后
print(data)
f.close()
for line in f:
     print(line) #注意:返回结果是字符串:str
f = open('Install.txt','r')
data = f.read()
print(data)

# 循环读取文件内容
while True:
	# 如果读到文件结尾 read()会读到空字符串
	data = f.read(1024)
	# 读到结尾跳出循环
	if not data:
		break
	print(data)
    
# 读取文件一行内容
data = f.readline(5)
print(data)

# 读取内容形成列表
data = f.readlines(20) # 读取前20个字节所在的所有行
print(data)
f.close()
  1. 写入文件

write(string)
功能: 把文本数据或二进制数据块的字符串写入到文件中去
参数:要写入的内容

  • 如果需要换行要自己在写入内容中添加\n

writelines(str_list)
功能:接受一个字符串列表作为参数,将它们写入文件。
参数: 要写入的内容列表

f = open('a.py','w')
# 写操作
f.write("hello \n")
f.write("你好 \n")
# 将列表中每一项分别写入文件内
l = ['hello world\n','hello kitty\n']
f.writelines(l)
f.close()

#a.py文件结果如下:
hello 
你好
hello world
hello kitty
# 生成文件对象 f
with open('a.py') as f:
	data = f.read()
	print(data)
	# with 语句块结束 f 自动销毁
  1. 关闭文件

打开一个文件后我们就可以通过文件对象对文件进行操作了,当操作结束后使用close()关闭这个对象可以防止一些误操作,也可以节省资源。

file_object.close()

  1. with操作

python中的with语句使用于对资源进行访问的场合,保证不管处理过程中是否发生错误或者异常都会执行规定的“清理”操作,释放被访问的资源,比如有文件读写后自动关闭、线程中锁的自动获取和释放等。

with语句的语法格式如下:

with context_expression [as target(s)]:
    with-body

通过with方法可以不用close(),因为with生成的对象在语句块结束后会自动处理,所以也就不需要close了,但是这个文件对象只能在with语句块内使用。

with open('file','r+') as f:
    f.read()

注意

  1. 加b的打开方式读写要求必须都是字节串
  2. 无论什么文件都可以使用二进制方式打开,但是二进制文件使用文本方式打开读写会出错

其他操作

刷新缓冲区

​ 缓冲:系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,再由缓冲区送到磁盘中去。从磁盘中读数据,则一次从磁盘文件将一批数据读入到内存缓冲区中,然后再从缓冲区将数据送到程序的数据区。

​ 缓冲区功能:减少和磁盘的交互次数,提高了读写效率。

​ 刷新缓冲区条件:

  1. 缓冲区被写满
  2. 程序执行结束或者文件对象被关闭
  3. 行缓冲遇到换行
  4. 程序中调用flush()函数
f = open('a.py','w')
while True:
    data = input(">>")
    if not data:
        break
    f.write(data + '\n')
    f.flush()  # 刷新缓冲区
f.close()

flush()
该函数调用后会进行一次磁盘交互,将缓冲区中的内容写入到磁盘。

文件偏移量
'''
   编写一个程序,向一个文件写入如下内容
   1. 2019-1-1  18:18:18
   2. 2019-1-1  18:18:20
   每隔2秒写入一次,每条占一行。
   ctrl-c/红点 结束程序,下次启动后序号要跟之前的连续
   需要可以在编辑器中实时看到文件写入情况
'''
# import time
# while True:
#     f = open('a.py','w')
#     f.write('2019-1-1  18:18:18\n')
#     f.write('2019-1-1  18:18:20\n')
#     print('---------------')
#     time.sleep(2)


import time
f = open('a.py','ab+')#因为要实时看到,每次追加,所以用a+,w只会每次修改,不能叠加.
#文件偏移量定位到开头
f.seek(0)
n = 0
#获取多少行
for line in f:
    n += 1
    print (n)

# while True:
#     n += 1
#     time.sleep(2)
#     s = '%d. %s\n'%(n,time.ctime())
#     f.write(s.encode())
#     f.flush()  #随时查看
  1. 定义

打开一个文件进行操作时系统会自动生成一个记录,记录中描述了我们对文件的一系列操作。其中包括每次操作到的文件位置。文件的读写操作都是从这个位置开始进行的。

  1. 基本操作

tell()
功能:获取文件偏移量大小

seek(offset[,whence])
功能:移动文件偏移量位置
参数:offset 代表相对于某个位置移动的字节数。负数表示向前移动,正数表示向后移动。
whence是基准位置的默认值为 0,代表从文件开头算起,1代表从当前位置算起,2 代表从文件末尾算起。

  • 必须以二进制方式打开文件时基准位置才能是1或者2
文件描述符
  1. 定义

系统中每一个IO操作都会分配一个整数作为编号,该整数即这个IO操作的文件描述符。

  1. 获取文件描述符

fileno()
通过IO对象获取对应的文件描述符

文件管理函数

  1. 获取文件大小

os.path.getsize(file)

  1. 查看文件列表

os.listdir(dir)

  1. 查看文件是否存在

os.path.exists(file)

  1. 判断文件类型

os.path.isfile(file)

  1. 删除文件

os.remove(file)

网络编程基础

计算机网络功能主要包括实现资源共享,实现数据信息的快速传递。

OSI七层模型

制定组织: ISO(国际标准化组织)

作用:使网络通信工作流程标准化

应用层 : 提供用户服务,具体功能有应用程序实现
表示层 : 数据的压缩优化加密
会话层 : 建立用户级的连接,选择适当的传输服务
传输层 : 提供传输服务
网络层 : 路由选择,网络互联
链路层 : 进行数据交换,控制具体数据的发送
物理层 : 提供数据传输的硬件保证,网卡接口,传输介质

优点

  1. 建立了统一的工作流程
  2. 分部清晰,各司其职,每个步骤分工明确
  3. 降低了各个模块之间的耦合度,便于开发

四层模型(TCP/IP模型)

背景 : 实际工作中工程师无法完全按照七层模型要求操作,逐渐演化为更符合实际情况的四层

在这里插入图片描述

数据传输过程
  1. 发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包。
  2. 发送的消息经过多个节点(交换机,路由器)传输,最终到达目标主机。
  3. 目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息。

在这里插入图片描述

网络协议

在网络数据传输中,都遵循的规定,包括建立什么样的数据结构,什么样的特殊标志等。

网络基础概念

  • IP地址

功能:确定一台主机的网络路由位置

查看本机网络地址命令: ifconfig(linux命令),ipconfig(wind命令)

结构

IPv4 点分十进制表示 172.40.91.185 每部分取值范围0–255(现在大部分电脑都是IPv4)
IPv6 128位 扩大了地址范围

  • 域名

定义: 给网络服务器地址起的名字

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

ping [ip] : 测试和某个主机是否联通

  • 端口号(port)

作用:端口是网络地址的一部分,用于区分主机上不同的网络应用程序。

特点:一个系统中的应用监听端口不能重复

取值范围: 1 – 65535

1–1023 系统应用或者大众程序监听端口
1024–65535 自用端口

传输层服务

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

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

  2. 实现手段 : 在通信前需要建立数据连接,通信结束要正常断开连接。

三次握手(建立连接)

客户端向服务器发送消息报文请求连接
服务器收到请求后,回复报文确定可以连接
客户端收到回复,发送最终报文连接建立

在这里插入图片描述

四次挥手(断开连接)

主动方发送报文请求断开连接
被动方收到请求后,立即回复,表示准备断开
被动方准备就绪,再次发送报文表示可以断开
主动方收到确定,发送最终报文完成断开

在这里插入图片描述

  1. 适用情况 : 对数据传输准确性有明确要求,传数文件较大,需要确保可靠性的情况。比如:网页获取,文件下载,邮件收发。

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

  1. 传输特点 : 不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。

  2. 适用情况 : 网络较差,对传输可靠性要求不高。比如:网络视频,群聊,广播

socket套接字编程

套接字介绍

  1. 套接字 : 实现网络编程进行数据传输的一种技术手段

  2. Python实现套接字编程:import socket

  3. 套接字分类

流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)

数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现udp网络传输方案。(无连接–udp协议–不可靠–数据报套接字)

tcp套接字编程

服务端流程

在这里插入图片描述

'''
  tcp_server  tcp套接字服务端功能流程
  注意:注意流程及函数的使用
'''
import socket
from time import sleep

# 创建tcp套接字
sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址
sockfd.bind(('0.0.0.0', 8888))
# 设置监听
sockfd.listen(5)
# 阻塞等待客户端连接

while True:
    print('Waiting for connect.....')
    try:
        connfd, addr = sockfd.accept()  # 等待处理客户端连接要求
        print('Connect from', addr)  # 打印连接的客户端
    except KeyboardInterrupt:
        print('服务端退出')
        break
    except Exception as e:
        print(e)
        continue

    # 收发消息
    while True:
        data = connfd.recv(1024)  # 接收客户端消息
        # 连接的客户端退出,recv会立即返回空字符串
        if not data:  # 连接的客户端退出recv会立即返回空字符串
            break
        print(data.decode())
        sleep(0.1)
        n = connfd.send(b'thanks#')  # 'thanks#'共7个字节
        print('Send %d bytes' % n)  # 所有 %d = 7
    connfd.close()

# 关闭套接字
sockfd.close()
  1. 创建套接字
sockfd=socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
功能:创建套接字
参数:  socket_family  网络地址类型 AF_INET表示ipv4
	socket_type  套接字类型 SOCK_STREAM(流式)  SOCK_DGRAM(数据报)
	proto  通常为0  选择子协议
返回值: 套接字对象
  1. 绑定地址

本地地址 : ‘localhost’ , ‘127.0.0.1’
网络地址 : ‘172.40.91.185’
自动获取地址: ‘0.0.0.0’

在这里插入图片描述

sockfd.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port)  ('0.0.0.0',8888)
  1. 设置监听
sockfd.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小
参数 : 监听队列大小
  1. 等待处理客户端连接请求
connfd,addr = sockfd.accept()
功能: 阻塞等待处理客户端请求
返回值: connfd  客户端连接套接字
         addr  连接的客户端地址
  1. 消息收发
data = connfd.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容

n = connfd.send(data)
功能 : 发送消息
参数 :要发送的内容  bytes格式
返回值: 发送的字节数
  1. 关闭套接字
sockfd.close()
功能:关闭套接字
客户端流程
'''
  tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
    data = input('Msg:')
    # data为空退出循环
    if not data:
        break
    sockfd.send(data.encode())#发送字节串
    data = sockfd.recv(1024)
    print('Server:',data.decode())
#关闭套接字
sockfd.close()
#结果:客户端输入:Msg:11111111
		   显示:Server:thanks#
           输入空时结束客户端输入
 上述程序的服务端:Waiting for connect.....
				Connect from ('***.***.*.***', 58779)
				11111111
				Send 7 bytes  

在这里插入图片描述

  1. 创建套接字

注意:只有相同类型的套接字才能进行通信

  1. 请求连接
sockfd.connect(server_addr)
功能:连接服务器
参数:元组  服务器地址
  1. 收发消息

注意: 防止两端都阻塞,recv send要配合

  1. 关闭套接字
tcp 套接字数据传输特点
  • tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
  • tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError
  • 一个监听套接字可以同时连接多个客户端,也能够重复被连接
网络收发缓冲区
  1. 网络缓冲区有效的协调了消息的收发速度
  2. send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。
tcp粘包

原因:tcp以字节流方式传输,没有消息边界。多次发送的消息被一次接收,此时就会形成粘包。

影响:如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。

处理方法

  1. 人为的添加消息边界
  2. 控制发送速度

UDP套接字编程

服务端流程

在这里插入图片描述

'''
  udp_server.py 服务端
'''
from socket import *
#创建udp套接字
sockfd = socket(AF_INET,SOCK_DGRAM)
#绑定地址
server_addr = (('0.0.0.0',8888))
sockfd.bind(server_addr)
#循环收发
while True:
    data,addr = sockfd.recvfrom(5)
    print('收到的消息:',data.decode())
    sockfd.sendto(b'Thanks',addr)

#关闭套接字
sockfd.close()
  1. 创建数据报套接字
sockfd = socket(AF_INET,SOCK_DGRAM)
  1. 绑定地址
sockfd.bind(addr)
  1. 消息收发
data,addr = sockfd.recvfrom(buffersize)
功能: 接收UDP消息
参数: 每次最多接收多少字节
返回值: data  接收到的内容
		addr  消息发送方地址

n = sockfd.sendto(data,addr)
功能: 发送UDP消息
参数: data  发送的内容 bytes格式
	  addr  目标地址
返回值:发送的字节数
  1. 关闭套接字
sockfd.close()
客户端流程

在这里插入图片描述

'''
  udp_client.py udp客户端
'''
from socket import *
#服务器地址
ADDR = ('***.***.*.***',8888)
sockfd = socket(AF_INET,SOCK_DGRAM)
while True:
    data = input('Msg:')
    if not data:
        break
    sockfd.sendto(data.encode(),ADDR)
    msg,addr = sockfd.recvfrom(1024)
    print('From server:',msg.decode())

sockfd.close()
#结果:客户端输入:Msg:11111111
		   From server:thanks#
           输入空时结束客户端输入
 上述程序的服务端:收到的消息: 11111111
  1. 创建套接字
  2. 收发消息
  3. 关闭套接字

总结 :tcp套接字和udp套接字编程区别

  1. 流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输
  2. tcp套接字会有粘包,udp套接字有消息边界不会粘包
  3. tcp套接字保证消息的完整性,udp套接字则不能
  4. tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
  5. tcp套接字使用send,recv收发消息,udp套接字使用sendto,recvfrom

#查单词服务端:
from socket import *
f = open('dict.txt')
def find_word(word):
    f.seek(0) # 每次查找前先将位置定位到开头
    for line in f:# 每次获取一行
        w = line.split(' ')[0]
  # 如果遍历到的单词已经大于目标,就结束查找
        if w > word:
            break
        elif w == word:
            return line
    return "没有找到单词"
sockfd = socket(AF_INET,SOCK_DGRAM)
sockfd.bind(('0.0.0.0',8888))
while True:# 循环接受单词返回单词解释
    word,addr = sockfd.recvfrom(1024)
    # 调用查单词
    result = find_word(word.decode())
    sockfd.sendto(result.encode(),addr)
f.close()
sockfd.close()
'''
  使用udp完成从客户端输入单词,得到单词解释
  客户端可以循环输入单词
'''
#查找单词客户端
from socket import *
ADDR = ('***.***.*.***',8888)
sockfd = socket(AF_INET,SOCK_DGRAM)
dict = open('dict.txt','r')
while True:
    word = input('word:')
    if not word:
        break
    sockfd.sendto(word.encode(),ADDR)
    word,addr = sockfd.recvfrom(1024)
    print(word.decode())

sockfd.close()

socket套接字属性

【1】 sockfd.type 套接字类型

【2】 sockfd.family 套接字地址类型

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

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

【5】 sockfd.getpeername() 获取连接套接字客户端地址

【6】 sockfd.setsockopt(level,option,value)
功能:设置套接字选项
参数: level 选项类别 SOL_SOCKET
option 具体选项内容
value 选项值

在这里插入图片描述

struct模块进行数据打包

'''
  使用udp在客户端输入学生信息,在服务端将学生信息写入文件
  要求每个学生的信息各占一行
'''
#学生信息服务端:
from socket import *
import struct
#定义数据传输格式
st = struct.Struct('i20sif')
#创建套接字接受内容
s = socket(AF_INET,SOCK_DGRAM)
s.bind(('0.0.0.0',8888))
#打开文件
f = open('student.txt','a')

#循环接收单词
while True:
    data,addr = s.recvfrom(1024)
    #将信息转换为元组数据(1,b'lili',13,85)
    data =st.unpack(data)
    info = '%d %-10s %d %.2f\n'%data #-为左对齐,10为宽度
    f.write(info)
    f.flush()
f.close()
s.close()
#学生信息客户端:
from socket import *
import struct
#定义数据传输格式
st = struct.Struct('i20sif')
#创建套接字接受内容
s = socket(AF_INET,SOCK_DGRAM)
#服务端地址
ADDR = ('***.***.*.***',8888)
while True:
    print('------------------')
    id = int(input('ID:'))
    name = input('Name:').encode()
    age = int(input('Age:'))
    score = float(input('Score:'))
    #数据打包
    data = st.pack(id,name,age,score)
    s.sendto(data,ADDR)
sockfd.close()
  1. 原理: 将一组简单数据进行打包,转换为bytes格式发送。或者将一组bytes格式数据,进行解析。
  2. 接口使用
Struct(fmt)
功能: 生成结构化对象
参数:fmt  定制的数据结构

st.pack(v1,v2,v3....)
功能: 将一组数据按照指定格式打包转换为bytes
参数:要打包的数据
返回值: bytes字节串

st.unpack(bytes_data)
功能: 将bytes字节串按照指定的格式解析
参数: 要解析的字节串
返回值: 解析后的内容

struct.pack(fmt,v1,v2,v3...)
struct.unpack(fmt,bytes_data)

说明: 可以使用struct模块直接调用pack unpack。此时这两函数第一个参数传入fmt。其他用法功能相同

在这里插入图片描述

HTTP传输

HTTP协议 (超文本传输协议)
  1. 用途 : 网页获取,数据的传输

  2. 特点

  • 应用层协议,传输层使用tcp传输
  • 简单,灵活,很多语言都有HTTP专门接口
  • 无状态,协议不记录传输内容
  • http1.1 支持持久连接,丰富了请求类型
  1. 网页请求过程

1.客户端(浏览器)通过tcp传输,发送http请求给服务端
2.服务端接收到http请求后进行解析
3.服务端处理请求内容,组织响应内容
4.服务端将响应内容以http响应格式发送给浏览器
5.浏览器接收到响应内容,解析展示
在这里插入图片描述

HTTP请求(request)
'''
  http请求过程演示
'''
from socket import *
s = socket()
s.bind(('0.0.0.0',8888))
s.listen(3)
c,addr = s.accept()
print('Connect from',addr)
#获取请求
data = c.recv(4096)
c.send(b'hello world')
print(data)
c.close()
s.close()
'''
  httpserver v1.0
  基本要求: 1.获取来自浏览器的请求
           2.判断请求内容是否为/
           3.如果是,则将 index.html 发送给浏览器,如果不是,则告知浏览器sorry
           4.注意组织http响应格式,判断 200 or 404
'''
from socket import *

# 处理客户端请求
def request(connfd):
    # 直接获取HTTP请求
    data = connfd.recv(4096)
    # 防止浏览器断开
    if not data:
        return

    # 提取请求内容
    request_line = data.decode().split('\n')[0]
    info = request_line.split(' ')[1]

    # 判断请求内容是否为 /
    if info == '/':
        with open('index.html') as f:
            response="HTTP/1.1 200 OK\r\n"
            response+="Content-Type:text/html\r\n"
            response+="\r\n"
            response+=f.read()
    else:
        response = "HTTP/1.1 404 Not Found\r\n"
        response += "Content-Type:text/html\r\n"
        response += "\r\n"
        response += "<h1>Sorry....</h1>"
    # 发送给浏览器
    connfd.send(response.encode())

# 搭建tcp服务
sockfd = socket()
sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sockfd.bind(('0.0.0.0',8888))
sockfd.listen(5)
while True:
    connfd,addr = sockfd.accept()
    print("Connect from",addr)
    request(connfd)  # 具体处理客户端请求

  • 请求行 : 具体的请求类别和请求内容
	GET         /        HTTP/1.1
	请求类别   请求内容     协议版本

请求类别:每个请求类别表示要做不同的事情

		GET : 获取网络资源
		POST :提交一定的信息,得到反馈
		HEAD : 只获取网络资源的响应头
		PUT : 更新服务器资源
		DELETE : 删除服务器资源
		CONNECT
		TRACE : 测试
		OPTIONS : 获取服务器性能信息
  • 请求头:对请求的进一步解释和描述
Accept-Encoding: gzip
  • 空行
  • 请求体: 请求参数或者提交内容
http响应(response)
  1. 响应格式:响应行,响应头,空行,响应体
  • 响应行 : 反馈基本的响应情况
HTTP/1.1     200       OK
版本信息    响应码   附加信息

响应码 :

1xx  提示信息,表示请求被接收
2xx  响应成功
3xx  响应需要进一步操作,重定向
4xx  客户端错误
5xx  服务器错误
  • 响应头:对响应内容的描述
Content-Type: text/html
  • 响应体:响应的主体内容信息

多任务编程

  1. 意义: 充分利用计算机CPU的多核资源,同时处理多个应用程序任务,以此提高程序的运行效率。

  2. 实现方案 :多进程 , 多线程

进程(process)

进程理论基础

  1. 定义 : 程序在计算机中的一次运行。
  • 程序是一个可执行的文件,是静态的占有磁盘。
  • 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
  1. 系统中如何产生一个进程
    【1】 用户空间通过调用程序接口或者命令发起请求
    【2】 操作系统接收用户请求,开始创建进程
    【3】 操作系统调配计算机资源,确定进程状态等
    【4】 操作系统将创建的进程提供给用户使用
    在这里插入图片描述

  2. 进程基本概念

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

  • PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程。

  • 进程ID(PID): 系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复。

    Linux查看进程ID : ps -aux

  • 父子进程 : 系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0个或多个子进程。父子进程关系便于进程管理。

查看进程树: pstree

  • 进程状态
  • 三态
    就绪态 : 进程具备执行条件,等待分配cpu资源
    运行态 : 进程占有cpu时间片正在运行
    等待态 : 进程暂时停止运行,让出cpu

在这里插入图片描述

  • 五态 (在三态基础上增加新建和终止)
    新建 : 创建一个进程,获取资源的过程
    终止 : 进程结束,释放资源的过程

在这里插入图片描述

  • 状态查看命令 : ps -aux --> STAT列
  						S 等待态

  		R 执行态
  		Z 僵尸
  						`+` 前台进程

  		l   有多线程的
  • 进程的运行特征
    【1】 多进程可以更充分使用计算机多核资源
    【2】 进程之间的运行互不影响,各自独立
    【3】 每个进程拥有独立的空间,各自使用自己空间资源

基于fork的多进程编程

fork使用

'''
 fork 进程创建演示
'''
import os
from time import sleep

# 创建子进程       #父子进程互不影响,独立进行,几乎同时运行
pid = os.fork()  # 生成父子进程,父进程的pid为子进程pid是正整数,子进程pid返回为0.

if pid < 0:
    print('create process failed')
elif pid == 0:
    # 只有子进程执行
    sleep(3)
    print('the new process')
else:
    # 只有父进程执行
    sleep(4)
    print('the old process')

print('process test over')
'''
 fork进程演示细节
'''
import os
import time

print('----------------')
a=1
def fun():
    print('fun-------')

pid = os.fork()
if pid <0:
    print('create process failde')
elif pid == 0:
    print('child process')
    print('a =',a )  # 从父进程空间拷贝了变量
    fun()
    a = 1000  # 只是修改了自己空间的a
else:
    time.sleep(2)
    print('parent process')
    print('a',a)

#结果:
----------------
child process
a = 1
fun-------
parent process
a 1

pid = os.fork()
功能: 创建新的进程
返回值:整数,如果创建进程失败返回一个负数,如果成功则在原有进程中返回新进程的PID,在新进程中返回0

注意

  • 子进程会复制父进程全部内存空间,从fork下一句开始执行。
  • 父子进程各自独立运行,运行顺序不一定。
  • 利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定搭配。
  • 父子进程有各自特有特征比如PID PCB 命令集等。
  • 父进程fork之前开辟的空间子进程同样拥有,父子进程对各自空间的操作不会相互影响。

进程相关函数

"""
获取进程PID号
"""
import os
from time import sleep

pid = os.fork()

if pid < 0:
    print("Error")
elif pid == 0:
    sleep(1)
    print("Child PID:",os.getpid()) # 自己pid
    print("Get parent PID:",os.getppid()) # 父pid
else:
    print("Parent PID:", os.getpid())  # 自己pid
    print("Get child PID:",pid)
    
#结果 :
parent PID: 22430
child PID: 22431
get child PID 22431
get parent PID: 22430
'''
 fork进程演示细节,孤儿进程及进程退出演示
'''
import os,sys
pid = os.fork()
#父子进程退出对对方互不影响,对方依然运行
if pid <0:
    print('error')
elif pid == 0:
    #os._exit(0) #子进程退出
    print('child process:')

else:
    sys.exit('退出父进程')
    print('parent process:')
    
#结果 :
退出父进程
child process:
如果 sys.exit 注释后则
为 :parent process:

os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID

os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID

os._exit(status)
功能: 结束一个进程
参数:进程的终止状态

sys.exit([status])
功能:退出进程
参数:整数 表示退出状态
字符串 表示退出时打印内容

孤儿和僵尸

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

特点: 孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。

  1. 僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程。

特点: 僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源。

  1. 如何避免僵尸进程产生
  • 使用wait函数处理子进程退出
'''
  模拟僵尸进程产生
'''
import os,sys
pid = os.fork()

if pid <0:
    print('error')
elif pid == 0:
    print('child PID:', os.getpid()) #子pid
    sys.exit(5)		#子进程结束但不影响父进程的执行

else:
    '''
      os.wait()处理僵尸
    '''
    pid,status = os.wait()
    print('pid:',pid)
    print('status',os.WEXITSTATUS(status))
    while True: #父进程不退出
        pass
 
#结果:
child PID: 22701
pid: 22701
status 5
	```		
	pid,status = os.wait()
	功能:在父进程中阻塞等待处理子进程退出
	返回值: pid  退出的子进程的PID
		status  子进程退出状态
	
	```
  • 创建二级子进程处理僵尸

‘’’
僵尸进程处理
‘’’
import os
import time

 def fun1():
     for i in range(3):
         time.sleep(2)
         print('写代码')
 
 def fun2():
     for j in range(2):
         time.sleep(4)
         print('测代码')
 
 pid = os.fork()
 
 if pid <0:
     print('error')
 
 elif pid == 0:
     p = os.fork()  #创建二级子进程
     if p == 0:
         fun1()
     else:
         os._exit(0)
 else:
     os.wait() #等待回收一级子进程
     fun2()
     
 #结果:写代码 写代码 测代码 写代码 测代码
 ```
 
 【1】 父进程创建子进程,等待回收子进程
 【2】 子进程创建二级子进程然后退出
 【3】 二级子进程称为孤儿,和原来父进程一同执行事件
  • 通过信号处理子进程退出

    原理: 子进程退出时会发送信号给父进程,如果父进程忽略子进程信号,则系统就会自动处理子进程退出。

    方法: 使用signal模块在父进程创建子进程前写如下语句 :

    import signal
    signal.signal(signal.SIGCHLD,signal.SIG_IGN)
    

    特点 : 非阻塞,不会影响父进程运行。可以处理所有子进程退出

群聊聊天室

'''
功能 : 类似qq群功能
【1】 有人进入聊天室需要输入姓名,姓名不能重复
【2】 有人进入聊天室时,其他人会收到通知:xxx 进入了聊天室
【3】 一个人发消息,其他人会收到:xxx : xxxxxxxxxxx
【4】 有人退出聊天室,则其他人也会收到通知:xxx退出了聊天室
【5】 扩展功能:服务器可以向所有用户发送公告:管理员消息: xxxxxxxxx
1. 需求分析 :  达到什么样的目的,形成什么样的运行流程
* 启动软件--> 输入姓名-->进入-->聊天(收发随意)-> 退出
            |    |
            ---不允许进入
2. 技术点的确定
  * 消息发送流程: client-->server->client
  * 网络选择 : udp 进行数据传输
  * 存储内容: name  address
    {name:address,}
    [(name,address),]
  * 消息收发互不影响
    使用多进程,让消息发送接收各占一个进程
    3. 结构和注意事项
  * 采用什么结构封装 : 函数
  * 编写一个功能,测试一个功能
  * 注释
4. 功能模块分析
  * 网络搭建
       客户端 : udp客户端
       服务端 : udp服务端
  * 进入聊天室
       客户端 : * 输入姓名
                * 向服务器发送请求
                * 接收结果
                * 允许则进入聊天,不允许则重写输入姓名
       服务器 : * 接受请求,区分请求类型
                * 判断用户是否存在
                * 如果允许进入,将其加入存储结构
                * 通知其他用户,告知本人可以登录
                * 如果不允许则结束,告知用户不可以进入
  * 聊天
       客户端 : * 创建新的进程
               * 一个进程循环的发送消息
               * 一个进程循环的接收消息
       服务端 : * 接收请求,判断请求类型
                * 将消息转发给其他用户
                  xxx : xxxxxxxxx
  * 退出聊天室
       客户端 : * 输入quit 或者 ctrl-c退出
               * 发送请求给服务端
               * 结束进程
       服务端 : * 接收请求,判断请求类型
                * 将退出消息发送给其他人
                * 将用户从字典删除
  * 管理员消息
5. 协议设定
    请求格式:
       登录 :  L name
       聊天 :  C name text
       退出 :  Q name
    响应格式:
       登录 : 成功(OK) 失败(失败原因)
       退出 : 给客户端发送 EXIT 让客户端接收进程退出
'''
#利用UDP创建服务端
from socket import *
import os,sys

#服务端地址
ADDR = ('0.0.0.0',8888)
#存储用户的结构{name:address}
user = {}

#处理登入
def do_login(s,name,addr):
   if name in user or '管理员' in name:
       s.sendto('用户存在'.encode(),addr)
       return
   #加入用户
   msg = '欢迎 %s 进入聊天室'%name
   for i in user:
       s.sendto(msg.encode(),user[i])
   user[name] = addr
   s.sendto(b'OK',addr)

#聊天
def do_chat(s,name,text):
   msg = '\n %s:%s'%(name,text)
   for i in user:
       #刨除本人
       if i!= name:
           s.sendto(msg.encode(),user[i])

#退出
def do_quit(s,name):
   msg = '\n %s 退出聊天室'%name
   for i in user:
       if i != name: #其他人
           s.sendto(msg.encode(),user[i])
       else:
           s.sendto(b'EXIT',user[i])
   del user[name] #删除该用户

#接收请求,分发给不同方法处理
def do_request(s):
   while True:
       #循环接收来自客户端请求
       data,addr = s.recvfrom(1024)
       tmp = data.decode().split(' ',2)#表示只切割前连个部分
       #根据不同请求类型分发函数处理
       #L 进入 C聊天 Q退出
       if tmp[0] == 'L':
           do_login(s,tmp[1],addr)
       elif tmp[0] == 'C':
           do_chat(s,tmp[1],tmp[2])
       elif tmp[0] == 'Q':
           if tmp[1] in user:
               do_quit(s, tmp[1])
#搭建网络
def main():
   #udp客户端
   s = socket(AF_INET,SOCK_DGRAM)
   s.bind(ADDR)
   #开辟新进程处理管理员消息
   pid = os.fork()
   if pid == 0:
       #子进程处理管理员消息
       while True:
           msg = input('管理员消息:')
           msg = 'C 管理员 ' +msg
           s.sendto(msg.encode(),ADDR)
   else:
       do_request(s)#处理客户端请求

main()
#利用UDP创建客户端
from socket import *
import os,sys

#服务端地址
ADDR = ('***.***.*.***',8888)

#进入聊天室
def login(s):
   while True:
       try:
           name = input('请输入名称:')
           if not name:
               continue
       except KeyboardInterrupt:
           sys.exit('谢谢使用')
       msg = 'L '+name
       s.sendto(msg.encode(),ADDR)
       #接收反馈结果
       data,addr = s.recvfrom(128)
       if data.decode() == 'OK':
           print('你已进入聊天室')
           return name
       else:
           print(data.decode())

#子进程函数
def send_msg(s,name):#循环发消息
   while True:
       try:
           text = input('头像:')
       except KeyboardInterrupt:
           text = 'quit'
       if text.strip() == 'quit':
           msg = 'Q' + name
           s.sendto(msg.encode(),ADDR)
           sys.exit('退出聊天室')
       msg = 'C %s %s'%(name,text)
       s.sendto(msg.encode(),ADDR)

def recv_msg(s):#循环接收消息
   while True:
       data,addr = s.recvfrom(2048)
       #接收进程退出
       if data.decode() == 'EXIT':
           sys.exit()
       print(data.decode()+'\n头像:',end='')

#客户端启动函数
def main():
   s = socket(AF_INET,SOCK_DGRAM)
   name = login(s) #请求进入聊天室

   #创建新的进程
   pid = os.fork()
   if pid < 0:
       print('error')
       return
   elif pid == 0:
       send_msg(s,name) #子进程接收消息
   else:
       recv_msg(s) #父进程接收消息

main()

multiprocessing 模块创建进程

进程创建方法

'''
  创建进程演示:1.编写进程函数
             2.生成进程对象
             3.启动进程
             4.回收进程
'''
import multiprocessing as mp
import time
a = 1
#进程执行函数
def fun():
    print('开始一个进程')
    time.sleep(2)
    global a
    # a =1000
    print('a = ',a)
    a =100
    print('子进程结束')
#创建进程对象
p = mp.Process(target=fun)
p.start() #开始进程
#父进程执行事件
time.sleep(3)
print('父进程执行了')
p.join()#回收进程()内为超时时间
print('a:',a)
print('----------------')
#结果 :
	开始一个进程
	a = 1
	子进程结束
	父进程执行了
	a: 1
	----------------
#因为只有子进程调用了函数 , 函数从上到下执行 , 所以 a=1,如果 a 放在 print 上则第一 a=1000, 另一个依然为 a=1
'''
 创建多个子进程
'''
import multiprocessing as mp
from time import sleep
import os
def th1():
    sleep(2)
    print('吃饭')
    print(os.getppid(),'--',os.getpid())
def th2():
    sleep(3)
    print('睡觉')
    print(os.getppid(),'--',os.getpid())
def th3():
    sleep(4)
    print('打豆豆')
    print(os.getppid(),'--',os.getpid())
things = [th1,th2,th3]
jobs = []
for th in things:
    p = mp.Process(target = th)
    jobs.append(p) #对进程对象进行存储
    p.start()
#一起回收
for i in jobs:
    i.join()
'''
  给进程函数传参
'''
from multiprocessing import Process
from time import sleep
#含有参数的进程函数
def worker(second,name):
    for i in range(3):
        sleep(second)
        print('I am %s'%name)
        print('I am working---')
#通过args 给函数位置传参
p = Process(target=worker,args=(2,'xiaoming'))
# p = Process(target=worker,args=(2,),kwargs={'name':'xiaoxiao'})
p.start()
p.join()
  1. 流程特点
    【1】 将需要子进程执行的事件封装为函数
    【2】 通过模块的Process类创建进程对象,关联函数
    【3】 可以通过进程对象设置进程信息及属性
    【4】 通过进程对象调用start启动进程
    【5】 通过进程对象调用join回收进程

  2. 基本接口使用

Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数 
	args 元组,用于给target函数位置传参
	kwargs 字典,给target函数键值传参
p.start()
功能 : 启动进程

注意:启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建

p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间
'''
  timeit.py文件:实质为一个装饰器
  求10000以内所有的质数之和,请分别使用单进程,4进程,10进程完成,
  然后记录每次完成的时间,求和用sum()函数
'''
import time

def timeit(func):#函数装饰器
    def wrapper(*args,**kwargs):
        start_time = time.time()
        res = func(*args,**kwargs)
        end_time = time.time()
        print('%s函数运行时间:%.3f'%(func.__name__,end_time -start_time))
        return res
    return wrapper
from timeit import timeit
from multiprocessing import Process

#判断是否为质数
def isPrime(i):
    if i <= 1:
        return False
    for j in range(2,i):
        if i % j ==0:
            return False
    return True

#单进程完成任务
@timeit
def no_multi_process():
    prime = []
    for i in range(1,100001):
        if isPrime(i):
            prime.append(i)
    sum(prime)

#自定义进程类
class Prime(Process):
    def __init__(self, prime, begin, end):
        self.prime = prime
        self.begin = begin  # 取值的开始
        self.end = end  # 取值的末尾
        super().__init__()

    def run(self):
        for i in range(self.begin, self.end):
            if isPrime(i):
                self.prime.append(i)
        sum(self.prime)

@timeit
def use_4_process():
    prime = []
    jobs = []
    for i in range(1, 100001, 25000):
        p = Prime(prime, i, i + 25000)
        jobs.append(p)
        p.start()
    [i.join() for i in jobs]

@timeit
def use_10_process():
    prime = []
    jobs = []
    for i in range(1, 100001, 10000):
        p = Prime(prime, i, i + 10000)
        jobs.append(p)
        p.start()
    [i.join() for i in jobs]

if __name__ == '__main__':
    no_multi_process()
    use_4_process()
    use_10_process()
#结果:no_multi_process函数运行时间:45.852
	  use_4_process函数运行时间:27.557
	  use_10_process函数运行时间:22.066

注意

  • 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响。
  • 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容。
  • multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成。
  • multiprocessing创建的子进程中无法使用标准输入
  1. 进程对象属性
"""
进程对象属性
"""

from multiprocessing import Process
import time

def tm():
    for i in range(3):
        print(time.ctime())
        time.sleep(2)

p = Process(target = tm,name = 'Tarena')

# 父进程退出,其所有子进程也退出
p.daemon = True

p.start()  # 进程真正产生

print("Name:",p.name)  # 进程名
print("PID:",p.pid) # pid号
print("is alive:",p.is_alive()) # 是否在生命周期
#结果:Name: Tarena
	  PID: 17460
	  is alive: True

p.name 进程名称

p.pid 对应子进程的PID号

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

p.daemon 设置父子进程的退出关系

  • 如果设置为True则子进程会随父进程的退出而结束
  • 要求必须在start()前设置
  • 如果daemon设置成True 通常就不会使用 join()

自定义进程类

'''
  自定义进程类:三进程
'''
from multiprocessing import Process
#自定义进程类
class MyProcess(Process):
    def __init__(self,val):
        self.val =val
        super().__init__()
    def fun1(self):
        print('第一步')
    def fun2(self):
        print('第二步')
    def fun3(self):
        print('第三步')
    #作为流程启动函数
    def run(self):
        for i in range(self.val):
            self.fun1()
            self.fun2()
            self.fun3()

if __name__ == '__main__':
    p = MyProcess(2)
    p.start()
    p.join()
#结果:
	第一步
	第二步
	第三步
	第一步
	第二步
	第三步
  1. 创建步骤
    【1】 继承Process类
    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性
    【3】 重写run()方法

  2. 使用方法
    【1】 实例化对象
    【2】 调用start自动执行run方法
    【3】 调用join回收线程

进程池实现

'''
  进程池的使用案例
'''
from multiprocessing import Pool
from time import sleep,ctime
#进程池事件
def worker(msg):
    sleep(2)
    print(ctime(),'--',msg) #当前时间年月日,时分秒.

#创建进程池,注父进程结束,子进程自动销毁
pool = Pool()#括号内默认为计算机几核处理系统,就几项一起做
#向进程池队列中添加事件
for i in range(7):
    msg = 'liu %d'%i
    pool.apply_async(func=worker,args=(msg,))

#关闭进程池
pool.close()
#回收进程池中的进程
pool.join()
# 结果 :
Mon Aug 5 19:37:47 2019 -- liu 2
Mon Aug 5 19:37:47 2019 -- liu 0
Mon Aug 5 19:37:47 2019 -- liu 1
Mon Aug 5 19:37:47 2019 -- liu 3
Mon Aug 5 19:37:49 2019 -- liu 5
Mon Aug 5 19:37:49 2019 -- liu 4
Mon Aug 5 19:37:49 2019 -- liu 6
  1. 必要性
    【1】 进程的创建和销毁过程消耗的资源较多
    【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
    【3】 进程池技术很好的解决了以上问题。

  2. 原理

创建一定数量的进程来处理事件,事件处理完进 程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。

  1. 进程池实现

【1】 创建进程池对象,放入适当的进程

from multiprocessing import Pool

Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定

【2】 将事件加入进程池队列执行

pool.apply_async(func,args,kwds)
功能: 使用进程池执行 func事件
参数: func 事件函数
      args 元组  给func按位置传参
      kwds 字典  给func按照键值传参
返回值: 返回函数事件对象

【3】 关闭进程池

pool.close()
功能: 关闭进程池

【4】 回收进程池中进程

pool.join()
功能: 回收进程池中进程

进程间通信(IPC)

  1. 必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。

  2. 常用进程间通信方法

管道 消息队列 共享内存 信号 信号量 套接字

管道通信(Pipe)

注意 :1.multiprocessing 中提供的通信只用于亲缘关系进程间的通信

  1. 管道在父进程中创建 , 子进程从父进程中获取管道对象
'''
pipe.py管道操作
注意:1.multiprocessing中提供的通信只用于亲缘关系进程间的通信
    2.管道在父进程中创建,子进程从父进程中获取管道对象
'''
from multiprocessing import Process,Pipe

#创建管道对象 若()内参数为False,则表示:fd1只能recv,fd2只能send.
fd1,fd2 = Pipe()#括号内默认为True
#APP1可以使用app2提供的信息登入
def app1():
    print('启动app1,请登入')
    print('请求app2授权')
    #写管道
    fd1.send('app1可以用你的账户登入吗?')
    data = fd1.recv()
    if data:
        print('登入成功:',data)

def app2():
    request = fd2.recv() #阻止等待读取管道
    print(request)
    fd2.send(('liu','123'))#发送python数据类型

p1 = Process(target=app1)
p2 = Process(target=app2)
p1.start()
p2.start()
p1.join()
p2.join()
#结果
启动 app1, 请登入
请求 app2 授权
app1 可以用你的账户登入吗 ?
登入成功 : ('liu', '123')
  1. 通信原理

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

  1. 实现方法
from  multiprocessing import Pipe

fd1,fd2 = Pipe(duplex = True)
功能: 创建管道
参数:默认表示双向管道
如果为False 表示单向管道
返回值:表示管道两端的读写对象
	如果是双向管道均可读写
	如果是单向管道fd1只读  fd2只写

fd.recv()
功能 : 从管道获取内容
返回值:获取到的数据

fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据

消息队列

'''
 消息队列演示
 注意:消息存入与去除关系为先入先出
'''
from multiprocessing import Queue,Process
from time import sleep
from random import randint
#创建队列
q = Queue(5) #最大存储5个消息
def request():
    for i in range(6):
        x = randint(1,100)
        y = randint(1,100)
        q.put((x,y))#写消息队列
        print('--------')

def handle():
    while not q.empty():
        data = q.get() #读消息队列
        print('x+y=',data[0] +data[1])
        sleep(1)

p1 = Process(target=request)
p2 = Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()
#结果 :
--------
--------
--------
--------
--------
x+y= 152
--------
x+y= 153
x+y= 110
x+y= 120
x+y= 106
x+y= 183

1.通信原理

在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。

  1. 实现方法
from multiprocessing import Queue

q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象

q.put(data,[block,timeout])
功能:向队列存入消息
参数:data  要存入的内容
block  设置是否阻塞 False为非阻塞
timeout  超时检测

q.get([block,timeout])
功能:从队列取出消息
参数:block  设置是否阻塞 False为非阻塞
timeout  超时检测
返回值: 返回获取到的内容

q.full()   判断队列是否为满
q.empty()  判断队列是否为空
q.qsize()  获取队列中消息个数
q.close()  关闭队列

共享内存

'''
 value.py开辟共享内存空间
 注意:共享内存中只能有一个值
'''
from multiprocessing import Process,Value
import time
import random
#创建共享内存
money = Value('i',5000)
def man():
    for i in range(30):
        time.sleep(0.01)
        #修改共享内存
        money.value += random.randint(1,1000)

def girl():
    for i in range(30):
        time.sleep(30)
        time.sleep(0.005)
        money.value -= random.randint(100,800)

p1 = Process(target=man)
p2 = Process(target=girl)
p1.start()
p2.start()
p1.join()
p2.join()
print('一个月余额:',money.value)
#结果:一个月余额: 5000
"""
array.py共享内存中存放列表,字节串
"""
from multiprocessing import Process,Array

# 创建共享内存,初始数据 [1,2,3,4]
# shm = Array('i',[1,2,3,4])
# shm = Array('i',4) # 开辟4个整形的列表空间
shm = Array('c',b'hello')

def fun():
    # 共享内存对象可以迭代
    for i in shm:
        print(i)
    shm[0] = b'H' # 修改共享内存

p = Process(target=fun)
p.start()
p.join()
for i in shm:
    print(i)
print(shm.value) # 整体打印字节串
#结果:b'h'
b'e'
b'l'
b'l'
b'o'
b'hello'
  1. 通信原理:在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。

  2. 实现方法

在这里插入图片描述

from multiprocessing import Value,Array

obj = Value(ctype,data)
功能 : 开辟共享内存
参数 : ctype  表示共享内存空间类型 'i'  'f'  'c'
       data   共享内存空间初始数据
返回值:共享内存对象

obj.value  对该属性的修改查看即对共享内存读写


obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype  表示共享内存数据类型
      data   整数则表示开辟空间的大小,其他数据类型表示开辟空间存放的初始化数据
返回值:共享内存对象

Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。

* 可以使用obj.value直接打印共享内存中的字节串

信号量(信号灯集)

​ 信号量可以当做是一种资源,执行任务需要消耗信号量资源,这样可以控制进程执行行为

'''
sem.py信号量演示
注意: 信号量可以当做是一种资源,执行任务需要消耗信号量资源,这样可以控制进程执行行为
'''
from multiprocessing import Process,Semaphore
from time import sleep
import os

#创建信号量灯集
sem = Semaphore(3)
#任务函数(系统中最多能够同时运行三个该任务)
def handle():
    sem.acquire()# 消耗一个信号量
    print('%s执行任务'%os.getpid())
    sleep(2)
    print('%s拯救了宇宙'%os.getpid())
    sem.release()#增加一个信号量

jobs = []
for i in range(3):
    p = Process(target=handle)
    jobs.append(p)
    p.start()

for i in jobs:
    i.join()
#结果 :
26874 执行任务
26875 执行任务
26876 执行任务
26874 拯救了宇宙
26875 拯救了宇宙
26876 拯救了宇宙
  1. 通信原理

给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。

  1. 实现方法
from multiprocessing import Semaphore

sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象

sem.acquire()  将信号量减1 当信号量为0时阻塞
sem.release()  将信号量加1
sem.get_value() 获取信号量数量
"""
用两个子进程分别拷贝图片的上半部分和下半部分
"""

from multiprocessing import Process
import os

filename = "./timg.jpg"
size = os.path.getsize(filename)

# 所有进程使用的是同一个IO,相互有影响
# fr = open(filename,'rb')
# print(fr.fileno())

# 复制上半部分
def top():
    fr = open(filename,'rb')
    print(fr.fileno())
    fw = open('top.jpg','wb')
    n = size//2
    fw.write(fr.read(n))
    fr.close()
    fw.close()

# 下半部分
def bot():
    fr = open(filename, 'rb')
    print(fr.fileno())
    fw = open('bot.jpg', 'wb')
    fr.seek(size//2,0)
    fw.write(fr.read())
    fr.close()
    fw.close()

p1 = Process(target=top)
p2 = Process(target=bot)
p2.start()
p1.start()
p1.join()
p2.join()

线程编程(Thread)

线程基本概念

  1. 什么是线程
    【1】 线程被称为轻量级的进程
    【2】 线程也可以使用计算机多核资源,是多任务编程方式
    【3】 线程是系统分配内核的最小单元
    【4】 线程可以理解为进程的分支任务

  2. 线程特征
    【1】 一个进程中可以包含多个线程
    【2】 线程也是一个运行行为,消耗计算机资源
    【3】 一个进程中的所有线程共享这个进程的资源
    【4】 多个线程之间的运行互不影响各自运行
    【5】 线程的创建和销毁消耗资源远小于进程
    【6】 各个线程也有自己的ID等特征

threading模块创建线程

'''
 thread1.py 线程基础使用
 步骤: 1.创建线程对象
      2.启动线程
      3.回收线程
'''
from threading import Thread
from time import sleep
import os

a = 1

# 线程函数
def music():
    global a
    print("a = ",a)
    a = 10000
    for i in range(3):
        sleep(2)
        print(os.getpid(),"播放: 葫芦娃")

# 线程对象
t = Thread(target = music)
t.start() # 启动线程

for i in range(3):
    sleep(1)
    print(os.getpid(),"播放: 黄河大合唱")

t.join() # 回收线程

print("-----------")
print("a:",a)
#结果:
a =  1
9860 播放: 黄河大合唱
98609860 播放: 黄河大合唱
 播放: 葫芦娃
9860 播放: 黄河大合唱
9860 播放: 葫芦娃
9860 播放: 葫芦娃
-----------
a: 10000
"""
线程基础演示
"""
from threading import Thread
from time import sleep

# 含有参数的线程函数
def fun(sec,name):
    print("线程函数传参")
    sleep(sec)
    print("%s执行完毕"%name)

# 创建多个线程
jobs=[]
for i in range(3):
    t = Thread(target=fun,args=(2,),kwargs={'name':'T%d'%i})
    #target绑定fun函数,2秒传递给sec,键name对应的值传递给形参name
    jobs.append(t)  # 存线程对象
    t.start()

for i in jobs:
    i.join()
#结果:线程函数传参
	  线程函数传参
	  线程函数传参
	  T2执行完毕T0执行完毕T1执行完毕

【1】 创建线程对象

from threading import Thread 

t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
     args   元组 给线程函数位置传参
     kwargs 字典 给线程函数键值传参

【2】 启动线程

 t.start()

【3】 回收线程

 t.join([timeout])

线程对象属性

'''
 thread_attr.py 线程属性案例
'''
from threading import Thread
from  time import sleep
def fun():
    sleep(3)
    print('线程属性示例')
t = Thread(target=fun)
t.setDaemon(True)#主线程退出,分支线程也退出
t.start()
t.setName('liu')#设置修改线程名称
print('Name:',t.getName())
#获取线程名称
print('Daemon:',t.isDaemon())
#t.join()
#结果 :Name: liu
	  Daemon: True
#若注释 t.setDaemon(True) 且打开 t.join()
#结果:Name: liu
     Daemon: False
     线程属性示例

t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称

t.is_alive() 查看线程是否在生命周期

t.daemon 设置主线程和分支线程的退出关系
t.setDaemon() 设置daemon属性值
t.isDaemon() 查看daemon属性值

daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用。

自定义线程类

'''
 自定义线程类
'''
from threading import Thread
from time import sleep,ctime
class MyThread(Thread):
    def __init__(self,target,args=(),kwargs={}):#关键字传参
        self.target = target
        self.args = args
        self.kwargs = kwargs
        super().__init__()
    #添加其他方法run
    def run(self):
        self.target(*self.args,**self.kwargs)
def player(sec,song):
    for i in range(3):
        print('Playing %s:%s'%(song,ctime()))
        sleep(sec)
t = MyThread(target=player,args=(3,),kwargs={'song':'凉凉'})
t.start()
t.join()
#结果 :
Playing 凉凉 :Tue Aug < 19:29:1< 2019
Playing 凉凉 :Tue Aug < 19:29:19 2019
Playing 凉凉 :Tue Aug < 19:29:22 2019
  1. 创建步骤
    【1】 继承Thread类
    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性
    【3】 重写run()方法

  2. 使用方法
    【1】 实例化对象
    【2】 调用start自动执行run方法
    【3】 调用join回收线程

同步互斥

线程间通信方法

  1. 通信方法

线程间使用全局变量进行通信

  1. 共享资源争夺
  • 共享资源:多个进程或者线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。

  • 影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序。

  1. 同步互斥机制

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

在这里插入图片描述

互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。

在这里插入图片描述

线程同步互斥方法

线程Event
'''
  event互斥方案
'''
from threading import Event,Thread
s = None  # 全局变量用于通信
e = Event() # 事件对象
def yangzilong():
    print('小明前来拜山')
    global s
    s = '小明小强小红'
    e.set() #修改完s

t = Thread(target = yangzilong)
t.start()
print('口令对是自己人')
e.wait()#阻塞等待
if s == '小明小强小红':
    print('三个和尚')
    print('确认过,你是自己人')
else:
    print('打死他')
t.join()

#结果:小明前来拜山
	  口令对是自己人
	  三个和尚
      确认过,你是自己人
from threading import Event

e = Event()  创建线程event对象

e.wait([timeout])  阻塞等待e被set

e.set()  设置e,使wait结束阻塞

e.clear() 使e回到未被设置状态

e.is_set()  查看当前e是否被设置
线程锁 Lock
'''
  线程锁,Lock
'''
from threading import Thread,Lock
a = b = 0
lock = Lock()
def value():
    while True:
        lock.acquire()
        if a != b:
            print('a=%d,b=%d'%(a,b))
        lock.release()#解锁操作
t = Thread(target=value)
t.start()
while True:
    with lock: #with上锁
        a +=1
        b +=1  #语句块结束解锁
t.join()
from  threading import Lock

lock = Lock()  创建锁对象
lock.acquire() 上锁  如果lock已经上锁再调用会阻塞
lock.release() 解锁

with  lock:  上锁
...
...
	 with代码块结束自动解锁

死锁及其处理

  1. 定义

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

在这里插入图片描述

  1. 死锁产生条件
from time import sleep
from threading import Thread,Lock

# 交易类
class Account:
    def __init__(self,_id,balance,lock):
        self.id = _id  # 谁
        self.balance = balance # 有多少钱
        self.lock = lock # 锁

    # 取钱
    def withdraw(self,amount):
        self.balance -= amount # 取多少

    # 存钱
    def deposit(self,amount):
        self.balance += amount # 存多少

    # 查看余额
    def get_balance(self):
        return self.balance

# 创建两个账户
Tom = Account('Tom',5000,Lock())
Alex = Account('Alex',8000,Lock())

# 转账行为
def transfer(from_,to,amount):
    # 从 from_ --> to  转amount
    if from_.lock.acquire(): # 锁住自己账户
        from_.withdraw(amount) # 自己账户扣除
        sleep(0.5)
        if to.lock.acquire(): # 对方账户上锁
            to.deposit(amount) # 对方账户增加
            to.lock.release() # 对方解锁
        from_.lock.release() # 自己解锁
    print("%s给%s转了%d"%(from_.id,to.id,amount))

t1 = Thread(target=transfer,args=(Tom,Alex,2000))
t2 = Thread(target=transfer,args=(Alex,Tom,3500))

t1.start()
t2.start()

t1.join()
t2.join()

print(Tom.get_balance())
print(Alex.get_balance())

死锁发生的必要条件

  • 互斥条件:指线程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  • 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放。
  • 不剥夺条件:指线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放,通常CPU内存资源是可以被系统强行调配剥夺的。
  • 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即进程集合{T0,T1,T2,···,Tn}中的T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。

死锁的产生原因

简单来说造成死锁的原因可以概括成三句话:

  • 当前线程拥有其他线程需要的资源
  • 当前线程等待其他线程已拥有的资源
  • 都不放弃自己拥有的资源
  1. 如何避免死锁

死锁是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生。通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率。

'''
  使用多线程下载一个文件.
'''
from threading import Thread,Lock
import os #导入文件模块
lock =Lock()
urls = ['/home/tarena/',
'/home/tarena/音乐/',
'/home/tarena/文档/',
'/home/tarena/liu/',
'/home/tarena/模板/'
]
#先判断列表中哪个元素有想要的文件
filename = input('要下载的文件:')
list01 = []
for i in urls:
    if os.path.exists(i+filename):#判断文件是否存在
        list01.append(i+filename)

#没有文件
if not list01:
    print('没有资源')
    os._exit(0) #所有进程退出
#计算文件大小
file_size = os.path.getsize(list01[0])
#将文件分块
block_size = file_size // len(list01)
#打开一个本地文件
fd = open(filename,'wb')
#下载一块内容
def download(path,num):
    f = open(path,'rb')
    seek_num = block_size * (num-1)
    f.seek(seek_num)
    data = f.read(block_size)
    with lock:
        fd.seek(seek_num)
        fd.write(data)
    # fd.seek(seek_num)
    # #下载过程
    # size = block_size
    # while True:
    #     if size <= 1024:
    #         data = f.read(size)
    #         fd.write(data)
    #         break
    #     else:
    #         data = f.read(1024)
    #         fd.write(data)
    #         size -= 1024
num = 1
jobs = []
for path in list01:
    t = Thread(target=download,args=(path,num))
    jobs.append(t)
    t.start()
    num += 1 #下载第几块
for i in jobs:
    i.join()

python线程GIL

Python 线程 : 只适合高延迟 , 多阻塞的 IO 行为 。

  1. python线程的GIL问题 (全局解释器锁)

什么是GIL :由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。

导致后果: 因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞高延迟IO时可以提升程序效率,其他情况并不能对效率有所提升。

GIL问题建议

  • 尽量使用进程完成无阻塞的并发行为
  • 不使用c作为解释器 (Java C#)
  1. 结论 : 在无阻塞状态下,多线程程序和单线程程序执行效率几乎差不多,甚至还不如单线程效率。但是多进程运行相同内容却可以有明显的效率提升。

进程线程的区别联系

区别联系

  1. 两者都是多任务编程方式,都能使用计算机多核资源
  2. 进程的创建删除消耗的计算机资源比线程多
  3. 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
  4. 一个进程可以有多个分支线程,两者有包含关系
  5. 多个线程共享进程资源,在共享资源操作时往往需要同步互斥处理
  6. 进程线程在系统中都有自己的特有属性标志,如ID,代码段,命令集等。

使用场景

  1. 任务场景:如果是相对独立的任务模块,可能使用多进程,如果是多个分支共同形成一个整体任务可能用多线程

  2. 项目结构:多种编程语言实现不同任务模块,可能是多进程,或者前后端分离应该各自为一个进程。

  3. 难易程度:通信难度,数据处理的复杂度来判断用进程间通信还是同步互斥方法。

要求

  1. 对进程线程怎么理解/说说进程线程的差异
  2. 进程间通信知道哪些,有什么特点
  3. 什么是同步互斥,你什么情况下使用,怎么用
  4. 给一个情形,说说用进程还是线程,为什么
  5. 问一些概念,僵尸进程的处理,GIL问题,进程状态

并发网络通信模型

常见网络模型

  1. 循环服务器模型 :循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。

优点:实现简单,占用资源少
缺点:无法同时处理多个客户端请求

适用情况:处理的任务可以很快完成,客户端无需长期占用服务端程序。udp比tcp更适合循环。

  1. 多进程/线程网络并发模型:每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时再销毁该进程/线程。

优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求。
缺点: 资源消耗较大

适用情况:客户端同时连接量较少,需要处理行为较复杂情况。

  1. IO并发模型:利用IO多路复用,异步IO等技术,同时处理多个客户端IO请求。

    优点 : 资源消耗少,能同时高效处理多个IO行为
    缺点 : 只能处理并发产生的IO事件,无法处理cpu计算

    适用情况:HTTP请求,网络传输等都是IO行为。

基于fork的多进程网络并发模型

'''
 fork_sercer.py 基于fork的多进程网络并发模型
 1. 创建监听套接字
 2. 等待接收客户端请求
 3. 客户端连接创建新的进程处理客户端请求
 4. 原进程继续等待其他客户端连接
 5. 如果客户端退出,则销毁对应的进程
 tcp_server tcp套接字服务端功能流程
'''
from socket import *
import os
import signal #处理僵尸进程
ADDR = ('0.0.0.0',8888)
#客户端处理函数,循环收发消息
def handle(c):
    while True:
        data = c.recv(1024).decode()
        if not data:
            break
        print(data)
        c.send(b'ok')
#创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
#处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
print('listen the part 8888---')
while True:
    #循环等待客户端连接
    try:
        c,addr = s.accept()
        print('Connect from',addr)
    except KeyboardInterrupt:
        os._exit(0)
    except Exception as e:
        print(e)
        continue
    #创建新的进程
    pid = os.fork()
    if pid == 0:
        #子进程要处理具体的客户端请求
        s.close()
        handle(c)
        os._exit(0)
    else:
        c.close()
'''
  tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
    data = input('Msg:')
    # data为空退出循环
    if not data:
        break
    sockfd.send(data.encode())#发送字节串
    data = sockfd.recv(1024)
    print('Server:',data.decode())
#关闭套接字
sockfd.close()
实现步骤
  1. 创建监听套接字
  2. 等待接收客户端请求
  3. 客户端连接创建新的进程处理客户端请求
  4. 原进程继续等待其他客户端连接
  5. 如果客户端退出,则销毁对应的进程

基于threading的多线程网络并发

'''
 thread_server.py基于threading的多线程网络并发
 1. 创建监听套接字
 2. 循环接收客户端连接请求
 3. 当有新的客户端连接创建线程处理客户端请求
 4. 主线程继续等待其他客户端连接
 5. 当客户端退出,则对应分支线程退出
'''
from socket import *
from threading import Thread
import os
ADDR = ('0.0.0.0',8888)
#客户端处理函数,循环收发消息
def handle(c):
    while True:
        data = c.recv(1024).decode()
        if not data:
            break
        print(data)
        c.send(b'ok')

#创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
print('listen the part 8888---')
while True:
    #循环等待客户端连接
    try:
        c,addr = s.accept()
        print('Connect from',addr)
    except KeyboardInterrupt:
        os._exit(0)
    except Exception as e:
        print(e)
        continue
    #创建新的线程处理请求
    client = Thread(target=handle,args=(c,))
    client.setDaemon(True)
    client.start()
'''
  tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
    data = input('Msg:')
    # data为空退出循环
    if not data:
        break
    sockfd.send(data.encode())#发送字节串
    data = sockfd.recv(1024)
    print('Server:',data.decode())
#关闭套接字
sockfd.close()
#结果:本客户端:Msg:1222
			  Server: ok
			  Msg:999
			  Server: ok
			  Msg:666
			  Server: ok
			  Msg:输入空格结束
#对应上述服务端:listen the part 8888---
			  Connect from ('***.***.*.***', 57436)
			  1222
			  999
			  666
实现步骤
  1. 创建监听套接字
  2. 循环接收客户端连接请求
  3. 当有新的客户端连接创建线程处理客户端请求
  4. 主线程继续等待其他客户端连接
  5. 当客户端退出,则对应分支线程退出

ftp 文件服务器

'''
  ftp 文件服务器
  功能
	【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
	【2】 客户端可以查看服务器文件库中有什么文件。
	【3】 客户端可以从文件库中下载文件到本地。
	【4】 客户端可以上传一个本地文件到文件库。
	【5】 使用print在客户端打印命令输入提示,引导操作
  技术点确认:
     1.并发:多线程并发
     2.数据传输:tcp传输,文件传输都是tcp传输
  结构设计:
     将基本功能封装为类
  功能模块:
     1.搭建网络通信
     2.查看文件列表
     3.下载文件
     4.上传文件
     5.客户端退出
  协议:(协议类型 参数)
      查看列表 L
      退出    Q
      下载    get filename
      上传    put filename
'''
from socket import *
from threading import Thread
import os,sys
import time

# 全局变量
ADDR = ('0.0.0.0',8888)
FTP = "/home/tarena/FTP/" # 文件库路径

# 功能类 (线程类)
# 查文档, 下载,上传
class FTPServer(Thread):
    def __init__(self,connfd):
        super().__init__()
        self.connfd = connfd

    # 处理文件列表
    def do_list(self):
        # 获取文件列表
        files = os.listdir(FTP)
        if not files:
            self.connfd.send("文件库为空".encode())
            return
        else:
            self.connfd.send(b'OK')
            time.sleep(0.1)
        # 拼接文件
        filelist = ''
        for file in files:
            filelist += file + '\n'
        self.connfd.send(filelist.encode())

    def do_get(self,filename):
        try:
            f = open(FTP+filename,'rb')
        except Exception:
            # 文件不存在
            self.connfd.send('文件不存在'.encode())
            return
        else:
            self.connfd.send(b'OK')
            time.sleep(0.1)

        # 发送文件
        while True:
            data = f.read(1024)
            if not data:
                time.sleep(0.1)
                self.connfd.send(b'##')
                break
            self.connfd.send(data)

    def do_put(self,filename):
        if os.path.exists(FTP+filename):
            self.connfd.send("文件已存在".encode())
            return
        else:
            self.connfd.send(b'OK')
        # 接收文件
        f = open(FTP + filename,'wb')
        while True:
            data = self.connfd.recv(1024)
            if data == b'##':
                break
            f.write(data)
        f.close()

    # 循环接受来自客户端的请求
    def run(self):
        while True:
            request=self.connfd.recv(1024).decode()
            if not request or request == 'Q':
                return # 线程退出
            elif request == 'L':
                self.do_list()
            elif request[0] == 'G':
                filename = request.split(' ')[-1]
                self.do_get(filename)
            elif request[0] == 'P':
                filename = request.split(' ')[-1]
                self.do_put(filename)

# 启动函数
def main():
    # 创建监听套接字
    s = socket()
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    s.bind(ADDR)
    s.listen(5)

    print("Listen the port 8080....")

    while True:
        # 循环等待客户端连接
        try:
            c, addr = s.accept()
            print("Connect from", addr)
        except KeyboardInterrupt:
            os._exit(0)
        except Exception as e:
            print(e)
            continue

        # 创建新的线程处理请求
        client = FTPServer(c)
        client.setDaemon(True)
        client.start()

main()
#利用TCP创建客户端
import time
from socket import *
import sys

# 服务器地址
ADDR = ('***.***.*.***',8888)

# 文件处理类
class FTPClient:
    # 所有函数都使用sockfd,所以把它变为属性变量
    def __init__(self,sockfd):
        self.sockfd = sockfd

    def do_list(self):
        self.sockfd.send(b'L') # 发送请求
        # 等待回复 (服务端能否满足请求)
        data = self.sockfd.recv(128).decode()
        if data == 'OK':
            # 一次性接收所有文件
            data = self.sockfd.recv(4096)
            print(data.decode())
        else:
            print(data)

    def do_quit(self):
        self.sockfd.send(b'Q') # 退出请求
        self.sockfd.close()
        sys.exit("谢谢使用")

    def do_get(self,filename):
        # 发送请求
        self.sockfd.send(('G '+filename).encode())
        # 等待回复
        data = self.sockfd.recv(128).decode()
        if data == 'OK':
            f = open(filename,'wb')
            # 循环接收内容,写入文件
            while True:
                data = self.sockfd.recv(1024)
                if data == b'##': # 发送完成
                    break
                f.write(data)
            f.close()
        else:
            print(data)

    def do_put(self,filename):
        try:
            f = open(filename,'rb')
        except Exception as e:
            print("该文件不存在")
            return
        # 发送请求
        filename = filename.split('/')[-1]
        self.sockfd.send(('P '+filename).encode())
        # 等待反馈
        data = self.sockfd.recv(128).decode()
        if data == 'OK':
            while True:
                data = f.read(1024)
                if not data:
                    time.sleep(0.1)
                    self.sockfd.send(b'##')
                    break
                self.sockfd.send(data)
            f.close()
        else:
            print(data)

# 启动函数
def main():
    sockfd = socket()
    try:
        sockfd.connect(ADDR)
    except Exception as e:
        print(e)
        return

    ftp = FTPClient(sockfd) # 实例化对象,用于调用功能
    # 循环发送请求给服务器
    while True:
        print("""\n
          =========Command============
          ****       list        ****
          ****    get   file     ****
          ****    put   file     ****
          ****       quit        ****
          ============================
        """)
        cmd = input("输入命令:")
        if cmd.strip() == 'list':
            ftp.do_list()
        elif cmd.strip() == 'quit':
            ftp.do_quit()
        elif cmd[:3] == 'get':
            filename = cmd.split(' ')[-1]
            ftp.do_get(filename)
        elif cmd[:3] == 'put':
            filename = cmd.split(' ')[-1]
            ftp.do_put(filename)
        else:
            print("请输入正确命令")

main()

#结果:此客户端:


          =========Command============
          ****       list        ****
          ****    get   file     ****
          ****    put   file     ****
          ****       quit        ****
          ============================
        
输入命令:11
请输入正确命令


          =========Command============
          ****       list        ****
          ****    get   file     ****
          ****    put   file     ****
          ****       quit        ****
          ============================
        
输入命令:get
文件不存在


          =========Command============
          ****       list        ****
          ****    get   file     ****
          ****    put   file     ****
          ****       quit        ****
          ============================
        
输入命令:put
该文件不存在


          =========Command============
          ****       list        ****
          ****    get   file     ****
          ****    put   file     ****
          ****       quit        ****
          ============================
        
输入命令:quit
谢谢使用
  1. 功能
    【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
    【2】 客户端可以查看服务器文件库中有什么文件。
    【3】 客户端可以从文件库中下载文件到本地。
    【4】 客户端可以上传一个本地文件到文件库。
    【5】 使用print在客户端打印命令输入提示,引导操作

IO并发

IO 分类

IO分类:阻塞IO ,非阻塞IO,IO多路复用,异步IO等

阻塞IO :默认形态 , 效率低 , 顺序执行

1.定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞IO是IO的默认形态。

2.效率:阻塞IO是效率很低的一种IO。但是由于逻辑简单所以是默认IO行为。

3.阻塞情况:

  • 因为某种执行条件没有满足造成的函数阻塞
    e.g. accept input recv

  • 处理IO的时间较长产生的阻塞状态
    e.g. 网络传输,大文件读写

#### 非阻塞IO

'''
  block_io.py
  socked 非阻塞io示例:
'''
from socket import *
from time import *

f = open('log.txt','a+')
#tcp服务端
s = socket()
s.bind(('0.0.0.0',8888))
s.listen(3)

#非阻塞io设置
# s.setblocking(False)
#超时时间
s.settimeout(2)
while True:
    print('waiting from connect--')
    try:
        connfd,addr = s.accept()
    except (BlockingIOError,timeout) as e:
        sleep(2)
        f.write('%s:%s\n'%(ctime(),e))
        f.flush()
    else:
        print('connect from',addr)
        data = connfd.recv(1024).decode()
        print(data)
'''
  tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
    data = input('Msg:')
    # data为空退出循环
    if not data:
        break
    sockfd.send(data.encode())#发送字节串
    data = sockfd.recv(1024)
    print('Server:',data.decode())
#关闭套接字
sockfd.close()

  1. 定义 :通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
  • 设置套接字为非阻塞IO

sockfd.setblocking(bool)
功能:设置套接字为非阻塞IO
参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞

  • 超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。

    sockfd.settimeout(sec)
    功能:设置套接字的超时时间
    参数:设置的时间

IO多路复用:

  1. 同时监控多个 IO 事件。

  2. 有 IO 可以处理则告诉应用。

  3. 在应用中处理 IO。

  4. 定义

同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。

  1. 具体方案

select方法 : windows linux unix
poll方法: linux unix
epoll方法: linux

select 方法
'''
 select tcp 服务端:
 思路分析:
【1】 将关注的IO放入对应的监控类别列表
【2】 当IO装备就绪时,通知select函数返回
【3】 遍历返回值列表,处理就绪的IO
'''
from socket import *
from select import select
#创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
#设置关注的IO列表
rlist = [s] #s用于等待处理连接
wlist = []
xlist = []

#循环IO监控
while True:
    # print(rlist)
    rs,ws,xs = select(rlist,wlist,xlist)
    # print('-----',rs,ws)
    #遍历返回值列表,判断哪个IO就绪
    for r in rs:
        if r is s:
            c,addr = r.accept()
            print('connect from',addr)
            rlist.append(c)#增加新的关注的IO
        else:#表明有客户端发送消息
            data = r.recv(1024).decode()
            if not data:#客户端退出则取消对其关注
                rlist.remove(r)
                r.close()
                continue
            print(data)
            # r.send(b'OK')
            wlist.append(r)

    for w in ws:
        w.send(b'OK')
        wlist.remove(w)

    for x in xs:
        pass
'''
  tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
    data = input('Msg:')
    # data为空退出循环
    if not data:
        break
    sockfd.send(data.encode())#发送字节串
    data = sockfd.recv(1024)
    print('Server:',data.decode())
#关闭套接字
sockfd.close()
#结果:本客户端:Msg:11
		      Server: OK
			  Msg:22
			  Server: OK
			  Msg:
#服务端:connect from ('***.***.*.***', 60369)
		11
		22
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

select 实现tcp服务

【1】 将关注的IO放入对应的监控类别列表
【2】通过select函数进行监控
【3】遍历select返回值列表,确定就绪IO事件
【4】处理发生的IO事件

注意

wlist中如果存在IO事件,则select立即返回给ws
处理IO过程中不要出现死循环占有服务端的情况
IO多路复用消耗资源较少,效率较高


###@@扩展: 位运算

定义 : 将整数转换为二进制,按二进制位进行运算

运算符号:

  		&  按位与
  		|  按位或
  		^  按位异或
  		<< 左移
  		>> 右移
e.g.  14 --> 01110
      19 --> 10011

14 & 19 = 00010 = 200
14 | 19 = 11111 = 3111
14 ^ 19 = 11101 = 29 相同为0不同为1
14 << 2 = 111000 = 56 向左移动低位补0
14 >> 2 = 11 = 3  向右移动去掉低位

poll方法
'''
 poll_server 完成TCP套接服务
 【1】 创建套接字
 【2】 将套接字register
 【3】 创建查找字典,并维护
 【4】 循环监控IO发生
 【5】 处理发生的IO
'''
from socket import *
from select import *
#创建监听套接字作为关注的IO
s =socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(3)
#创建poll对象
p = poll()
#建立查找字典,通过IO的fileno查找io对象
#始终与register的IO保持一致.
fdmap = {s.fileno():s}

#关注s
p.register(s,POLLIN | POLLERR)

#循环监控IO发生
while True:
    events = p.poll()#阻塞等待IO发生
    #遍历查看哪个IO准备就绪了.
    for fd,event in events:
        print(events)
        if fd == s.fileno():
            c,addr = fdmap[fd].accept()
            print('connect from',addr)
            #关注客户端连接套接字
            p.register(c,POLLIN | POLLHUP)
            fdmap[c.fileno()] = c #维护字典
        elif event & POLLIN:
            data = fdmap[fd].recv(1024).decode()
            if not data:
                p.unregister(fd)
                fdmap[fd].close()
                del fdmap[fd]
                continue
            print(data)
            fdmap[fd].send(b'OK')
p = select.poll()
功能 : 创建poll对象
返回值: poll对象
p.register(fd,event)   
功能: 注册关注的IO事件
参数:fd  要关注的IO
      event  要关注的IO事件类型
  	     常用类型:POLLIN  读IO事件(rlist)
		      POLLOUT 写IO事件 (wlist)
		      POLLERR 异常IO  (xlist)
		      POLLHUP 断开连接 
		  e.g. p.register(sockfd,POLLIN|POLLERR)

p.unregister(fd)
功能:取消对IO的关注
参数:IO对象或者IO对象的fileno
events = p.poll()
功能: 阻塞等待监控的IO事件发生
返回值: 返回发生的IO
        events格式  [(fileno,event),()....]
        每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型

poll_server 步骤

【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
epoll方法
  1. 使用方法 : 基本与poll相同

    • 生成对象改为 epoll()
    • 将所有事件类型改为EPOLL类型
  2. epoll特点

    • epoll 效率比select poll要高
    • epoll 监控IO数量比select要多
    • epoll 的触发方式比poll要多 (EPOLLET边缘触发)

协程技术

基础概念
  1. 定义:纤程,微线程。是允许在不同入口点不同位置暂停或开始的计算机程序,简单来说,协程就是可以暂停执行的函数。

  2. 协程原理 : 记录一个函数的上下文,协程调度切换时会将记录的上下文保存,在切换回来时进行调取,恢复原有的执行内容,以便从上一次执行位置继续执行。

  3. 协程优缺点

优点

  1. 协程完成多任务占用计算资源很少
  2. 由于协程的多任务切换在应用层完成,因此切换开销少
  3. 协程为单线程程序,无需进行共享资源同步互斥处理

缺点

协程的本质是一个单线程,无法利用计算机多核资源


####扩展延伸@标准库协程的实现

python3.5以后,使用标准库asyncio和async/await 语法来编写并发代码。asyncio库通过对异步IO行为的支持完成python的协程。虽然官方说asyncio是未来的开发方向,但是由于其生态不够丰富,大量的客户端不支持awaitable需要自己去封装,所以在使用上存在缺陷。更多时候只能使用已有的异步库(asyncio等),功能有限


第三方协程模
  1. greenlet模块
'''
 协程行为示例
'''
from greenlet import *
def fun1():
    print('执行fun1')
    gr2.switch()
    print('结束fun1')
    gr2.switch()
def fun2():
    print('执行fun2')
    gr1.switch()
    print('结束fun2')
#将函数变成协程
gr1 = greenlet(fun1)
gr2 = greenlet(fun2)

gr1.switch()#选择要执行的协程函数
#结果:执行fun1
	  执行fun2
	  结束fun1
	  结束fun2
'''
 gevent生成协程示例
'''
import gevent
from gevent import monkey
monkey.patch_time()#修改对time模块中阻塞的解释器行为
from time import sleep

#协程函数
def fun1(a,b):
    print('running fun1---',a,b)
    # gevent.sleep(3)
    sleep(3)
    print('fun1 again--')
def fun2():
    print('running fun2---')
    # gevent.sleep(2)
    sleep(2)
    print('fun2 again--')

#生成协程对象
f = gevent.spawn(fun1,1,2)
g = gevent.spawn(fun2)
gevent.joinall([f,g])
#结果:running fun1--- 1 2
	  running fun2---
	  fun2 again--
	  fun1 again--
  • 安装 : sudo pip3 install greenlet

  • 函数

greenlet.greenlet(func)
功能:创建协程对象
参数:协程函数

g.switch()
功能:选择要执行的协程函数
  1. gevent模块
"""
gevent生成协程演示
"""

import gevent
from gevent import monkey
monkey.patch_time() # 修改对time模块中阻塞的解释行为
from time import sleep

# 协程函数
def foo(a,b):
    print("Running foo ...",a,b)
    # gevent.sleep(3)
    sleep(3)
    print("Foo again..")

def bar():
    print("Running bar ...")
    # gevent.sleep(2)
    sleep(2)
    print("Bar again..")

# 生成协程对象
f = gevent.spawn(foo,1,2)
g = gevent.spawn(bar)

gevent.joinall([f,g]) #阻塞等待f,g代表的协程执行完毕
#结果:Running foo ... 1 2
	  Running bar ...
	  Bar again..
	  Foo again..
"""
gevent server 基于协成的tcp并发
思路 : 1. 每个客户函数端设置为协成
      2. 将socket模块下的阻塞变为可以触发协程跳转
"""
import gevent
from gevent import monkey
monkey.patch_all() # 执行脚本,修改socket
from socket import *

def handle(c):
    while True:
        data = c.recv(1024).decode()
        if not data:
            break
        print(data)
        c.send(b'OK')
    c.close()

# 创建tcp套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)

# 循环接收来自客户端连接
while True:
    c,addr = s.accept()
    print("Connect from",addr)
    # handle(c) # 处理具体客户端请求
    gevent.spawn(handle,c) # 协程方案
"""
tcp_client.py  tcp客户端流程
"""

from socket import *

# 创建tcp套接字
sockfd = socket()  # 默认参数-->tcp套接字

# 连接服务端程序
server_addr = ('127.0.0.1',8888)
sockfd.connect(server_addr)

# 发送接收消息
while True:
    data = input("Msg:")
    # data为空退出循环
    if not data:
        break
    sockfd.send(data.encode()) # 发送字节串
    data = sockfd.recv(1024)
    print("Server:",data.decode())

# 关闭套接字
sockfd.close()
#结果:本客户端:Msg:11
		  	  Server: OK
			  Msg:22
			  Server: OK
			  Msg:
#上面服务端:Connect from ('127.0.0.1', 62174)
			11
			22
  • 安装:sudo pip3 install gevent

  • 函数

gevent.spawn(func,argv)
功能: 生成协程对象
参数:func  协程函数
     argv  给协程函数传参(不定参)
返回值: 协程对象

gevent.joinall(list,[timeout])
功能: 阻塞等待协程执行完毕
参数:list  协程对象列表
     timeout 超时时间

gevent.sleep(sec)
功能: gevent睡眠阻塞
参数:睡眠时间

* gevent协程只有在遇到gevent指定的阻塞行为时才会自动在协程之间进行跳转
如gevent.joinall(),gevent.sleep()带来的阻塞
  • monkey脚本

作用:在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。

转换方法:gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。

使用方法

【1】 导入monkey

		from gevent  import monkey

【2】 运行相应的脚本,例如转换socket中所有阻塞

		monkey.patch_socket()

【3】 如果将所有可转换的IO阻塞全部转换则运行all

		monkey.patch_all()

【4】 注意:脚本运行函数需要在对应模块导入前执行

HTTPServer v2.0

'''
  HTTPServer v2.0
  基本要求: 1.获取来自浏览器的请求
           2.判断请求内容是否为/
           3.如果是,则将 index.html 发送给浏览器
             如果不是,则告知浏览器sorry
           4.注意组织http响应格式,判断 200 or 404
           5.根据请求组织数据内容
		   6.将数据内容形成http响应格式返回给浏览器
  升级点 :
	    【1】采用IO并发,可以满足多个客户端同时发起请求情况
		【2】做基本的请求解析,根据具体请求返回具体内容,
		    同时满足客户端简单的非网页请求情况
        【3】通过类接口形式进行功能封装
  技术分析:  select IO  多路复用 TCP传输
  封装设计: 类封装
  在用户的使用角度进行流程设计 1.你的类要实现什么功能
                          2.你希望用户如何使用你的类
  基本原则: 1.当实现的功能比较复杂,需分步实现
            将实例装换为对象,用对象调用指定方法,按步骤实现
           2.实现的类需要用户自己去决定很多内容细节,你去实现一个
           父类,再让用户去继承你的功能,在子类中自我完善.
           3.当实现的类功能并不复杂是一个简单的服务功能,你尽量替用户
           完善功能,不能替用户决定的量通过参数的传入即可.
HTTPServer v2.0
env: python3.6
IO多路复用:http浏览
'''
from socket import *
from select import select
#具体功能服务
class HTTPServer:
    def __init__(self,host='0.0.0.0',post=80,dir=None):
        self.host = host
        self.post = post
        self.dir = dir
        self.address = (host,post)
        #select监控列表
        self.rlist = []
        self.wlist = []
        self.xlist = []
        #直接创建TCP套接字
        self.create_socket()
        self.bind()

    #创建套接字
    def create_socket(self):
        self.sockfd = socket()
        self.sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

    #绑定客户端
    def bind(self):
        self.sockfd.bind(self.address)

    #启动入口
    def serve_forever(self):
        self.sockfd.listen(3)
        print('listen the port %d'%self.post)
        #搭建IO多路复用监控各种IO请求
        self.rlist.append(self.sockfd)
        while True:
            rs,ws,xs = select(self.rlist,self.wlist,self.xlist)
            for r in rs:
                #有浏览器连接过来
                if r is self.sockfd:
                    c,addr = r.accept()
                    self.rlist.append(c)
                else:#处理客户端请求
                    self.handle(r)

    #处理客户端请求
    def handle(self,connfd):
        #接收HTTP请求
        request = connfd.recv(1024)
        print(request)
        #客户端断开
        if not request:
            self.rlist.remove(connfd)
            connfd.close()
            return
        #提取请求内容(将字节串按行切割)
        request_line = request.splitlines()[0]
        info = request_line.decode().split(' ')[1]
        print('请求内容:',info)
        #根据请求内容进行数据处理
        #分两类:1.请求网页,2.其他
        if info == '/' or info[-5:] == '.html':
            self.get_html(connfd,info)
        else:
            self.get_data(connfd,info)

    #处理网页
    def get_html(self,connfd,info):
        if info == '/':#请求主页
            filename = self.dir + '/index.html'
        else:
            filename = self.dir + info
        try:
            fd = open(filename)
        except Exception:#网页不存在
            response = "HTTP/1.1 404 Not Found\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response += "<h1>Sorry-----</h1>"
        else:
            response = "HTTP/1.1 200 OK\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response += fd.read()

        finally:#将内容发送给浏览器
            connfd.send(response.encode())


    #处理其他
    def get_data(self,connfd,info):
        f = open(self.dir+'/timg.jpg','rb')
        data = f.read()
        response = "HTTP/1.1 200 OK\r\n"
        response += "Content-Type:image/jpeg\r\n"
        response += "\r\n"
        response = response.encode() +data
        connfd.send(response)



#用户如何用HTTPServer
if __name__ == '__main__':
    '''
    通过HTTPServer快速启动服务,用于展示自己的网页
    '''
    #需要用户自己决定的内容
    HOST = '0.0.0.0'
    POST = 8888
    DIR = './static' #网页存储位置
    httpd = HTTPServer()
    httpd.serve_forever()#服务启动入口
1. 主要功能 :
   【1】 接收客户端(浏览器)请求
   	【2】 解析客户端发送的请求
   	【3】 根据请求组织数据内容
   	【4】 将数据内容形成http响应格式返回给浏览器

   2. 升级点 :
      【1】 采用IO并发,可以满足多个客户端同时发起请求情况
      【2】 做基本的请求解析,根据具体请求返回具体内容,同时满足客户端简单的非网页请求情况

【3】 通过类接口形式进行功能封装

4】 注意:脚本运行函数需要在对应模块导入前执行

HTTPServer v2.0

'''
  HTTPServer v2.0
  基本要求: 1.获取来自浏览器的请求
           2.判断请求内容是否为/
           3.如果是,则将 index.html 发送给浏览器
             如果不是,则告知浏览器sorry
           4.注意组织http响应格式,判断 200 or 404
           5.根据请求组织数据内容
		   6.将数据内容形成http响应格式返回给浏览器
  升级点 :
	    【1】采用IO并发,可以满足多个客户端同时发起请求情况
		【2】做基本的请求解析,根据具体请求返回具体内容,
		    同时满足客户端简单的非网页请求情况
        【3】通过类接口形式进行功能封装
  技术分析:  select IO  多路复用 TCP传输
  封装设计: 类封装
  在用户的使用角度进行流程设计 1.你的类要实现什么功能
                          2.你希望用户如何使用你的类
  基本原则: 1.当实现的功能比较复杂,需分步实现
            将实例装换为对象,用对象调用指定方法,按步骤实现
           2.实现的类需要用户自己去决定很多内容细节,你去实现一个
           父类,再让用户去继承你的功能,在子类中自我完善.
           3.当实现的类功能并不复杂是一个简单的服务功能,你尽量替用户
           完善功能,不能替用户决定的量通过参数的传入即可.
HTTPServer v2.0
env: python3.6
IO多路复用:http浏览
'''
from socket import *
from select import select
#具体功能服务
class HTTPServer:
    def __init__(self,host='0.0.0.0',post=80,dir=None):
        self.host = host
        self.post = post
        self.dir = dir
        self.address = (host,post)
        #select监控列表
        self.rlist = []
        self.wlist = []
        self.xlist = []
        #直接创建TCP套接字
        self.create_socket()
        self.bind()

    #创建套接字
    def create_socket(self):
        self.sockfd = socket()
        self.sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

    #绑定客户端
    def bind(self):
        self.sockfd.bind(self.address)

    #启动入口
    def serve_forever(self):
        self.sockfd.listen(3)
        print('listen the port %d'%self.post)
        #搭建IO多路复用监控各种IO请求
        self.rlist.append(self.sockfd)
        while True:
            rs,ws,xs = select(self.rlist,self.wlist,self.xlist)
            for r in rs:
                #有浏览器连接过来
                if r is self.sockfd:
                    c,addr = r.accept()
                    self.rlist.append(c)
                else:#处理客户端请求
                    self.handle(r)

    #处理客户端请求
    def handle(self,connfd):
        #接收HTTP请求
        request = connfd.recv(1024)
        print(request)
        #客户端断开
        if not request:
            self.rlist.remove(connfd)
            connfd.close()
            return
        #提取请求内容(将字节串按行切割)
        request_line = request.splitlines()[0]
        info = request_line.decode().split(' ')[1]
        print('请求内容:',info)
        #根据请求内容进行数据处理
        #分两类:1.请求网页,2.其他
        if info == '/' or info[-5:] == '.html':
            self.get_html(connfd,info)
        else:
            self.get_data(connfd,info)

    #处理网页
    def get_html(self,connfd,info):
        if info == '/':#请求主页
            filename = self.dir + '/index.html'
        else:
            filename = self.dir + info
        try:
            fd = open(filename)
        except Exception:#网页不存在
            response = "HTTP/1.1 404 Not Found\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response += "<h1>Sorry-----</h1>"
        else:
            response = "HTTP/1.1 200 OK\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response += fd.read()

        finally:#将内容发送给浏览器
            connfd.send(response.encode())


    #处理其他
    def get_data(self,connfd,info):
        f = open(self.dir+'/timg.jpg','rb')
        data = f.read()
        response = "HTTP/1.1 200 OK\r\n"
        response += "Content-Type:image/jpeg\r\n"
        response += "\r\n"
        response = response.encode() +data
        connfd.send(response)



#用户如何用HTTPServer
if __name__ == '__main__':
    '''
    通过HTTPServer快速启动服务,用于展示自己的网页
    '''
    #需要用户自己决定的内容
    HOST = '0.0.0.0'
    POST = 8888
    DIR = './static' #网页存储位置
    httpd = HTTPServer()
    httpd.serve_forever()#服务启动入口
1. 主要功能 :
   【1】 接收客户端(浏览器)请求
   	【2】 解析客户端发送的请求
   	【3】 根据请求组织数据内容
   	【4】 将数据内容形成http响应格式返回给浏览器

   2. 升级点 :
      【1】 采用IO并发,可以满足多个客户端同时发起请求情况
      【2】 做基本的请求解析,根据具体请求返回具体内容,同时满足客户端简单的非网页请求情况

【3】 通过类接口形式进行功能封装
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值