Bootstrap

Python多任务及网络编程

在这里插入图片描述


1、多任务完成多进程

进程创建步骤

  1. 导入进程包

    import multiprocessing
    
  2. 通过进程类创建进程对象

    进程对象 = multiprocessing.Process()
    
  3. 启动进程执行任务

    进程对象.start()
    

通过进程类创建进程对象

进程对象 = multiprocessing.Process(target=任务名)
参数名说明
target执行的目标任务名,这里指的是函数名(方法名)
name进程名,一般不用设置
group进程组,目前只能使用None

案例演示

"""
    进程创建与启动代码
"""

import multiprocessing
import time


def coding():
    for i in range(3):
        print("Coding...")
        time.sleep(1)


def music():
    for i in range(3):
        print("music...")
        time.sleep(1)


if __name__ == '__main__':
    # 创建子进程
    coding_process = multiprocessing.Process(target=coding)
    # 创建子进程
    music_process = multiprocessing.Process(target=music)
    # 启动进程
    coding_process.start()
    music_process.start()

进程执行带有参数的任务

参数名说明
args以元组的方式给执行任务传参
kwargs以字典的方式给执行任务传参
  • args参数的使用

    # target: 进程执行的函数名
    # args: 表示以元组的方式给函数传参
    coding_process = multiprocess.Process(target=coding, args=(3, ))
    coding_process.start()
    
  • kwargs参数的使用

    # target: 进程执行的函数名
    # kwargs: 表示以字典的方式给函数传参
    music_process = multiprocess.Process(target=music, kwargs={"num": 3})
    music_process.start()
    

2、获取进程编号

  1. 获取当前进程编号
    • getpid()方法
  2. 获取当前父进程编号
    • getppid()方法

os.getpid()的使用

import os
def work():
    # 获取当前进程的编号
    print("work当前的编号:", os.getpid())

os.getppid()的使用

import os
def work():
    # 获取当前进程的编号
    print("work当前的编号:", os.getpid())
    # 获取当前父进程的编号
    print("work父进程的编号:", os.getppid())

3、进程间不共享全局变量

​ 进程间是不共享全局变量的

​ 实际上创建一个子进程就是把主进程的资源进行拷贝产生了一个新的进程,这里主进程和子进程是相互独立的

"""
    进程间不共享全局变量
"""

import multiprocessing
import time

# 全局变量
my_list = []


def write():
    for i in range(5):
        my_list.append(i)
    print("write添加完成")
    print("write进程中my_list = %s" % my_list)


def read():
    print("read进程中my_list = %s" % my_list)


if __name__ == '__main__':
    # 线程创建
    write_process = multiprocessing.Process(target=write)
    read_process = multiprocessing.Process(target=read)
    # 启动线程
    write_process.start()
    time.sleep(1)
    read_process.start()

4、主进程和子进程的结束顺序

​ 主进程会等待所有的子进程执行完毕后才会结束

设置守护进程

子进程.daemon = True
import multiprocessing
import time


def work():
    for i in range(10):
        print("工作中...")
        time.sleep(0.2)


if __name__ == '__main__':
    # 创建进程
    work_process = multiprocessing.Process(target=work)
    # 创建守护主进程,主进程执行完毕后直接销毁子进程
    work_process.daemon = True
    # 执行进程
    work_process.start()

    time.sleep(1)
    print("主进程结束")

销毁子进程

子进程.terminate()

5、多线程完成多任务

线程的创建步骤

  1. 导入线程模块

    import threading
    
  2. 通过线程类创建线程对象

    线程对象 = threading.Thread(target=任务名)
    
  3. 启动线程执行任务

    线程对象.start()
    

通过线程类创建线程对象

参数名说明
target执行的目标任务名,这里指的是函数名(方法名)
name进程名,一般不用设置
group进程组,目前只能使用None

获取线程信息

current_thread = threading.current_thread()

6、主线程的子线程的结束顺序

​ 主线程会等待所有子线程执行完毕后才结束

设置守护主线程

work_thread = threading.Thread(target=work, daemon=True)

或者

work_thread.setDaemon(True)

7、线程间共享全局变量

​ 线程之间共享全局变量

import threading
import time

# 全局变量
my_list = []


def write():
    for i in range(5):
        my_list.append(i)
        time.sleep(0.2)
    print("write添加成功")
    print("write中list=%s" % my_list)


def read():
    print("read中my_list=%s" % my_list)


if __name__ == '__main__':
    # 创建线程
    write_threading = threading.Thread(target=write)
    read_threading = threading.Thread(target=read)
    # 启动线程
    write_threading.start()
    read_threading.start()

线程间共享全局变量数据出现错误问题

​ 多线程同时操作全局变量,容易出现数据错误

解决方法:

​ 同步:就是协同步调,按预定的先后次序进行运行

​ 使用线程同步:保证同一时刻只能由一个线程去操作全局变量

线程同步方式:

​ 互斥锁

8、互斥锁

​ 对共享数据进行锁定,保证同一时刻只有一个线程去操作

注意:

​ 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程进行等待,等待锁使用完释放后,其他等待的线程再去抢这个锁

互斥锁的使用

  1. 互斥锁的创建

    mutex = threading.lock()
    
  2. 上锁

    mutex.acquire()
    
  3. 释放锁

    mutex.release()
    
"""
    @File      : 互斥锁.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 9:57
"""

import threading

# 全局变量
g_num = 0


def sum_num1():
    # 上锁
    mutex.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    # 解锁
    mutex.release()
    print("g_num1:", g_num)


def sum_num2():
    # 上锁
    mutex.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    # 解锁
    mutex.release()
    print("g_num2:", g_num)


if __name__ == '__main__':
    # 创建锁
    mutex = threading.Lock()
    # 创建子进程
    sum_num1_threading = threading.Thread(target=sum_num1)
    sum_num2_threading = threading.Thread(target=sum_num2)
    # 启动线程
    sum_num1_threading.start()
    sum_num2_threading.start()

9、死锁

​ 一直等待对方释放锁的情景就是死锁

死锁的结果

​ 会造成应用程序停止相应,不能再处理其他任务

"""
    @File      : 死锁.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 10:03
"""

import threading

# 全局变量
g_num = 0


def sum_num1():
    print("sum_num1...")
    # 上锁
    mutex.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print("g_num1:", g_num)


def sum_num2():
    print("sum_num2...")
    # 上锁
    mutex.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print("g_num2:", g_num)


if __name__ == '__main__':
    # 创建锁
    mutex = threading.Lock()
    # 创建子进程
    sum_num1_threading = threading.Thread(target=sum_num1)
    sum_num2_threading = threading.Thread(target=sum_num2)
    # 启动线程
    sum_num1_threading.start()
    sum_num2_threading.start()

注意

  1. 使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁
  2. 死锁一旦产生就会造成应用程序的停止相应,应用程序无法再继续执行下去

10、线程和进程对比

关系对比

  1. 线程是依附在进程里面的,没有进程就没有线程
  2. 一个进程默认提供一条线程,进程可以创建多个线程

区别对比

  1. 进程之间不共享全局变量
  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁或线程同步
  3. 创建进程的资源开销要比创建线程的资源开销大
  4. 进程是操作系统资源分配的基本单位,线程是 CPU 调度的基本单位
  5. 线程不能够独立执行,必须依存在进程中

优缺点对比

  1. 进程优缺点
    • 优点:可以使用多核
    • 缺点:资源开销大
  2. 线程优缺点
    • 优点:资源开销小
    • 缺点:不能使用多核

11、Python3 编码转换

  • 网络传输是以二进制数据传输的

数据的编码转化

函数名说明
encode编码,将字符串转化为字节码
decode解码,将字节码转化为字符串

提示:
encode() 和 decode() 函数可以接受参数,encoding 是指在编解码过程中使用的编码方案

bytes.decode(encoding="utf-8")
str.encode(encoding="utf-8")

12、TCP 客户端程序开发

TCP 网络应用程序开发分为:

  • TCP 客户端程序开发
  • TCP 服务端程序开发

TCP 客户端程序开发流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 创建客户端套接字对象(买电话)
  2. 和服务端套接字建立连接(打电话)
  3. 发送数据(说话)
  4. 接收数据(接听)
  5. 关闭客户端套接字(挂电话)

socket 类的介绍

  1. 导入 socket 模块

    import socket
    
  2. 创建客户端 socket 对象使用 socket 类

    socket.socket(AddressFamily, Type)
    
    参数名说明
    AddressFamilyIP 地址类型,分为 IPv4 和 IPv6
    Type传输协议类型

开发客户端使用到的函数

方法名说明
connect和服务端套接字建立连接
send发送数据
recv接收数据
close关闭数据

案例演示:

"""
    @File      : 10TCP客户端程序开发.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 10:35
"""

import socket

if __name__ == '__main__':
    # 1、创建客户端套接字对象
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2、和服务端套接字建立连接
    tcp_client_socket.connect(("192.168.200.1", 8080))
    # 3、发送数据
    tcp_client_socket.send("hello,server".encode(encoding="utf-8"))
    # 4、接收数据 recv阻塞等待数据的到来
    recv_data = tcp_client_socket.recv(1024)
    print(recv_data.decode(encoding="utf-8"))
    # 5、关闭客户端套接字
    tcp_client_socket.close()

注意创建 TCP 套接字时:

  • 参数1:AF_INET,表示 IPv4 地址类型
  • 参数2:SOCK_STREAM,表示 TCP 传输协议类型

13、TCP 服务端程序开发

TCP 服务端程序开发流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 创建服务端套接字对象
  2. 绑定 IP 地址和端口号
  3. 设置监听
  4. 等待接收客户端的连接请求
  5. 接收数据
  6. 发送数据
  7. 关闭套接字

TCP 服务端程序开发相关函数

方法名说明
bind绑定 IP 地址和端口号
listen设置监听
accept等待接受客户端的连接请求
send发送数据
recv接收数据

案例演示:

"""
    @File      : 11TCP服务端程序开发.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 12:27
"""

import socket

if __name__ == '__main__':
    # 1. 创建服务端套接字对象
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2. 绑定 IP 地址和端口号 ps:ip地址栏为空则默认本机ip
    tcp_server_socket.bind(("192.168.200.1", 8888))
    # 3. 设置监听 128:代表服务端等待排队连接的最大数量
    tcp_server_socket.listen(128)
    # 4. 等待接收客户端的连接请求 accept阻塞等待 返回一个用于和客户端通socket,客户端的地址
    conn_socket, ip_port = tcp_server_socket.accept()
    print("客户端地址:", ip_port)
    # 5. 接收数据
    recv_data = conn_socket.recv(1024)
    print("接收到的数据:", recv_data.decode(encoding="utf-8"))
    # 6. 发送数据
    conn_socket.send("客户端你的数据我收到了".encode())
    # 7. 关闭套接字
    conn_socket.close()
    tcp_server_socket.close()

14、TCP 网络应用程序注意点

  1. 当 TCP 客户端程序想要和 TCP 服务端程序进行通信的时候必须要先建立连接
  2. TCP 客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的。
  3. TCP 服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序。
  4. listen 后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息
  5. 当 TCP 客户端程序和 TCP 服务端程序连接成功后,TCP 服务器端程序会产生一个新的套接字,收发客户端消息使用该套接字。
  6. 关闭 accept 返回的套接字意味着和这个客户端已经通信完毕
  7. 当客户端的套接字调用 close 后,服务器端的 recv 会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的数据长度也为0

注意

​ 在使用服务端程序进行通信时,每一次的通信都有缓冲时间,为了取消缓冲时间,可以对 socket 进行设置

# 设置端口复用
tcp_server_socket.setsocket(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

15、socket 之 send 和 recv 原理剖析

TCP socket 的发送和接收缓冲区

​ 当创建一个 TCP socket 对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间

send 原理剖析

send 是不是直接把数据发给服务端?

​ 不是,想要发送数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区,再有操作系统控制网卡把发送缓冲区的数据发送给服务端网卡

recv 原理剖析

recv 是不是直接从客户端接收?

​ 不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区,应用程序再从接收缓冲区获取客户端发送的数据

16、多任务版 TCP 服务端程序开发

实现步骤分析

  1. 直接使用 while 循环而开发的 TCP 服务端程序不能同时服务于多个客户端
  2. 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞
"""
    @File      : 12多任务版TCP服务端程序开发.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 14:50
"""

import socket
import threading


# 处理客户端函数
def handle_client(conn_socket):
    # 5. 接收数据
    recv_data = conn_socket.recv(1024)
    print("接收到的数据:", recv_data.decode(encoding="utf-8"))
    # 6. 发送数据
    conn_socket.send("客户端你的数据我收到了".encode())
    # 7. 关闭套接字
    conn_socket.close()


if __name__ == '__main__':
    # 1. 创建服务端套接字对象
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2. 绑定 IP 地址和端口号 ps:ip地址栏为空则默认本机ip
    tcp_server_socket.bind(("192.168.200.1", 8888))
    # 3. 设置监听 128:代表服务端等待排队连接的最大数量
    tcp_server_socket.listen(128)
    # 添加for循环,完成多任务功能,range(2),即为同时与两个客户端通信
    for i in range(2):
        # 4. 等待接收客户端的连接请求 accept阻塞等待 返回一个用于和客户端通socket,客户端的地址
        conn_socket, ip_port = tcp_server_socket.accept()
        print("客户端地址:", ip_port)

        # 使用多线程去接收多个客户端的请求
        sub_thread = threading.Thread(target=handle_client, args=(conn_socket,))
        # 开启子线程
        sub_thread.start()

    tcp_server_socket.close()

17、静态 Web 服务器-返回固定页面数据

开发自己的静态 Web 服务器

开发步骤:

  1. 编写一个 TCP 服务端程序
  2. 获取浏览器发送的 HTTP 请求报文数据
  3. 读取固定页面数据,把页面数据组装成 HTTP 响应报文数据发送给浏览器
  4. HTTP 响应波阿文数据发送完成以后,关闭服务于客户端的套接字
"""
    @File      : 01_静态Web服务器_返回固定页面数据.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 15:37
"""

import socket

if __name__ == '__main__':
    # 1. 编写一个 TCP 服务端程序
    # 创建 socket
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口复用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定地址
    tcp_server_socket.bind(("", 8888))
    # 设置监听
    tcp_server_socket.listen()
    while True:
        # 2. 获取浏览器发送的 HTTP 请求报文数据
        client_socket, client_addr = tcp_server_socket.accept()
        # 获取浏览器的请求信息
        client_request_data = client_socket.recv(1024).decode()
        # 打印请求报文
        print(client_request_data)
        # 3. 读取固定页面数据,把页面数据组装成 HTTP 响应报文数据发送给浏览器
        with open("./static/index.html", "rb") as f:
            file_data = f.read()

        # 应答行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 应答头
        response_header = "Server:PythonWeb\r\n"
        # 应答体
        response_body = file_data
        # 组装数据
        response_data = (response_line + response_header + "\r\n").encode() + response_body
        # 发送数据
        client_socket.send(response_data)
        # 4. HTTP 响应波阿文数据发送完成以后,关闭服务于客户端的套接字
        client_socket.close()

18、静态 Web 服务器-返回指定页面数据

分析步骤

  1. 获取用户请求资源的路径
  2. 根据请求资源的路径,读取指定文件的数据
  3. 组装指定文件数据的响应报文,发送给浏览器
  4. 判断请求的文件在服务端不存在,组装 404 状态的响应报文,发送给浏览器
"""
    @File      : 02_静态Web服务器_返回指定页面数据.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 16:26
"""

import socket

if __name__ == '__main__':
    # 1. 编写一个 TCP 服务端程序
    # 创建 socket
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口复用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定地址
    tcp_server_socket.bind(("", 8080))
    # 设置监听
    tcp_server_socket.listen(128)
    while True:
        # 2. 获取浏览器发送的 HTTP 请求报文数据
        client_socket, client_addr = tcp_server_socket.accept()
        # 获取浏览器的请求信息
        client_request_data = client_socket.recv(1024).decode()
        # 打印请求报文
        print(client_request_data)
        # 获取用户请求资源的路径
        request_path = client_request_data.split(" ")[1]

        if request_path == "/":
            request_path = "/index.html"

        # 3. 读取固定页面数据,把页面数据组装成 HTTP 响应报文数据发送给浏览器
        # 根据请求资源的路径,读取指定文件的数据
        try:
            with open("./static" + request_path, "rb") as f:
                file_data = f.read()
        except Exception as e:
            # 返回404错误数据
            # 应答行
            response_line = "HTTP/1.1 404 Not Found\r\n"
            # 应答头
            response_header = "Server:PythonWeb\r\n"
            # 应答体
            response_body = "404 Not Found"
            # 组装数据
            response_data = (response_line + response_header + "\r\n" + response_body).encode()
            # 发送数据
            client_socket.send(response_data)
        else:
            # 应答行
            response_line = "HTTP/1.1 200 OK\r\n"
            # 应答头
            response_header = "Server:PythonWeb\r\n"
            # 应答体
            response_body = file_data
            # 组装数据
            response_data = (response_line + response_header + "\r\n").encode() + response_body
            # 发送数据
            client_socket.send(response_data)
        finally:
            # 4. HTTP 响应报文数据发送完成以后,关闭服务于客户端的套接字
            client_socket.close()

19、静态 Web 服务器-多任务版

分析步骤

  1. 当客户端和服务端建立连接成功,创建子线程专门处理客户端的请求,防止主线程阻塞
"""
    @File      : 03_静态Web服务器_多任务版.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 17:34
"""

import socket
import threading


def client_request(client_socket):
    # 获取浏览器的请求信息
    client_request_data = client_socket.recv(1024).decode()
    # 获取用户请求资源的路径
    request_path = client_request_data.split(" ")[1]
    if request_path == "/":
        request_path = "/index.html"

    # 3. 读取固定页面数据,把页面数据组装成 HTTP 响应报文数据发送给浏览器
    # 根据请求资源的路径,读取指定文件的数据
    try:
        with open("./static" + request_path, "rb") as f:
            file_data = f.read()
    except Exception as e:
        # 返回404错误数据
        # 应答行
        response_line = "HTTP/1.1 404 Not Found\r\n"
        # 应答头
        response_header = "Server:PythonWeb\r\n"
        # 应答体
        response_body = "404 Not Found"
        # 组装数据
        response_data = (response_line + response_header + "\r\n" + response_body).encode()
        # 发送数据
        client_socket.send(response_data)
    else:
        # 应答行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 应答头
        response_header = "Server:PythonWeb\r\n"
        # 应答体
        response_body = file_data
        # 组装数据
        response_data = (response_line + response_header + "\r\n").encode() + response_body
        # 发送数据
        client_socket.send(response_data)
    finally:
        # 4. HTTP 响应报文数据发送完成以后,关闭服务于客户端的套接字
        client_socket.close()


if __name__ == '__main__':
    # 1. 编写一个 TCP 服务端程序
    # 创建 socket
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口复用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定地址
    tcp_server_socket.bind(("", 8080))
    # 设置监听
    tcp_server_socket.listen(128)
    while True:
        # 2. 获取浏览器发送的 HTTP 请求报文数据
        client_socket, client_addr = tcp_server_socket.accept()
        # 创建子线程
        client_thread = threading.Thread(target=client_request, args=(client_socket,))
        # 开启子线程
        client_thread.start()

20、静态 Web 服务器-面向对象开发

分析步骤

  1. 把提供服务的 Web 服务器抽象成一个类(HTTPWebServer)
  2. 提供 Web 服务器的初始化方法,在初始化方法里面创建 socket 对象
  3. 提供一个开启 Web 服务器的方法,让 Web 服务器处理客户端请求的操作
"""
    @File      : 04_静态Web服务器_面向对象开发.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 19:40
"""
import socket
import threading


class HttpWebServer():
    def __init__(self):
        # 1. 编写一个 TCP 服务端程序
        # 创建 socket
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口复用
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        # 绑定地址
        self.tcp_server_socket.bind(("", 8080))
        # 设置监听
        self.tcp_server_socket.listen(128)

    def client_request(self, client_socket):
        # 获取浏览器的请求信息
        client_request_data = client_socket.recv(1024).decode()
        # 获取用户请求资源的路径
        request_path = client_request_data.split(" ")[1]
        if request_path == "/":
            request_path = "/index.html"

        # 3. 读取固定页面数据,把页面数据组装成 HTTP 响应报文数据发送给浏览器
        # 根据请求资源的路径,读取指定文件的数据
        try:
            with open("./static" + request_path, "rb") as f:
                file_data = f.read()
        except Exception as e:
            # 返回404错误数据
            # 应答行
            response_line = "HTTP/1.1 404 Not Found\r\n"
            # 应答头
            response_header = "Server:PythonWeb\r\n"
            # 应答体
            response_body = "404 Not Found"
            # 组装数据
            response_data = (response_line + response_header + "\r\n" + response_body).encode()
            # 发送数据
            client_socket.send(response_data)
        else:
            # 应答行
            response_line = "HTTP/1.1 200 OK\r\n"
            # 应答头
            response_header = "Server:PythonWeb\r\n"
            # 应答体
            response_body = file_data
            # 组装数据
            response_data = (response_line + response_header + "\r\n").encode() + response_body
            # 发送数据
            client_socket.send(response_data)
        finally:
            # 4. HTTP 响应报文数据发送完成以后,关闭服务于客户端的套接字
            client_socket.close()

    def start(self):
        while True:
            # 2. 获取浏览器发送的 HTTP 请求报文数据
            client_socket, client_addr = self.tcp_server_socket.accept()
            # 创建子线程
            client_thread = threading.Thread(target=self.client_request, args=(client_socket,))
            # 开启子线程
            client_thread.start()


if __name__ == '__main__':
    # 创建服务器对象
    my_web_server = HttpWebServer()
    # 启动服务器
    my_web_server.start()

21、静态 Web 服务器-命令行启动动态绑定端口号

步骤分析:

  1. 获取执行 Python程序的终端命令行参数
    • sys.argv
  2. 判断参数的类型,设置端口号必须是整型
  3. 给 Web 服务器类的初始化方法添加一个端口号参数,用于绑定端口号
"""
    @File      : 05_命令行启动动态绑定端口号.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/4 20:02
"""
import socket
import threading
import sys


class HttpWebServer():
    def __init__(self, port):
        # 1. 编写一个 TCP 服务端程序
        # 创建 socket
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口复用
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        # 绑定地址
        self.tcp_server_socket.bind(("", port))
        # 设置监听
        self.tcp_server_socket.listen(128)

    def client_request(self, client_socket):
        # 获取浏览器的请求信息
        client_request_data = client_socket.recv(1024).decode()
        # 获取用户请求资源的路径
        request_path = client_request_data.split(" ")[1]
        if request_path == "/":
            request_path = "/index.html"

        # 3. 读取固定页面数据,把页面数据组装成 HTTP 响应报文数据发送给浏览器
        # 根据请求资源的路径,读取指定文件的数据
        try:
            with open("./static" + request_path, "rb") as f:
                file_data = f.read()
        except Exception as e:
            # 返回404错误数据
            # 应答行
            response_line = "HTTP/1.1 404 Not Found\r\n"
            # 应答头
            response_header = "Server:PythonWeb\r\n"
            # 应答体
            response_body = "404 Not Found"
            # 组装数据
            response_data = (response_line + response_header + "\r\n" + response_body).encode()
            # 发送数据
            client_socket.send(response_data)
        else:
            # 应答行
            response_line = "HTTP/1.1 200 OK\r\n"
            # 应答头
            response_header = "Server:PythonWeb\r\n"
            # 应答体
            response_body = file_data
            # 组装数据
            response_data = (response_line + response_header + "\r\n").encode() + response_body
            # 发送数据
            client_socket.send(response_data)
        finally:
            # 4. HTTP 响应报文数据发送完成以后,关闭服务于客户端的套接字
            client_socket.close()

    def start(self):
        while True:
            # 2. 获取浏览器发送的 HTTP 请求报文数据
            client_socket, client_addr = self.tcp_server_socket.accept()
            # 创建子线程
            client_thread = threading.Thread(target=self.client_request, args=(client_socket,))
            # 开启子线程
            client_thread.start()


def main():
    # 获取执行python程序的终端命令行参数
    print(sys.argv)
    if len(sys.argv) != 2:
        print("格式错误 python3 xxx.py 8080")
        return
    # 判断参数的类型,设置端口号必须是整型
    if not sys.argv[1].isdigit():
        print("格式错误 python3 xxx.py 8080")
        return
    port = int(sys.argv[1])
    # 创建服务器对象
    my_web_server = HttpWebServer(port)
    # 启动服务器
    my_web_server.start()


if __name__ == '__main__':
    main()

22、数据库

启动 MySQL 服务

net start mysql

数据库操作的基本步骤

  1. 连接数据库
    • mysql -u用户名 -p密码
  2. 输入用户名和密码
  3. 完成对数据库的操作(SQL 语句完成)
  4. 完成对表结构和表数据的操作(SQL 语句完成)
  5. 退出数据库
    • exit / quit / ctrl+d
  • 查看版本信息
    • select version();
  • 查看时间
    • select now();

数据库基本操作命令

命令作用
show databases;查看所有数据库
select database;查看当前使用的数据库
create database 数据库名 charset utf8;创建数据库
use 数据库名;使用数据库
drop database 数据库名;删除数据库-慎重

数据表基本操作命令

命令作用
show tables;查看当前数据库中所有表
desc 表名;查看表结构
show create table 表名;查看表的创建语句-详细过程

表结构修改命令

命令作用
alter table 表名 add 列名 类型;添加字段
alter table change 原名 新名 类型及约束;重命名字段
alter table 表名 modify 列名 类型及约束;修改字段类型
alter table 表名 drop 列名;删除字段
drop table 表名;删除表

23、Python 连接 MySQL 数据库

PyMysql 使用步骤

  1. 导入 pymysql 包
    • import pymysql
  2. 创建连接对象
    • 调用 pymysql 模块中的 connect() 函数来创建连接对象
    • conn = connect(参数列表)
      • host:连接的 mysql 主机,如果本机是localhost
      • port:连接的 mysql 主机的端口,默认是 3306
      • user:连接的用户名
      • password:连接的密码
      • database:数据库的名称
      • charset:通信采用的方式,推荐使用 utf8
    • 连接对象 conn 的相关操作
      • 关闭连接conn.close()
      • 提交数据conn.commit()
      • 撤销数据conn.rollback()
  3. 获取游标对象
    • 获取游标对象的目的就是要执行 sql 语句,完成对数据库的增删改查
    • 调用连接对象的cursor()方法
      • cur = conn.cursor()
    • 游标操作说明:
      1. 使用游标执行 SQL 语句: execute(operation [parameters]) 执行 SQL 语句,返回受影响的行数,主要用于执行 insert、update、delete、select等语句
      2. 获取查询结果集中的一条数据:cur.fetchone()返回一个元组,如 (1, ‘张三’)
      3. 获取查询结果集中的所有数据:cur.fetchall()返回一个元组,如 ((1, ‘张三’), (2, ‘李四’))
      4. 关闭游标:cur.close(),表示和数据库操作完成
  4. pymysql 完成数据的增删改查
    • 增删改查的 sql 语句
      • sql = select * from 数据表
    • 执行 sql 语句完成相关操作
      • cur.execute(sql)
  5. 关闭游标和连接(注意顺序)
    1. 先关闭游标
      • cur.close()
    2. 后关闭连接
      • conn.close()
"""
    @File      : 01_查询操作.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 10:00
"""

# 导入pymysql包
import pymysql

# 创建连接对象
conn = pymysql.connect(
    host="localhost",
    port=3306,
    user="root",
    password="wh1t3zz1002",
    database="python_test_1",
    charset="utf8"
)
# 获取游标对象
cur = conn.cursor()
# pysql 完成数据库的查询操作
sql = "SELECT * FROM Students;"
# 这里获取的是sql影响的行数
cur.execute(sql)
content = cur.fetchall()
print(content)
# 关闭游标和连接
cur.close()
conn.close()

注意:

​ 在进行对数据库中的数据改动的时候,MySQL 默认启动事务,需要提交事物才能对数据库产生影响

conn.commit()

防止 SQL 语句注入

方法:将要输入的内容参数化

"""
    @File      : 03_SQL注入.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 10:44
"""
# 导入pymysql包
import pymysql

# 创建连接对象
conn = pymysql.connect(
    host="localhost",
    port=3306,
    user="root",
    password="wh1t3zz1002",
    database="python_test_1",
    charset="utf8"
)
# 获取游标对象
cur = conn.cursor()

# # 不安全的方式
# # 若输入' or 1 or '会导致SQL注入,泄露全部信息
# find_name = input("请输入要查询的学生姓名:")
# sql = "SELECT * FROM Students WHERE name='%s'" % find_name
# cur.execute(sql)
# content = cur.fetchall();
# for i in content:
#     print(i)

# 安全的方式-SQL语句参数化
find_name = input("请输入要查询的学生姓名:")
sql = "SELECT * FROM Students WHERE name=%s"
cur.execute(sql, [find_name])
content = cur.fetchall()
for i in content:
    print(i)

# 关闭游标和连接
cur.close()
conn.close()

24、闭包

函数名的作用:

  1. 函数名存放的是函数所在空间的地址
  2. 函数名()执行的是函数名所存放空间地址中的代码
  3. 函数名可以像普通变量一样赋值

闭包

闭包的定义:

​ 在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包

闭包的构成条件:

  1. 在函数嵌套(函数里面再定义函数)的前提下
  2. 内部函数使用了外部函数的变量(还包括外部函数的参数)
  3. 外部函数返回了内部函数
"""
    @File      : 01_闭包.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 11:08
"""


# 1. 在函数嵌套(函数里面再定义函数)的前提下
def func_out(num1):
    def func_inner(num2):
        # 2. 内部函数使用了外部函数的变量(还包括外部函数的参数)
        num = num1 + num2
        print("现在的值:", num)

    # 3. 外部函数返回了内部函数
    return func_inner


# 创建闭包实例
f = func_out(10)
# 执行闭包
f(1)
f(2)

闭包内修改外部变量

​ 修改闭包内使用的外部函数变量使用nonlocal关键字来完成

"""
    @File      : 03_nonlocal的使用.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 14:07
"""


# 外部函数
def func_out(num1):
    # 内部函数
    def func_inner(num2):
        nonlocal num1
        num1 = num2 + 10

    print(num1)
    func_inner(10)
    print(num1)
    return func_inner


if __name__ == '__main__':
    func_out(10)

25、装饰器

装饰器的作用

​ 在不改变原有函数的源代码的情况下,给函数增加新的功能

装饰器语法糖的用法:

@装饰器名称即可完成对已有函数的装饰操作

"""
    @File      : 04_装饰器语法糖.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 14:15
"""


# 1、定义一个装饰器(装饰器的本质是闭包)
def check(fn):
    def inner():
        print("登陆验证。。。")
        fn()

    return inner


# 2、使用装饰器装饰函数(增加一个登录功能)
# 解释器遇到 @check,会立即执行 comment = check(comment)
@check
def comment():
    print("发表评论")


if __name__ == '__main__':
    comment()

装饰器的使用案例

"""
    @File      : 05_装饰器的使用.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 14:27
"""

import time


# 1、定义装饰器
def get_time(fn):
    def inner():
        start = time.time()
        fn()
        end = time.time()

        print("时间:", end - start)

    return inner


@get_time
def func():
    for i in range(100000):
        print(i)


if __name__ == '__main__':
    func()

26、类装饰器

__call__方法的使用

​ 一个类里面一旦实现了__call__方法

​ 那么这个类创建的对象就是一个可调用对象,可以向调用函数一样进行调用

"""
    @File      : 08_类装饰器.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 14:52
"""


# 定义类装饰器
class Check(object):
    def __init__(self, fn):
        self.__fn = fn

    def __call__(self, *args, **kwargs):
        print("登陆")
        self.__fn()


# 被装饰的函数
@Check
def comment():
    print("发表评论")


if __name__ == '__main__':
    comment()

27、property 属性

​ property 属性就是负责把类中的一个方法当作属性进行使用

定义 property 属性有两种方式

  1. 装饰器方式
  2. 类属性方式

装饰器方法

"""
    @File      : 01_property属性_装饰器方法.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 15:04
"""


class Person(object):
    def __init__(self):
        self.__age = 0

    # 获取属性
    @property
    def age(self):
        return self.__age

    # 修改属性
    @age.setter
    def age(self, new_age):
        self.__age = new_age


if __name__ == '__main__':
    p = Person()
    print(p.age)
    # 修改属性
    p.age = 100
    print(p.age)

类属性方式

property 的参数说明

  • 第一个参数是获取属性时要执行的方法
  • 第二个参数是设置属性时要执行的方法
"""
    @File      : 02_property属性_类属性方法.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 15:11
"""


class Person:
    def __init__(self):
        self.__age = 0

    def get_age(self):
        return self.__age

    def set_age(self, new_age):
        self.__age = new_age

    age = property(get_age, set_age)


if __name__ == '__main__':
    p = Person()
    print(p.age)
    p.age = 100
    print(p.age)

28、with 语句和上下文管理器

with 语句的作用

​ 文件操作的时候使用 with 语句可以自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作

with open("123.txt") as f:
    file_data = f.read()
print(file_data)

上下文管理器

​ with 语句之所以这么强大,背后是由上下文管理器做支撑的

​ 也就是说刚才使用 f = open("xxx")中 open函数 创建的 f文件对象 就是一个上下文管理器对象

​ 一个类只要实现了__enter__()__exit__()这两个方法

​ 通过该类创建的对象我们就称之为上下文管理器

  • __enter__()表示上文方法,需要返回一个操作文件对象
  • __exit__()表示下文方法,with 语句执行完成会自动执行,即使出现异常也会执行该方法
"""
    @File      : 04_上下文管理器.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 15:25
"""


# 1、定义一个File类
class File:
    def __init__(self, FilePath, FileModel):
        self.FilePath = FilePath
        self.FileModel = FileModel

    # 2、实现上文管理器
    def __enter__(self):
        self.f = open(self.FilePath, self.FileModel)
        print("上文管理器调用")
        return self.f

    # 2、实现下文管理器
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        print("下文管理器调用")


if __name__ == '__main__':
    # 3、使用with语句完成操作
    with File("123.txt", "r") as f:
        file_data = f.read()
    print(file_data)

29、生成器的创建方式

生成器的作用

​ 根据程序设计者指定的规则循环生成数据,当条件不成立时则生成数据结束

​ 数据并不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存

创建生成器的方式

  1. 生成器推导式

    • data = (x for x in range(100))	# 生成器推导式
      next(data)	# 使用next查看data
      
  2. yield 关键字

yield 关键字

注意点:

  1. 代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续执行下去
  2. 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个 StopIteration 异常,表示停止迭代异常
  3. while 循环内部没有处理异常操作,需要手动添加处理异常操作
  4. for 循环内部自动处理了停止迭代异常,使用起来更加方便
def MyGenerator(num):
    for i in range(num):
        print("开始执行")
        yield i
        print("生成完成")


if __name__ == '__main__':
    g = MyGenerator(5)
    for i in g:
        print(i)

斐波那契数列案例

"""
    @File      : 07_斐波那契数列.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/5 15:54
"""


# 得到前num行数
def Fibonacci(num):
    f1 = 0
    f2 = 1

    # 记录生成了几个数字
    index = 0

    while index < num:
        result = f1
        f1, f2 = f2, f1 + f2
        yield result
        index += 1


if __name__ == '__main__':
    f = Fibonacci(20)
    for i in f:
        print(i, end=" ")

30、深拷贝浅拷贝

浅拷贝

​ copy 函数是浅拷贝,只对可变类型的第一层对象进行拷贝

​ 对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象

​ 不可变类型进行浅拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用

深拷贝

​ 深拷贝使用 deepcopy 函数

​ 只要发现拷贝对有可变类型就会对该对象到最后一个可变类型的每一层对象进行拷贝

​ 对每一层拷贝的对象都会开哦i新的内存空间进行存储

31、正则表达式

概念:

​ 正则表达式就是记录文本规则的代码

特点:

  • 正则表达式的语法可读性差
  • 正则表达式通用性很强,能够适用于很多编程语言

re 模块

​ 在 Python 中需要通过正则表达式对字符串进行匹配的时候,可以使用一个 re 模块

# 导入re模块
import re

# 使用match方法进行匹配操作
result = re.match(正则表达式, 要匹配的字符串)

# 如果上一步匹配到数据的话,可以使用group方法来提取数据
result.gruop()

匹配单个字符

代码功能
.匹配任意一个字符(除了\n
[]匹配[]中列举的字符
\d匹配数字,即0-9
\D匹配非数字
\s匹配空白,即空格,tab 键
\S匹配非空白
\w匹配非特殊字符,即a-z、A-Z、0-9、_、汉字
\W匹配非特殊字符,即非字母、非数字、非汉字

匹配多个字符

代码功能
*匹配前一个字符出现 0 次或无限次,即可有可无
+匹配前一个字符出现 1 次或无限次,即至少有 1 次
?匹配前一个字符出现 1 次或 0 次,即要么有 1 次,要么没有
{m}匹配前一个字符出现 m 次
{m,n}匹配前一个字符出现从 m 到 n 次

匹配开头和结尾

代码功能
^匹配字符串开头
$匹配字符串结尾
[^指定字符]匹配除了指定字符以外的所有字符

匹配分组

代码功能
``
(ab)将括号中字符作为一个分组
\num引用分组 num 匹配到的字符串
(?P<name>)分组起别名
(?P=name)引用别名为 name 分组匹配到的字符串

案例演示

"""
    @File      : 05_匹配分组.py
    @Software  : PyCharm
    @Author    : Crisp077
    @CreateTime: 2022/9/6 20:26
"""

import re

# ①需求:在列表中["apple","banana","orange","pear"],匹配apple和pear
fruit = ["apple", "banana", "orange", "pear"]
for value in fruit:
    result = re.match("apple|pear", value)
    if result:
        info = result.group()
        print(info)
    else:
        continue

# ②需求:匹配出163、126、qq等邮箱
mail = ["[email protected]", "[email protected]", "[email protected]", "!#[email protected]"]
for value in mail:
    result = re.match("[a-zA-Z0-9]{4,20}@(qq|163|126)\.com", value)
    if result:
        info = result.group()
        print(info)
    else:
        continue

# ③需求:匹配qq:10567这样的数据,提取出来qq文字和qq号码
# group(0)代表的是匹配的所有数据 1:第一个分组 2:第二个分组 顺序是从左到右依次排序
result = re.match("(qq):([1-9]\d{4,11})", "qq:10567")
if result:
    info = result.group()
    print(info)
    qq = result.group(1)
    print(qq)
    num = result.group(2)
    print(num)
else:
    print("匹配失败")

# ④需求:匹配出<html>hh</html>
result = re.match("<([a-zA-Z1-6]{4})>.*</\\1>", "<html>hh</html>")
if result:
    info = result.group()
    print(info)
else:
    print("匹配失败")

# ⑤需求:匹配出<html><h1>www.itcast.cn</h1></html>
result = re.match("<([a-zA-Z1-6]{4})><([a-zA-Z1-6]{2})>.*</\\2></\\1>", "<html><h1>www.itcast.cn</h1></html>")
if result:
    info = result.group()
    print(info)
else:
    print("匹配失败")

# ⑥需求:匹配出<html><h1>www.itcast.cn</h1></html>
result = re.match("<(?P<html>[a-zA-Z1-6]{4})><(?P<h1>[a-zA-Z1-6]{2})>.*</(?P=h1)></(?P=html)>",
                  "<html><h1>www.itcast.cn</h1></html>")
if result:
    info = result.group()
    print(info)
else:
    print("匹配失败")

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;