闭包的构成条件(三步走)
第一步:有嵌套
存在外部函数,函数内部定义了内部函数(函数嵌套)
第二步:有引用
内部函数引用了外部函数作用域中的变量(非全局变量)
第三步:有返回(return)
外部函数将内部函数作为返回值进行return
案例一:普通的嵌套函数
python
def outer():
a = 10
def inner():
pass # 内部函数没有使用a
return inner
f = outer()
内存实例图:

文本内存解释:
【调用 outer() 阶段】 栈空间(outer栈帧)
├─ 变量 a → 内存地址:0x0001 值=10
└─ 创建函数对象 inner → 堆地址:0x1001
堆内存
0x1001 : inner函数对象
⚠️ inner.closure = None 【没有 cell 单元格!】
【outer执行完毕,return inner】
outer栈帧销毁!
└─ 局部变量a(0x0001) 引用计数变为0
└─ GC回收地址0x0001,a彻底消失
变量 f → 指向堆 0x1001(inner函数)
最终状态: f(0x1001) 保留,但是变量a已经被销毁,无法访问a
2.【构成闭包】的代码(关键:内部函数引用外部变量)
python
def outer():
a = 10 # 外部函数局部变量
def inner():
print(a) # ✅ 内部函数引用外部变量a
return inner
# f 接收内部函数对象
f = outer()
内存示例图:

文本内存讲解:
-
执行
outer()操作系统开辟栈空间,创建局部变量a=10(假设内存地址:0x1001); -
在内存中创建内部函数对象
inner(地址:0x2001)。 -
检测到:inner 引用了外部变量 a Python 自动给
inner函数对象创建一个属性:__closure__(闭包单元格 cell)cell单元格专门保存外部变量的内存地址0x1001。⭐重点:inner 不再是单纯的函数,它携带了单元格,记录外部变量地址!
-
outer 函数执行完毕,正常要销毁局部变量 正常情况:outer 栈销毁 →
0x1001内存释放。 但是!cell 单元格持有0x1001的引用计数! 引用计数 ≠ 0 → GC 垃圾回收不能回收这块内存! -
return inner,变量f = inner(0x2001)变量 f 指向内部函数对象,只要 f 不被删除,inner和它里面的cell就一直存在。 -
执行
f()inner 运行时,通过 cell 单元格里保存的地址0x1001,找到变量 a 的值打印。
在闭包的内部实现对外部变量的修改操作(nonlocal关键字)
nonlocal关键字用于在嵌套函数中声明一个变量不是局部变量,而是来自外层函数的变量。使用nonlocal可以修改外层函数中的变量值,而不仅仅是读取它。
python
# 1.有嵌套
def outer():
# 局部变量
num = 20
def inner():
# 2.有引用(内部函数引用了外部函数的局部变量)
nonlocal num
num = 10
print(f'outer函数中的num变量值:{num}')
inner()
print(f'执行inner函数中的num变量值:{num}')
# # 3.有返回值(把内部函数的名称返回=》内存地址返回)
# return inner
# 调用outer函数
outer()
输出结果:

使用场景
当需要在嵌套函数中修改外层函数的变量时,必须使用nonlocal声明。如果不使用nonlocal,Python会认为这是一个新的局部变量。
python
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
与global的区别
nonlocal用于访问外层函数的变量,而global用于访问全局变量。两者都允许修改对应作用域的变量。
python
global_var = 10
def outer():
local_var = 20
def inner():
global global_var
nonlocal local_var
global_var += 1
local_var += 1
inner()
print(global_var, local_var)
多层嵌套
nonlocal可以在多层嵌套函数中使用,它会沿着作用域链向上查找最近的变量。
python
def outer():
x = 1
def middle():
x = 2
def inner():
nonlocal x
x = 3
inner()
print(x) # 输出3
middle()
print(x) # 输出1
注意事项
nonlocal声明的变量必须在外层函数中存在,否则会引发SyntaxError。它不能用于全局变量,也不能创建新的变量。
python
def outer():
def inner():
nonlocal x # 错误:x未在外层作用域定义
x = 10
inner()
闭包的综合案例
案例一:
python
def func():
result = 0
def inner(num):
nonlocal result
result += num
print(result)
return inner
func = func()
func(1) # 1
func(2) # 3
运行结果:

代码分析:
先约定地址标识(方便理解):
- 堆地址
0x8001:整数对象0 - 堆地址
0x9001:inner函数对象 - 堆地址
0xA001:cell 单元格(保存对 result 对应整数对象的引用)
阶段 1:执行 func = func() (外层 func 调用)
1. 栈内存:创建外层 func 栈帧
栈帧内局部名字 result → 指针指向 堆 0x8001(int 对象 0)
2. 堆内存创建 inner 函数对象 0x9001
因为inner内部引用了外层变量result,Python 自动生成 cell 单元格 0xA001
3. return inner,外层 func 函数执行结束
- 外层 func 栈帧销毁,栈上名字
result消失! - 关键:
cell(0xA001)仍然持有0x8001整数对象的引用 对象引用计数≠0,GC不能回收堆上 0x8001 - 全局名字
func重新绑定,指向堆地址0x9001(inner 函数)
⚠重点:现在不存在外层 func 了,全局 func 变量就是 inner 函数,cell 单元格持续保存着数字对象的引用。
阶段 2:第一次调用 func(1) → inner(1)
- 开辟 inner 临时栈帧,形参
num=1 nonlocal result:告知解释器不要在 inner 栈内创建局部 result,去 cell 寻找外层绑定对象result += num等价逻辑:- 通过 cell 单元格
0xA001拿到指针 →0x8001(对象0) - 计算
0 + 1 = 1,新建整数对象 1,地址0x8002 - ✅ 修改 cell 单元格内部保存的指针! cell (0xA001) 不再指向
0x8001,更新指针 → 堆地址 0x8002(int 对象 1) - 原来的整数
0(0x8001)失去所有引用,后续会被 GC 回收
- 通过 cell 单元格
- print 输出
1 - inner 临时栈帧销毁(形参 num 消失)
当前内存状态:
python
全局func → inner(0x9001)
inner绑定cell(0xA001)
cell指针 → 0x8002(int对象1)
阶段 3:第二次调用 func(2) → inner(2)
- 新建 inner 临时栈帧,
num=2 - 通过 cell 单元格
0xA001读取指向对象0x8002(值1) - 计算
1 + 2 = 3,新建对象3,地址0x8003 - 再次更新 cell 内指针 :cell(0xA001) →
0x8003(int对象3) - print 输出
3 - inner 临时栈销毁
通过闭包深入理解装饰器
1.装饰器的定义
就是给已有函数增加额外功能的函数 ,它本质上就是一个闭包函数
装饰器的功能特点:
不修改已有函数的功能特点
不修改已有函数的调用方式
给已有函数增加额外的功能
2.装饰器的雏形
代码示例:
python
#定义一个装饰器
#参数fn,要求传递要修饰函数的名称
#1.有嵌套
def check(fn):#fn为局部变量 fn 局部变量地址指向 comment
def inner():
#编写装饰器功能
print('先登陆')
#2.有引用
fn() #相当于 调用comment()方法
return inner#返回地址
#定义一个已有函数
def comment():
print('编写评论')
# 调用装饰器代码装饰comment
comment = check(comment) #将comment的类名的内存地址传入check方法中
comment() #由于comment指向inner函数,使用comment()代表执行inner函数
输出结果:

代码分析:
约定内存地址方便理解:
- 原始
comment函数对象:0x1001 check内部生成的inner函数对象:0x2001- cell 单元格:
0x3001
阶段 1:执行 comment = check(comment)
执行 check(comment)
- 全局名字
comment指向0x1001 - 传入 check,形参
fn(check 栈帧内局部变量名)→ 指针指向0x1001
在 check 内部创建 inner 函数对象 0x2001 inner 内部引用了外部变量fn,形成闭包
python
inner(0x2001)
└─ __closure__ → cell单元格(0x3001)
cell保存指针 → 0x1001(原始comment函数)
3.return inner,check 函数执行结束
- check 栈帧销毁,栈上局部名字
fn消失 - 但是
cell(0x3001)依旧持有0x1001的引用,原始 comment 函数不会被回收 4.comment = inner
全局变量comment重新绑定:
✅ 旧关联:comment → 0x1001(断开)
✅ 新关联:comment → 0x2001(inner 函数)
阶段2:执行comment()
等价调用inner()
python
def inner():
print('先登陆')
fn()
- 打印
先登陆 - 执行
fn():inner 通过自身 cell 单元格0x3001,拿到指针0x1001 - 调用原始函数对象
0x1001,输出编写评论
内存链路简图
python
执行完成 comment = check(comment) 之后:
全局变量 comment ────> inner(0x2001)
↓
cell单元格(0x3001)
↓
原始comment函数(0x1001)
核心结论(装饰器本质)
- Python 装饰器 = 闭包的典型应用
check是装饰器外层函数;inner闭包负责增强功能 + 调用原函数comment = check(comment)是变量重绑定,偷换了全局名字指向- cell 单元格永久保存原始函数的地址,保证在 inner 内部可以随时调用原函数
装饰器的定义:
装饰器是一种设计模式,允许在不修改原有代码结构的情况下,动态地为函数或类添加额外功能。它通过将目标函数(或类)作为参数传递给另一个函数(装饰器函数),并在装饰器内部对目标函数进行包装或扩展,最终返回增强后的函数。
装饰器的语法:
装饰器通过 @ 符号加装饰器函数名放置在目标函数定义之前:
python
#1.有嵌套
def check(func):
def inner():
#编写装饰器功能(修饰代码)
print('登陆功能')
#2.与引用
func()
#3.有返回
return inner
#定义一个已有函数
@check #用@ + 修饰器名称 的注解 来调用定义的装饰器
def comment():
print('编写评论')
#调用comment函数
comment()
类装饰器
装饰器也可以实现为类,只需实现 __call__ 方法:
python
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 输出:Call 1 of say_hello \n Hello!
say_hello() # 输出:Call 2 of say_hello \n Hello!
装饰器的作用:
装饰器的使用场景
- 日志记录:记录函数调用信息。
- 性能测试:统计函数执行时间。
示例:统计函数执行时间
示例代码:
python
import time
#定义一个装饰器,用于获取某个函数的执行实际(1.有嵌套 2.有引用
def get_time(fn):
def inner():
#给fn函数添加额外的功能
start = time.time()
#执行了原函数
fn()
end = time.time()
print(f'{fn}函数执行一个花费了{end-start}s')
#把内存函数的地址使用return返回
return inner
#原有函数
@get_time
def func():
list1 = []
for i in range(100000):
list1.append(i)
#调用函数
func()
输出结果:

代码分析:
- 定义外层函数
get_time、业务函数func;语法糖@get_time等价执行func = get_time(func)。 - 调用
get_time(func):原func函数地址传入形参fn,内部创建inner,inner引用fn,构成闭包 ,随后返回inner。 - 全局变量
func重新绑定为inner函数对象。 - 执行
func(),实际运行inner:- 获取起始时间
start; - 通过闭包 cell 保存的地址调用原始
func,循环生成列表; - 获取结束时间
end,计算并打印函数执行耗时。
- 获取起始时间
核心要点:
get_time执行完毕栈帧销毁,但inner内 cell 单元格持有原func函数引用,原函数不会被 GC 回收,供后续调用。
通用装饰器的使用
1.装饰器带有参数的函数
示例:可以日志打印生成一个装饰器
python
#编写装饰器,用于给每个函数增加一个日志输出功能
def logging(fn):
def inner(num1,num2):
print(f'-- 输出日志:正在{fn.__name__}执行函数--')
fn(num1,num2)
return inner
@logging
def sum_num(num1,num2):
result = num1+num2
print(result)
sum_num(10,20)
输出结果:

案例:不定长参数
python
def logging(fn):
def wrapper(*args,**kwargs):
#添加装饰器代码(输出日志信息)
print(f'-- 输出日志:正在{fn.__name__}执行函数--')
fn(*args,**kwargs)
return wrapper
@logging
def sum_num(*args,**kwargs):
result = 0
# *args代表不定长元组参数,args = (10,20)
for num in args:
result += num
#**kwargs代表不定长字典参数, kwargs = {a:2,b:3)
for num in kwargs.values():
result += num
print(result)
#sum_mun带四个参数,而且参数类型不用,10和20以元组形式传递,a = 2 ,b =3以字典传递
sum_num(10,20,a = 2,b = 3)
输出结果:

带有返回装饰器
返回装饰器是一种特殊类型的装饰器,其核心功能是修改或增强被装饰函数的返回值。这类装饰器通常在被装饰函数执行完毕后,对其返回结果进行加工、验证或转换,再返回新的结果。
基本结构
python
def return_decorator(func):
def wrapper(*args, **kwargs):
# 调用原始函数并获取返回值
result = func(*args, **kwargs)
# 对返回值进行处理
modified_result = ... # 处理逻辑
return modified_result
return wrapper
典型应用场景
-
结果格式化
将原始返回值转换为特定格式(如JSON、字符串等):
pythondef to_json(func): def wrapper(*args, **kwargs): import json result = func(*args, **kwargs) return json.dumps(result) return wrapper @to_json def get_data(): return {'key': 'value'} -
结果验证
检查返回值是否符合预期条件:
pythondef validate_non_negative(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) if result < 0: raise ValueError("Result must be non-negative") return result return wrapper -
结果缓存
缓存函数返回值以避免重复计算:
pythondef cache_result(func): cached = {} def wrapper(*args): if args in cached: return cached[args] result = func(*args) cached[args] = result return result return wrapper -
类型转换
强制将返回值转换为指定类型:
pythondef enforce_type(target_type): def decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return target_type(result) return wrapper return decorator @enforce_type(float) def divide(a, b): return a / b
注意事项
- 链式调用:多个返回装饰器会按从下到上的顺序生效。
- 副作用处理:确保装饰器不会意外修改原始返回值的不可变属性。
- 性能影响:复杂的返回值处理可能增加调用开销。
高级用法
结合参数化装饰器实现动态控制:
python
def multiply_return(factor):
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * factor
return wrapper
return decorator
@multiply_return(3)
def get_number():
return 5
返回装饰器通过拦截和修改返回值,为函数提供灵活的后期处理能力,是Python装饰器模式中的重要分支。
python3网络编程(socket)
计算机网络介绍
1.IP地址的概念
IP地址就是标识网络中设备的一个地址,好比现实生活中的家庭地址。
网络中的设备效果图

2.IP地址的表现形式
说明:
IP地址分为两类:IPv4和IPv6
IPv4是目前使用的IP地址
IPv6是未来使用的IP地址
IPv4是由点分十进制组成
IPv6是由冒号十六进制组成

3.IP地址的作用
IP地址的作用是标识网络中唯一的一台设备的,也就是说通过IP地址能找到网络中某台设备。
IP地址作用效果图:

4.查看计算机的IP地址
Linux和mac 使用ifconfig这个命令
Windows使用ipconfig这个命令
说明:
ifconfig与ipconfig都是查看网卡信息的,网卡信息中包含这个设备对应的IP地址。
5.检测网络是否正常
检查网络是否正常使用ping命令
检查网络是否正常效果图:

6.问题思考
问:不同电脑上的飞秋之间进行数据通信,它是如何保证把数据给飞秋而不是其他软件?
答:其实,每运行一个网络程序都会有一个端口,想要给对应程序发送数据,找到对应的端口号即可。端口就是传输数据的通道,好比教室的门,就是数据传输必经之路。
端口效果图:

而且每一个端口号都会有一个对应的端口号(操作系统为了统一管理这么多端口,就对端口进行了编号,这就是端口号,端口号其实就是一个数字,好比我们现实生活中的门牌号),想要找到端口通过端口号即可。

最终通信结果:通过ip找到对应的设备,通过端口号找到对应的端口号,然后通过端口号把数据给应用程序。
7.扩展:端口号扩展
①知名端口号
知名端口号是指众所周知的端口号,范围从 0 到 1023,这些端口号一般固定分配给一些服务,比如 21 端口分配给 FTP(文件传输协议)服务,22 端口分配给 SSH(安全外壳协议,主要用于远程连接与文件传输),25 端口分配给 SMTP(简单邮件传输协议)服务,80 端口分配给 HTTP 服务,443 端口分配给 HTTPS 服务等等。
②动态端口号
一般程序员开发应用程序使用端口号称为动态端口号。 动态端口号的范围是从 1024 到 65535,如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用。
注意:当运行一个程序默认会有一个端口号,当程序退出时,所占用的这个端口号就会被释放。
TCP的介绍
1、网络应用程序之间的通信流程
之前我们学习了 IP 地址和端口号,通过 IP 地址能够找到对应的设备,然后再通过端口号找到对应的端口,再通过端口把数据传输给应用程序,这里要注意,数据不能随便发送,在发送之前还需要选择一个对应的传输协议,保证程序之间按照指定的传输规则进行数据的通信,而这个传输协议就是我们今天学习的 TCP。
2.TCP概念
TCP :传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。

面向连接的效果图:

3.TCP通信步骤:
1.创建连接
2.传输数据
3.关闭连接
4.TCP协议的优点
可靠性高
TCP通过确认机制、重传机制和校验和确保数据准确无误地传输。每发送一个数据段都会等待接收方的确认,未收到确认时会重传,保证数据完整性。
有序传输
TCP为每个数据包分配序列号,接收方按序列号重组数据,确保数据按发送顺序到达,避免乱序问题。
流量控制
通过滑动窗口机制动态调整发送速率,防止接收方因缓冲区不足导致数据丢失,有效管理网络拥塞。
连接导向
通信前需建立三次握手连接,结束后通过四次挥手释放连接。这种面向连接的特性适合需要稳定传输的场景(如文件传输、网页浏览)。
支持全双工通信
允许双方同时发送和接收数据,提高通信效率。
5.TCP协议的缺点
建立连接开销大
三次握手和四次挥手过程引入额外延迟,尤其在短连接场景(如HTTP请求)中显著影响性能。
传输效率较低
为保证可靠性,TCP需维护序列号、确认、重传等机制,增加协议头开销(通常20字节),占用更多带宽和CPU资源。
实时性差
重传机制和拥塞控制可能导致延迟波动,不适合实时性要求高的应用(如视频会议、在线游戏)。
灵活性不足
TCP严格保证数据顺序和完整性,某些场景(如音视频流)允许部分数据丢失但需要低延迟,此时TCP的机制反而成为瓶颈。
拥塞控制可能过保守
TCP的拥塞避免算法(如慢启动)在网络状况良好时可能无法充分利用带宽,降低吞吐量。
适用场景对比
- 适用场景:需高可靠性的应用(网页浏览、电子邮件、文件传输)。
- 不适用场景:实时音视频、在线游戏、IoT传感器数据等低延迟或可容忍丢包的应用(通常选用UDP)。
socket的介绍(套接字)
1.问题思考
到目前为止我们学习了 IP 地址和端口号还有 TCP 传输协议,为了保证数据的完整性和可靠性我们使用 TCP 传输协议进行数据的传输,为了能够找到对应设备我们需要使用 IP 地址,为了区别某个端口的应用程序接收数据我们需要使用端口号,那么通信数据是如何完成传输的呢?
2.socket套接字
socket是进程之间通信一个工具,好比现实生活中的插座,所有的家用电器想要工作都是基于插座进行,进程之间想要进行网络通信需要基于这个socket。

3.socket的作用与应用场景
socket的作用:负责进程之间的网络数据传输,好比数据的搬运工
socket使用场景:
不夸张的说,只有跟网络相关的应用程序或者软件都使用到了socket
TCP网络应用程序开发流程
1.介绍
TPC网络应用程序开发分为:
TCP客户端程序开发
TCP服务端程序开发
说明:
客户端程序是指运行在用户设备上的程序
服务端程序是指运行在服务器设备上的程序,专门为客户提供数据服务
2.具体流程介绍(TCP客户端开发五步走)

socket类的介绍
python
1.导入socket模块
import socket
2.创建客户端socket对象使用socket类
socket.socket(AddressFamily,Type)
客户端socket类的参数和方法说明:
| 参数名 | 说明 |
|---|---|
| AddressFamily | IP 地址类型,分为 IPv4 和 IPv6(图中笔误写为 TPv4) |
| Type | 传输协议类型(TCP/UDP) |
开发客户端需要使用的函数:
| 方法名 | 说明 |
|---|---|
| connect | 与服务端套接字建立网络连接 |
| send | 向服务端发送数据 |
| recv | 接收服务端返回的数据 |
| close | 关闭当前套接字连接,释放端口资源 |
python3编码转换
1.为什么要学习编码转换
在计算机网络中,时间都是以二进制的形式进行传输的。所以在网络传输数据的时候,数据需要先编码转化为二进制(bytes)数据类型
2.数据的编码转换

转换方法说明:
| 函数名 | 说明 |
|---|---|
| encode | 编码,将字符串转化为字节码 |
| decode | 解码,将字节码转化为字符串 |
提示:encode()和decode()函数可以接受参数,encoding是指在编码解码过程中使用的编码方案。
字符串编码:
python
str.encode(encoding="utf-8")
二进制解码:
python
bytes.decode(encoding = 'utf-8')
客户端代码示例
python
import socket
#1.创建套接字对象(socket.AF_INET => IPv4,socket.SOCK_STREAM => TCP协议)
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#2.与服务器端建立连接
tcp_client_socket.connect(('127.0.0.1', 8080))
#3.发送消息(不能是文本类型,必须是字节流数据)
tcp_client_socket.send('hello world'.encode('utf-8'))
#4.接受消息(服务器端返回的数据,默认类型都是字节流类型,必须进行解码)
recv_data = tcp_client_socket.recv(1024).decode('utf-8')
print(recv_data)
#5.关闭套接字对象
tcp_client_socket.close()
TCP服务器端程序开发
1.开发TCP服务端程序开发步骤
① 创建服务端端套接字对象
② 绑定端口号
③ 设置监听
④ 等待接受客户端的连接请求
⑤ 接收数据
⑥ 发送数据
⑦ 关闭套接字
2.示例代码:
python
import socket
# 1. 创建TCP服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定本机IP和端口(要和客户端connect里的端口8080保持一致)
tcp_server_socket.bind(('127.0.0.1', 8080))
# 3. 设置监听,参数代表最大等待连接数
tcp_server_socket.listen(128)
print("服务端已启动,等待客户端连接...")
# 4. 等待接收客户端连接,accept阻塞等待
client_conn, client_addr = tcp_server_socket.accept()
print(f"客户端 {client_addr} 已接入")
# 5. 接收客户端发来的字节数据,解码成字符串
recv_data = client_conn.recv(1024).decode("utf-8")
print("收到客户端消息:", recv_data)
# 6. 给客户端回发响应,字符串转字节发送
client_conn.send("消息已收到".encode("utf-8"))
# 7. 关闭和当前客户端的连接
client_conn.close()
# 8. 关闭服务端主套接字,不再接收新连接
tcp_server_socket.close()
3.服务器端开发多客户版本
python
#导入模块
import socket
#1.创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#2.绑定ip和端口,其参数是一个元组类型的数据
tcp_server_socket.bind(('127.0.0.1', 8080))
#3.设置监听
tcp_server_socket.listen(128)
#4.等待客户端连接(阻塞)
while True:
new_socket,ip_port = tcp_server_socket.accept()
#5.接收客户端发送过来的消息
recv_data = new_socket.recv(1024).decode("utf-8")
print(f'{ip_port}客户端发送的消息:{recv_data}')
#6.处理请求,把处理结果返回客户端
content = '信息已经接受'.encode("utf-8")
new_socket.send(content)
#7.关闭套接字
new_socket.close()
- 创建服务端套接字对象:
socket.socket() - 绑定端口:
bind() - 设置监听:
listen(128) - 等待连接:
accept()(阻塞) - 接收数据:
recv().decode() - 发送响应:
send(编码后字节数据) - 关闭和当前客户端的连接:
new_socket.close()
TCP服务器端开发面向对象(单线程)
代码示例:
python
#导入模块
import socket
#创建一个webserver服务器端类
class WebServer(object):
#用于对象初始化
def __init__(self):
#创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定ip和端口号
tcp_server_socket.bind(('',8080))
#设置监听
tcp_server_socket.listen(128)
#把tcp_server_socket赋值给自身
self.tcp_server_socket = tcp_server_socket
#定义一个handle_client_request()
def handle_client_request(self,new_socket,ip_port):
#接收客户端请求
recv_data = new_socket.recv(1024).decode("utf-8")
print(f'{ip_port}客户端发送的消息:{recv_data}')
#返回消息给客户端
content = '已收到'.encode("utf-8")
new_socket.send(content)
#处理完成后,关闭新套接字对象
new_socket.close()
#定义一个start方法,用于启动WebServer,接受客户端
def start(self):
while True:
#等待客户端连接
new_socket,ip_port = self.tcp_server_socket.accept()
#定义一个方法,用于接收和发送消息
self.handle_client_request(new_socket,ip_port)
#def __str__(self): 输出对象的信息,必须返回字符串
# def __del__(self): 当销毁对象时会自动调用__del__魔术方法
#创建程序执行入口,实例化WebServer类生成对象
if __name__ == '__main__':
#实例化对象
ws = WebServer()
#调用自身方法,用于启动服务
ws.start()
代码讲解:
1. 类定义 class WebServer (object)
采用面向对象编程思想封装 TCP 服务端,将套接字创建、端口绑定、监听、客户端请求处理全部封装在类内部,结构清晰,便于功能扩展。
2. init 构造方法
程序实例化对象时自动执行。
- socket.socket (socket.AF_INET,socket.SOCK_STREAM):创建 IPv4、TCP 协议的服务端套接字;
- bind (('',8080)):绑定端口 8080。填写空字符串 '' 代表监听本机所有网卡 IP,局域网内其他设备均可访问;如果填写 127.0.0.1,则仅本机程序能够连接;
- listen (128):开启监听,参数 128 代表等待连接队列最大数量;
- 将套接字对象赋值给实例属性 self.tcp_server_socket,供类中其他方法调用。
3. handle_client_request(new_socket,ip_port)
负责处理单个客户端的通信请求。
- new_socket:专属套接字,专门用于和当前接入的客户端收发数据;
- ip_port:元组类型,保存客户端的 IP 地址与端口;
- recv (1024):接收客户端数据,一次最多读取 1024 字节,通过 decode ("utf-8") 将字节码转为字符串;
- 原有代码只实现打印客户端消息,缺少数据响应与套接字关闭逻辑。
4. start () 启动方法
- while True 无限循环,实现持续接收客户端连接;
- accept () 是阻塞方法,若无客户端发起连接,程序会暂停在此处等待;
- 成功建立连接后,调用 handle_client_request 处理本次客户端通信;
注意:当前为单线程串行模式,同一时间只能处理一个客户端,后续客户端需要排队等待。
TCP服务器端开发面向对象(多线程)--直接取消socket.close()
python
#导入模块
import socket
#创建一个webserver服务器端类
class WebServer(object):
#用于对象初始化
def __init__(self):
#创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定ip和端口号
tcp_server_socket.bind(('',8080))
#设置监听
tcp_server_socket.listen(128)
#把tcp_server_socket赋值给自身
self.tcp_server_socket = tcp_server_socket
#定义一个handle_client_request()
def handle_client_request(self,new_socket,ip_port):
#接收客户端请求
recv_data = new_socket.recv(1024).decode("utf-8")
print(f'{ip_port}客户端发送的消息:{recv_data}')
#返回消息给客户端
content = '已收到'.encode("utf-8")
new_socket.send(content)
#处理完成后,关闭新套接字对象
#new_socket.close()
#定义一个start方法,用于启动WebServer,接受客户端
def start(self):
while True:
#等待客户端连接
new_socket,ip_port = self.tcp_server_socket.accept()
#定义一个方法,用于接收和发送消息
self.handle_client_request(new_socket,ip_port)
#def __str__(self): 输出对象的信息,必须返回字符串
# def __del__(self): 当销毁对象时会自动调用__del__魔术方法
#创建程序执行入口,实例化WebServer类生成对象
if __name__ == '__main__':
#实例化对象
ws = WebServer()
#调用自身方法,用于启动服务
ws.start()