10.25网络编程到并发编程

软件开发架构

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操作,就会进入阻塞态

同步与异步

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

阻塞与非阻塞

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

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

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

创建进程的两种方式

一、

from multiprocessing import Process

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

二、

from multiprocessing import Process

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

回收进程资源的两种条件

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

获取PID号

from multiprocessing import cu

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

僵尸进程
凡是子进程结束后,PID号还在,主进程意外死亡,没法给子进程回收资源
每个子进程结束后,都会变成僵尸进程(占用PID)
缺点:
操作系统中PID号是有限的,如果子进程PID无法正常回收,则占用PID号。
资源浪费
若PID号满,则无法创建新的进程
孤儿进程
凡是子进程没有结束,但是主进程意外死亡,操作系统优化机制(孤儿院),会将没有主进程,并且存活的进程,在该进程结束后回收资源

守护进程

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

进程间数据是隔离的

子进程会产生一个自己的名称空间,与主进程的名称空间不互通

互斥锁

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

from multiprocessing import Process
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的内存管理不是"线程安全"的
例:

一个数据要被线程引用时,如果还没有被线程引用,可能被垃圾回收机制引用(被当成垃圾回收,垃圾回收机制也是一个线程),因为当时数据的引用计数为0,GIL就是为了保护数据的安全,不会被当作垃圾回收掉(数据被GIL引用着,引用计数不为0)。
内存管理
	垃圾回收机制
**注意:**多个线程过来执行,一旦遇到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

原文地址:https://www.cnblogs.com/793564949liu/p/11755941.html