day_32

软件开发架构

C/S:

client:客户端

server:服务端

优点:占用网络资源少,软件的使用稳定

缺点:服务端更新后,客户端也要更新,需要使用多个软件,需要下载多个客户端

B/S:

Browser:浏览器

server:服务端

服务端与客户端作用:

服务端:24小时不间断提供服务

客户端:需要体验服务端时,再去连接服务端,并享受服务

网络编程

七层协议

应用层,表示层,会话层,传输层,网络层,数据链路层,物理连接层

  • 物理连接层

    基于电信号发送二进制数据

  • 数据链路层

    1. 规定好电信号的分组方式

    2. 必须要有一块网卡

      mac地址:

      12位唯一的16进制字符串

      • 前6位:厂商号
      • 后6位:流水号

    以太网协议:

    在同一个局域网内通信。

    • 单播

      1对1通信

    • 广播

      多对多通信

      • 广播风暴:

        不能夸局域网通信

  • 网络层

    • ip:定位局域网的位置

    • arp协议:

      将mac地址获取,并解析成ip和port

  • 传输层

    • TCP

      特点:

      tcp协议称之为流式协议

      想要通信,必须建立连接,并建立双向通道

      • 三次握手,四次挥手

        • 三次握手建连接

          • 客户端往服务端发送请求建立通道
          • 服务端要确认客户端的请求,并网客户端也发送请求建立通道
          • 客户端接收到服务端建立连接的请求,并返回确认
          • 建立双向通道
        • 双向通道:

          • 反馈机制

            客户端往服务端发送请求获取数据,服务端务必返回数据,客户端确认收到,否则会反复发送,一直到某个时间段内,会停止发送

        • 四次挥手断连接

          • 客户端往服务端发送断开连接,服务端返回确认收到
          • 服务端需要再次发送断开连接请求
          • 客户端返回确认收到
          • 最终确认断开连接
    • UDP

      1. 数据不安全
      2. 不需要建立双向通道
      3. 传输速度快
      4. 不会有粘包问题
      5. 客户端发送数据,不需要服务端确认收到
    • TCP和UPD的区别:

      TCP:比喻成在打电话

      UDP:比喻成发送短信

  • 应用层

    • ftp

    • http:

      可以携带一堆数据

    • http+ssl

socket

socket用来写台阶自客户端与服务端的模块,内部帮我们封装好了7层协议需要做的事情

socket套接字模板

- 服务端:
            import socket
            server = socket.socket()
            server.bind(
                (ip, port)
            )  # 绑定手机号
            server.listen(6)  # 半连接池: 可以接待7个客户端
            # 监听连接
            conn, addr =server.accept()
            # 接收消息
            data = conn.recv(1024)
            # 发送消息
            conn.send('消息内容'.encode('utf-8'))

- 客户端:
            import socket
            client = socket.socket()
            client.connect(
                (ip, port)
            )
            # 发送消息
            client.send()
            # 接收消息
            client.recv(1024)

粘包问题

  1. 不能确认对方发送数据的大小
  2. 在短时间内,间隔时间短,并且数据量小的情况,默认将这些数据打包成一个多次发送的数据---》一次性发送

struct解决粘包问题

初级版:
        i: 4
        可以将一个数据的长度打包成一个固定长度的报头.
        struct.pack('模式i', '源数据长度')
        data = 'gagawagwaga'
        # 打包成报头
        headers = struct.pack('i', len(data))

        # 解包获取数据真实长度
        data = struct.unpack('i', headers)[0]

        注意: 以什么方式打包,必须以什么方式解包.

        升级版:
            先将数据存放到字典中,将字典打包发送过去
            - 字典的好处:
                - 真实数据长度
                - 文件的描述信息
                - 发送的数据,更小

                dic = {
                    'data_len': 1000000000000000000000046546544444444444444444444444444444444444444,
                    文件的描述信息
                }

上传大文件数据

# 客户端
        dic = {
                文件大小,
                文件名
               }

        with open(文件名, 'rb') as f:
            for line in f:
                client.send(line)


        # 服务端
        dic = {
                文件大小,
                文件名
               }
        init_recv = 0
        with open(文件名, 'wb') as f:
            while init_recv < 文件大小:
                data = conn.recv(1024)
                f.write(data)
                init_recv += len(data)

socketserver

可以支持并发

import socketserver
# 定义类
        # TCP: 必须继承BaseRequestHandler类
        class MyTcpServer(socketserver.BaseRequestHandler):

            - handle
                # 内部实现了
                server = socket.socket()
                server.bind(
                    ('127.0.0.1', 9527)
                )
                server.listen(5)  ---

                while True:
                    conn, addr = server.accept()
                    print(addr)

            # 必须重写父类的handle, 当客户端连接时会调用该方法
            def handle(self):
                print(self.client_address)
                while True:
                    try:
                        # 1.接收消息
                        # request.recv(1024) == conn.recv(1024)
                        data = self.request.recv(1024).decode('utf-8')
                        send_msg = data.upper()
                        self.request.send(send_msg.encode('utf-8'))

                    except Exception as e:
                        print(e)
                        break

并发编程

多道技术

  • 多道:切换+保存状态

    • 空间上的复用

      支持多个程序使用

    • 时间上的复用

      • 遇到IO操作就会切换程序
      • 程序占用CPU时间过长会切换

并发与并行

并发:看起来像同时运行:多道技术

并行:真正意义上的同时运行:多核下

进程

进程是资源单位,每创建一个进程都会生成一个名称空间,占用内存资源

  • 程序与进程

    程序就是一堆代码,进程就是一堆代码运行的过程

  • 进程调度

    • 时间片轮转法

      10个进程,将固定时间,等分成10份时间片,分配给每一个进程

    • 分级反馈队列

      1级最高

  • 进程的三个状态

    • 就绪态

      创建多个进程,必须要排队准备运行

    • 运行态

      进程开始运行,1. 结束 2. 阻塞

    • 阻塞态

      当运行态遇到IO操作,就会进阻塞态

  • 同步与异步

    提交任务的方式

    • 同步:同步提交,串行,一个任务结束后,另一个任务才能提交并执行
    • 异步:异步提交,多个任务可以并发运行
  • 阻塞与非阻塞

    • 阻塞:

      阻塞态

    • 非阻塞:

      就绪态

      运行态

  • 同步和异步、阻塞和非阻塞的区别

    两者是不同的概念,不能混为一谈

  • 创建进程的两种方式

    1. p=Process(target=任务,args=(任务的参数,))

      p.deamon=True #必须放在start()前,否则报错

      p.start() # 向操作系统提交创建进程的任务

      p.join() # 向操作系统发送请求,等所有子进程结束,父进程再结束

    2. class MyProcess(Process):

      ​ def run(self):

      ​ 任务的过程

      p=Process(target=任务,args=(任务的参数,))

      p.deamon=True #必须放在start()前,否则报错

      p.start() # 向操作系统提交创建进程的任务

      p.join() # 向操作系统发送请求,等所有子进程结束,父进程再结束

  • 回收进程资源的两种条件

    • 调用join让子进程结束后,主进程才能结束
    • 主进程正常结束

僵尸进程与孤儿进程

僵尸进程:凡是子进程结束后,pid号还在,主进程意外死亡的都会变成僵尸进程

孤儿进程:凡是子进程没有结束,而主进程已经意外结束的,就是孤儿进程,操作系统的优化机制会回收这些进程

守护进程

只要父进程结束,所有的子进程都必须结束

互斥锁

将并发编程穿行,牺牲执行效率,保证数据安全

from multiprocessing import Lock
mutex=Lock()
# 加锁
mutex.acquire()
# 修改数据
mutex.reliease()

队列

  • FIFO队列:先进先出

    from multiprocessing import Queue
    q=Queue(5)
    # 添加数据,若队列添加数据满了,则等待
    q.put()
    # 添加数据,若队列添加数据满了,直接报错
    q.put_nowait()
    
    # 获取队列中的数据
    q.get() # 若队列中没有数据,会卡住等待
    q.get_nowait() # 若队列中没有数据,会直接报错
    

堆栈

LIFO

IPC进程间通信

  • 进程间的数据是个例的
  • 队列可以让进程间实现通信
  • 把一个程序放入队列中,另一个程序从队列中获取,实现进程间数据交互

生产者与消费者 模型

生产者:生产数据

消费者:使用数据

目的是为了保证供需平衡

通过队列实现,生产者将数据扔进队列中,消费者从队列中获取数据

可以保证一边生产一边消费

线程

  • 什么是线程

    • 进程:资源单位

    • 线程:执行单位

      • 创建进程时,会自带一个线程

      一个进程下可以创建多个线程

  • 线程的好处

    节省资源开销

  • 进程与线程的优缺点

    • 进程

    优点:多核下可以并行执行,计算密集型情况下提高效率

    缺点:开销资源远高于线程

    • 线程

      优点:占用资源远小于进程,IO密集型情况下提高效率

      缺点:无法利用多核优势

线程间数据是共享的

GIL全局解释锁

只有Cpython才有自带一个GIL全局解释器锁

  1. GIl本质上是一个互斥锁

  2. GIL是为了阻止同一个进程内多个线程同时执行(并行)

    • 单个进程下的多个线程不发实现并行,但能实现并发
  3. 这把锁主要是因为Cpython的内存管理不是“线程安全”的

    • 内存管理
      • 垃圾回收机制

    注意:多个线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器锁,交给下一个先进来的线程

    总结:GIL的存在就是为了保证线程安全的,保证数据安全

多线程使用的好处

  • 多线程:

    IO密集型,提高效率

  • 多进程:

    计算密集型,提高效率

死锁现象

递归锁

解决死锁现象

信号量

信号量也是一把锁,可以让多个任务一起使用

互斥锁:

只能让一个任务使用

信号量:

可以让多个任务一起使用

sm= Semaphore(5)

线程队列

使用场景:

若线程间数据不安全情况下使用,为了保证线程间数据的安全

import queue

  • FIFO 先进先出队列

    queue.Queue()

  • LIFO 后进后出队列

    queue.LifoQueue()

  • 优先级队列

    • 根据数字大小判断,判断出队优先级

    • 进队数据是无序的

      queue.PriorityQueue()

event事件

可以控制线程的执行,让一些线程控制另一些线程的执行

e=Event()

  • 线程1

    e.set() # 给线程2发送信号,让他执行

  • 线程2

    e.wait() # 等待线程1的信号

进程池与线程池

为了控制进程/线程创建的数量,保证了硬件能正常运行

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
pool1=ProcessPoolExecutor() # 默认CPU个数
pool2=ThreadPoolExecutor() # CPU个数*5

# 将函数地址的执行结果,给回调函数
pool3.submit(函数地址,参数).add_done_callback(回调函数地址)
  • 回调函数(必须接受一个参数res):

    获取值

    res2 =res.result()

协程

  • 进程:资源单位

  • 线程:执行单位

  • 协程:单线程下实现并发,不是任何的单位,是程序员Yy出来的名字

  • 单线程下实现并发

    好处是节省资源,单线程《多线程《多进程

    • IO密集型下:

      协程有优势

    • 计算密集型下:

      进程有优势

  • 高并发

    • 多进程+多线程+协程(Nginx)

协程的创建:

​ 手动实现切换+保存状态:

  • yield

  • 函数一直调用next()

    会不停的切换

    yield不能监听IO操作的任务

    • gevent可以

gevent

from gevent import monkey
monkey.patch_all() # 设置监听所有IO
from gevent import spawn,joinall # 实现  切换+保存状态

#实现了单线程下并发
s1=spawn(任务一)
s2=spawn(任务二)
joinall([s1,s2])

IO模型

  • 阻塞IO
  • 非阻塞IO
  • 多路复用IO
  • 异步IO
posted @ 2019-10-25 20:05  mqb11  阅读(129)  评论(0编辑  收藏  举报