网络编程

网络编程

1.基本概念

  1. mac地址 :,直译为媒体访问控制地址,也称为局域网地址(LAN Address),以 太网地址(Ethernet Address)或物理地址(Physical Address),它是一个用来确认网上设备位置的地址
  2. IP地址 ;IP地址是指互联网协议地址(英语:Internet ProtocolAddress,又译为网际协议地址),是IP Address的缩写。IP 地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽 物理地址的差异。
  3. 交换机 :交换机只认识mac地址,不认识IP地址。负责局域网内的通信,从网段内部帮你找到具体的机器。能进行广播、单 播、组播的操作

2.网络开发架构

  1. C/S架构 :Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。
    C/S架构的优点:可以离线使用、功能更完善、安全性更高
  2. B/S架构 :Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。
    B/S架构的优点:不用安装就可以使用、统一PC端用户的入口。因此B/S架构是未来的趋势

3.socket:套接字

【server.py】 
import socket 
# 创建一个server端的对象,即实例化一个socket类对象。第一个socket是模块名,第二个是类名。 
sk = socket.socket()
 # 给server端绑定一个地址,【注意】括号里面是一个元组,【注意】元组的第一项是字符串(只能写自己的IP地址或者 本地回环地址,不能写别人的IP地址),元组的第二项是数字(端口号) 
 sk.bind(('127.0.0.1', 9001)) 
 # 开始监听,可以接收客户端给我的连接了
 sk.listen()
 # 建立连接 conn是连接,addr是对面的IP地址 
 conn, addr = sk.accept() 
 # conn, _ = sk.accept() 由于这里并没有用到addr,因此可将其写成 _ 
 #本机向对方说话 
 conn.send(b'hello')
 #接收对方说的话,最多接收1024字节
 msg = conn.recv(1024) print(msg)
 # 关闭与对方的连接 
 conn.close()
 # 关闭服务 
 sk.close()
                   
                   
 【client.py】 
 import socket
sk = socket.socket() 
# 给server端绑定一个地址,【注意】这也是一个元组,【注意】元组的两项必须和服务端sk.bind的元组保持一致 sk.connect(('127.0.0.1',9001)) 
msg = sk.recv(1024)
print(msg)
sk.send(b'byebye') # 因为客户端和服务端的机制不一样,所以这里是sk不是conn 
 sk.close() #【注意】 # 启动的时候,一定要先启server端,否则报错

4.OSI七层协议、OSI五层协议

【第五层】应用层:写的python代码

【第四层】传输层:封装了和端口(port)相关的内容,或解析了和端口相关的内容(有TCP协议、UDP协议)(设备:四层路由器、四层交换机)

【第三层】网络层:与IP相关(有IPv4协议、IPv6协议)(设备:路由器、三层交换机)

【第二层】数据链路层:和mac地址相关(有ARP协议)(设备:网卡、二层交换机)

【第一层】物理层:与电信号相关

5.TCP协议和UDP协议

  1. TCP协议(类比打电话)例子:线下缓存高清电影、qq远程控制、发邮件 :tcp协议 : 效率低 面向连接\可靠\全双工的通信

需要先建立连接 然后才能通信

特点:占用连接、可靠(消息不会丢失,依赖回执机制)、实时性高、慢 全双工的通信

总体来说,不在乎时间的,在乎数据准确性的,一般都是TCP协议

  1. UDP协议(类比发消息) :udp协议 : 效率高 无连接的\不可靠

例子:qq发消息、微信消息、在线播放视频(用户在乎快,在乎是否流畅,不在乎是否丢帧)

不需要建立连接 就可以通信的

不占用连接、不可靠(消息因为网络不稳定丢失)、快

总体来说,在乎时间的,一对多操作的,一般都是UDP协议

6.三次握手四次挥手

# 三次握手
    # 客户端向服务器端发送syn请求,
    # 服务端向客户端回复ack并发送syn请求,
    # 客户端接收到请求之后再回复ack表示建立连接
    # 由客户端的connect + 服务端的accept
# 四次挥手
    # 客户端向服务端发送fin请求,
    # 服务端回复ack确认
    # 服务端向客户端发送fin请求,
    # 客户端回复ack确认
    # 有客户端的close和服务端的close

7.tcp协议多次联系沟通

server端

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))   # 申请操作系统的资源
sk.listen()代表socket服务的开启

while True:  # 为了和多个客户端进行握手
    conn,addr = sk.accept() # 能够和多个客户端进行握手了
    print('conn : ',conn)
    while True:
        send_msg = input('>>>')
        conn.send(send_msg.encode('utf-8')) 直接通过连接发送消息,不需要写地址
        if send_msg.upper() == 'Q':
            break
        msg = conn.recv(1024).decode('utf-8')
        if msg.upper() == 'Q': break
        print(msg)
    conn.close()    # 挥手 断开连接

sk.close()      # 归还申请的操作系统的资源

# str -encode('utf-8')-> bytes
# str -encode('gbk')-> bytes
# '你' 字符
# utf-8  b'181921'      b'181921' -decode('utf-8')-> 你
# gbk    b'17210'       b'17210'  -decode('gbk')  -> 你

client端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))  客户端/tcp协议的方法,和server端建立连接


while True:
    msg = sk.recv(1024)
    msg2 = msg.decode('utf-8')
    if msg2.upper() == 'Q':break
    print(msg,msg2)
    send_msg = input('>>>')
    sk.send(send_msg.encode('utf-8'))
    if send_msg.upper() == 'Q':
            break
sk.close()

8.udp协议多次链接

server端

import socket

sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9001))
while True:
    msg,addr= sk.recvfrom(1024)  接受消息和地址
    print(msg.decode('utf-8'))
    msg = input('>>>')
    sk.sendto(msg.encode('utf-8'),addr 需要写一个对方的地址

client端

import socket

sk = socket.socket(type=socket.SOCK_DGRAM)
server = ('127.0.0.1',9001)
while True:
    msg = input('>>>')
    if msg.upper() == 'Q':break
    sk.sendto(msg.encode('utf-8'),server)
    msg = sk.recv(1024).decode('utf-8')
    if msg.upper() == 'Q':break
    print(msg)

9.粘包

  • 粘包现象:同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。
  • 为什么出现:因为tcp协议是流式传输,只出现在tcp协议中,因为tcp协议 多条消息之间没有边界,并且还有一大堆优化算法
  • 发送端:发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
  • 接受端:接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
  • 怎么处理:设置边界

10.struct模块

struct模块:该模块可以把一个类型,如数字,转成固定长度的bytes

import struct

num1 = 129469649
num2 = 123
num3 = 8

ret1 = struct.pack('i',num1)
print(len(ret1))
ret2 = struct.pack('i',num2)
print(len(ret2))
ret3 = struct.pack('i',num3)
print(len(ret3))

print(struct.unpack('i',ret1))
print(struct.unpack('i', ret2))
print(struct.unpack('i', ret3))

tcp与udp协议的多人多次通信总结

 # 和一个人通信说多句话
    # 和一个人聊完再和其他人聊
    # socket() tcp协议的server
    # bind 绑定一个ip和端口
    # listen 监听,代表socket服务的开启
    # accept 等,到有客户端来访问和客户端建立连接
    # send 直接通过连接发送消息,不需要写地址
    # recv 只接收消息
    # connect 客户端/tcp协议的方法,和server端建立连接
    # close 关闭服务/连接
    
 # udp协议的多人通信
    # socket(type=socket.SOCK_DGRAM)
    # sendto 需要写一个对方的地址
    # recvfrom 接收消息和地址
    # close 关闭服务/连接
# 每一句话什么意思?执行到哪儿程序会阻塞?为什么阻塞?什么时候结束阻塞?
    # input()  # 等待,直到用户输入enter键
    # accept 阻塞,有客户端来和我建立完连接之后
    # recv   阻塞,直到收到对方发过来的消息之后
    # recvfrom 阻塞,直到收到对方发过来的消息之后
    # connect 阻塞,直到server端结束了对一个client的服务,开始和当前client建立连接的时候
# 粘包现象
    # 什么粘包?
        # 两条或更多条分开发送的信息连在一起就是粘包现象
    # 发生在发送端 : 发送间隔短,数据小,由于优化机制就合并在一起发送了
    # 发生在接收端 : 接收不及时,所以数据就在接收方的缓存端黏在一起了
    # 粘包发生的本质 : tcp协议的传输是流式传输 数据与数据之间没有边界
    # 怎么解决粘包 : 自定义协议 struct模块
        # 先发送四字节的数据长度       # 先接受4字节 知道数据的长度
        # 再按照长度发送数据           # 再按照长度接收数据

tcp协议的自定义协议解决粘包问题

  • recv(1024)不代表一定收到1024个字节,而是最多只能收到这么多
  • 两条连续发送的数据一定要避免粘包问题
  • 先发送数据长度,在发送数据
    发送的数据相关的内容组成json:先发json的长度,再发json,json中存了接下来要发送的数据长度,在发送数据

    server端
    import json
    import socket
    # 接收
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()

    conn,_ =sk.accept()
    msg = conn.recv(1024).decode('utf-8')
    msg = json.loads(msg)

    with open(msg['filename'],'wb') as f:
    while msg['filesize'] > 0:
    content = conn.recv(1024)
    msg['filesize'] -= len(content)
    f.write(content)
    conn.close()
    sk.close()

    client
    import os
    import json
    import socket
    # 发送
    sk = socket.socket()
    # sk.connect(('192.168.14.109',9012))
    sk.connect(('127.0.0.1',9001))

    # 文件名\文件大小
    abs_path = r'D:\python22期\day28 课上视频\3.网络基础概念.mp4'
    filename = os.path.basename(abs_path)
    filesize = os.path.getsize(abs_path)
    dic = {'filename':filename,'filesize':filesize}
    str_dic = json.dumps(dic)
    sk.send(str_dic.encode('utf-8'))

    with open(abs_path,mode = 'rb') as f:
    while filesize>0:
    content = f.read(1024)
    filesize -= len(content)
    sk.send(content)
    sk.close()

11验证客户端的合法性

利用hashlib模块进行

server端
import os
import socket
import hashlib

secret_key = b'alex_sb'
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

conn,addr = sk.accept()
# 创建一个随机的字符串
rand = os.urandom(32)
# 发送随机字符串
conn.send(rand)

# 根据发送的字符串 + secrete key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()

# 等待接收客户端的摘要结果
res_client = conn.recv(1024).decode('utf-8')
# 做比对
if res_client == res:
    print('是合法的客户端')
    # 如果一致,就显示是合法的客户端
    # 并可以继续操作
    conn.send(b'hello')
else:
    conn.close()
    # 如果不一致,应立即关闭连接
    
client端
import socket
import hashlib

secret_key = b'alex_sb979'
sk = socket.socket()
sk.connect(('127.0.0.1',9001))

# 接收客户端发送的随机字符串
rand = sk.recv(32)
# 根据发送的字符串 + secret key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()
# 摘要结果发送回server端
sk.send(res.encode('utf-8'))
# 继续和server端进行通信
msg = sk.recv(1024)
print(msg)

12.并发的TCP协议server端--socketserver

#当客户端来链接的时候直接和handle中中的代码交互self.request==conn
server端
import time
import socketserver

class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        while True:
            try:
                content = conn.recv(1024).decode('utf-8')
                conn.send(content.upper().encode('utf-8'))
                time.sleep(0.5)
            except ConnectionResetError:
                break
server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()


client端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9001))

while True:
    sk.send(b'hello')
    content = sk.recv(1024).decode('utf-8')
    print(content)

13.操作系统基础

1.I/O操作:输入Input输出Qutput (相对内存来说)

  • 输入是怎么输入的:键盘\input\read\recv
  • 输出是怎么输出的;显示器\print\write\send

2.多道操作系统:一个程序遇到io就让出cpu给别人

  • 顺序的一个一个执行的思路变成共同存在一台计算机中,其中一个程序执行让出cpu之后,另一个程序能继续使用cpu
  • 来提高cpu的利用率
  • 单纯的切换会不会占用时间:会
  • 但是多道操作系统的原理整体还是节省了时间,提高了cpu的利用率
  • 时空复用的概念

3.单cpu 分时操作系统: 把时间分成很小的段,每个时间都是一个时间片

  • 没有提高cpu的利用率 \提高了用户的体验

4.实时操作系统

14.并发相关介绍

进程:进行中的程序

  • 占用资源 需要操作系统调度
  • pid:能够唯一标识一个进程
  • 进程是计算机中最小的资源分配单位

并发;

  • 多个程序同时进行:只有一个cpu,多个程序轮流在一个cpu中执行
    宏观上:多个程序同时执行

      微观上:多个程序轮流在一个cpu中执行 本质上还是串行

并行 并行比较高效

  • 多个程序同时执行,并且同时在多个cpu上执行

同步

  • 在做a事的时候发起b事件,必须等待b事件结束之后才能继续做a事件

异步:在做a事的时候发起b事件,不需要等待b事件结束之后就可以做a事件

阻塞 :如果cpu不工作 input accept sleep connect

非阻塞:如果cpu工作

线程

  • 线程是进程中的一个单位,不能脱离进程存在
  • 线程是计算机中能够被cpu调度的最小单位

同步阻塞 调用函数必须等待结果\cpu不工作 例如 input sleep

input sleep recv recvfrom

同步非阻塞 调用函数必须等待结果\cpu工作 例如 调用了一个高计算的函数 strip eval('1+2+4')

  # ret = eval('1+2+3+4+5')
# def func(*args):
#     count = 0
#     for i in args:
#         count += i
#     return count
# a = 1
# b = 2
# c = a + b
# d = func(a,b,c)
# print(d)

异步非阻塞(阻塞 blocking) 调用函数不需要立即获取结果,也不需要等 例如 start() terminate()

异步阻塞 调用函数不需要立即获取结果,而是继续做其他的事,在获取结果的时候不知道先获取谁的,但是总之需要等(阻塞)

15.进程

进程的三状态图

  • 就绪 运行 阻塞

进程的调度算法

  • 给所有的进程分配资源或者分配cpu使用权的一种方法
  • 短作业优先
  • 先来先服务
  • 多级反馈算法
    • 多个任务队列,优先级从高到低
    • 新来的任务总是优先级最高的
    • 每一个新任务几乎会立即或得一个时间片时间
    • 执行完一个时间片之后会降到下一级的队列中
    • 总是优先级最高的任务都执行完了才执行优先级低的队列
    • 并且优先级越高时间片越短

进程的开启和关闭

multiprocessing模块 多元的处理进程的模块

# import os
# from multiprocessing import Process
#
# def func():
#     print(os.getpid(),os.getppid())
#     # pid process id           进程id
#     # ppid parent process id   父进程id
# if __name__   == '__main__':
#     # 只会在主进程中执行的所有的代码你写在name = main下
#     print('main :',os.getpid(),os.getppid())
#     p = Process(target=func)
#     p.start()

# 为什么要用 if __name__ == '__main__':
# 能不能给子进程传递参数
# import os
# from multiprocessing import Process
#
# def func(name,age):
#     print(os.getpid(),os.getppid(),name,age)
#
# if __name__ == '__main__':
#     # 只会在主进程中执行的所有的代码你写在name = main下
#     print('main :',os.getpid(),os.getppid())
#     p = Process(target=func,args=('alex',84))
#     p.start()

# 能不能获取子进程的返回值   # 不能

# 能不能同时开启多个子进程
# import os
# import time
# from multiprocessing import Process
#
# def func(name,age):
#     print('%s start'%name)
#     time.sleep(1)
#     print(os.getpid(),os.getppid(),name,age)
#
# if __name__ == '__main__':
#     # 只会在主进程中执行的所有的代码你写在name = main下
#     print('main :',os.getpid(),os.getppid())
#     arg_lst = [('alex',84),('太白', 40),('wusir', 48)]
#     for arg in arg_lst:
#         p = Process(target=func,args=arg)
#         p.start()  # 异步非阻塞

# join的用法   
import os
import time
import random
from multiprocessing import Process

def func(name,age):
    print('发送一封邮件给%s岁的%s'%(age,name))
    time.sleep(random.random())
    print('发送完毕')

if __name__ == '__main__':
 #方法一
    arg_lst = [('大壮',40),('alex', 84), ('太白', 40), ('wusir', 48)]
    p_lst = []
    for arg in arg_lst:
        p = Process(target=func,args=arg)
        p.start()
        p_lst.append(p)
    for p in p_lst:p.join()
    #方法二
    p_l = []
    p = Process(target=func, args=('大壮',40))
    p.start()
    p_l.append(p)
    p = Process(target=func, args=('alex', 84))
    p.start()
    p_l.append(p)
    p = Process(target=func, args=('太白', 40))
    p.start()
    p_l.append(p)
    p = Process(target=func, args=('wusir', 48))
    p.start()
    p_l.append(p)
    for p in p_l:p.join()
    print('所有的邮件已发送完毕') #多个子进程是异步执行不需要等待结果 且进程执行的慢 所以print先打印完毕

# 同步阻塞 异步非阻塞
    # 同步阻塞 join
    # 异步非阻塞 start

# 多进程之间的数据是否隔离
# from multiprocessing import Process
# n = 0
# def func():
#     global n
#     n += 1
#
# if __name__ == '__main__':
#     p_l = []
#     for i in range(100):
#         p = Process(target=func)
#         p.start()
#         p_l.append(p)
#     for p in p_l:p.join()
#     print(n)

使用多进程实现一个并发的socket的server

sever端
def talk(conn):
    while True:
        msg = conn.recv(1024).decode('utf-8')
        ret = msg.upper().encode('utf-8')
        conn.send(ret)
    conn.close()

if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        Process(target = talk,args=(conn,)).start()
    sk.close()
    
    
    client端
    import time
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9001))

while True:
    sk.send(b'hello')
    msg =sk.recv(1024).decode('utf-8')
    print(msg)
    time.sleep(0.5)

sk.close()
    

Process类拾遗

  • 开启进程的另一种方法
    • 面向对象的方法,通过继承和重写run方法完成了启动子进程
    • 通过重写init方法和调用父类的init完成了给子进程传参数

      import os
      import time
      from multiprocessing import Process

      class MyProcess(Process):
      def init(self,a,b,c):
      self.a = a
      self.b = b
      self.c = c
      super().__init__()

      def run(self):
          time.sleep(3)
          print(os.getppid(),os.getpid(),self.a,self.b,self.c)

      if name == 'main':
      p = MyProcess(1,2,3)
      p.start()
      print(p.pid,p.ident)
      print(p.name)
      print(p.is_alive())
      p.terminate() # 强制结束一个子进程 同步 异步非阻塞
      print(p.is_alive())
      time.sleep(0.01)
      print(p.is_alive())

Process类的一些其他方法\属性

name pid  ident  

     daemon(默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置)

terminate()   isalive()

p = MyProcess(1,2,3)
    p.start() #启动进程 并调用该子进程的p.run()
    print(p.pid,p.ident)# 进程的pid
    print(p.name)  
    print(p.is_alive())
    p.terminate()   # 强制结束一个子进程   同步  异步非阻塞
    print(p.is_alive())  #若p仍然运行,返回True
    time.sleep(0.01)
    print(p.is_alive()) 

1守护进程

守护进程使用方法:在start一个进程之前设置daemon = True

  • 守护进程会等待主进程代码结束后就立即结束
  • 守护进程会等待其他子进程结束m?不会
  • 为什么守护进程不是等主进程结束后才结束
    • 因为主进程要最后结束,回收守护进程资源

      import time

      from multiprocessing import Process

      def son1():

      while True:

      time.sleep(1)

      def son2(): # 执行10s

      for i in range(10):

      print('in son2')

      time.sleep(1)

      if name == 'main': # 3s

      p1 = Process(target=son1)

      p1.daemon = True # 表示设置p1是一个守护进程

      p1.start()

      p2 = Process(target=son2,)

      p2.start()

      time.sleep(3)

      print('in main')

2.进程同步Lock 锁

为了解决进程之间的数据安全问题

#抢票系统例子
import json
import time
from multiprocessing import Process,Lock

def search(i):
    with open('ticket',encoding='utf-8') as f:
        ticket = json.load(f)
    print('%s :当前的余票是%s张'%(i,ticket['count']))

def buy_ticket(i):
    with open('ticket',encoding='utf-8') as f:
        ticket = json.load(f)
    if ticket['count']>0:
        ticket['count'] -= 1
        print('%s买到票了'%i)
    time.sleep(0.1)
    with open('ticket', mode='w',encoding='utf-8') as f:
        json.dump(ticket,f)

def get_ticket(i,lock):
    search(i)
    with lock:   # 代替acquire和release 并且在此基础上做一些异常处理,保证即便一个进程的代码出错退出了,也会归还钥匙
        buy_ticket(i)


if __name__ == '__main__':
    lock = Lock()     # 互斥锁
    for i in range(10):
        Process(target=get_ticket,args=(i,lock)).start()

# import time
# from multiprocessing import Lock,Process
# def func(i,lock):
#     lock.acquire()   # 拿钥匙
#     print('被锁起来的代码%s'%i)
#     lock.release()  # 还钥匙
#     time.sleep(1)
#
# if __name__ == '__main__':
#     lock = Lock()
#     for i in range(10):
#         p = Process(target=func,args=(i,lock))
#         p.start()

3.进程之间的通信 ---队列

# 进程之间数据隔离
# 进程之间通信(IPC) Inter Process communication
    # 基于文件 :同一台机器上的多个进程之间通信
        # Queue 队列
            # 基于socket的文件级别的通信来完成数据传递的
    # 基于网络 :同一台机器或者多台机器上的多进程间通信
        # 第三方工具(消息中间件)
            # memcache
            # redis
            # rabbitmq
            # kafka

# from multiprocessing import Queue,Process
# def pro(q):
#     for i in range(10):
#         print(q.get())
# def son(q):
#     for i in range(10):
#         q.put('hello%s'%i)
#
# if __name__ == '__main__':
#     q = Queue()
#     p = Process(target=son,args=(q,))
#     p.start()
#     p = Process(target=pro, args=(q,))
#     p.start()

4.生产者消费者模型

# 生产者消费者模型
    # 爬虫的时候
    # 分布式操作 : celery
    # 本质 :就是让生产数据和消费数据的效率达到平衡并且最大化的效率
    
# import time
# import random
# from multiprocessing import Queue,Process
#
# def consumer(q): # 消费者:通常取到数据之后还要进行某些操作
#     for i in range(10):
#         print(q.get())
#
# def producer(q): # 生产者:通常在放数据之前需要先通过某些代码来获取数据
#     for i in range(10):
#         time.sleep(random.random())
#         q.put(i)
#
# if __name__ == '__main__':
#     q = Queue()
#     c1 = Process(target=consumer,args=(q,))
#     p1 = Process(target=producer,args=(q,))
#     c1.start()
#     p1.start()

import time
import random
from multiprocessing import Queue,Process

def consumer(q,name): # 消费者:通常取到数据之后还要进行某些操作
    while True:
        food = q.get()
        if food:
            print('%s吃了%s'%(name,food))
        else:break

def producer(q,name,food): # 生产者:通常在放数据之前需要先通过某些代码来获取数据
    for i in range(10):
        foodi = '%s%s'%(food,i)
        print('%s生产了%s'%(name,foodi))
        time.sleep(random.random())
        q.put(foodi)

if __name__ == '__main__':
    q = Queue()
    c1 = Process(target=consumer,args=(q,'alex'))
    c2 = Process(target=consumer,args=(q,'alex'))
    p1 = Process(target=producer,args=(q,'大壮','泔水'))
    p2 = Process(target=producer,args=(q,'b哥','香蕉'))
    c1.start()
    c2.start()
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    q.put(None)
    q.put(None)
    
#生产者消费者爬虫例子    
import requests
from multiprocessing import Process,Queue
url_dic = {
    'cnblogs':'https://www.cnblogs.com/Eva-J/articles/8253549.html',
    'douban':'https://www.douban.com/doulist/1596699/',
    'baidu':'https://www.baidu.com',
    'gitee':'https://gitee.com/old_boy_python_stack__22/teaching_plan/issues/IXSRZ',
}

def producer(name,url,q):
    ret = requests.get(url)
    q.put((name,ret.text))

def consumer(q):
    while True:
        tup = q.get()
        if tup is None:break
        with open('%s.html'%tup[0],encoding='utf-8',mode='w') as f:
            f.write(tup[1])

if __name__ == '__main__':
    q = Queue()
    pl = []
    for key in url_dic:
        p = Process(target=producer,args=(key,url_dic[key],q))
        p.start()
        pl.append(p)
    Process(target=consumer,args=(q,)).start()
    for p in pl:p.join()
    q.put(None)

5.进程之间的数据共享--Manager

进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此

from multiprocessing import Process,Manager,Lock

def change_dic(dic,lock):
    with lock:
        dic['count'] -= 1

if __name__ == '__main__':
    # m = Manager()
    with Manager() as m:
        lock = Lock()
        dic = m.dict({'count': 100})
        # dic = {'count': 100}
        p_l = []
        for i in  range(100):
            p = Process(target=change_dic,args=(dic,lock))
            p.start()
            p_l.append(p)
        for p in p_l : p.join()
        print(dic)

6.进程总结

from multiprocessing import Process
# 开启进程的另一种方式
# class 类名(Process):
#     def __init__(self,参数):
#         self.属性名 = 参数
#         super().__init__()
#     def run(self):
#         print('子进程要执行的代码')
# p = 类名()
# p.start()

# 守护进程 : 会等待主进程代码结束之后就立即结束
# p = 类名()
# p.daemon = True   # 设置守护进程
# p.start()
# 一般情况下,多个进程的执行顺序,可能是:
    # 主进程代码结束--> 守护进程结束-->子进程结束-->主进程结束
    # 子进程结束 -->主进程代码结束-->守护进程结束-->主进程结束

# 锁:会降低程序的运行效率,保证数据的安全.
from multiprocessing import Lock  # 互斥锁
lock = Lock()
# lock.acquire()
# ...
# lock.release()
#
# with lock:
#     ...

# 队列 ipc 进程之间通信 -- 数据安全
    # 基于socket\pickle\Lock实现的
    # pipe管道基于socket\pickle实现的,没有锁数据不安全
from multiprocessing import Queue,Pipe

# 生产者消费者模型
    # 把原本获取数据处理数据的完整过程进行了 解耦
    # 把生产数据和消费数据分开,根据生产和消费的效率不同,来规划生产者和消费者的个数,
       # 让程序的执行效率达到平衡
# 如果你写了一个程序所有的功能\代码都放在一起,不分函数不分类也不分文件
# 紧耦合的程序

# 拆分的很清楚的程序
# 松耦合的程序

16.线程

1.线程概念

概念:能被操作系统调度(给cpu执行)的最小单位

同一个进程中的多个线程同时被cpu执行???? 可能

数据共享,操作系统调度的最小单位,可以利用多核, 操作系统调度, 数据不安全开启关闭切换时间开销小

在cpython中的多线程___节省io操作的时间

  • gc垃圾回收机制 线程
    • 引用计数 + 分代回收
  • 全局解释器锁的出现主要是为了完成gc的回收机制,对不同线程的引用计数的变化记录的更加精准
  • 全局解释器锁 GIL
    • 导致了同一个进程中的多个线程只能有一个线程真正被cpu执行

2.threading模块开启线程

#面向函数开启线程
import os
import time
from threading import Thread,current_thread,enumerate,active_count
# from multiprocessing import Process as Thread
def func(i):
    print('start%s'%i,current_thread().ident)
    time.sleep(1)
    print('end%s'%i)
if __name__ == '__main__':
    tl = []
    for i in range(10):
        t = Thread(target=func,args=(i,))
        t.start()
        print(t.ident,os.getpid())
        tl.append(t)
    print(enumerate(),active_count())
    for t in tl:t.join()
    print('所有的线程都执行完了')
    
 # current_thread() 获取当前所在的线程的对象 current_thread().ident通过ident可以获取线程id
# 线程是不能从外部terminate
# 所有的子线程只能是自己执行完代码之后就关闭
# enumerate 列表 存储了所有活着的线程对象,包括主线程
# active_count 数字 存储了所有活着的线程个数.包括主线程

3.面向对象的方式起线程

from threading import Thread
class MyThread(Thread):
    def __init__(self,a,b):
        self.a = a
        self.b = b
        super().__init__()

    def run(self):
        print(self.ident)

t = MyThread(1,2)
t.start()  # 开启线程 才在线程中执行run方法
print(t.ident)

threading  
对象 = 实例化结果:指定target  args
    start 
    join
    ident

4.线程之间的数据共享

from threading import Thread
n = 100

def func():
    global n
    n -= 1

t_l = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_l.append(t)
for t in t_l:
    t.join()
print(n)

5.守护线程

1.主线程会等待子线程才结束

  • 原因:主线程结束后进程就会结束

2.守护线程随着主线程结束而结束

3.守护线程会在主线程的代码结束之后继续守护其他子线程吗? 会的

4.守护线程和守护进程区别

  • 如果主进程代码结束之后还有其他子进程在运行,守护进程不守护
  • 如果主线程代码结束之后还有其他子线程在运行,守护线程也守护

5.为什么

  • 守护线程和守护进程的结束原理不同
  • 守护进程需要主进程来回收资源
  • 守护线程是随着进程的结束才结束的7
    • 其他子线程-->主线程结束-->主进程结束-->整个进程中的所有资源都被回收-->守护线程也会被回收
  1. 进程是资源分配单位
    子进程都需要他的父进程来回收资源
    线程是进程中的资源
    所有的线程都会随着进程的结束而被回收

    import time
    from threading import Thread

    def son():

     while True:
         print('in son')
         time.sleep(1)

    def son2():

     for i in range(3):
         print('in son2 ****')
         time.sleep(1)

    # flag a 0s
    t = Thread(target=son)
    t.daemon = True
    t.start()
    Thread(target=son2).start()
    # flag b

6.线程中的数据不安全现象

# += -=  *= /= while if 数据不安全   + 和 赋值是分开的两个操作
# append pop strip数据安全 列表中的方法或者字典中的方法去操作全局变量的时候 数据安全的
# 线程之间也存在数据不安全

from threading import Thread
n = 0
def add():
    for i in range(500000):
        global n
        n += 1
def sub():
    for i in range(500000):
        global n
        n -= 1

t_l = []
for i in range(2):
    t1 = Thread(target=add)
    t1.start()
    t2 = Thread(target=sub)
    t2.start()
    t_l.append(t1)
    t_l.append(t2)
for t in t_l:
    t.join()
print(n)

from threading import Thread
import time
n = []
def append():
    for i in range(500000):
        n.append(1)
def pop():
    for i in range(500000):
        if not n:
            time.sleep(0.0000001)
        n.pop()

t_l = []
for i in range(20):
    t1 = Thread(target=append)
    t1.start()
    t2 = Thread(target=pop)
    t2.start()
    t_l.append(t1)
    t_l.append(t2)
for t in t_l:
    t.join()
print(n)

7.线程锁

from threading import Thread,Lock
n = 0
def add(lock):
    for i in range(500000):
        global n
        with lock:
            n += 1
def sub(lock):
    for i in range(500000):
        global n
        with lock:
            n -= 1

t_l = []
lock = Lock()
for i in range(2):
    t1 = Thread(target=add,args=(lock,))
    t1.start()
    t2 = Thread(target=sub,args=(lock,))
    t2.start()
    t_l.append(t1)
    t_l.append(t2)
for t in t_l:
    t.join()
print(n)

import dis
a = 0
def func():
    global a
    a += 1
'''
锁
0 LOAD_GLOBAL
2 LOAD_CONST 
4 INPLACE_ADD
# GIL锁切换了
6 STORE_GLOBAL
释放锁
'''

from threading import Thread,Lock
import time
n = []
def append():
    for i in range(500000):
        n.append(1)
def pop(lock):
    for i in range(500000):
        with lock:
            if not n:
                time.sleep(0.0000001)    # 强制CPU轮转
            n.pop()

t_l = []
lock = Lock()
for i in range(20):
    t1 = Thread(target=append)
    t1.start()
    t2 = Thread(target=pop,args=(lock,))
    t2.start()
    t_l.append(t1)
    t_l.append(t2)
for t in t_l:
    t.join()
print(n)


# 不要操作全局变量,不要在类里操作静态变量
# += -= *= /= if while 数据不安全
# queue logging 数据安全的

8.线程锁的单例模式

import time
class A:
    from threading import Lock
    __instance = None
    lock = Lock()
    def __new__(cls, *args, **kwargs):
        with cls.lock:
            if not cls.__instance:
                time.sleep(0.000001)   # cpu轮转
                cls.__instance = super().__new__(cls)
        return cls.__instance

def func():
    a = A()
    print(a)
from threading import Thread
for i in range(10):
    Thread(target=func).start()

9.互斥锁和递归锁

Lock 互斥锁 效率高

RLock 递归锁 (recursion) 效率相对低

l = Lock()
l.acquire()
print('希望被锁住的代码')
l.release()

rl = RLock()  # 在同一个线程中可以被acquire多次
rl.acquire()
print('希望被锁住的代码')
rl.release()

from threading import Thread,RLock as Lock

def func(i,lock):
    lock.acquire()
    lock.acquire()
    print(i,': start')
    lock.release()
    lock.release()
    print(i, ': end')

lock = Lock()
for i in range(5):
    Thread(target=func,args=(i,lock)).start()
    
    
 锁死现象
# 死锁现象是怎么产生的?
    # 多把(互斥/递归)锁 并且在多个线程中 交叉使用
        #     fork_lock.acquire()
        #     noodle_lock.acquire()
        #
        #     fork_lock.release()
        #     noodle_lock.release()
    # 如果是互斥锁,出现了死锁现象,最快速的解决方案把所有的互斥锁都改成一把递归锁
        #  程序的效率会降低的
    # 递归锁 效率低 但是解决死锁现象有奇效
    # 互斥锁 效率高 但是多把锁容易出现死锁现象

    # 一把互斥锁就够了

10.队列

import queue   # 线程之间数据安全的容器队列
from queue import Empty  # 不是内置的错误类型,而是queue模块中的错误
1.q = queue.Queue(4)   # fifo 先进先出的队列
q.get()
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print('4 done')
q.put_nowait(5)
print('5 done')
try:
    q.get_nowait()
except Empty:pass
print('队列为空,继续其他内容')

2.from queue import LifoQueue   # last in first out 后进先出 栈
lq = LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())
print(lq.get())
print(lq.get())

3.from queue import PriorityQueue  # 优先级队列

priq = PriorityQueue()
priq.put((2,'alex'))
priq.put((1,'wusir'))
priq.put((0,'太白'))

print(priq.get())
print(priq.get())
print(priq.get())

11.池

  • 开好的线程或者进程会一直存在池中,可以被多个任务反复调用,,这样极大的减少了开启关闭调度线程 或者进程的时间开销
  • 池中的线程/进程个数控制了操作系统需要调度的任务个数,控制池中的单位,有利于提高操作系统的效率,减轻了操作系统的负担
  • 实例化创建池 向池中中提交
    发展过程
    # 发展过程
    # threading模块 没有提供池
    # multiprocessing模块 仿照threading写的 Pool
    # concurrent.futures模块 线程池,进程池都能够用相似的方式开启\使用

1.线程池

import time
import random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor
def func(a,b):
    print(current_thread().ident,'start',a,b)
    time.sleep(random.randint(1,4))
    print(current_thread().ident,'end')

if __name__ == '__main__':
    tp = ThreadPoolExecutor(4)
    for i in range(20):
        tp.submit(func,i,b=i+1)
 
# 实例化 创建池
# 向池中提交任务,submit 传参数(按照位置传,按照关键字传)

2.进程池

#1.进程池模型
import os
import time,random
from concurrent.futures import ProcessPoolExecutor
def func(a,b):
    print(os.getpid(),'start',a,b)
    time.sleep(random.randint(1,4))
    print(os.getpid(),'end')

if __name__ == '__main__':
    tp = ProcessPoolExecutor(4)
    for i in range(20):
        tp.submit(func,i,b=i+1)
        
        
#1. 获取任务结果
import os
import time,random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a,b):
    print(os.getpid(),'start',a,b)
    time.sleep(random.randint(1,4))
    print(os.getpid(),'end')
    return a*b

if __name__ == '__main__':
    tp = ProcessPoolExecutor(4)
    futrue_l = {}
    for i in range(20):         # 异步非阻塞的
        ret = tp.submit(func,i,b=i+1)
        futrue_l[i] = ret
        # print(ret.result())   # Future未来对象
    for key in futrue_l:       # 同步阻塞的
        print(key,futrue_l[key].result())
#3. map  只适合传递简单的参数,并且必须是一个可迭代的类型作为参数
import os
import time,random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a):
    print(os.getpid(),'start',a[0],a[1])
    time.sleep(random.randint(1,4))
    print(os.getpid(),'end')
    return a[0]*a[1]

if __name__ == '__main__':
    tp = ProcessPoolExecutor(4)
    ret = tp.map(func,((i,i+1) for i in range(20)))
    for key in ret:       # 同步阻塞的
        print(key)

#4.回调函数 : 效率最高的
import time,random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor

def func(a,b):
    print(current_thread().ident,'start',a,b)
    time.sleep(random.randint(1,4))
    print(current_thread().ident,'end',a)
    return (a,a*b)

def print_func(ret):       # 异步阻塞
    print(ret.result())

if __name__ == '__main__':
    tp = ThreadPoolExecutor(4)
    futrue_l = {}
    for i in range(20):         # 异步非阻塞的
        ret = tp.submit(func,i,b=i+1)
        ret.add_done_callback(print_func)  # ret这个任务会在执行完毕的瞬间立即触发print_func函数,并且把任务的返回值对象传递到print_func做参数
        # 异步阻塞 回调函数 给ret对象绑定一个回调函数,等待ret对应的任务有了结果之后立即调用print_func这个函数
        # 就可以对结果立即进行处理,而不用按照顺序接收结果处理结果

3.回调函数

from concurrent.futures import ThreadPoolExecutor
import requests
import os

def get_page(url):    # 访问网页,获取网页源代码   线程池中的线程来操作
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def parse_page(res):   # 获取到字典结果之后,计算网页源码的长度,把https://www.baidu.com : 1929749729写到文件里   线程任务执行完毕之后绑定回调函数
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)

if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]
    # 获得一个线程池对象 = 开启线程池
    tp = ThreadPoolExecutor(4)
    # 循环urls列表
    for url in urls:
        # 得到一个futrue对象 = 把每一个url提交一个get_page任务
        ret = tp.submit(get_page,url)
        # 给futrue对象绑定一个parse_page回调函数
        ret.add_done_callback(parse_page)   # 谁先回来谁就先写结果进文件

# 不用回调函数:
    # 按照顺序获取网页 百度 python openstack git sina
    # 也只能按照顺序写
# 用上了回调函数
    # 按照顺序获取网页 百度 python openstack git sina
    # 哪一个网页先返回结果,就先执行那个网页对应的parserpage(回调函数)


# 会起池\会提交任务
# 会获取返回值\会用回调函数

# 1.所有的例题 会默
# 2.进程池(高计算的场景,没有io(没有文件操作\没有数据库操作\没有网络操作\没有input)) : >cpu_count*1  <cpu_count*2
#   线程池(一般根据io的比例定制) : cpu_count*5
# 5*20 = 100并发

17.协程

定义

# 进程是是程序正在运行的实例,是cpu资源分配和调度的最小单位
# 线程
    #能被操作系统调度(给cpu执行)的最小单位
    # 正常的开发语言 多线程可以利用多核
    # cpython解释器下的多个线程不能利用多核 : 规避了所有io操作的单线程
# 协程
    # 是操作系统不可见的
    # 协程本质就是一条线程 多个任务在一条线程上来回切换
    # 利用协程这个概念实现的内容 : 来规避IO操作,就达到了我们将一条线程中的io操作降到最低的目的

# import time
# def func1():
#     print('start')
#     time.sleep(1)
#     print('end')
#
# def func2():
#     print('start')
#     time.sleep(1)
#     print('end')

# 切换 并 规避io 的两个模块
# gevent =  利用了  greenlet    底层模块完成的切换 + 自动规避io的功能
# asyncio = 利用了  yield    底层语法完成的切换 + 自动规避io的功能
    # tornado 异步的web框架
    # yield from - 更好的实现协程
    # send - 更好的实现协程
    # asyncio模块 基于python原生的协程的概念正式的被成立
    # 特殊的在python中提供协程功能的关键字 : aysnc await

# 进程 数据隔离 数据不安全  操作系统级别  开销非常大  能利用多核
# 线程 数据共享 数据不安全  操作系统级别  开销小      不能利用多核   一些和文件操作相关的io只有操作系统能感知到
# 协程 数据共享 数据安全    用户级别      更小        不能利用多核   协程的所有的切换都基于用户,只有在用户级别能够感知到的io才会用协程模块做切换来规避(socket,请求网页的)

# 用户级别的协程还有什么好处:
    # 减轻了操作系统的负担
    # 一条线程如果开了多个协程,那么给操作系统的印象是线程很忙,这样能多争取一些时间片时间来被CPU执行,程序的效率就提高了

# a = 1
# def func():
#     global a
#     # 切换
#     a += 1
#     # 切换
#
# import dis
# dis.dis(func)
# 对于操作系统 : python代码--> 编译 --> 字节码 --> 解释 --> 二进制010101010010101010
# 二进制 反编译过来的 --> LOAD_GLOBAL

# 4cpu
# 进程 :5个进程
# 线程 :20个
# 协程 :500个
# 5*20*500 = 50000

2.gevent内置模块

协程例子
# import gevent
#
# def func():    # 带有io操作的内容写在函数里,然后提交func给gevent
#     print('start func')
#     gevent.sleep(1)
#     print('end func')
#'
# g1 = gevent.spawn(func)
# g2 = gevent.spawn(func)
# g3 = gevent.spawn(func)
# gevent.joinall([g1,g2,g3])
# g1.join()   # 阻塞 直到协程g1任务执行结束
# g2.join()   # 阻塞 直到协程g1任务执行结束
# g3.join()   # 阻塞 直到协程g1任务执行结束
import time
print(time.sleep)


from gevent import monkey
monkey.patch_all()
import time
import gevent

def func():    # 带有io操作的内容写在函数里,然后提交func给gevent
    print('start func')
    time.sleep(1)
    print('end func')

g1 = gevent.spawn(func)
g2 = gevent.spawn(func)
g3 = gevent.spawn(func)
gevent.joinall([g1,g2,g3])

3.asyncio 内置模块

import asyncio

async def func(name):
    print('start',name)
    # await 可能会发生阻塞的方法
    # await 关键字必须写在一个async函数里
    await asyncio.sleep(1)
    print('end')

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([func('alex'),func('太白')]))

aiohttp模块 并发的爬虫

flask轻量级的web框架/sanic异步的轻量级的web框架

4.使用协程实现并发的tcp server端

#server端
import socket
print(socket.socket)          # 在patch all之前打印一次
from gevent import monkey    # gevent 如何检测是否能规避某个模块的io操作呢?
monkey.patch_all()
import socket
import gevent
print(socket.socket)           # 在patch all之后打印一次,如果两次的结果不一样,那么就说明能够规避io操作
def func(conn):
    while True:
        msg = conn.recv(1024).decode('utf-8')
        MSG = msg.upper()
        conn.send(MSG.encode('utf-8'))

sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

while True:
    conn,_ = sk.accept()
    gevent.spawn(func,conn)
#client
import time
import socket
from threading import Thread
def client():
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    while True:
        sk.send(b'hello')
        msg = sk.recv(1024)
        print(msg)
        time.sleep(0.5)

for i in range(500):
    Thread(target=client).start()

协程总结

# gil锁
    # 全局解释器锁,Cpython解释器下有的
    # 导致了python的多线程不能利用多核(不能并行)

# 内容回顾
# 池 concurrent.futures
    # 进程池 p = ProcessPoolExecutor(n)
    # 线程池 p = ThreadPoolExecutor(n)
    # future = submit 提交任务
        # future.result()获取结果
    # map 循环提交任务
    # add_done_callback 回调函数
# 协程
    # 协程:本质是一个线程
        #  能够在一个线程内的多个任务之间来回切换
        # 节省io操作的时间也只能是和网络操作相关的
        # 特点:数据安全,用户级别,开销小,不能利用多核,能够识别的io操作少
    # gevent 第三方模块  完成并发的socket server
        # 协程对象.spawn(func,参数)
        # 能识别的io操作也是有限的
        # 并且要想让gevent能够识别一些导入的模块中的io操作
        # from gevent import monkey;monkey.patch_all()
    # asyncio 内置模块
        # await 写好的asyncio中的阻塞方法
        # async 标识一个函数时协程函数,await语法必须用在async函数中
# import asyncio
# async def func():
#     print('sjkhaf')
#     await asyncio.sleep(1)
#     print('sjkhaf')
# loop = asyncio.get_event_loop()
# loop.run_until_complete(func())
# loop.run_until_complete(asyncio.wait([func(),func(),func()]))

转载于:https://www.cnblogs.com/cui-xuefei/p/11234680.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值