client:客户端
server:服务端
browser:浏览器(客户端)
server:服务端
应用层
表示层
会话层
传输层
网络层
数据链路层
物理连接层
物理连接层功能:基于电信号发送二进制数据
顾名思义就是一些物理的连接,比如网线、路由器等, 间传的是电信号,即010101...这些二进制位。
数据链路层功能:定义了电信号的分组
数据链路层是来对电信号来做分组的。为了统一标准,就有了以太网协议ethernet
每一台连接网线的电脑都必须要由一块 "网卡"。
使用交换机可以让多台电脑连接到一起,基于以太网协议和MAC地址发送数据。
单播
单播是一对一发送消息
广播
在同一个局域网内,可以一对多发送消息
广播的弊端:广播风暴;不能夸局域网通信
网络层功能:靠mac地址广播获取信息数据,并不合适,于是有了网络层,引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
IP地址功能:用于标识唯一的一台计算机(局域网)的地址。
使用的是点分十进制
最小值: 0.0.0.0
最大值: 255.255.255.255
arp协议功能:广播的方式发送数据包,获取目标主机的Mac地址并解析
传输层功能:建立端口到端口的通信
补充:端口范围0-65535,0-1023为系统占用端口
TCP协议特点:TCP协议是流式协议;通信必须建立双向通道;接收到消息后一定会确认收到信息
双向通道的建立 -- 三次握手,四次挥手
双向通道特点:客户端往服务端发送请求获取数据,服务端务必返回数据,客户端确认收到,反则会反复发送,一直到某个时间段内,会停止发送
UDP协议特点:
1) 数据不安全
? 2) 不需要建立双向通道
? 3) 传输速度快
? 4) 不会有粘包问题
? 5) 客户端发送数据,不需要服务端确认收到,爱收不收
应用层功能:规定应用程序的数据格式,因为数据多种多样,必须规定好数据的组织形式。
应用层协议:http、ftp等...
socket用来写套接字客户端与服务端的模块,内部帮我们封装好了7层协议需要做的事情
# 服务端
import socket
# 默认指定TCP协议
server = socket.socket()
server.bind(
# ip + port
('127.0.0.1', 9527)
)
# 开机,等待接听
server.listen(5) # listen(5) 半连接池
# 监听是否有消息
# conn: 相当于服务端往客户端挖的管道
conn, addr = server.accept()
print(addr)
data = conn.recv(1024).decode('utf-8') # 可以接收1024字节数据
print(data)
# 发送消息
conn.send(b'hello xiao tank')
# 通道关闭
conn.close()
server.close()
# 客户端
import socket
client = socket.socket()
# 往服务端拨号
# client: 相当于客户端往服务端挖的管道
client.connect(
# ip + port: 寻找服务端
('127.0.0.1', 9527)
)
# 客户端向服务端说话
client.send('你好'.encode('utf-8'))
data = client.recv(1024)
print(data)
# 关闭连接
client.close()
用来通过代码往cmd创建一个管道,并且发送命令和接收cmd返回的结果
# subprocess的简单使用
import subprocess
# 参数的固定写法
obj = subprocess.Popen(
'cmd命令',
shell=True,
# 接收正确结果
stdout=subprocess.PIPE,
# 接收错误结果
stderr=subprocess.PIPE
)
# 接收正确和错误的信息
success = obj.stdout.read()
error = obj.stderr.read()
# 拼接后输出
msg = success + error
print(msg)
只有TCP有粘包现象,UDP永远不会粘包
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
TCP协议会将多次连续发送数据量小、并且时间间隔短的数据一次性打包发送,而导致粘包问题
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
import socket
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1', 9527))
server.listen(5)
while True:
conn, addr = server.accept()
try:
# 先接收字典报头
headers = conn.recv(4)
# 解包获取字典真实数据长度
data_len = struct.unpack('i', headers)[0]
# 获取字典真实数据
bytes_data = conn.recv(data_len)
# 反序列得到字典
back_dic = json.loads(bytes_data.decode('utf-8'))
print(back_dic)
# 拿到字典的文件名,文件大小
file_name = back_dic.get('file_name')
file_size = back_dic.get('file_size')
init_data = 0
# 1.以文件名打开文件,准备写入
with open(file_name, 'wb') as f:
# 一点一点接收文件,并写入
while init_data < file_size:
data = conn.recv(1024)
# 2.开始写入视频文件
f.write(data)
init_data += len(data)
print(f'{file_name}接收完毕!')
except Exception as e:
print(e)
break
conn.close()
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 9527))
# 1.打开一个视频文件,获取视频数据大小
with open(r'D:\aaa.mp4', 'rb') as f:
movie_bytes = f.read()
# 关闭文件
# 2.为视频文件组织一个字典,字典内有视频的名称,视频大小
send_dic = {
'file_name': 'aaa.mp4',
'file_size': len(movie_bytes) # 10G
}
# 3.先打包字典,发送headers报头,再发送真实字典数据
json_data = json.dumps(send_dic)
bytes_data = json_data.encode('utf-8')
headers = struct.pack('i', len(bytes_data))
# 发送报头
client.send(headers)
# 发送真实字典数据
client.send(bytes_data)
# 4.接着发送真实视频文件数据
init_data = 0
num = 1
with open(r'D:\aaa.mp4', 'rb') as f:
while init_data < len(movie_bytes):
# 最后一次获取,有多少拿多少
send_data = f.read(1024)
print(send_data, num)
num += 1
# 每次发送1024数据
client.send(send_data)
# 为初始发送数据 + 已发送数据的长度
init_data += len(send_data)
python内置模块,可以简化socket套接字服务端的代码。
# socketserver 服务端
import socketserver
# 定义类
# TCP: 必须继承BaseRequestHandler类
class MyTcpServer(socketserver.BaseRequestHandler):
# 必须重写父类的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')
self.request.send(send_msg.encode('utf-8'))
except Exception as e:
print(e)
break
if __name__ == '__main__':
# socketserver.TCPServer只能有一个服务端 服务 一个客户端
# server = socketserver.TCPServer(
# ('127.0.0.1', 8888), MyTcpServer
# )
# ThreadingTCPServer: 有多个服务端 服务 客户端
server = socketserver.ThreadingTCPServer(
('127.0.0.1', 8888), MyTcpServer
)
# 永久执行服务
server.serve_forever()
# 客户端
import socket
client = socket.socket()
client.connect(
('127.0.0.1', 8888)
)
while True:
send_msg = input('客户端: ')
client.send(send_msg.encode('utf-8'))
back_msg = client.recv(1024)
print(back_msg.decode('utf-8'))
空间上的复用
一个cpu可以提供给多个用户去使用
时间上的复用
遇到IO操作就会切换程序
程序占用CPU时间过长切换
并发: 看起来像同时运行,实际上是使用多道技术,不断的切换,保存状态
? 并行: 真正意义上的同时运行,在多核的情况下
进程是资源单位,每创建一个进程都会生成一个名称空间,占用内存资源
程序与进程的区别
程序就是一堆代码
进程就是一堆代码运行的过程
当下操作系统的进程调度大多是 时间片轮转法加分级反馈队列
10个进程,将固定时间,等分成10份时间片,分配给每一个进程
将执行优先分为多层级别
一级:优先级最高
二级:优先级第二
以此类推....
就绪态:
所有进程创建时都会进入就绪态,准备调度
运行态:
调度后的进程,进入运行态
阻塞态:
凡是遇到IO操作的进程,都会进入阻塞态
若IO结束,必须重新进入就绪态
指的是提交任务的方式
阻塞
阻塞态,遇到IO一定会阻塞
非阻塞:
就绪态,运行态
# 一:
p = Process(target=任务, args=(任务的参数, ))
p.daemon = True # 必须放在start()前,否则报错
p.start() # 向操作系统提交创建进程的任务
p.join() # 向操作系统发送请求, 等所有子进程结束,父进程再结束
# 二:
class MyProcess(Process):
def run(self): # self == p
# 任务的过程
p = MyProcess()
p.daemon = True # 必须放在start()前,否则报错
p.start() # 向操作系统提交创建进程的任务
p.join() # 向操作系统发送请求, 等所有子进程结束,父进程再结束
指的是子进程已经结束,但pid号还存在,未销毁
缺点:占用pid号,占用操作系统资源
指的是子进程还在执行,但父进程意外结束
操作系统优化机制:
提供一个福利院,帮你回收没有父亲的子进程
指的是主进程结束后,该主进程产生的所有子进程跟着结束,并回收
将并发变成串行,牺牲执行效率,保证数据安全
# 互斥锁的简单使用
from multiprocessing import Lock
mutex = Lock()
# 加锁
mutex.acquire()
# ...修改数据的代码
# 释放锁
mutex.release()
队列是先进先出(FIFO)
相当于内存中产生一个队列空间,可以存放多个数据,先进去的数据排在前面
from multiprocessing import Queue
# 设置五个数据
q = Queue(5)
# 添加数据,若队列添加数据满了,则等待
q.put()
# 添加数据,若队列添加数据满了,直接报错
q.put_nowait()
# 获取队列中的数据
q.get() # 若队列中没数据,会卡住等待
q.get_nowait() # 若队列中没数据,会直接报错
堆栈是先进后出 (LIFO)
进程间数据时相互隔离的,若想实现进程间通信,可以利用队列
生产者:生产数据的
消费者:使用数据的
在程序中:通过队列,生产者把数据添加队列中,消费者从队列中获取数据
这样为了保证 供需平衡
线程和进程都是虚拟单位,目的是为了更好的描述某件事物
节约资源开销
注意:线程间数据是共享的;线程不能实现并行,线程只能实现并发,进程可以实现并行
只有Cpython才有自带一个GIL全局解释器锁,因为CPython的内存线程不是安全的
注意:多个线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器锁,交给下一个先进来的线程
死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程 ,解决方式就是递归锁
解决死锁现象
mutex = Lock() # 只能引用1次
mutex1, mutex2 = RLock() # 可以引用多次
+1, 只要这把锁计数为0释放该锁, 让下一个人使用, 就不会出现死锁现象.
信号量也是一把锁, 可以让多个任务一起使用
互斥锁:
只能让一个任务使用
信号量:
可以让多个任务一起使用.
sm = Semaphore(5) 可以让5个任务使用
使用场景:若线程间数据不安全情况下使用线程队列, 为了保证线程间数据的安全.
使用方式import queue
FIFO: 先进先出队列
queue.Queue()
LIFO: 后进先出队列
queue.LifoQueue()
优先级队列:
根据数字大小判断,判断出队优先级,进队数据是无序的
queue.PriorityQueue()
可以控制线程的执行,让一些线程控制另一些线程的执行.
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(200) # 200个
# 将函数地址的执行结果,给回调函数
pool4.submit(函数地址, 参数).add_done_callback(回调函数地址)
# 回调函数(必须接收一个参数res):
# 获取值
res2 = res.result()
注意:协程不是操作系统资源,他是程序起的名字,为了让单线程能实现并发
单线程下实现并发,节省资源,通过手动模拟操作系统“多道技术”,实现 切换+保存状态
手动实现切换 + 保存状态
yield保存
next() 切换
yield不能监听IO操作的任务,gevent来实现监听IO操作
from gevent import monkey
monkey.patch_all() # 可以监听该程序下所有的IO操作
import time
from gevent import spawn, joinall # 用于做切换 + 保存状态
def func1():
print('1')
# IO操作
time.sleep(1)
def func2():
print('2')
time.sleep(3)
def func3():
print('3')
time.sleep(5)
start_time = time.time()
s1 = spawn(func1)
s2 = spawn(func2)
s3 = spawn(func3)
# 必须传序列类型
joinall([s1, s2, s3])
end_time = time.time()
print(end_time - start_time)
原文:https://www.cnblogs.com/1012zlb/p/11773272.html