网络编程

第二十七章

网络编程

  1. 概念

    网络架构
    • C/S:qq 微信 浏览器 英雄联盟 需要安装
      • C:client客户端
      • S:server服务端
    • B/S:百度 淘宝 码云 只要在浏览器输入网址就可以直接使用
      • B:browser浏览器
      • S:server服务端
    • B/S架构中的浏览器 也是客户端的一种
      • B/S是C/S架构中的一种
    • B/S更好:更节省资源 不用更新 不依赖环境
      • 统一了所有web程序的入口
    • C/S架构:安全性 程序比较庞大
    • 移动端
      • app
      • 微信小程序:统一了所有web程序的入口
      • 支付宝:统一了所有和钱有关的事
  2. mac地址

    • 是一个物理地址
    • 唯一的表示你的网络设备
  3. IP地址

    • 是一个逻辑地址
    • 是可以根据你的位置变化发生改变的
    • 能够在广域网中快速的定位你
  4. ipv4地址

    • 4位点分十进制
    • 0.0.0.0 - 255.255.255.255
    • 2**32
  5. 公网和内网

    • 公网0.0.0.0 - 255.255.255.255(不包含保留字段的ip)
      • 你能够在任意一个地方去访问的ip地址
    • 内网 所有的内网ip都要使用保留字段
      • 只能在一个区域内使用,出了这个区域就用不了了
      • 192.168.0.0 - 192.168.255.255 2**16
      • 10.0.0.0 - 10.255.255.255 2**20
      • 172.16.0.0 - 172.32.255.255 2**24
  6. 局域网和广域网的区别:相对于谁

  7. 路由器和交换机

    • 交换机完成局域网内通信
      • 通过ip找mac地址:arp协议
      • 单播 广播 组播
    • 路由器完成局域网间通信
  8. 网关ip

    • 所有与局域网外部通信的时候所过的关口
    • 所有的请求都会在这里换上网关ip对外通信
  9. 子网掩码

    • 可以判断要寻找的机器是不是在一个局域网中
    • 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
  10. ipv6
  • 6位冒分十六进制
  • 0:0:0:0:0:0 - FFFF:FFFF:FFFF:FFFF:FFFF:FFFF
  • mac ip 定位到一台机器
  1. port端口
    • 0-65535
    • ip + port能够唯一的确认网络上的一台机器上的一个服务
  2. 协议
    • arp协议:地址解析协议,通过一台机器的ip地址找到mac地址
      • 交换机 广播 单播
    • ip协议:ipv4 ipv6
      • 内网保留字段
      • 特殊ip地址
        • 127.0.0.1 本地回环地址
        • 0.0.0.0 表示你的所有网卡的地址
  3. 简单的socket通信
client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
    a = input('输入内容,q退出')
    if a.upper() == 'Q':
        sk.send(a.encode('utf-8'))
        break
    else:
        i = sk.recv(1024).decode('utf-8')
        if i.upper() == 'Q':
            break
        print(i)
        sk.send(a.encode('utf-8'))
sk.close()
server端
import socket
sk = socket.socket()			###实例化一个对象
sk.bind(('127.0.0.1',9001))		###绑定一个地址 ip + 端口
###127.0.0.1 永远标识本机地址
###不过交换机 排除一些网络问题
sk.listen()						###开始监听客户的请求
conn,addr = sk.accept()			###接收一个连接请求
while True:
    a = input('请输入内容,q退出')
    if a.upper() == 'Q':
        conn.send(a.encode('utf-8'))
        break
    else:
        conn.send(a.encode('utf-8'))
        i = conn.recv(1024).decode('utf-8')
        if i.upper() == 'Q':
            break
        print(i)

conn.close()					###关闭与客户端的连接
sk.close()						###关闭服务端

第二十八章

  1. socket部分涉及的协议 -- 面试高频

    1. osi七层协议

      1. 应用层

      2. 表示层

      3. 会话层

      4. 传输层

      5. 网络层

      6. 数据链路层

      7. 物理层
    2. osi五层协议 互联网传输的通用协议

      1. 五层:应用层 内容 http https

      2. 四层:传输层 端口 四层交换机 四层路由器 UDP TCP 协议

      3. 三层:网络层 ip ipv4 ipv6 路由器 三层交换机

      4. 二层:数据链路层 mac arp协议 网卡 二层交换机

      5. 一层:物理层

    3. TCP/IP

      1. 网络层 arp

    4. TCP协议 上传\下载\发邮件

      1. 可靠:消息传递过程中不会丢失

        1. 建立连接

          1. 三次握手
        2. 消息传递

          1. 可靠传输
        3. 断开连接

          1. 四次挥手
      2. 面向连接

      3. 速度慢

      4. 传递的数据无限

      5. 实现全双工通信

      6. 流式传输,数据与数据之间没有明显的界限,所以可能会出现黏包现象

      7. 面试回答三次握手和四次挥手

        1. 三次握手是tcp协议建立连接的过程

        2. 由客户端发起一个syn请求,服务器收到并回复(syn、ack),客户端收到ack和syn后再回复一个ack

        3. 在原生的socket代码中三次握手是由accept connect

        4. 四次挥手是由close

    5. UDP协议 即时通讯工具 在线播放视频

      1. 不可靠:消息没有实时性

      2. 面向数据报

      3. 速度快

      4. 传递的数据有限

      5. 一对一 一对多 多对多

    server端
    from socket import  socket,SOCK_DGRAM
    sk = socket(type= SOCK_DGRAM)
    sk.bind(('127.0.0.1',9001))
    msg,addr = sk.recvfrom(1024)
    
    
    client端
    from socket import socket,SOCK_DGRAM
    sk = socket(type= SOCK_DGRAM)
    ip = ('127.0.0.1',9002)
    sk.sendto(msg,ip)
    

第二十九章

TCP协议

黏包现象

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

黏包现象会发生在发送端

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

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

黏包现象会发生在接收端

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

数据与数据之间没有边界

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

黏包问题解决办法:

计算要发送的数据字节长度

把字节的长度编程4字节

发送4字节

发送数据

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)



client端
import struct
import socket

sk = socket.socket()

def proto_recv():
    len_msg = sk.recv(4)
    len_msg = struct.unpack('i', len_msg)[0]		###解包取元组中的第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)

简单的文件传输:

server端
import socket

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

conn,addr = sk.accept()
with open(r'D:\Python_s25\day29\1.内容回顾.py','rb') as f:
    content = f.read()
    conn.send(content)

conn.close()
sk.close()



client端
import socket

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

content = sk.recv(40960)
with open(r'内容回顾.py','wb') as f:
    f.write(content)

sk.close()

struct模块:

import struct

ret = struct.pack('i',197274000)
print(ret)

# 能够把一个任意大小的数据 转换成固定的 4个字节

res = struct.unpack('i',b'\x90)\xc2\x0b')
print(res[0])

# -2147483648 ~ +2147483647  # 最大2g

第三十章

  1. TCP作业讲解

    1. 显示序号可以用枚举列表enumerate
    2. 信息传递最好用字典,josn传递
    3. 调用函数可以用反射对象是 sys.modules(name)
    4. 先写注释
  2. 验证客户端合法性(统一用户)客户端添加秘钥进行加密

    1. 登录 : 只要有个性化设计的时候就需要登录(不同用户)
    2. 登录和合法性验证二选一,如果做登录功能就不需要做合法性验证了
    server端
    import os
    import socket
    import hashlib
    SECRET_KEY = b'alexbigsb'
    
    def check_client(conn):
        randbytes = os.urandom(32)
        conn.send(randbytes)
    
        md5 = hashlib.md5(SECRET_KEY)
        md5.update(randbytes)
        code = md5.hexdigest()
        code_cli = conn.recv(32).decode('utf-8')
        return code == code_cli
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    while True:
        conn,addr = sk.accept()
        if not check_client(conn):continue
        print('进程正常的通信了')
    
    
    一种加密方式
    # import os
    # import hmac  # hashlib
    # randbytes = os.urandom(32)
    # mac = hmac.new(SECRET_KEY,randbytes)
    # ret = mac.digest()
    # print(ret)
    
    client端
    import os
    import socket
    import hashlib
    SECRET_KEY = b'alexbigs'
    
    def check_client():
        randbytes = sk.recv(32)
        md5 = hashlib.md5(SECRET_KEY)
        md5.update(randbytes)
        code = md5.hexdigest().encode('utf-8')
        sk.send(code)
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    check_client()
    print('正常的客户端通信')
    
    
  3. 动态进度条

    def processBar(num, total):
        """
        进度条
        :param num:
        :param total:
        :return:
        """
        rate = num / total
        rate_num = int(rate * 100)
        if rate_num == 100:
            r = '\r%s>%d%%\n' % ('=' * rate_num, rate_num,)
        else:
            r = '\r%s>%d%%' % ('=' * rate_num, rate_num,)
        sys.stdout.write(r)
    processBar(1024,20480)
    
  4. 并发的socketserver

    1. BaseRequestHandler 基础请求操作符
    2. ThreadingTCPServer 线程实现的基于tcp协议的server
    server端
    from socketserver import BaseRequestHandler,ThreadingTCPServer
    class Myserver(BaseRequestHandler):
        def handle(self):
            conn = self.request
            内容
    server = ThreadingTCPServer(('127.0.0.1',9001),Myserver)
    server.serve_forever()
    

第三十一章

操作系统基础

发展:

穿孔卡片

高速磁带

多道操作系统

第一次提出了多个程序可以同时在计算机中被计算

1.遇到IO就让出CPU

2.把CPU让给其他程序,让其他程序能够使用CPU

3.CPU的让出这件事,占用时间 但是很少

4.两个程序来回在CPU上切换,不会乱

每个程序有独立的内存空间
每个程序在切换前后会把当前程序的状态记录下来

CPU计算和不计算(IO)操作

IO操作(网络操作/文件操作):输入输出:相对内存

阻塞sleep\input\recv\recvfrom\accept 是不需要CPU参与的

对文件的读取:对硬盘的操作一次读取相当于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条代码的时间,CPU每秒能处理五亿条python代码

把代码拆分成python解释器指令

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

1、先来先服务算法

2、短作业优先算法

3、时间片轮转算法 -- 分时操作系统

1w = 0.00005s

1、时间片到了才让出cpu

2、cpu的让出这件事 占用时间

3、降低工作效率,提高了用户体验

进程概念

程序与进程(计算机中最小的资源分配单位)

运行中的程序就是进程

进程与进程之间的数据时隔离的

线程(计算机中能被操作系统调度的最小单位)

每个程序执行到哪个位置是被记录下来的

在进程中,有一条线程是负责具体的执行程序的

进程的调度(由操作系统完成的)

被操作系统调度的,每个进程中至少有一个线程

短作业优先算法

先来先服务算法

时间片轮转算法

多级反馈算法(队列 包括前三个算法)

进程的启动 销毁

启动:交互 在一个进程中启动另一个 开机自启

负责启动一个进程的程序,被称为一个父进程

被启动的进程 被称为一个子进程

销毁:交互 被其他进程杀死(在父进程结束子进程)出错进程结束

父子进程

父进程开启子进程

父进程还要负责对结束的子进程进行资源回收

不回收的子进程就是僵尸进程

父进程结束之后子进程就成为孤儿进程 由操作系统接手

进程id -- > processid -- > pid

在同一台机器上 同一个时刻 不可能出现两个重复的进程id

进程id不能设置,是操作系统随机分配的

进程id随着多次运行一个程序可能会被多次分配,每一次都不一样

进程的三状态图

就绪 ready 运行run 阻塞bolck

查看当前进程的pid的父进程的pid

import os
import time

print(os.getpid())	#本进程id
print(os.getppid())  # parent process id	#父进程id
time.sleep(100)

模块multiprocessing模块 :内置模块

multiple 多元化的

processing 进程

把所有和进程相关的机制都封装在multiprocessing模块中了

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工作

第三十二章

Process模块补充

主程序是在子程序执行完毕之后才结束

主程序回收子程资源

同时开启多个子程序时,子程序的执行先后顺序是操作系统分配的,因为在一个子程序执行IO操作时会有另一个子程序进入被执行

import os
import time
from multiprocessing import Process

def son_process(i):
    print('start son',os.getpid(),i)
    time.sleep(1)
    print('end son')

if __name__ == '__main__':
    for i in range(5):
        Process(target=son_process,args=(i,)).start()
###
strat son 12256 1
strat son 13252 0
strat son 4088 2
strat son 15644 4
strat son 12292 3
end son
end son
end son
end son
end son

join:

会阻塞,等待对应的子程序执行结束后才会继续往下执行

from multiprocessing import Process
import time
def son_process(n):
    print('start',n)
    time.sleep(1)
    print('end',n)
if __name__ == '__main__':
    p_l = []
    for i in range(10):
        p = Process(target= son_process,args= (i,) )
        p.start()   
		### start相当于告诉操作系统要开启一个子进程,而子进程的调度是由操作系统控制的
        p_l.append(p)
    for i in p_l:
        i.join()    
        ### join 会循环查看每个子进程是否结束,未结束时会阻塞等待这个子进程结束,
        结束了就会查看下一个,都结束了才会执行下面的
    print('所有任务结束')

守护进程:守护进程会随着父进程的代码结束而结束

from multiprocessing import Process
import time
def son():
    while True:
        time.sleep(1)
        print('is son')
if __name__ == '__main__':
    p = Process(target= son)
    p.daemon = True     ###将当前子程序设置为守护进程
    p.start()
    time.sleep(5)       ###主程序会在自己的代码执行完之后杀死守护进程并回收

正常情况下,父进程永远会等着子进程结束

如果设置了守护进程,父进程的代码结束之后,守护进程也跟着结束。

代码结束和进程结束时两回事:

没设置守护进程时:

1、子进程的代码和主进程的代码自己执行自己的,互相之间没关系
2、如果主进程的代码先结束,主进程不结束,等待子进程代码结束,回收子进程的资源,主进程才结束
3、如果子进程的代码先结束,主进程边回收子进程的资源边执行自己的代码,当代码和资源都回收结束,主进程才结束

设置守护进程时:

1、子进程的代码和主进程的代码自己执行自己的,互相之间没关系
2、一旦主进程的代码先结束,主进程会先结束掉子进程,然后回收资源
面试题:求结果
from multiprocessing import Process
import time
def son():
    while True:
        time.sleep(1)
        print('is son')

def son2():
    print('start son2')
    time.sleep(10)
    print('end son2')

if __name__ == '__main__':
    p = Process(target= son)
    p.daemon = True
    p.start()
    Process(target= son2).start()
    time.sleep(5)
#守护进程不会守护除了主进程代码之外的其他子进程
###
start son2
is son
is son
is son
is son
end son2

Process对象方法

start()###开启子进程

join()###等待子进程执行完毕

terminate()###强行结束子进程 异步非阻塞操作

is_alive()###查看进程是否存活 返回布尔值

from multiprocessing import Process
import time
def son():
    while True:
        time.sleep(1)
        print('in son')

if __name__ == '__main__':
    p = Process(target=son)
    p.start()
    time.sleep(5)

    p.terminate()   # 异步非阻塞操作
    print(p.is_alive())     ###True  因为是terminate是异步的 所以 可能会同时执行
    time.sleep(0.1)
    print(p.is_alive())     ###False
    print('我还可以继续做其他的事情,主进程的代码并不结束')

面向对象的方法开启子进程:

from multiprocessing import Process
import os
class MyProcess(Process):
    def run(self):      ###重写Process中的run方法
        print(os.getpid())
if __name__ == '__main__':
    print('主:',os.getpid())
    MyProcess().start()



传参数:
from multiprocessing import Process
import os

class MyProcess(Process):
    
    def __init__(self,arg1,arg2):
        super().__init__()  ###执行父类中的init,必须要执行父类的init
        self.a1 = arg1      ###执行重写的init,可以加但是不能减
        self.a2 = arg2
        
    def run(self):      ###重写Process中的run方法
        print(os.getpid(),self.a1,self.a2)
        
if __name__ == '__main__':
    
    print('主:',os.getpid())
    MyProcess(1,2).start() ###开启子进程

进程之间的数据是否隔离:

n = 0
def son(i):
    global n
    n += 1
    print(n,i)

if __name__ == '__main__':
    p_l = []
    for i in range(5):
        p = Process(target= son,args=(i,))
        p.start()
        p_l.append(p)
    print(n)
###
0
1 0
1 2
1 3
1 1
1 4

数据安全(锁):用来保证数据安全

如果多个进程同时对一个文件进行操作会出现什么问题:

读数据:可以同时读

写数据:不能同时写

不上锁时多个进程同时操作同一个文件,可能同时读到这个文件,然后有的是读到上一个修改后的文件,也有可能会在另一个文件正在写的过程中读到一个空的文件,因为他们读文件的时候不是原子性的,所以不安全,上锁后每个进程走到上锁的代码时是原子性的,遵循先进先出原则,只有一个进程在操作文件,所以保证了数据的安全

from multiprocessing import Process,Lock
import os
def charge(lock):
    lock.acquire()      ###上锁
    with open('lt','r')as f:
        content = f.read()
    num = int(content)
    num += 1
    for i in range(10000):i += 1
    with open('lt','w')as f:
        f.write(str(num))
    lock.release()      ###解锁
if __name__ == '__main__':
    lock = Lock()		###实例化一个对象(锁)
    for i in range(10):
        Process(target= charge,args=(lock,)).start()

进程之间的数据共享(进程之间的通信)(IPC)

全称:Inter Process Communication

from multiprocessing import Process,Queue
def son(q):
    print(q.get(123))      ###从队列中取出

if __name__ == '__main__':
    q = Queue()       	 ###实例化一个队列
    p = Process(target= son,args=(q,))
    p.start()
    q.put(123)      	###发送到队列中

在进程之间维护数据之间的安全 -- 进程安全

队列是进程安全的(进程队列保证了进程的数据安全)

队列都是先进先出

队列是基于文件 + 锁实现的

队列一共提供两个方法:get put

get是一个同步阻塞方法,会阻塞直到数据的到来

get_notwait()同步非阻塞方法

put是一个同步阻塞方法,会阻塞直到队列不满

put_notwait()同步非阻塞方法

q.qsize() 查看当前队列有多少值 但是不准 因为程序之间是异步的

empty() 查看队列是否为空的

full() 查看队列是否为满

from multiprocessing import Process,Queue
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

第三十三章

  • 阉割版单例模块

    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)
            return cls.instance
    
    obj1 = Foo('小妹')
    obj2 = Foo('傻子')
    
    print(obj1,obj2)
    
  • 爬虫下载图片

    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)
    
    • 注意:以上代码是基本于串行来实现
  • 线程的概念

    形象的关系
    工厂--应用程序
    车间--进程
    工人--线程
    
  • 进程和线程的区别?

    进程师计算机资源分配的最小单位
    线程师计算机中可以被cpu调度的最小单位
    一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源,一个进程中至少有一个线程(一个应用程序中至少有一个进程)
    
    在python中因为有GIL锁,
    
    默认进程之间无法进行资源共享,如果进程想要通讯可以基于:文件/网络/Queue
    
  • 多线程的应用

    • 快速应用

      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 = 1000
      number = 0
      
      def _add(count):
      	flobal number
      	for i in range(count):
      	number +=1
      	
      t = threading.Thread(target = _add,args=(loop,))
      t.start()
      
      t.join()
      print(number)
      
      import threading
      
      示例2
      loop = 1000
      number = 0
      
      def _add(count):
      	global number
      	for in 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=_add,args(loop,))
      t1.start()
      t2.atart()
      
      print(number)
      
      
      import thrading
      
      示例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)
      
      
      import thrading
      
      示例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('小妹')
    
    
  • 线程名称的设置和获取

    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 XIAOMEI(threading.Thread):
    	def run(self):
    		print(‘执行此线程',self._args)
    		
    	obj = XIAOMEI(args=(100,))
    	obj.start()
    
    
  • 练习题:基于socket 和多线程实现类似于socketserver模块的功能

    import socket
    import threading
    
    def task(connect,address):
    	pass
    
    server = socket.socket()
    serber.bind('127.0.0.1',9000)
    server.listen(5)
    Whlie True:
    	conn,addr = server.accept()
    	#处理用户请求
    	t= threading.thread(target=task,args=(conn,addr,))
    	t.start()
    
    
  • 线程安全

    • 多线程同时去操作一个‘东西’,不要存在数据混乱
    • 线程安全:logging模块/列表/Queue
    • 线程不安全:自己做文件操作/同时修改一个数字
    • 使用锁来保证数据安全,来了多个线程,使用锁让他们排队,逐一执行
    • 但凡是待着判断 判断要做xxx,都要加锁
      • if while += -= *= /= 都需要加锁

    Lock 互斥锁

    import threading
    import time
    
    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
    #线程锁
    locs = threading.RLock()
    
    def task():
    	golbal 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()
    
    
  • 死锁

    • Lock嵌套锁
    • 多个锁交叉使用(尽量避免)
    import threading
    import time
    lock1 = threading.RLock()
    lock2 = threading.RLock()
    def fenglin():
        lock1.acquire()
        time.sleep(1)
        lock2.acquire()
    def rimo():
        lock2.acquire()
        time.sleep(1)
        lock1.acquire()
    t1 = threading.Thread(target=fenglin)
    t2 = threading.Thread(target=rimo)
    t1.start()
    t2.start()
    
  • 基于线程锁完成一个单例模式

    import threading
    import time
    class XIAOMEI():
    	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()
    
    data = Singleton('xiaomei')
    print(data)
    
    
  • GIL全局解释锁

    同一时刻保证一个进程中只有一个线程可以被cpu调度,所以在使用python开发时要注意:
    	计算密集型,用多进程
    	IO密集型,用多线程
    GIL在列表和字典中起到了线程安全的作用
    
    • python中如果创建多线程无法应用计算机的多核优势

第三十四章

线程池

Python2中没有提供线程池,python3之后解释器才提供了线程池.

保证程序中最多可以创建的线程个数,防止无节制的创建线程,导致性能降低.

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('其他操作,依赖线程池执行的结果')

函数的返回值存在submit返回的对象里面,用result()方法查看
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)

函数的返回值存在map的返回值中

回调函数

import requests
from concurrent.futures import ThreadPoolExecutor

def get_url(name,url):
    ret = requests.get(url)
    return name,ret.content

def dump(res):
    with open(res.result()[0],mode='wb') as f:
        f.write(res.result()[1])

tp = ThreadPoolExecutor(4)
dic = {
    'baidu':'http://www.baidu.com',
    'taobao':'http://www.taobao.com',
    'jd':'http://www.jd.com',
    'sina':'http://www.sina.com',
    'sogou':'http://www.sogou.com',
    'cnblog':'https://www.cnblogs.com/Eva-J/articles/9676220.html',
}
for k in dic:
    ret = tp.submit(get_url,k,dic[k])
    ret.add_done_callback(dump)
###requests会发生阻塞,等待所有线程都回来,使用回调函数之后哪个线程先回来就先执行哪个

进程池

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')

协程

from greenlet import greenlet


def test1():
    print('i1')
    gr2.switch()
    print('i3')
    gr2.switch()


def test2():
    print('i2')
    gr1.switch()
    print('i4')


gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
###单单是协程没用 必须要加上遇到IO自动切换

协程+IO切换

from gevent import monkey
monkey.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])

协程+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/'),
])

进程、线程、协程的区别:

(重中之重之重之重之重之重之重之重之重之重)

相同性:
	进程、协程、线程的区别:三个都可以提高并发
概念和关系:
	进程是计算机中分配资源的最小单位,进程之间数据不共享,进程开启和销毁的开销大,由操作系统调度,可以利用多个 cpu。
	线程是计算机中CPU调度的最小单位,线程之间资源共享,可以利用多核(但是CPython中有GIL锁),开启和销毁的开销小,由操作系统负责调度。
	协程又称为“微线程”,是基于代码人为创造的,资源开销小,协程能够识别的IO操作:网络操作,sleep,一个进程中可以有多个线程,一个线程可以创建多个协程
	协程和cpython解释器下的线程有啥区别 :资源开销小 能够把单线程的效率提高 协程能够识别的io操作不如线程多
使用场景:
    计算密集型,多进程;
    I0密集型,多线程/协程+ I0切换
    单纯的协程是没办法提高并发,只是代码之间的来回切换,加上I0自动切换才
    有意义,有I0操作用协程

队列

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)

栈:LifoQueue 后进先出栈

from queue import LifoQueue
lq = LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())

优先级队列:PriorityQueue 优先级队列

from queue import PriorityQueue
pq = PriorityQueue()
pq.put((10,'alex'))
pq.put((5,'egon'))
pq.put((15,'yuan'))
print(pq.get())
print(pq.get())
print(pq.get())

发邮件

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()

生产者消费者模型

就是把大量的数据放到队列中
然后有大量服务器从队列中取出整理这些数据
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(["炮手", 'zh309603@163.com'])
    msg['To'] = formataddr(["老板", to])
    msg['Subject'] = subject

    server = smtplib.SMTP_SSL("smtp.163.com", 465)
    server.login("zh309603", "zhzzhz123")  # 授权码
    server.sendmail('zh309603@163.com', [to, ], msg.as_string())
    server.quit()

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


def consumer():
    """
    消费者
    :return:
    """
    while True:
        print('消费者去队列中取了任务')
        info = q.get()
        print(info)
        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()
posted @ 2020-04-21 14:49  我的儿  阅读(190)  评论(0)    收藏  举报