1.网络编程
网络 :将具有独立功能的多台计算机通过通信链路和通信设备连接起来,在网络管理软件及网络通信协议下,实现资源共享和信息传递的虚拟平台

网络编程 :编写基于网络通信的软件或程序
网络编程作用:用来实现网络互连的不同计算机上运行的程序间可以进行数据交互
1.1 网络编程三要素
网编三要素:
- IP地址 :是网络中的网络接口(网卡)的唯一标识,用来定位计算机
- 端口号 :用于在计算机中标识进程的逻辑地址,通过端口号来找到指定的进程
- 协议 :定义通信规则,符合协议则可以通信,不符合则无法正常通信
1.1.1 ip地址
IP地址 :是网络中的网络接口(网卡)的唯一标识,用来定位计算机

查看 IP 地址:
- Linux 和 mac OS:使用 ifconfig 命令
- Windows:使用 ipconfig 命令
检查网络是否正常使用 ping 命令:
- ping www.baidu.com,检查外网是否连通
- ping 局域网 ip ,检查本机和局域网内另一台机器连通性,验证是否在同一个局域网
- ping 127.0.0.1 ,回环地址,检查本机 TCP/IP 协议栈是否正常工作
1.1.2 端口号
端口号 :用于在计算机中标识进程的逻辑地址,通过端口号来找到指定的进程


知名端口号 :指众所周知的端口号,范围从 0 到 1023
动态端口号 :一般程序员开发应用程序使用的端口号为动态端口号,范围从 1024 到 65535
netstat -ano命令:
- ‑a :All,显示全部连接(监听端口、已建立连接、TIME_WAIT 等)
- ‑n :Numeric,直接显示数字 IP 和端口,不解析域名,执行速度更快
- ‑o :Owner,显示每个连接对应的进程 PID(最重要)
1.1.3 协议
TCP协议 :是一种面向连接的,可靠的,基于字节流的传输层通信协议
TCP通信步骤:
- 创建连接
- 传输数据
- 关闭连接
面向连接:通信双方在传输数据之前需要建立连接
可靠传输:通过一套机制保证数据接收方收到完整无误、顺序正确的数据。
TCP创建连接三次握手:就是指 TCP 在建立连接的时候,需要客户端和服务端总共发送3次包以确认连接的建立

- 第一次握手:客户端向服务端发送请求,等待客户端确认
- 第二次握手:服务端收到请求后知道客户端请求建立连接,回复给客户端以确认连接请求
- 第三次握手:客户端收到确认后,再次发送请求确认服务器,客户端收到正确请求后,如果正确则连接建立成功,完成三次握手,随后客户端与服务端之间就可以传输数据了
TCP断开连接四次挥手:TCP连接是双向的,A连接B,B连接A都要断开

- 第一次挥手:当主机A(可以是客户端也可以是服务端)完成数据传输后,提出断开连接请求
- 第二次挥手:主机B收到请求后作出回应,确认这一方向上的TCP连接断开
- 第三次挥手:主机B完成数据传输后,提出断开连接请求
- 第四次挥手:主机A收到请求后作出回应,确认这一方向上的TCP连接断开
1.2 套接字Socket
1.2.1 基本概念
套接字(Socket) :IP地址:端口号,是操作系统提供给应用程序的编程接口(API),是进程间通信的一个工具

注意 :应用程序不用直接操作 TCP/UDP 报文,调用 socket 函数,交给操作系统内核完成封装、发送、接收
Socket 通俗理解 :网络通信的端点,两个程序通过 Socket 完成跨主机数据传输
**Socket 通信:****网络编程最典型的实现方法;**即:通信双方都拥有自己的 Socket 对象,数据在 Socket 之间通过数据包(UDP协议)或者字节流(TCP协议)的形式进行传输
1.2.2 Socket类
Socket(套接字)能实现不同主机之间的进程间通信。Python 中有专门的 Socket 类:
import socket
注意:要使用 Socket 类,需要使用 socket 模块下的 socket 类来创建实例对象

案例:创建一个 Socket 对象

1.3 TCP程序开发流程
1.3.1 TCP程序开发流程
TCP网络应用程序开发分为:
- TCP客户端程序开发(ps:浏览器)
- TCP服务端程序开发
注意:
- 客户端程序:指运行在用户设备上的程序,主动发起连接(主动去找服务器建立 TCP 连接)
- 服务端程序:指运行在服务器设备上的程序,专门为客户端提供数据服务,被动等待连接(守在 IP 端口上监听别人来连)

- 服务器端 socket()创建监听套接字:也叫做被动套接字,只负责接受新来的客户端连接
- accept():在服务器端,该函数会返回一个新的套接字(通信套接字),用来和客户端收发信息
- close():在服务器端,关闭和客户端1,客户端2,...,客户端n的通信套接字(不主动关闭)
- 与所有的客户端通信结束:服务器端关闭监听套接字(被动套接字:不主动关闭)
参数解释:
当服务器端有了socket对象后,常用于服务器端的函数如下:

当客户端有了socket对象后,常用于客户端的函数如下:

能同时应用于客户端和服务器端的函数如下:

案例:客户端和服务器端相互发送一句话消息
服务器端代码如下:
import socket
#1.创建服务器端socket对象,用于监听客户端连接
listen_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.绑定IP地址和端口号
listen_socket.bind(('127.0.0.1',8080))
#3.开始监听客户端连接
listen_socket.listen(5)
#4.接受客户端连接,返回通信socket对象和客户端地址
communication_socket,client_address = listen_socket.accept()
#5.接收客户端数据,最多接收1024字节
data = communication_socket.recv(1024).decode('utf-8')
#6.发送数据给客户端,b标识将字符串转为2进制形式(有中文不行)
communication_socket.sendall(b'Hello, Client!')
#7.关闭通信socket对象,监听socket一般不关闭
communication_socket.close()
客户端代码如下:
import socket
#1.创建socket对象
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.连接服务器
client_socket.connect(('127.0.0.1',8080))
#3.发送数据
client_socket.sendall(b'Hello, Server!')
#4.接收数据
data = client_socket.recv(1024).decode('utf-8')
print(f"客户端收到:{data}")
#5.关闭socket对象
client_socket.close()
print("客户端已关闭")
1.3.2 编解码问题
在网络中,数据是以二进制编码的形式进行传递的,所以在网络中传输数据的时候就牵涉到数据的编码和解码问题。

- 编码 (encode) :**字符串 → 字节 bytes(发送数据用)**人看懂的文字,转成网络可以传输的二进制字节。
- **解码(decode):字节 bytes → 字符串(接收数据用)**网卡收到的二进制字节,转回人能看懂的文字
注意:
- 只要出现乱码 问题,就一定是编解码方式不一致的原因
- 英文字母,数字,特殊符号在任何码表中都占一个字节,并且UTF‑8、GBK、ASCII 这几种常用编码里,它们的单字节二进制和 ASCII 完全一样;中文无法使用 ASCII 编码,它在gbk中占2个字节,在utf-8中占3个字节
- 编码语法:'字符串'.encode(码表)
- 解码语法:二进制.decode(码表)
- 二进制数据的特殊写法:b"英文字母 数字 特殊符号"(该方式针对中文无效)
案例:中文字符串编解码

1.3.3 端口号重用问题(扩展:意义不大)
当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要大概等1-2mins
解决办法有两种:
- 更换服务端端口号
- 设置端口号复用:让服务端程序退出后端口号立即释放(一般目前的电脑都自动支持端口号复用)
设置端口号复用代码如下:

1.3.4 TCP网络程序注意点
- TCP 服务端程序必须绑定端口号,否则客户端找不到这个 TCP 服务端程序,为了更稳定,建议把 IP 也绑定
- accept () 前的套接字是被动套接字(监听套接字),只负责接收新的客户端的连接请求,不能收发消息
- accept()函数调用后,当 TCP 客户端程序和 TCP 服务端程序连接成功后, TCP 服务器端程序会产生一个新的套接字(通信套接字),用于收发客户端消息
- 对于服务器端的被动 socket,关闭时需慎重
- 当客户端的套接字调用 close 后,服务器端的 recv 会解阻塞,返回的数据长度为 0,用于判断客户端是否已经下线
1.3.5 文件上传与文件下载
文件上传:客户端给服务端传送文件
文件下载:服务端给客户端传送文件
下列代码以文件上传为例,文件下载与此同理,只需调换收发双方即可
案例:客户端给服务器端上传文件
服务端代码如下:
import socket
#1.创建服务器端socket对象,用于监听客户端连接
listen_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.绑定IP地址和端口号
listen_socket.bind(('127.0.0.1',8081))
#3.开始监听客户端连接
listen_socket.listen(5)
#4.接受客户端连接,返回通信socket对象和客户端地址
communication_socket,client_address = listen_socket.accept()
#5.读取客户端上传的文件数据
#5.1 关联目的地文件
with open('./Data/received_file.txt','ab') as f:
# 5.2 循环读取数据
while True:
#5.3 读取数据
data = communication_socket.recv(8192)
#5.4 判断数据是否为空
if len(data) == 0:
break
#5.5 将读取到的数据写入本地文件
f.write(data)
#6.关闭通信socket对象,监听socket一般不关闭
communication_socket.close()
print("文件上传成功")
客户端代码如下:
import socket
#1.创建socket对象
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.连接服务器
client_socket.connect(('127.0.0.1',8081))
#3.发送文件
#3.1 关联数据源文件
with open('./Data/send_file.txt', 'rb') as f:
#3.2 循环读取数据
while True:
#3.3 具体的读取操作
data = f.read(8192)
#3.4 及时发送数据
client_socket.sendall(data)
#3.5 判断数据是否读取完毕
if len(data) == 0:
break
#4.关闭socket对象
client_socket.close()
print("客户端已关闭")
2.多任务
2.1 多任务概念
多任务 :在同一时间段内,计算机内执行多个任务
多任务作用 :多个任务同时执行能够充分利用CPU资源,大大提高程序执行效率
多任务的两种表现形式:
- 并行 :同一时间点同时执行多个任务
- 并发 :同一时间段内,交替执行多个任务(给人的感觉好像是并行,但其实同一时间点只有一个任务在执行)
多任务有两种实现手段:进程和线程
注意:
- 单核CPU是并发执行多任务的
- 多核是并行执行多任务的
图解如下:

2.2. 进程
进程 :是CPU分配资源的最小单位;是操作系统进行资源分配和调度运行的基本单位(在不引入线程的前提下)
一个进程就是程序的一次执行(进行中)
2.2.1 多进程实现
Python 中多进程的基本方式:程序运行起来形成主进程(main);在主进程上创建子进程
进程的创建步骤:
1.导入工具包
import multiprocessing
2.通过包内的进程类创建实例进程对象
子进程对象 = multiprocessing.Process()

3.启动进程执行任务
进程对象.start()
2.2.1.1 多进程执行无参函数
**案例:**使用多进程来模拟一边编写代码,一边听音乐
import multiprocessing as mul
import time
#coding函数
def coding():
for i in range(3):
print("编写coding中......")
time.sleep(1)
#听音乐函数
def listen_music():
for i in range(3):
print("听音乐中......")
time.sleep(1)
if "__name__" == "__main__":
#分别创建进程对象
coding_process = mul.Process(target=coding)
music_process = mul.Process(target=listen_music)
#执行进程
coding_process.start()
music_process.start()
注意:由于我的电脑是多核处理器,两个进程是并行进行的
2.2.1.2 多进程执行有参函数
案例:使用多进程模拟小明一边编写 num 行代码,一边听 count 首音乐的功能
import multiprocessing as mul
import time
#coding函数
def coding(num):
for i in range(num):
print("编写coding中......")
time.sleep(2)
#听音乐函数
def listen_music(count):
for i in range(count):
print("听音乐中......")
time.sleep(2)
if __name__ == "__main__":
#分别创建进程对象
coding_process = mul.Process(target=coding, args=(5,))
music_process = mul.Process(target=listen_music, args=(5,))
#执行进程
coding_process.start()
music_process.start()
2.2.2 获取进程的PID
进程标识符 / 进程编号(PID):在一台计算机内唯一标识一个进程,方便进程管理
在一个操作系统中,一个进程的进程号是唯一的,进程号可以反复使用
获取进程编号目的:验证主进程和子进程的关系,可以得知子进程是由哪个主进程创建出来的
获取进程编号的两种操作:
- 获取当前进程号
- 获取当前父进程号
获取进程编号:需要导入 os 模块
-
os.getpid()/ multiprocessing.current_process().pid:获取当前进程编号
import os
pid = os.getpid()
#或者 pid = multiprocessing.current_process().pid
2.os.getppid():获取父进程编号
import os
ppid = os.getppid()
注意:main 函数中创建的进程,如果没有特殊指定,他的父进程都是 main 进程;而 main 进程的父进程是 pycharm
2.2.3 进程注意事项
2.2.3.1 进程之间不共享数据
进程之间不共享全局变量
子进程相当于父进程的**"副本",会将父进程的"main 外资源" 拷贝一份**,即:各是各的
案例:定义一个公共容器,一个进程负责写数据,一个进程负责读数据
import multiprocessing as mul
import time
#1.定义公共容器
list1 = []
#2.定义写函数
def write():
for i in range(10):
list1.append(i)
print(f"添加数据:{i}")
print(f"write函数:{list1}")
#3.定义读函数
def read():
time.sleep(3)
print(f"read函数:{list1}")
#4.main外资源
print(f"我是main外资源,看我执行了几次")
#5.主函数main
if __name__ == "__main__":
#6. 创建两个子进程
p1 = mul.Process(target=write)
p2 = mul.Process(target=read)
#7. 启动子进程
p1.start()
p2.start()
#8.main内资源
print(f"我是main内资源,看我执行了几次")
结果如下:
我是main外资源,看我执行了几次
我是main内资源,看我执行了几次
我是main外资源,看我执行了几次
添加数据:0
添加数据:1
添加数据:2
添加数据:3
添加数据:4
添加数据:5
添加数据:6
添加数据:7
添加数据:8
添加数据:9
write函数:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
我是main外资源,看我执行了几次
read函数:[]
图解:


2.2.3.2 主进程会等待所有子进程执行结束再结束
默认情况下,主进程会等待所有子进程执行结束再结束
为了不让主进程等待子进程,有如下几种方法:
方法1:子进程设置守护主进程(推荐:人在塔在,塔亡人亡)
目的:主进程退出子进程便销毁,不让主进程再等待子进程结束
代码如下:

注意:
- 守护进程退出,非守护进程不受影响
- 非守护进程退出,所有守护进程立即跟着退出
方法2:子进程自己主动终止(不推荐)
守护进程或子进程提前结束

2.3 线程
线程 :是CPU调度的基本单位。每个进程至少都有一个线程,而这个线程就是我们通常讲的主线程
作用 :充分利用CPU资源,用来实现多任务编程,提高程序执行效率
- 进程是CPU资源分配的基本单位,一旦创建一个进程就会分配一定的资源
- 线程依附于进程执行
- 线程间共享同一进程的资源
图解如下:

2.3.1 多线程实现
线程的创建步骤:
1.导入工具包
import threading
2.通过包内的线程类创建实例线程对象
线程对象 = threading.Thread()

3.启动进程执行任务
线程对象.start()
2.3.1.1 多线程执行无参函数
案例:使用多线程来模拟一边写代码,一边听音乐
import threading
import time
#coding函数
def coding():
for i in range(3):
print("编写coding中......")
time.sleep(1)
#听音乐函数
def listen_music():
for i in range(3):
print("听音乐中......")
time.sleep(1)
if "__name__" == "__main__":
#分别创建线程对象
coding_thread = threading.Thread(target=coding)
music_thread = threading.Thread(target=listen_music)
#执行进程
coding_thread.start()
music_thread.start()
图解如下:

2.3.1.2 多线程执行有参函数
案例:使用多线程模拟小明一边编写 num 行代码,一边听 count 首音乐的功能
import threading
import time
#coding函数
def coding(num):
for i in range(num):
print("编写coding中......")
time.sleep(2)
#听音乐函数
def listen_music(count):
for i in range(count):
print("听音乐中......")
time.sleep(2)
if __name__ == "__main__":
#分别创建进程对象
coding_thread = threading.Thread(target=coding, args=(5,))
music_thread = threading.Thread(target=listen_music, args=(5,))
#执行进程
coding_thread.start()
music_thread.start()
2.3.2 线程注意事项
2.3.2.1 线程之间执行是无序的
线程之间执行是无序的,它是由 CPU 调度决定的,CPU 调度哪个线程,哪个线程就执行,没有调度的线程是不能执行的
CPU调度资源的策略:
- 均分时间片
- 抢占式调度
2.3.2.2 主线程会等待所有子线程执行结束后再结束
默认情况下,主线程会等待所有子线程执行结束后再结束。
我们可以设置守护主线程来解决:
守护主线程:主线程退出,子线程便跟着销毁不再执行
守护主线程方式:
线程名 = threading.Thread(target = 函数名, daemon = True)
2.3.2.2 线程之间共享同进程的资源
线程之间共享全局变量,但是可能会出现安全问题导致错误,可以使用互斥锁来解决
为什么会有安全问题?
比如线程1还没有来得及执行完函数的完整动作,就被线程2抢走了资源,就可能出现问题
互斥锁
当线程获得资源时便给该资源上锁,其他线程无法访问;当线程的函数动作执行完毕后,释放资源,其他的线程便可以访问
2.3.3 互斥锁
互斥:同一资源,同一时间只允许一个任务使用
同步:多个任务按预定次序、协调先后执行
互斥锁:对共享数据进行锁定,保证同一时间只有一个线程去操作
注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程进行等待,等锁使用完释放后,其他线程再去枪这个锁
互斥锁的使用流程:
1.创建互斥锁对象
mutex = threading.Lock()
2.上锁
mutex.acquire()
3.释放锁
mutex.release()




