1. 网络编程
1.1 网络基础认知
1.1.1 什么是网络
什么是网络 : 计算机网络功能主要包括实现资源共享,实现数据信息的快速传递。
1.1.2 网络通信标准
1.1.2.1 OSI模型
1.1.2.2 TCP/IP模型
-
七层模型过于理想,结构细节太复杂
-
在工程中应用实践难度大
-
实际工作中以TCP/IP模型为工作标准流程
1.1.2.3 网络协议
-
什么是网络协议:在网络数据传输中,都遵循的执行规则。
-
网络协议实际上规定了每一层在完成自己的任务时应该遵循什么规范。
1.1.2.4 服务端与客户端
-
服务端(Server):服务端是为客户端服务的,服务的内容诸如向客户端提供资源,保存客户端数据,处理客户端请求等。
-
客户端(Client) :也称为用户端,是指与服务端相对应,为客户提供一定应用功能的程序,我们平时使用的手机或者电脑上的程序基本都是客户端程序。
1.1.3 通信地址
1.1.3.1 IP地址
-
IP地址 : 即在网络中标识一台计算机的地址编号。
-
IP地址分类
-
IPv4 :192.168.1.5
-
IPv6 :fe80::680a:76cf:ab11:2d73
-
-
IPv4 特点
-
分为4个部分,每部分是一个整数,取值分为0-255
-
-
IPv6 特点
-
分为8个部分,每部分4个16进制数,如果出现连续的数字 0 则可以用 :: 省略中间的0
-
-
IP地址相关命令
-
ifconfig : 查看Linux系统下计算机的IP地址
-
-
公网IP和内网IP
-
公网IP指的是连接到互联网上的公共IP地址,可以与外部网络进行数据分享。(将来进公司,公司会申请公网IP作为网络项目的被访问地址)
-
内网IP指的是一个局域网络范围内由网络设备分配的保留IP地址,不能访问局域网外的网络空间。
-
1.1.3.2 端口号
端口:网络地址的一部分,在一台计算机上,每个网络程序对应一个端口。
端口号特点
-
取值范围: 0 —— 65535 的整数
-
一台计算机上的网络应用所使用的端口不会重复
-
通常 0——1023 的端口会被一些有名的程序或者系统服务占用,个人一般使用大于 1024 的端口
-
lsof -i :[port] 显示一个端口是否被占用,需要管理员权限执行
1.2 套接字通信
1.2.1 套接字简介
1、套接字(Socket) : 实现网络编程进行数据传输的一种技术手段,网络上各种各样的网络服务大多都是基于 Socket 来完成通信的。
2、Python套接字编程模块:import socket
1.3 TCP 网络传输
1.3.1 TCP传输特点
(1) 传输特征 : 提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复。
(2) 可靠性保障机制(都是操作系统网络服务自动帮应用完成的):
-
在通信前需要建立数据连接 (面向连接的传输)
-
确认应答机制
-
通信结束要正常断开连接
(3) 三次握手(建立连接)
-
客户端向服务器发送消息报文请求连接
-
服务器收到请求后,回复报文确定可以连接
-
客户端收到回复,发送最终报文连接建立
(4)四次挥手(断开连接)
-
主动方发送报文请求断开连接
-
被动方收到请求后,立即回复,表示准备断开
-
被动方准备就绪,再次发送报文表示可以断开
-
主动方收到确定,发送最终报文完成断开
1.3.2 TCP服务端
1、创建套接字
sock=socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
功能:创建套接字
参数:family 网络地址类型 AF_INET表示ipv4
type 套接字类型 SOCK_STREAM 表示tcp套接字
返回值: 套接字对象
2、绑定地址
-
本地地址 : '127.0.0.1'
-
网络地址 : '172.40.91.185' (通过ifconfig查看)
-
自动获取地址: '0.0.0.0'
sock.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port) ('0.0.0.0',8888)
3、设置监听
sock.listen()
功能 : 将套接字设置为监听套接字
4、处理客户端连接请求
conn,addr = sock.accept()
功能: 阻塞等待处理客户端请求
返回值: conn 客户端连接套接字
addr 连接的客户端地址
5、消息收发
data = conn.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容
n = conn.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数
6、关闭套接字
sock.close()
功能:关闭套接字
'''
TCP客户端
'''
from socket import *
SERVER_ADDR = ("0.0.0.0",8888)
# 创建套接字,AF_INET:标识ipv4的地址 SOCK_STREAM:表示tcp套接字
sock = socket(family=AF_INET,type=SOCK_STREAM)
# 绑定本机网络地址
sock.bind(SERVER_ADDR)
# 设置监听
sock.listen()
print("等待客户端连接.....")
#处理客户端连接请求,conn:客户端连接套接字 addr:客户端地址
conn,addr = sock.accept()
print("客户端连接成功,客户端IP地址:",addr)
#收发数据
'''
conn.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容
'''
data = conn.recv(1024)
print(data.decode())
'''
conn.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数
'''
conn.send(b"thanks")
# 关闭套接字
conn.close()
sock.close()
1.3.3 TCP客户端
1、创建TCP套接字
2、请求连接
sock.connect(server_addr)
功能:连接服务器
参数:地址元组,访问服务器使用的地址
3、收发消息
注意: 防止两端都阻塞,recv send要配合
4、关闭套接字
'''
TCP客户端
'''
from socket import *
SERVER_ADDR = ("127.0.0.1",8888)
# 创建套接字,参数使用默认值
sock_clinet = socket()
# 连接服务器
sock_clinet.connect(SERVER_ADDR)
# 收发数据
msg = input("请输入:")
sock_clinet.send(msg.encode())
data = sock_clinet.recv(1024)
print(data.decode())
# 关闭连接
sock_clinet.close()
1.3.4 TCP套接字细节
-
tcp连接中当一端退出,另一端调用recv时会返回一个空字节串。
-
tcp连接中如果一端已经不存在,仍然试图通过send向其发送数据则会产生BrokenPipeError
-
一个服务端可以同时连接多个客户端,也能够重复被连接
1.3.5 TCP粘包问题
-
网络收发缓冲区 : 为了解决数据再传输过程中可能产生的速度不协调问题,操作系统在内存中设置了缓冲区
-
粘包产生原因
-
tcp以字节流方式进行数据传输,在接收时不区分消息边界
-
发送速度比较快,消息同时到达,存储在缓冲区被一次性接收
-
-
带来的影响
-
如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会影响到就接收内容的解析理解。
-
-
处理方法
-
消息格式化处理,如人为的添加消息边界,用作消息之间的分割
-
控制发送的速度
-
2. 网络通信协议
2.1 TCP协议
2.1.1 传输流程
-
发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包。
-
发送的消息经过多个节点(交换机,路由器)传输,最终到达目标主机。
-
目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息。
2.1.2 TCP协议首部信息
2.2 HTTP协议
2.2.1 HTTP协议 (超文本传输协议)
-
用途 : 网页获取,数据的传输
-
特点
-
应用层协议,使用tcp进行数据传输
-
简单,灵活,很多语言都有HTTP专门接口
-
有丰富的请求类型
-
可以传输的数据类型众多
-
2.2.2 访问网页流程
2.2.3 HTTP请求
1、请求行 : 具体的请求类别和请求内容
GET / HTTP/1.1
请求类别 请求内容 协议版本
请求类别:每个请求类别表示要做不同的事情
GET : 获取网络资源
POST :提交一定的信息,得到反馈
HEAD : 只获取网络资源的响应头
PUT : 更新服务器资源
DELETE : 删除服务器资源
2、请求头:对请求的进一步解释和描述
Accept-Encoding: gzip
3、空行
4、请求体: 请求参数或者提交内容
2.2.4 HTTP响应
1、响应行 : 反馈基本的响应情况
HTTP/1.1 200 OK
版本信息 响应码 附加信息
响应码 :
1xx 提示信息,表示请求被接收
2xx 响应成功
3xx 响应需要进一步操作,重定向
4xx 客户端错误
5xx 服务器错误
常用响应码:
200 OK
404 Not Found
2、响应头:对响应内容的描述
Content-Type: text/html
3、空行
4、响应体:响应的主体内容信息
3. 多任务编程技术
3.1 进程
3.1.1 进程概述
定义:程序在计算机中的一次执行过程
程序是一个可执行的文件,是静态的占有磁盘
进程是一个动态的过程描述,占有计算机运行资源,是一个独立的运行单元,有一定的生命周期
进程命令
ps -u
kill -9 pid 可以用于杀死一个进程
3.1.2 多进程编程
-
使用模块 : multiprocessing
-
创建流程
【1】 将需要新进程执行的事件封装为函数
【2】 通过模块的Process类创建进程对象,关联函数
【3】 通过进程对象调用start启动进程
-
主要类和函数使用
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数
args 元组,用于给target函数位置传参
kwargs 字典,给target函数键值传参
daemon bool值,让子进程随父进程退出
p.start()
功能 : 启动进程
p.join([timeout])
功能:阻塞等待子进程退出
参数:最长等待时间
进程创建示例:
"""
进程创建示例 01
"""
import multiprocessing as mp
from time import sleep
a = 1 # 全局变量
# 进程目标函数
def fun():
print("开始运行一个进程")
sleep(4) # 模拟事件执行事件
global a
print("a =",a) # Yes
a = 10000
print("进程执行结束")
# 实例化进程对象
process = mp.Process(target=fun)
# 启动进程 进程产生 执行fun
process.start()
print("我也做点事情")
sleep(3)
print("我也把事情做完了...")
process.join() # 阻塞等待子进程结束
print("a:",a) # 1 10000
"""
进程创建示例02 : 含有参数的进程函数
"""
from multiprocessing import Process
from time import sleep
# 含有参数的进程函数
def worker(sec,name):
for i in range(3):
sleep(sec)
print("I'm %s"%name)
print("I'm working....")
# 元组位置传参
# p = Process(target=worker,args=(2,"Tom"))
# 关键字传参
p = Process(target=worker,
args = (2,),
kwargs={"name":"Tom"},
daemon=True) # 子进程伴随父进程结束
p.start()
sleep(3)
-
进程执行现象理解
-
新的进程是原有进程的子进程,子进程复制父进程全部内存空间,一个进程可以创建多个子进程。
-
子进程只执行指定的函数,执行完毕子进程生命周期结束,但是子进程也拥有其他父进程资源。
-
各个进程在执行上互不影响,也没有必然的先后顺序关系。
-
进程创建后,各个进程空间独立,相互没有影响。
-
multiprocessing 创建的子进程中无法使用标准输入(即无法使用input)。
-
3.1.3 进程相关函数
os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID
os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID
"""
创建多个子进程
"""
from multiprocessing import Process
from time import sleep
import sys, os
def th1():
sleep(3)
print("吃饭")
print(os.getppid(), "--", os.getpid())
def th2():
# sys.exit("不能睡觉了") # 进程结束
sleep(1)
print("睡觉")
print(os.getppid(), "--", os.getpid())
def th3():
sleep(2)
print("打豆豆")
print(os.getppid(), "--", os.getpid())
# 循环创建子进程
jobs = [] # 存放每个进程对象
for th in [th1, th2, th3]:
p = Process(target=th)
jobs.append(p) # 存入jobs
p.start()
# 确保三件事都结束
for i in jobs:
i.join()
print("三件事完成")
3.1.4 创建进程类
1、创建步骤
【1】 继承Process类
【2】 重写__init__
方法添加自己的属性,使用super()加载父类属性
【3】 重写run()方法
2、使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
"""
自定义进程类
"""
from multiprocessing import Process
from time import sleep
class MyProcess(Process):
def __init__(self, value):
self.value = value
super().__init__() # 调用父类的init
# 重写run 作为进程的执行内容
def run(self):
for i in range(self.value):
sleep(2)
print("自定义进程类。。。。")
p = MyProcess(3)
p.start() # 将 run方法作为进程执行
3.1.5 进程间通信
1、必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
2、常用进程间通信方法:消息队列,网络套接字等。
3、消息队列使用
- 通信原理: 在内存中开辟空间,建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
- 实现方法
from multiprocessing import Queue
q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象
q.put(data)
功能:向队列存入消息
参数:data 要存入的内容
q.get()
功能:从队列取出消息
返回值: 返回获取到的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列中消息个数
进程间通信示例:
from multiprocessing import Process,Queue
# 创建消息队列
q = Queue(5)
# 子进程函数
def handle():
while True:
cmd = q.get() # 取出指令
if cmd == "1":
print("\n完成指令1")
elif cmd == "2":
print("\n完成指令2")
# 创建进程
p = Process(target=handle,daemon=True)
p.start()
while True:
cmd = input("指令:")
if not cmd:
break
q.put(cmd) # 通过队列给子进程
3.2 线程 (Thread)
3.2.1 线程概述
什么是线程
【1】 线程被称为轻量级的进程,也是多任务编程方式
【2】 线程可以理解为进程中再开辟的分支任务
【3】 线程也是一个运行行为,消耗计算机资源
【4】 一个进程中的所有线程共享这个进程的资源
【5】 多个线程之间的运行同样互不影响各自运行
【6】 线程的创建和销毁过程给计算机带来的压力远小于进程
3.2.2 多线程编程
线程模块的用法几乎和进程一模一样,完全可以仿照完成。
-
线程模块: threading
-
创建方法
【1】 创建线程对象
from threading import Thread
t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
daemon bool值,主线程推出时该分支线程也推出
【2】 启动线程
t.start()
【3】等待分支线程结束
t.join([timeout])
功能:阻塞等待分支线程退出
参数:最长等待时间
3.2.3 创建线程类
-
创建步骤
【1】 继承Thread类
【2】 重写
__init__
方法添加自己的属性,使用super()加载父类属性【3】 重写run()方法
-
使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
from threading import Thread
from time import sleep
class MyThread(Thread):
def __init__(self,song):
self.song = song
super().__init__() # 得到父类内容
# 线程要做的事情
def run(self):
for i in range(3):
sleep(2)
print("播放:",self.song)
t = MyThread("让我们荡起双桨")
t.start() # 运行run
3.2.4 线程互斥锁
-
线程通信方法: 线程间使用全局变量进行通信
-
共享资源争夺
-
共享资源:多线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
-
影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要互斥机制协调。
-
-
互斥锁机制
当一个线程占有资源时会进行加锁处理,此时其他线程就无法操作该资源,直到解锁后才能操作。
-
线程锁 Lock
from threading import Lock
lock = Lock() 创建锁对象
lock.acquire() 上锁 如果lock已经上锁再调用会阻塞
lock.release() 解锁
Lock使用示例:
from threading import Thread, Lock
lock = Lock() # 创建锁
a = b = 0
def value():
while True:
lock.acquire() # 上锁
if a != b:
print("a = %d,b = %d" % (a, b))
lock.release() # 解锁
t = Thread(target=value)
t.start()
while True:
lock.acquire()
a += 1
b += 1
lock.release()
-
线程锁的注意问题:死锁问题
死锁产生条件
* 互斥条件:即使用了互斥锁。
* 请求和保持条件:锁住一定资源不解锁的情况先再请求锁住其他资源。
* 不剥夺条件:不会受到线程外部的干扰,终止锁行为。
* 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,如 T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。
3.2.5 GIL问题
-
什么是GIL问题 (全局解释器锁)
由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。
-
导致后果 因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞任务时可以提升程序效率,其他情况并不能对效率有所提升。
3.2.6 进程线程的区别联系
-
区别联系
-
两者都是多任务编程方式
-
进程的创建销毁过程给计算机带来的压力比线程多
-
进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
-
一个进程可以有多个分支线程,两者有包含关系
-
多个线程共享进程资源,在共享资源操作时往往需要互斥锁处理
-
Python线程存在GIL问题,但是进程没有。
-
使用场景
-
任务场景:一个大型服务,往往包含多个独立的任务模块,每个任务模块又有多个小独立任务构成,此时整个项目可能有多个进程,每个进程又有多个线程。
-
编程语言:Java,C#之类的编程语言在执行多任务时一般都是用线程完成,因为线程资源消耗少;而Python由于GIL问题往往使用多进程。