网络编程py

徘徊边缘 提交于 2020-03-05 15:13:10

概念 day27

​ 网络架构

​ C/S :qq 微信 浏览器 英雄联盟 穿越火线 王者荣耀 安装

​ C:client 客户端

​ S:server 服务端

​ B/S :百度 淘宝 码云 只要在浏览器输入网址就可以直接使用了

​ B:browser 浏览器

​ S:server 服务端

​ B/S更好: 更节省资源 不用更新 不依赖环境

​ 统一了所有web程序的入口

​ C/S架构: 安全性 程序比较庞大

​ 移动端

​ app

​ 微信小程序 : 统一了所有web程序的入口

​ 支付宝 : 统一了所有和钱相关的事儿

​ mac

​ 是一个物理地址

​ 唯一的标识你的网络设备

​ ip 地址

​ 是一个逻辑地址

​ 是可以根据你的位置变化发生改变的

​ 能够在广域网中快速的定位你

​ ipv4地址:

​ 4为点分十进制

​ 0.0.0.0-255.255.255.255

​ 2**32

​ 公网和内网:

​ 公网 0.0.0.0-255.255.255.255(不包含保留字段的ip) 你能够在任意一 个地方去访问的ip地址

​ 内网 所有的内网ip都要使用保留字段 只能在一个区域内使用,出了 这个区域就用不了了 192.168.0.0 - 192.168.255.255 10.0.0.0 - 10.255.255.255 172.16.0.0 - 172.32.255.255

​ 路由器和交换机:

​ 交换机完成局域网内的通信

​ 通过ip找mac地址 : arp协议

​ 单播 广播 组播

​ 路由器完成局域网间通信

arp协议:通过你的ip地址 ,获取(解析)你的Mac地址

​ 网关:所有与局域网外部通信的时候所有的关口,所有的请求都会在这里换上网关ip对面

     网关ip
     子网掩码(了解):可以判断要寻找的机器是不是在同一个局域网中
         255.0.0.0
         255.255.0.0
         255.255.255.0

         ip 和子网掩码 按位与运算
         192.168.13.26  # 255.255.0.0
         11000000.10101000.00001101.00011010
         11111111.11111111.00000000.00000000
         11000000.10101000.00000000.00000000 = 192.168.0.0

         192.168.12.134
         255.255.0.0
         11000000.10101000.00001100.10000110
         11111111.11111111.00000000.00000000
         11000000.10101000.00000000.00000000 = 192.168.0.0
     ipv6
         6位冒分十六进制
         0:0:0:0:0:0 - FFFF:FFFF:FFFF:FFFF:FFFF:FFFF
 mac ip 定位到一台机器
 port 端口
     0-65535
        
     
     ip + port能够唯一的确认网络上的一台机器上的一个服务
        
       代表本机:  127.0.0.1

简单的socket通信 (套接字

conn <socket.socket fd=420, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.168.12.65', 9001), raddr=('127.0.0.1', 63789)>
addr ('127.0.0.1', 63789)

socket

bind ()绑定 listen()监听 accept() 接收 send() 发送 recv() 接收 close()关闭 connect() 连接

arp协议: 地址解析 通过一台机器的ip地址找到mac地址

​ #交换机 广播 单播

端口:0-65535 用来标识一台机器上的服务

day28

socket 套接字过程

# osi七层协议
    # 应用层
    # 表示层
    # 会话层
    # 传输层
    # 网络层
    # 数据链路层
    # 物理层
# osi五层协议
    # 应用层(五层)
    # 传输层(四层)     端口 UDP TCP   四层交换机 四层路由器
    # 网络层(三层)     ipv4 ipv6协议  路由器 三层交换机
    # 数据链路层(二层)  mac arp协议   网卡 (二层)交换机
    # 物理层(一层)

# TCP/IP
    # 网络层 arp

# TCP协议 上传下载\发邮件 可靠 面向连接 速度慢 能传递的数据长度不限
    # 建立连接 三次握手
    # 消息传递 可靠传输
    # 断开连接 四次挥手
    
# UDP协议 即时通讯工具 不可靠 面向数据报 速度快 能传递的数据长度有限
    # 不管对方在不在 直接发送
    # 不占连接
    # 随时可以收发消息

TCP:https://baike.baidu.com/item/TCP/33012?fromtitle=TCP%E5%8D%8F%E8%AE%AE&fromid=8988699&fr=aladdin

UDP:https://baike.baidu.com/item/UDP?fromtitle=UDP%E5%8D%8F%E8%AE%AE&fromid=421768

飞秋通信

# from socket import socket,SOCK_DGRAM
# sk = socket(type=SOCK_DGRAM)
# server = ('192.168.12.156',2425)
# sk.sendto('1:111:eva:eva:32:你好'.encode('gbk'),server)

sercer 服务端

# import socket
# sk = socket.socket(type=socket.SOCK_DGRAM)
from socket import socket,SOCK_DGRAM

sk = socket(type=SOCK_DGRAM)
sk.bind(('127.0.0.1',9001))

while True:
    msg,cli_addr = sk.recvfrom(1024)
    print(msg.decode('utf-8'))
    msg = input('>>>')
    if msg.upper() == 'Q':continue
    sk.sendto(msg.encode('utf-8'),cli_addr)

# 1.udp协议调通
# 2.可以根据用户输入的内容任意发送
# 3.可以根据用户的意愿发送多次消息
# 4.可以多人通信

cleint 客户端

from socket import socket,SOCK_DGRAM

sk = socket(type=SOCK_DGRAM)
server_addr = ('127.0.0.1',9001)

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

day29

粘包

1.粘包现象只存在 tcp , udp 不会出现粘包

  1. 出现粘包的情况

    ​ 1.当多条消息发送时接受变成了一条或者出现接收不准确的情况

    ​ 2.粘包现象会发生在发送端

    ​ @ 两条消息间隔时间短,长度短 就会把两条消息在发送之前就拼在一起

    ​ @ 节省每一次发送消息回复的网络资源

    ​ 3.粘包现象会发生在接收端

    ​ @多条消息发送到缓存端,但没有被及时接收,或者接收的长度不足一次发送的长度

    ​ @数据与数据之间没有边界

    ​ 本质: 发送的每一条数据之间没有边界

3.解决粘包的方法

​ 模块 struct

​ 1.作用 struct模块可以将一个类型转成固定长度的bytes

import struct
ret = struct.pack("i",123) # 转换数字
print(ret)
ret = struct.unpack("i",ret) # 反转后获取的是一个元组
print(ret)
print(ret[0])
#server

import struct
import socket

def proto_send(msg):
    msg = msg.encode('utf-8')
    len_msg = len(msg)
    proto_len = struct.pack('i', len_msg)  # 把字节的长度编程4字节,i代表int
    conn.send(proto_len)
    conn.send(msg)

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

conn,addr = sk.accept()
msg1 = 'hello'
msg2 = 'world'
proto_send(msg1)
proto_send(msg2)

# 计算要发送的数据字节长度
# 把字节的长度编程4字节
# 发送4字节
# 发送数据

# 通过socket的tcp协议上传文件


#client

import struct
import socket

sk = socket.socket()

def proto_recv():
    len_msg = sk.recv(4)
    len_msg = struct.unpack('i', len_msg)[0]
    msg = sk.recv(len_msg)
    return msg

sk.connect(('192.168.12.26',9001))
for i in range(1000000):2*i
msg1 = proto_recv()
print(msg1)
msg2 = proto_recv()
print(msg2)



day31

内容回顾

# 网络
# osi五层协议
# 应用层  要发送的数据 http/https协议
# 传输层  端口 tcp/udp协议     四层路由器/四层交换机
        # tcp 面向连接 可靠 速度慢 长度不受限 全双工 流式传输
            # 文件传输\邮件\实时通信
            # 三次握手 :记那张图(SYN ACK) accept connect
                # 1.三次握手是tcp协议建立连接的过程
                # 2.由客户端发起一个syn请求,服务端接收并回复(syn\ack)
                #   客户端收到ack和syn之后再回复一个ack
                # 3.在原生的socket代码中三次握手是由accept connect
            # 数据的交互 : 粘包现象 粘包成因
            # 四次挥手 :记那张图(FIN ACK) close close
        # udp 无连接 面向数据报 不可靠 速度快 长度受限制 一对一 一对多 多对多
            # 短消息类\在线观看视频
# 网络层  ip协议              路由器/三层交换机
# 数据链路层 mac地址 arp协议   网卡/交换机
# 物理层


# 局域网和广域网的区别 : 相对论
# 内网和公网的区别 : ip地址的区别
# 特殊的ip : 0.0.0.0 127.0.0.1
# 什么是交换机\路由器

# 代码
    # socket模块
        # tcp服务
        # udp服务 参数
    # tcp的粘包
        # 如何解决
    # socketserver模块
        # 固定的格式(背)

操作系统基础

# 穿孔卡片
# 高速磁带
# -- 操作系统
    # 多道操作系统
        # 第一次提出了多个程序可以同时在计算机中被计算
        # 1.遇到IO就让出CPU
        # 2.把CPU让给其他程序,让其他程序能够使用CPU
        # 3.CPU的让出这件事 占用时间
        # 4.两个程序来回在CPU上切换,不会
            # 每个程序有独立的内存空间
            # 每个程序在切换的前后会把当前程序的状态记录下来

# CPU计算和不计算(IO)操作
# IO操作(网络操作\文件操作) : 输入输出:相对内存
    # 阻塞: sleep\input\recv\accept\recvfrom是不需要cpu参与的
    # 对文件的读取 : 对硬盘的操作一次读取相当于90w条代码
    # Input : 向内存输入数据
        # 读\load\input\recv\recvfrom\accept\connect\close
    # Output : 从内存输出数据
        # 写\dump\print\send\sendto\accept\connect\close
    # 所有的IO操作本质都是文件操作
        # input\print input是写入文件,然后通过读取文件把输入的内容加载到内存
        #               print是直接写入文件,然后通过文件展示给用户看
        # socket中的交互方法 : 都是文件操作
            # send 是向缓存文件中写
            # recv 是从缓存文件中读
        # 也就是说只要涉及到IO操作 至少就是一个0.009s=就是CPU执行90w条python代码的时间

# 0.009s
# 500000000条指令/s  /5 = 100000000条python代码/s
# 0.009s * 100000000 = 900000条python代码

# import dis
# a = 1
# def func():
#     global a
#     a+=1
# dis.dis(func)

# 1.老教授和研究生
    # 研究生 5min 没有IO操作 先来先服务(FIFS)
    # 老教授  24h 没有IO操作
    # 研究生 3min 没有IO操作 短作业优先算法

# 2.时间片轮转算法 -- 分时操作系统
    # 1w = 0.00005s
    # 1.时间片到了才让出CPU
    # 2.CPU的让出这件事 占用时间
    # 3.减低工作效率,提高了用户体验

进程概念

# 程序与进程(计算机中最小的资源分配单位)
    # 运行中的程序 就是 进程
    # 进程与进程之间的数据是隔离的
# 线程(计算机中能被操作系统调度的最小单位)
    # 每个程序执行到哪个位置是被记录下来的
    # 在进程中 有一条线程是负责具体的执行程序的

# 进程的调度(由操作系统完成的) :
    # 被操作系统调度的,每个进程中至少有一个线程
    # 短作业优先算法
    # 先来先服务算法
    # 时间片轮转算法
    # 多级反馈算法
# 进程的启动 销毁
    # 进程的启动 : 交互(双击) 在一个进程中启动另一个 开机自启动
        # 负责启动一个进程的程序 被称为一个父进程
        # 被启动的进程 被成为一个子进程
    # 销毁 : 交互  被其他进程杀死(在父进程结束子进程)  出错进程结束
# 父子进程
    # 父进程开启子进程
    # 父进程还要负责对结束的子进程进行资源的回收
# 进程id --> processid --> pid
    # 在同一台机器上 同一个时刻 不可能有两个重复的进程id
    # 进程id不能设置 是操作系统随机分配的
    # 进程id随着多次运行一个程序可能会被多次分配 每一次都不一样
# 进程的三状态图
    # 就绪ready 运行run  阻塞block


# import os
# import time
#
# print(os.getpid())
# print(os.getppid())  # parent process id
# time.sleep(100)

# 2.模块multiprocessing模块 :内置模块
# multiple 多元化的
# processing 进程
# 把所有和进程相关的机制都封装在multiprocessing模块中了

# 3.学习这个模块
import os
import time
from multiprocessing import Process

def func():
    '''
    在子进程中执行的func
    :return:
    '''
    print('子进程 :',os.getpid(),os.getppid())
    time.sleep(3)
if __name__ == '__main__':
    p = Process(target=func)
    p.start()
    print('主进程 :',os.getpid())

# 并行 : 多个程序同时被CPU执行
# 并发 : 多个程序看起来在同时运行
# 同步 : 一个程序执行完了再调用另一个 并且在调用的过程中还要等待这个程序执行完毕
# 异步 : 一个程序在执行中调用了另一个 但是不等待这个任务完毕 就继续执行 start
# 阻塞 : CPU不工作
# 非阻塞 : CPU工作

day32

from multiprocessing import Process

Process 模块

进程

  1. 什么是进程

    运行中的程序就是正在运行的程序,进程是资源单位

  2. 开启一个进程发送了什么

    在内存中开启一个进程空间,将主进程的资源复制一份,进程之间空间是隔离的,数据不共享】

  3. 进程的状态

    1. 运行一个进程首先会进入就绪状态,拿到cpu后进入运行状态
  4. 运行:运行中遇到IO进入阻塞状态

    1. 阻塞:阻塞结束后重新进入就绪状态,等待cpu资源
  5. 就绪:拿到cpu资源后进入运行状态

  6. 进程的理论

    1. 串行
      1. 逐个完成任务
    2. 并发
      1. 一个cpu完成多个任务(cpu快速切换),看起来像同时完成
    3. 并行
      1. 多个cpu执行多个任务,真正的同时完成
    4. 阻塞
      1. 当cpu在遇到IO就是阻塞
    5. 非阻塞
      1. 程序没有IO,就是非阻塞

创建进程

  1. windows系统下必须创建进程必须在mian中

    args的参数必须是元祖类型

    from multiprocessing import Process
    def a(name):
        print(name)
    if __name__ == '__main__':
        p = Process(target=a,args=("张三",))
        p.start() # 开启进程
    from multiprocessing import Process
    class A(Process):
        def __init__(self,name):
            super().__init__() # 继承父类init
            self.name = name
        def run(self): # 开启进程自动执行run方法
            print(self.name)
    if __name__ == '__main__':
        a = A("张三")
        a.start() # 开启进程

5.进程的pid

每个进程都有一个唯一的pid

cmd中可以通过:pid tasklist获取进程pid

python获取进程pid需要导入os模块

from multiprocessing import Process
import os
def a():
    print(os.getpid()) # 获取当前线程pid
    print(os.getppid()) # 获取父线程的pid
if __name__ == '__main__':
    p = Process(target=a)
    p.start()

6.join

join是一种阻塞,主进程要等待设置join的子进程执行完毕后再执行

from multiprocessing import Process
import time
def A(num):
    print(num)
    time.sleep(num)
if __name__ == '__main__':
    a = Process(target=A,args=(1,))
    ti = time.time()
    a.start()
    a.join()
    print(time.time()-ti)

7.进程对象其他属性

from multiprocessing import Process
def a():
    pass
if __name__ == '__main__':
    p = Process(target=a)
    p.start() # 进程名:Process-1
    print(p.name)
    print(Process.is_alive(p))# 获取进程状态
    Process.terminate(p) # 杀死进程
    print(Process.is_alive(p))

8.守护进程daemon

设置为守护进程的子进程,会在主进程结束后马上结束,设置守护进程需要在开启进程前设置

 守护进程
# 守护进程不会守护除了主进程代码之外的其他子进程
# 守护进程会随着父进程的代码结束而结束
# import time
# def son():
#     while True:
#         time.sleep(1)
#         print('in son')
#
#
# if __name__ == '__main__':
#     p = Process(target=son)
#     p.daemon = True   # 将当前的子进程设置为守护进程
#     p.start()
#     time.sleep(5)

# 正常情况下 父进程永远会等着子进程结束
# 如果设置了守护进程 父进程的代码结束之后 守护进程也跟着结束

# 子进程结束之后,父进程才会结束

# 代码结束和进程结束是两回事儿
# 没设置守护进程
    # 1.子进程的代码和主进程的代码自己执行自己的,互相之间没关系
    # 2.如果主进程的代码先结束,主进程不结束,等子进程代码结束,回收子进程的资源,主进程才结束
    # 3.如果子进程的代码先结束,主进程边回收子进程的资源边执行自己的代码,当代码和资源都回收结束,主进程才结束
# 设置了守护进程
    # 1.子进程的代码和主进程的代码自己执行自己的,互相之间没关系
    # 2.一旦主进程的代码先结束,主进程会先结束掉子进程,然后回收资源,然后主进程才结束

9.僵尸进程

在开启进程后,父进程监视子进程运行状态,当子进程运行结束后一段时间内,将子进程回收,父进程和子进程是异步关系,不能在子进程结束后马上捕捉子进程的状态,如果子进程立即释放内存,父进程就无法再检测子进程的状态了,unix提供了一种机制,子进程在结束后会释放大部分内存,但会保留进程号、结束时间、运行状态,供主进程检测和回收

  1. 什么僵尸进程

    子进程在结束后被父进程回收前的状态就是僵尸进程

  2. 危害

    如果父进程由于某些原因一直未对子进程回收,这时子进程就会一直占用内存和进程号

  3. 解决方式

    将父进程杀死,使子进程成为孤儿进程,等待init进行回收

  4. 孤儿进程

    父进程已经结束了,但是子进程还在运行,这时子进程就是孤儿进程,孤儿进程由init回收,

# 数据安全(锁) :用来保证数据安全
# 如果多个进程同时对一个文件进行操作会出现什么问题
    # 1.读数据 : 可以同时读
    # 2.写数据 : 但不能同时写
from multiprocessing import Process,Lock
def change(lock):
    print('一部分并发的代码,多个进程之间互相不干扰的执行着')
    lock.acquire()  # 给这段代码上锁
    with open('file','r') as f:
        content = f.read()
    num = int(content)
    num += 1
    for i in range(1000000):i+=1
    with open('file','w') as f:
        f.write(str(num))
    lock.release()  # 给这段代码解锁
    print('另一部分并发的代码,多个进程之间互相不干扰的执行着')

if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        Process(target=change,args=(lock,)).start()
# 当多个进程同时操作文件/共享的一些数据的时候就会出现数据不安全

# 开启多进程 同时执行100000行代码
# 其中20行涉及到了操作同一个文件
# 只给这20行代码枷锁,来保证数据的安全

进程与进程之间的通信

# 进程之间通信 IPC
# Inter Process Communication
from multiprocessing import Queue,Process

# def son(q):
#     print(q.get())
#
# if __name__ == '__main__':
#     q = Queue()
#     p = Process(target=son,args=(q,))
#     p.start()
#     q.put(123)
# 在进程之间维护数据的安全 -- 进程安全
# 队列是进程安全的(进程队列保证了进程的数据安全)
# 队列都是先进先出的
# 队列是基于文件 + 锁实现的
# 队列一共提供两个方法:get put

# q = Queue()
# q.put({1,2,3})
# num = q.get()  # get是一个同步阻塞方法,会阻塞直到数据来
# print(num)

# q = Queue(2)
# q.put({1,2,3})
# q.put({1,2,3})
# q.put({1,2,3})  # put是一个同步阻塞方法,会阻塞直到队列不满
import queue
# q = Queue(2)
# try:
#     for i in range(4):
#         q.put_nowait(i)  # put_nowait 同步非阻塞方法
# except queue.Full:
#     print(i)

# q2 = Queue(2)
# try:
#     print(q2.get_nowait())
# except queue.Empty:pass

# q = Queue(5)
# ret = q.qsize()  # 查看当前队列有多少值
# print(q.empty())
# print(q.full())

# 生产者消费者模型

# 队列Queue = 管道Pipe + 锁
# Pipe  基于文件实现的(socket+pickle)  =  数据不安全
# Queue 基于文件(socket+pickle)+锁(lock)实现的  =  数据安全
#      基于pipe+锁(lock)实现的

# IPC:
    # 内置的模块实现的机制 :队列\管道
    # 第三方工具 : redis rabbitMQ memcache


day33

  1. 今日概要

  • 爬虫
  • 线程的概念及与进程的区别
  • 多线程的引用
  • 线程安全(单例模式)
  • GIL(全局解释器锁
  1. 内容回顾&补充

    1. 面向对象继承

      class Thread(object):
          def __init__(self):
              pass
          def start(self):
              self.run()
      
          def run(self):
              print('thread.run')
      
      obj = Thread()
      obj.start()
      
      -----------------------------------
      class MyThread(Thread):
          def run():
              print('mythread.run')
      
      
      obj = MyThread()
      obj.start()
    2. 面向对象的上下文管理

      含有__enter__和__exit__方法的对象就是上下文管理器。
      class FOO():
          def __init__(self):
              print('实例化一个对象')
      
          def __enter__(self):
              print('进入')
      
          def__exit__(self,exc_type,exc_val,exc_tb)
              print('退出')
      
          obj = F00()
          with obj:
              print('正在执行')
      1. 阉割版的单例模式

        class Foo:
            instance = None
        
            def __init__(self, name):
                self.name = name
            def __new__(cls, *args, **kwargs):
                # 返回空对象
                if cls.instance:
                    return cls.instance
                cls.instance = object.__new__(cls) #借助父类object实例化一个对象
                return cls.instance          ## obj == __init__()
        
        obj1 = Foo('日魔')
        obj2 = Foo('SB')
        
        print(obj1,obj2)
      2. socketserver的实现原理

        • 内部创建进程或线程实现并发.

          .socketserver本质
          
          import socket
          import threading
          
          
          def task(connect,address):
              pass
          
          
          server = socket.socket()
          server.bind(('127.0.0.1',9000))
          server.listen(5)
          while True:
              conn,addr = server.accept()
              # 处理用户请求
              t = threading.Thread(target=task,args=(conn,addr,))
              t.start()
          
          
          .socketserver源码
          
          
          
          import socketserver
          
          
          class MyRequestHandler(socketserver.BaseRequestHandler):
          
              def handle(self):
                  data = self.request.recv(1024)
                  print(data)
          
          
          obj = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyRequestHandler)
          obj.serve_forever()

    ## 3. 今日详细

    ### 3.1爬虫下载图片的案例

        - ##### 安装和使用
    1. 安装第三方模块
        pip3 install requests
    
    2. 使用
        import requests
        url = 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg'
    
        # python伪造浏览器向地址发送请求
        rep = requests.get(url)
    
        # 请求返回回来的字节
        # print(rep.content)
    
        with open('xxxxxx.jpg',mode='wb') as f:
            f.write(rep.content)
    • 爬虫下载照片
      """
      1. 安装第三方模块
          pip3 install requests
      
      2. 使用
          import requests
          url = 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg'
      
          # python伪造浏览器向地址发送请求
          rep = requests.get(url)
      
          # 请求返回回来的字节
          # print(rep.content)
      
          with open('xxxxxx.jpg',mode='wb') as f:
              f.write(rep.content)
      
      """
      
      import requests
      
      url_list = [
          'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
          'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
          'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
      ]
      
      
      for url in url_list:
          ret = requests.get(url)
          file_name = url.rsplit('/',maxsplit=1)[-1]
          with open(file_name,mode='wb') as f:
              # 下载小文件
              # f.write(ret.content)
      
              # 下载大文件
              for chunk in ret.iter_content():
                  f.write(chunk)
    • 多线程爬虫
      import requests
      
      url_list = [
          'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
          'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
          'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
      ]
      
      # 串行示例
      # for url in url_list:
      #   ret = requests.get(url)
      #   file_name = url.rsplit('/',maxsplit=1)[-1]
      #   with open(file_name,mode='wb') as f:
      #       f.write(ret.content)
      
      # 基于多线程
      import threading
      def task(arg):
          ret = requests.get(arg)
          file_name = arg.rsplit('/', maxsplit=1)[-1]
          with open(file_name, mode='wb') as f:
              f.write(ret.content)
      
      for url in url_list:
          # 实例化一个线程对象
          t = threading.Thread(target=task,args=(url,))
          # 将线程提交给cpu
          t.start()

3.2 线程的概念&与进程的区别?

进程(Process)和线程(Thread)是操作系统的基本概念,

形象的关系

  • 工厂 -> 应用程序
  • 车间 -> 进程
  • 工人 -> 线程

进程和线程的区别?

进程是计算机资源分配的最小单位.
线程是计算机中可以被cpu调度的最小单位.
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源,一个进程中至少有一个线程(一个应用程序中至少有一个进程)

在Python中因为有GIL锁,他同一时刻保证一个进程中只有一个线程可以被cpu调度,所以在使用Python开发时要注意:
    计算密集型,用多进程.
    IO密集型,用多线程. 


默认进程之间无法进行资源共享,如果主要想要通讯可以基于:文件/网络/Queue.

3.3 多线程的应用

  • 快速应用

    import threading
    
    def task(arg):
        pass
    # 实例化一个线程对象
    t = threading.Thread(target=task,args=('xxx',))
    # 将线程提交给cpu
    t.start()
    import threading
    def task(arg):
        ret = requests.get(arg)
        file_name = arg.rsplit('/', maxsplit=1)[-1]
        with open(file_name, mode='wb') as f:
            f.write(ret.content)
    
    for url in url_list:
        # 实例化一个线程对象
        t = threading.Thread(target=task,args=(url,))
        # 将线程提交给cpu
        t.start()
  • 常见方法

    • t.start(),将线程提交给cpu,由cpu来进行调度

    • t.join(),等待

      import threading
      
      
      # 示例1
      """
      loop = 10000000
      number = 0
      
      def _add(count):
          global number
          for i in range(count):
              number += 1
      
      
      t = threading.Thread(target=_add,args=(loop,))
      t.start()
      
      t.join()
      print(number)
      """
      
      # 示例2
      """
      loop = 10000000
      number = 0
      
      def _add(count):
          global number
          for i in range(count):
              number += 1
      
      def _sub(count):
          global number
          for i in range(count):
              number -= 1
      
      t1 = threading.Thread(target=_add,args=(loop,))
      t2 = threading.Thread(target=_sub,args=(loop,))
      t1.start()
      t2.start()
      
      print(number)
      """
      
      
      # 示例3
      """
      loop = 10000000
      number = 0
      
      def _add(count):
          global number
          for i in range(count):
              number += 1
      
      def _sub(count):
          global number
          for i in range(count):
              number -= 1
      
      t1 = threading.Thread(target=_add,args=(loop,))
      t2 = threading.Thread(target=_sub,args=(loop,))
      t1.start()
      t2.start()
      t1.join() # t1线程执行完毕,才继续往后走
      t2.join() # t2线程执行完毕,才继续往后走
      
      print(number)
      """
      
      # 示例4
      """
      loop = 10000000
      number = 0
      
      def _add(count):
          global number
          for i in range(count):
              number += 1
      
      def _sub(count):
          global number
          for i in range(count):
              number -= 1
      
      t1 = threading.Thread(target=_add,args=(loop,))
      t2 = threading.Thread(target=_sub,args=(loop,))
      t1.start()
      t1.join() # t1线程执行完毕,才继续往后走
      t2.start()
      t2.join() # t2线程执行完毕,才继续往后走
      
      print(number)
      """
      • t.setDaemon() ,设置成为守护线程

        import threading
        import time
        
        def task(arg):
            time.sleep(5)
            print('任务')
        
        
        t = threading.Thread(target=task,args=(11,))
        t.setDaemon(True)
        t.start()
        
        print('END')
      • 线程名称的设置和获取

        import threading
        
        def task(arg):
            # 获取当前执行此代码的线程
            name = threading.current_thread().getName()
            print(name)
        
        for i in range(10):
            t = threading.Thread(target=task,args=(11,))
            t.setName('日魔-%s' %i )
            t.start()
      • run() , 自定义线程时,cpu调度执行的方法

        class RiMo(threading.Thread):
            def run(self):
                print('执行此线程',self._args)
        
        obj = RiMo(args=(100,))
        obj.start()

练习题: 基于socket 和 多线程实现类似于socketserver模块的功能.

import socket
import threading


def task(connect,address):
    pass


server = socket.socket()
server.bind(('127.0.0.1',9000))
server.listen(5)
while True:
    conn,addr = server.accept()
    # 处理用户请求
    t = threading.Thread(target=task,args=(conn,addr,))
    t.start()

3.4 线程安全

多个线程同时去操作一个"东西",不要存在数据混乱.

线程安全: logging模块 / 列表

线程不安全: 自己做文件操作 / 同时修改一个数字

使用锁来保证数据安全,来了多个线程,使用锁让他们排队,逐一执行.

  • Lock

    import threading
    import time
    
    
    num = 0
    # 线程锁
    lock = threading.Lock()
    
    
    def task():
        global num
        # # 申请锁
        # lock.acquire()
        # num += 1
        # time.sleep(0.2)
        # print(num)
        # # 释放锁
        # lock.release()
    
        with lock:
            num += 1
            time.sleep(0.2)
            print(num)
    
    for i in range(10):
        t = threading.Thread(target=task)
        t.start()
  • RLock,递归锁支持上多次锁

    import threading
    import time
    
    
    num = 0
    # 线程锁
    lock = threading.RLock()
    
    
    def task():
        global num
        # 申请锁
        lock.acquire()
        num += 1
        lock.acquire()
        time.sleep(0.2)
        print(num)
        # 释放锁
        lock.release()
        lock.release()
    
    
    for i in range(10):
        t = threading.Thread(target=task)
        t.start()
练习题: 基于线程锁完成一个单例模式.
import threading
import time
class Singleton:
    instance = None
    lock = threading.RLock()

    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):

        if cls.instance:
            return cls.instance
        with cls.lock:
            if cls.instance:
                return cls.instance
            time.sleep(0.1)
            cls.instance = object.__new__(cls)
        return cls.instance

def task():
    obj = Singleton('x')
    print(obj)

for i in range(10):
    t = threading.Thread(target=task)
    t.start()

# 执行1000行代码

data = Singleton('asdfasdf')
print(data

3.5 GIL

GIL,全局解释器锁.

同一时刻保证一个进程中只有一个线程可以被cpu调度,所以在使用Python开发时要注意:
    计算密集型,用多进程.
    IO密集型,用多线程. 
  • Python中如果创建多现场无法应用计算机的多核优势.

  • 4.重点总结

    • 初识爬虫
    • 单例模式 ,重点面试 (需要默写)
      • 为什么要加锁?
      • 为什么要做判断?
    • 进程和线程的区别? ,重点.面试
    • GIL锁, 重点面试
    • 线程的常用功能: start/join , 重点

day34

  1. 线程池

    1. 线程池的作用: 控制产生线程的数量,提高性能,降低资源消耗.

    2. 模块from concurrent.futures import ThreadPoolExecutor

    3. submit --向线程池提交线程

    4. shutdown --关闭这个线程池

    5. 在用完一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列。
      调用shutdown()方法后的线程池不再接受新任务,但会将以前所有的已提交的任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。

    6. 线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

    7.  ThreadPoolExecutor:限制开启线程的数量
       ################实例一####################
       import threading
       import time
       def task():
           time.sleep(2)
           print('任务')
      
      
       num = int(input('请输入要执行的任务个数:'))
       for i in range(num):
           t = threading.Thread(target=task)
           t.start()
      ##############################实例二################################
      ThreadPoolExecutor:限制开启线程的数量
      import time
      from concurrent.futures import ThreadPoolExecutor
      def task(n1, n2):
          time.sleep(2)
          print('任务')
      # 创建线程池
      pool = ThreadPoolExecutor(10)
      for i in range(100):
          pool.submit(task, i, 1)  #向线程池提交一个线程
      print('END')
      # 等线程池中的任务执行完毕之后,再继续往下走
      pool.shutdown(True)          #关闭这个线程池
      print('其他操作,依赖线程池执行的结果')
      
      ##############实例三################
      
      import time
      from concurrent.futures import ThreadPoolExecutor
      
      def task(arg):
          time.sleep(2)
          print('任务')
          return 666
      # 创建线程池
      pool = ThreadPoolExecutor(10)
      ret = pool.map(task,range(1,20))
      print('END',ret)
      pool.shutdown(True)
      for item in ret:
          print(item)
      ##################实例四##########################
      import time
      from concurrent.futures import ThreadPoolExecutor
      def task(n1, n2):
          time.sleep(2)
          print('任务')
          return n1+n2
      # 创建线程池
      pool = ThreadPoolExecutor(10)
      future_list = []
      for i in range(20):
          fu = pool.submit(task, i, 1)
          future_list.append(fu)
      
      pool.shutdown(True)
      for fu in future_list:
          print(fu.result())  #获取返回值
    8. 进程池

      python 2中没有线程池只有进程池
      python 3中线程池和进程池都有
      
      ####################实例#########################
      import time
      from concurrent.futures import ProcessPoolExecutor
      def task(n1, n2):
          time.sleep(2)
          print('任务')
      
      if __name__ == '__main__':
      
          # 创建线程池
          pool = ProcessPoolExecutor(10)
          for i in range(20):
              pool.submit(task, i, 1)
          print('END')
      
      
      
      
    9. 协程

      1. 进程,协程,线程的区别:三个都可以提高并发

        进程是计算机中资源分配的最小单位,线程是计算机中cpu可调度的最小单位:

        协程 又称为 '微线程' 是基于认为创造的,而进程和线程是真是存在的,一个进程可以有多个线程,一个线程可以创建多个协程

      2. 计算密集型------使用多进程

      3. IO密集型-------使用多线程/协程+IO切换

      4. 单纯的协程是没有办法提高并发的,只是代码之间的来回切换,加上 IO 自动切换才有意思,有IO操作使用协程

      #############协程实例############
      from greenlet import greenleet
      
      def test1():
          print('11')
          gr2.swith()
          print('33')
      
      def test2():
          print('22')
          gr1.swith()
          print('44')
      
      gr1 = greenlent(test1)
      gr2 = greenlent(test2)
      gr1.swith()
      
      
      gevent是基于greenlet开发的
      
    10. 协程+IO切换

      gevent 遇到IO自动切换
      
      from gevent import monkey
      monkey.patch_all()  #使用猴子补丁褒贬不一,但是官网上还是建议使用”patch_all()”,而且在程序的第一行就执行。
      
      import gevent
      import time
      
      
      def eat():
          print('eat food 1')
          time.sleep(3)
          print('eat food 2')
      
      
      def play():
          print('play 1')
          time.sleep(3)
          print('play 2')
      
      
      g1 = gevent.spawn(eat)
      g2 = gevent.spawn(play)
      gevent.joinall([g1, g2])  
      1. greenlet

        from greenlet import greenlet
        import time
        def a(name):
            print(name+"A1")
            g2.switch("这是")
            time.sleep(2)
            print(name+"A2")
            g2.switch()
        
        def b(name):
            print(name+"B1")
            g1.switch()
            print(name+"B2")
        g1 = greenlet(a)
        g2 = greenlet(b)
        g1.switch("这是")
        import gevent
        import time
        from gevent import monkey
        
        monkey.patch_all() # 打补丁,将下面所有的任务阻塞都打上标记
        def a(name):
            print(name + "A1")
            time.sleep(2)
            print(name + "A2")
        def b(name):
            print(name + "B1")
            time.sleep(1)
            print(name + "B2")
        g1 = gevent.spawn(a, "这是")
        g2 = gevent.spawn(b, "这是")
        # g1.join()
        # g2.join()
        gevent.joinall([g1,g2])
    11. 协程+IO切换 爬虫示例

      from gevent import monkey
      monkey.patch_all()
      
      
      import gevent
      import requests
      
      def f1(url):
          print('GET: %s' % url)
          data = requests.get(url)
          print('%d bytes received from %s.' % (len(data.content), url))
      
      def f2(url):
          print('GET: %s' % url)
          data = requests.get(url)
          print('%d bytes received from %s.' % (len(data.content), url))
      
      def f3(url):
          print('GET: %s' % url)
          data = requests.get(url)
          print('%d bytes received from %s.' % (len(data.content), url))
      
      gevent.joinall([
              gevent.spawn(f1, 'https://www.python.org/'),
              gevent.spawn(f2, 'https://www.yahoo.com/'),
              gevent.spawn(f3, 'https://github.com/'),
      ])

6. 队列Queue

  1. 队列Queue的概念相信大家都知道,我们可以用它的put和get方法来存取队列中的元素。gevent的队列对象可以让greenlet协程之间安全的访问。运行下面的程序,你会看到3个消费者会分别消费队列中的产品,且消费过的产品不会被另一个消费者再取到:

  2. 注意:协程队列跟线程队列是一样的,put和get方法都是阻塞式的

    """
    队列:
        Queue
        redis中的列表
        rabbitMQ
    
    
    """
    from queue import Queue
    q = Queue()
    
    """
    q.put('123')
    q.put(456)
    
    v1 = q.get()
    v2 = q.get()
    print(v1,v2)
    """
    
    # 默认阻塞
    v1 = q.get()
    print(v1)
  3. 发送邮件实例

    """
    1. 申请126或163邮箱
    
    2. 开启服务+授权码
    
    3. 通过代码发送
    """
    
    import smtplib
    from email.mime.text import MIMEText
    from email.utils import formataddr
    
    # 写邮件的内容
    msg = MIMEText('老板,我想演男一号,你想怎么着都行。', 'plain', 'utf-8')
    msg['From'] = formataddr(["炮手", 'zh309603@163.com'])
    msg['To'] = formataddr(["老板", '424662508@qq.com'])
    msg['Subject'] = "情爱的导演"
    
    server = smtplib.SMTP_SSL("smtp.163.com", 465)
    server.login("zh309603", "zhzzhz123") # 授权码
    server.sendmail('zh309603@163.com', ['424662508@qq.com', ], msg.as_string())
    server.quit()
  4. 生产者和消费者模型

    1. 三要素
      1. 生产者:产生数据
      2. 消费者:接受数据,进行处理
      3. 容器:队列,缓冲作用,平衡生产力和消费力
#############实例发邮件#################
import threading
from queue import Queue
import time

q = Queue()

def send(to,subject,text):
    import smtplib
    from email.mime.text import MIMEText
    from email.utils import formataddr

    # 写邮件的内容
    msg = MIMEText(text, 'plain', 'utf-8')
    msg['From'] = formataddr(["人人", '1614655582@qq.com'])    #我的邮件号
    msg['To'] = formataddr(["兽兽", to])       # 别人的邮件号
    msg['Subject'] = subject

    server = smtplib.SMTP_SSL("smtp.qq.com", 465)
    server.login("1614655582", "vxyklhhuhbqdeecd")  # 授权码自己的
    server.sendmail('1614655582@qq.com', [to, ], msg.as_string()) #我自己的 to代表是他的
    server.quit()

def producer(i):
    """
    生产者
    :return:
    """
    print('生产者往队列中放了10个任务',i)
    info = {'to':'865295648@qq.com', 'text':'你好我是你大爷速速回复','subject':'好友请求'}
    q.put(info)


def consumer():
    """
    消费者
    :return:
    """
    while True:
        print('消费者去队列中取了任务')
        info = q.get()
        print(info)
        time.sleep(10)
        send(info['to'],info['subject'],info['text'])

for i in range(10):
    t = threading.Thread(target=producer,args=(i,))
    t.start()

for j in range(5):
    t = threading.Thread(target=consumer)
    t.start()
发邮件实例2


import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr

for i in range(20):
    # 写邮件的内容
    msg = MIMEText('天凉了,注意保暖!暖暖暖', 'plain', 'utf-8')
    msg['From'] = formataddr(["最好友", '1614655582@qq.com'])
    msg['To'] = formataddr(["好友", '1612552152@qq.com'])
    msg['Subject'] = "温馨提示"

    server = smtplib.SMTP_SSL("smtp.qq.com", 465)
    server.login("1614655582", "vxyklhhuhbqdeecd") # 授权码
    server.sendmail('1614655582@qq.com', ['1612552152@qq.com', ], msg.as_string())
    server.quit()

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!