10.25网络编程到并发编程

匆匆过客 提交于 2019-12-02 16:56:01

软件开发架构

C/S:

Client:客户端
Server:服务端
优点:占用网络资源少,软件的使用稳定
缺点:服务端更新后,客户端也得跟着更新
需要使用多个软件,需要下载多个客户端

B/S

Browser:浏览器(客户端)
Server:服务端

服务端与客户端作用:

服务端:24小时不间断提供服务
客户端:需要体验服务端时,在去连接服务端,并享受

网络编程

互联网七层协议

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

物理连接层

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

数据链路层

规定好电信号的分组方式
必须要有一块网卡
mac地址:
12位唯一的16进制字符串
前6位:厂商号
后6位:流水号
以太网协议:
在同一个局域网内通信
单播
一对一
广播
多对多
广播风暴
不能跨局域网通信

网络层

IP:定位局域网
port:唯一标识一台计算机上的一个应用程序
arp协议:将mac地址获取,并解析成IP和port

运输层

TCP

特点:TCP协议称之为流式协议
若想要通信,必须建立连接,并建立双向通道

三次握手,四次挥手

三次握手建连接
客户端往服务端发送请求连接通道
服务端要确认客户端的请求,并往客户端也发送请求建立通道
客户端接收到服务端建立连接请求,并返回确认
建立双向通道
双向通道
反馈机制
客户端往服务端发送QQ群获取数据,服务端务必返回数据,客户端确认收到
否则会反复发送,一直到某个时间段内,会停止发送
四次挥手
C往S发送断开连接请求,S返回确认收到
S需要再次发送断开连接请求
C返回确认收到
最终确认断开连接

UDP

数据不安全
不需要建立双向通道
传输速度快
不会有粘包问题
客户端发送数据,不需要服务端确认收到

TCP与UDP的区别

TCP:好比打电话
UDP:好比发短信

应用层

ftp
http
可以携带一堆数据
http+ssl

socket

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

socket套接字模版

服务端

import socket
server = socket.socket()
server.bind(('127.0.0.1',9527)) #绑定IP和端口
server.listen(5) #半连接池:执行1人等待5人(共6人)
while True:
    #监听数据
    conn,addr = server.accept()
    print(addr) #打印监听到的客户端地址
    while True:
        try:
            #接收数据
            data = conn.recv(1024)
            if data == 0:
                continue
            #打印接收的数据
            print(data.decode('utf-8'))
            #发送数据
            conn.send(data)
         except Exception as e:
            print(e)
            break

客户端

import socket
client = socket.socket()
client.connect(('127.0.0.1',9527)) #连接IP和端口
while True:
    msg = input('输入信息:')
    #发送数据
    client.send(msg.encode('utf-8'))
    #接收数据
    data = client.recv(1024)
    print(data.decode('utf-8'))

subproess(了解)

用来通过代码往cmd创建一个管道,并且发送命令和接收cmd返回的结果

import subprocess
obj = subprocess.Popen(
    'cmd命令',
    shell = True,
    #接收正确结果
    stdout = subprocess.PIPE,
    #接收错误结果
    stderr = subprocess.PIPE
    )
    #接收的正确消息
    success = obj.stderr.read()
    #接收的错误消息
    error = obj.stderr.read()
    #两个消息的拼接
    msg = success + error

粘包问题

不能确定对方发送数据的大小
在短时间内,发送时间间隔短,且数据量小的数据,就会默认把数据打包发送(本该多次发送的数据,变一次性打包发送了)

struct解决粘包问题

初级版

struct.pack('i模式','原数据的长度') #i可以将一个数据的长度打包成一个固定长度(4)的报头
data = '1234567891234556677'
#打包成报头
headers = struct.pack('i',len(data))
#解包获取数据真实长度
data = struct.unpack('i',headers)[0]

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

服务端

import socket
import struct
server = socket.socket()
server.bind(('127.0.0.1',9527)) #绑定IP和端口
server.listen(5) #半连接池:执行1人等待5人(共6人)
while True:
    #监听数据
    conn,addr = server.accept()
    print(addr) #打印监听到的客户端地址
    while True:
        try:
            #先接收数据报头
            headers = conn.recv(4)
            #解报头
            data_len = struct.unpack('i',headers)[0]
            #在接收真实数据
            pack_data = conn.recv(data_len)
            if pack_data == 0:
                continue
            pack_data = pack_data.decode('utf-8')
            #打印接收的数据
            print(pack_data)
            pack_data2 = pack_data
            #发送数据
            headers2 = struct.pack('i',len(pack_data2.decode('utf-8')))
            conn.send(headers2)
            conn.send(pack_data2.encode('utf-8'))
         except Exception as e:
            print(e)
            break
    conn.close()

客户端

import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1',9527)) #连接IP和端口
while True:
    msg = input('输入信息:')
    #获取数据报头
    headers = struct.pack('i',len(msg.encode('utf-8')))
    #先发送报头
    client.send(headers)
    #在发送真实数据
    client.send(msg.encode('utf-8'))
    #接收报头
    headers2 = client.recv(4)
    #获取真实数据长度
    data_len = struct.unpack('i',headers2)[0]
    #接收数据
    data = client.recv(data_len)
    #打印接收数据
    print(data.decode('utf-8'))

升级版

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

上传大文件数据

客户端

dic = {
    文件大小,
    文件名
}
with open(文件名,'rb')as fr:
    for line in fr:
        client.send(line)

服务端

dic = {
    文件大小,
    文件名
}
init_recv = 0
with open(文件名,'wb')as fw:
    while init_recv < 文件大小:
        data = conn.recv(1024)
        fw.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:
                #接收消息
                #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

TCP

SOCK_DGRAM
conn.recv()

UDP模版

SOCK_DGRAM
server.recvfrom()

服务端

import socket
server = socket.socket(type = socket.SOCK_DGRAM)
server.bind((ip,port))
data,addr = server.recvfrom(1024)
server.sendto(data,addr)

客户端

import socket
client = socket.socket(type = socket.SOCK_DGRAM)
ip_port = (ip,port)
client.sendto(data,ip_port)
data,_ = client.recvfrom(1024) # _ 接收的是无关变量值(约定俗成当成无)
print(data)

并发编程

多道技术

单道

多个用户使用CPU时是串联运行的
必须让上一个程序运行完,才能运行下一个程序

多道

切换+保存状态

空间上的复用

支持多个程序使用

时间上的复用

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

并发与并行

并发:看起来像同时运行:多道技术
并行:真正意义上的同时运行:多核状态

进程

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

程序与进程

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

进程调度

时间片轮转法

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

分级反馈

1级别
2级别
3级别
……
数字越低级别越高,1级别等级最高

进程的三种状态

就绪态

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

运行态

进程开始运行后,要么结束,要么阻塞

阻塞态

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

同步与异步

提交任务的方式
同步:同步提交,串行,一个任务结束后,下一个任务才能开始
异步:异步提交,多个任务可以并发运行

阻塞与非阻塞

阻塞:阻塞态
非阻塞:就绪态和运行态

同步和异步,阻塞和非阻塞的区别

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

创建进程的两种方式

一、

p = Process(target=任务,args=(任务的参数,)) #任务的参数是元组,必须要加逗号
p.daemon = True #必须放在strat()前,否则会报错
p.start() #向操作系统提交创建进程的任务
p.join() #向操作系统发送请求,等所有子进程结束,父进程在结束

二、

class MyProcess(Process):
    def run(self): #self == p
        任务的过程
    p = MyProcess()
    p.daemon = True #必须放在start()前,否则报错
    p.start() #向操作系统提交创建进程的任务
    p.join() #向操作系统发送请求,等所有子进程结束后,父进程在结束

回收进程资源的两种条件

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

僵尸进程与孤儿进程(了解)

僵尸进程
凡是子进程结束后,PID号还在,主进程意外死亡,没法给子进程回收资源
每个子进程结束后,都会变成僵尸进程(PID)
孤儿进程
凡是子进程没有结束,但是主进程意外死亡,操作系统优化机制(孤儿院),会将没有主进程,并且存活的进程,在该进程结束后回收资源

守护进程

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

互斥锁

将并发变成串行,牺牲了执行效率,保证了数据的安全

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

队列

FIFO队列:先进先出队列

from multiprocessing import Queue
#队列数为5
q = Queue(5)
#添加数据,若队列满了,则等待
q.put()
#添加数据,若队列满了,直接报错
q.put_nowait()

#获取队列中的数据
#若队列中没有数据,会卡住等待
q.get()
#若队列中没有数据,则报错
q.get_nowait()

堆栈

LIFO:后进先出队列

IPC进程间通信

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

生产者和消费者

生产者:生产数据
消费者:使用数据
为了保证,供需平衡
通过队列实现:生产者将数据存入队列,消费者从队列中获取数据

线程

什么是线程

进程
资源单位
线程
执行单位
创建一个进程,就会自带一个线程
一个进程下可以创建多个线程

线程优点

减少CPU的内存占用

进程与线程的优缺点

进程
优点:
多核下可以实现并行
计算密集型情况下可以提高效率
缺点:
占用CPU内存资源大
线程
优点:
占用CPU内存资源小
IO密集型情况下可以提高效率
缺点:
无法利用多核优势

同一进程内线程间的数据是共享的

GIL全局解释器锁

只有Cpython才有自带一个GIL全局解释器锁
GIL本质上就是一个互斥锁
GIL是为了阻止同一个进程内多个线程同时执行(并行)
单个进程下的多个线程无法实现并行,但能实现并发
GIL锁主要是因为Cpython的内存管理不是"线程安全"的
内存管理
垃圾回收机制
注意:多个线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器锁,交给下一个先进来的线程
总结:GIL的存在就是为了保证线程安全的,保证数据安全

多线程优点

多线程
IO密集型,提高效率
多进程
计算密集型,提高效率

死锁现象(了解)

当A锁钥匙在B锁内,B锁钥匙在A锁内时,就会出现死锁现象

递归锁

解决死锁问题

mutex = Lock() #只能引用一次
mutex1,mutex2 = RLock() #可以引用多次
只要锁的引用计数为0就释放该锁,让下一个让使用,就不会出现死锁现象

信号量

信号量也是一把锁,可以让多个任务一起执行
互斥锁
只能让一个任务使用
信号量
可以让多个任务一起使用
sm = Semaphore(5) #可以让5个任务使用

线程队列

使用场景
线程间数据不安全情况下使用线程队列,为了保证线程间的数据安全

import queue
#FIFO:先进先出队列
queque.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 = ProcessPoolExecutor(100) #进程个数上限为100
pool4 = ThreadPoolExecutor(100) #线程个数上限为100
#将函数地址的执行结果,给回调函数
pool4.submit(函数地址,参数).add_done_callback(回调函数地址)
回调函数(必须接收一个参数res):
    #获取值
    res2 = res.result()

协程

进程:资源单位
线程:执行单位
协程:单线程下实现并发,不是任何的单位,是程序员自己取的名字
单线程下实现并发,节省资源,单线程<多线程<多进程
IO密集型:协程有优势
计算密集型:进程有优势
高并发:多进程+多线程+协程

协程的创建

手动实现切换+保存状态
yield:保存状态
函数一直调用next()会不停的切换
yield不能监听IO操作的任务
gevent来实现监听IO操作

gevent

pip install gevent
from gevent import monkey
monkey.patch_all() #设置监听所有IO
from gevent import spawn,joinall #实现切换+保存状态
#实现单线程下实现并发
s1 = spawn(任务1)
s2 = spawn(任务2)
joinall([s1,s2])

IO模型(了解)

阻塞IO
非阻塞IO
多路复用IO
异步IO

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