网络编程和多线程

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通信步骤:

  1. 创建连接
  2. 传输数据
  3. 关闭连接

面向连接:通信双方在传输数据之前需要建立连接

可靠传输:通过一套机制保证数据接收方收到完整无误、顺序正确的数据

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网络程序注意点

  1. TCP 服务端程序必须绑定端口号,否则客户端找不到这个 TCP 服务端程序,为了更稳定,建议把 IP 也绑定
  2. accept () 前的套接字是被动套接字(监听套接字),只负责接收新的客户端的连接请求,不能收发消息
  3. accept()函数调用后,当 TCP 客户端程序和 TCP 服务端程序连接成功后, TCP 服务器端程序会产生一个新的套接字(通信套接字),用于收发客户端消息
  4. 对于服务器端的被动 socket,关闭时需慎重
  5. 当客户端的套接字调用 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 模块

  1. 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调度资源的策略:

  1. 均分时间片
  2. 抢占式调度
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()
相关推荐
CV山月1 小时前
《DPO 算法详解:不训练奖励模型,如何让大模型直接学会人类偏好?》
人工智能·经验分享·python·大模型·强化学习·研究生
神仙别闹1 小时前
基于C#实现(WinForm)文件管理系统
java·开发语言·c#
Mr_liu_6661 小时前
ns3-gym例子解析_基础例子与wifi例子_DQN(3)
开发语言·c++·python·dqn·ns3
weixin_440730501 小时前
playwright实战-渠道应用操作
开发语言·前端·python
asdzx671 小时前
Python 解析 Excel 数据、图片与图表的实现方案
python·excel
砚底藏山河1 小时前
存储选型实战:CSV-SQLite-MySQL同机基准(魔码量化实战 #02)
java·数据库·python·金融·maven
陈年老古董1 小时前
Python模拟MapReduce分治思想 | 从单文件统计到大文件拆分聚合 学习笔记
开发语言·hadoop·笔记·python·学习
qetfw1 小时前
Debian Postfix + Dovecot 基础配置:DNS、Maildir、IMAP 与邮件队列验证
linux·服务器·网络·debian
quantdash_cc1 小时前
数据 API 的稳定性应该如何长期监控?从量化数据监控体系到 QuantDash 实践
开发语言·python·数据分析·量化交易·股票数据·quantdash