Chapter3_第一个asyncio应用程序
1、用图示展示通过套接字实现基本的C/S基本通信流程
一图胜千言。以下是一个简单的服务器端和客户端交互的图示,展示了它们之间的基本通信流程:
(即在Python中使用套接字进行基本的网络编程.)
+-------------------+ +-------------------+
| 客户端 (Client) | | 服务器 (Server) |
+-------------------+ +-------------------+
| |
| 创建套接字 (socket()) |
|----------------------------------------> |
| |
| |
| | 创建套接字 (socket())
| |<----------------------
| |
| | 绑定 (bind())
| |<----------------------
| |
| | 监听 (listen())
| |<----------------------
| |
| 连接 (connect()) |
|----------------------------------------> |
| |
| | 接受连接 (accept())
| |<----------------------
| |
| |
| 发送数据 (send()) |
|----------------------------------------> |
| |
| | 接收数据 (recv())
| |<----------------------
| |
| |
| |
| | 发送数据 (send())
| |---------------------->|
| |
| 接收数据 (recv()) |
|<---------------------------------------- |
| |
| |
| 关闭连接 (close()) |
|----------------------------------------> |
| |
| | 关闭连接 (close())
| |<----------------------
| |
交互流程详细说明
-
创建套接字:
- 服务器:使用
socket()函数创建服务器套接字。 - 客户端:使用
socket()函数创建客户端套接字。
- 服务器:使用
-
绑定和监听(服务器端):
- 服务器:使用
bind()函数将服务器套接字绑定到指定的IP地址和端口号。然后使用listen()函数开始监听连接请求。
- 服务器:使用
-
连接(客户端发起):
- 客户端:使用
connect()函数连接到服务器的IP地址和端口号。
- 客户端:使用
-
接受连接(服务器端):
- 服务器:使用
accept()函数接受客户端的连接请求。此操作会返回一个新的套接字对象,用于与客户端通信。
- 服务器:使用
-
数据传输:
- 客户端:使用
send()函数发送数据到服务器。 - 服务器:使用
recv()函数接收客户端发送的数据。 - 服务器:使用
send()函数发送数据到客户端。 - 客户端:使用
recv()函数接收服务器发送的数据。
- 客户端:使用
-
关闭连接:
- 客户端:通信完成后,使用
close()函数关闭客户端套接字。 - 服务器:通信完成后,使用
close()函数关闭与客户端通信的套接字。服务器通常继续监听新的连接请求。
- 客户端:通信完成后,使用
2、如何理解套接字的概念、和用法
套接字的概念和作用
概念: 套接字(Socket)是网络编程中用于描述IP地址和端口的接口。它是网络通信的端点,通过它应用程序可以在网络上进行数据的发送和接收。
作用:
- 数据传输:套接字允许不同计算机之间进行数据交换。
- 网络编程:是网络通信的基础,通过套接字,开发者可以编写客户端和服务器程序。
- 跨平台通信:套接字提供一种标准化的通信方式,能够在不同操作系统之间进行数据传输。
通俗解释
可以把套接字想象成一个电话插孔,连接在一条网络“电话线”上。一个应用程序可以通过插入一个“电话插头”(创建一个套接字),
然后拨打一个号码(连接到一个IP地址和端口),与另一端的应用程序进行通信。服务器端相当于接听电话的一方,而客户端相当于拨打电话的一方。
Python代码举例
服务器端代码
import socket # 创建一个套接字对象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定套接字到本地IP地址和端口 server_socket.bind(('localhost', 12345)) # 开始监听,允许最多1个连接 server_socket.listen(1) print("服务器已启动,等待连接...") # 接受连接 client_socket, client_address = server_socket.accept() print(f"连接已建立:{client_address}") # 接收客户端发送的数据 data = client_socket.recv(1024).decode() # 这里写成client_socket.并没有错,后面追问会详细解释。 print(f"收到的数据:{data}") # 发送数据到客户端 client_socket.send("Hello from the server!".encode()) # 关闭套接字 client_socket.close() server_socket.close()
客户端代码
import socket # 创建一个套接字对象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接到服务器 client_socket.connect(('localhost', 12345)) # 发送数据到服务器 client_socket.send("Hello from the client!".encode()) # 接收服务器发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}") # 关闭套接字 client_socket.close()
服务器和客户端的套接字说明
-
服务器套接字:
- 创建和绑定:服务器套接字通过
socket()创建,并使用bind()将其绑定到指定的IP地址和端口号。 - 监听:通过
listen()开始监听连接请求。 - 接受连接:使用
accept()接受客户端的连接请求,返回一个新的套接字对象用于与客户端通信。 - 通信:使用返回的客户端套接字对象进行数据收发。
- 关闭:完成通信后,关闭客户端套接字和服务器套接字。
- 创建和绑定:服务器套接字通过
-
客户端套接字:
- 创建:客户端套接字通过
socket()创建。 - 连接:使用
connect()连接到服务器的IP地址和端口号。 - 通信:连接成功后,通过套接字进行数据发送和接收。
- 关闭:通信完成后,关闭套接字。
- 创建:客户端套接字通过
3、追问2:这一段代码是不是写错了。
# 接收客户端发送的数据
data = client_socket.recv(1024).decode() print(f"收到的数据:{data}")
我理解应该是: # 接收客户端发送的数据
data = server_socket.recv(1024).decode() print(f"收到的数据:{data}")
你提到的代码部分其实没有错误。让我解释一下原因,并重述一下代码的背景和正确用法。
在服务器-客户端模型中,服务器和客户端各自有各自的套接字对象。服务器套接字负责监听连接请求,而一旦连接被接受,会生成一个新的套接字对象用于与客户端通信。客户端的套接字对象用于与服务器进行通信。
以下是代码的正确结构和解释:
服务器端代码
import socket # 创建一个套接字对象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定套接字到本地IP地址和端口 server_socket.bind(('localhost', 12345)) # 开始监听,允许最多1个连接 server_socket.listen(1) print("服务器已启动,等待连接...") # 接受连接 client_socket, client_address = server_socket.accept() print(f"连接已建立:{client_address}") # 接收客户端发送的数据 data = client_socket.recv(1024).decode() # 使用client_socket接收数据 print(f"收到的数据:{data}") # 发送数据到客户端 client_socket.send("Hello from the server!".encode()) # 关闭套接字 client_socket.close() server_socket.close()
客户端代码
import socket # 创建一个套接字对象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接到服务器 client_socket.connect(('localhost', 12345)) # 发送数据到服务器 client_socket.send("Hello from the client!".encode()) # 接收服务器发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}") # 关闭套接字 client_socket.close()
关键点解释:
-
服务器端:
server_socket用于监听新的连接请求。- 一旦有客户端连接,
server_socket.accept()会返回一个新的套接字对象client_socket,这个新套接字用于与客户端通信。 - 因此,服务器在接收客户端发送的数据时,应该使用
client_socket.recv(1024),而不是server_socket.recv(1024)。
-
客户端:
- 客户端使用
client_socket与服务器通信。 - 发送数据时,使用
client_socket.send(data)。 - 接收数据时,使用
client_socket.recv(1024)。
- 客户端使用
结论:
你的原代码是正确的:
# 接收客户端发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}")
使用的是client_socket,这是在accept()之后专门用于与客户端进行通信的套接字,而不是用于监听连接请求的server_socket。
4、追问:为什么一旦有客户端连接,server_socket.accept()会返回一个新的套接字对象client_socket,这个新套接字用于与客户端通信。?? 难道不能使用原来的服务器套接字对象?
另外新的套接字对象为什么要命名为client_socket?而不是server_client? 这样不是更符合直觉吗?
这是因为在套接字编程中,监听和通信的职责需要分离,以确保服务器能够同时处理多个客户端的连接请求。让我们详细解释一下为什么会有这种设计以及命名的原因。
服务器套接字的职责
-
监听套接字 (
server_socket):server_socket的主要职责是监听来自客户端的连接请求。- 它通过调用
listen()方法进入监听状态,并通过accept()方法接受新的连接。 server_socket不直接用于数据传输。它的作用是等待并接受连接请求。
-
通信套接字 (
client_socket):- 一旦
server_socket接受了一个连接,它会返回一个新的套接字对象,即client_socket,用于与这个特定的客户端进行通信。 client_socket专门用于处理与客户端的数据传输,这样服务器可以继续监听新的连接请求。
- 一旦
为什么不使用原来的服务器套接字 (server_socket)?
- 分离监听和通信:如果使用
server_socket进行数据传输,服务器将无法继续监听其他客户端的连接请求。通过创建新的通信套接字 (client_socket),服务器可以同时处理多个客户端的连接。 - 并发处理:服务器通常需要能够同时处理多个客户端的连接。使用独立的通信套接字 (
client_socket) 可以让每个连接独立运行,便于在多线程或异步环境中进行并发处理。
命名为 client_socket 的原因
- 表意清晰:命名为
client_socket是为了表明这个套接字对象专门用于与特定客户端通信。它有助于区分监听套接字 (server_socket) 和通信套接字。 - 惯例:这种命名方式是一种编程惯例,旨在使代码更易于理解和维护。尽管
server_client也可以是一个有效的名称,但client_socket更加简洁并且在网络编程中广泛使用。
示例代码复述
以下是一个更详细的示例,说明如何在服务器端和客户端进行套接字通信:
服务器端代码
import socket # 创建一个服务器套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定服务器套接字到本地IP地址和端口 server_socket.bind(('localhost', 12345)) # 开始监听连接请求,允许最多5个待处理连接 server_socket.listen(5) print("服务器已启动,等待连接...") while True: # 接受客户端连接请求 client_socket, client_address = server_socket.accept() print(f"连接已建立:{client_address}") # 接收客户端发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}") # 发送数据到客户端 client_socket.send("Hello from the server!".encode()) # 关闭客户端套接字 client_socket.close()
客户端代码
import socket # 创建一个客户端套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接到服务器 client_socket.connect(('localhost', 12345)) # 发送数据到服务器 client_socket.send("Hello from the client!".encode()) # 接收服务器发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}") # 关闭客户端套接字 client_socket.close()
总结
- 分离职责:服务器套接字 (
server_socket) 用于监听连接请求,通信套接字 (client_socket) 用于与客户端进行数据传输。 - 并发处理:通过创建新的通信套接字,服务器能够同时处理多个客户端的连接请求。
- 命名惯例:命名为
client_socket是为了清晰表达其用途,即与特定客户端的通信。
import socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = ('127.0.0.1', 8000) server_socket.bind(server_address) server_socket.listen()
在你的代码中,setsockopt 方法的使用有助于设置套接字选项,从而优化其行为。
在这种情况下,你使用了 socket.SO_REUSEADDR 选项,这是一个常见且有用的套接字选项。让我详细解释一下这段代码的作用:
逐行解释
-
创建套接字对象:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
这行代码创建了一个新的套接字对象。
socket.AF_INET 表示使用IPv4地址族,
socket.SOCK_STREAM 表示这是一个TCP套接字。
2.设置套接字选项:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
这行代码设置套接字选项,使得在服务器关闭后,可以立即重新使用相同的地址和端口。具体来说:
socket.SOL_SOCKET:表示正在设置套接字级别的选项。socket.SO_REUSEADDR:允许重用本地地址。1:启用该选项。
作用:通常,当一个套接字被关闭后,它的端口可能会被标记为 "已占用" 一段时间(这个状态称为 TIME_WAIT)。启用
SO_REUSEADDR选项可以让你的服务器在关闭后立即重新绑定到同一个地址和端口,而不会遇到 "地址已在使用" 的错误。 -
绑定地址和端口:
server_address = ('127.0.0.1', 8000) server_socket.bind(server_address)
-
这行代码将服务器绑定到本地地址
'127.0.0.1'和端口8000。127.0.0.1是回环地址,表示服务器只能接受来自本地计算机的连接。 -
开始监听:
server_socket.listen()
-
这行代码使服务器进入监听状态,准备接受客户端的连接请求。默认情况下,
listen()的参数(待处理连接的最大数量)为 0,意味着使用系统默认值。
setsockopt 的使用场景
使用 setsockopt 方法设置 SO_REUSEADDR 选项的场景包括但不限于:
- 开发和测试:在开发或测试过程中,服务器需要频繁重启。在这种情况下,
SO_REUSEADDR可以避免每次重启服务器时出现 "地址已在使用" 的错误。 - 高可用性:在某些高可用性系统中,当主服务器故障后,备用服务器需要快速接管相同的地址和端口。
完整示例
以下是一个完整的服务器代码示例,展示了如何处理客户端连接和数据通信:
import socket # 创建服务器套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定地址和端口 server_address = ('127.0.0.1', 8000) server_socket.bind(server_address) # 开始监听连接请求 server_socket.listen() print("服务器已启动,等待连接...") while True: # 接受客户端连接 client_socket, client_address = server_socket.accept() print(f"连接已建立:{client_address}") # 接收客户端发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}") # 发送数据到客户端 client_socket.send("Hello from the server!".encode()) # 关闭客户端套接字 client_socket.close()
客户端代码示例
以下是对应的客户端代码示例:
import socket # 创建客户端套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接到服务器 server_ip = '127.0.0.1' server_port = 8000 client_socket.connect((server_ip, server_port)) # 发送数据到服务器 client_socket.send("Hello from the client!".encode()) # 接收服务器发送的数据 data = client_socket.recv(1024).decode() print(f"收到的数据:{data}") # 关闭客户端套接字 client_socket.close()
网络配置注意事项
-
防火墙设置:
- 确保服务器电脑的防火墙允许通过端口
8000的入站和出站流量。 - 在Windows上,可以通过高级防火墙设置来添加规则。
- 在Linux上,可以使用
iptables或ufw来配置防火墙规则。
- 确保服务器电脑的防火墙允许通过端口
-
同一局域网内:
- 确保两台电脑在同一个局域网内,否则它们无法通过局域网IP地址进行通信。
- 如果两台电脑不在同一个局域网内,需要通过公网IP地址进行通信,并在路由器上进行端口转发配置,这超出了局域网内简单通信的范畴。
验证连接
-
运行服务器端代码: 在服务器电脑上运行服务器端代码,确保服务器正在监听。
-
运行客户端代码: 在客户端电脑上运行客户端代码,连接到服务器的实际IP地址,测试通信是否成功。
通过这些步骤,你可以在两台不同的电脑上成功模拟服务器和客户端的网络通信。
6、如下代码的理解。 [from example3-7.py]
import selectors import socket from selectors import SelectorKey from typing import List, Tuple selector = selectors.DefaultSelector() server_socket = socket.socket() server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = ('127.0.0.1', 8000) server_socket.setblocking(False) server_socket.bind(server_address) server_socket.listen() selector.register(server_socket, selectors.EVENT_READ) while True: events: List[Tuple[SelectorKey, int]] = selector.select(timeout=1) #A if len(events) == 0: #B print('No events, waiting a bit more!') for event, _ in events: event_socket = event.fileobj #C if event_socket == server_socket: #D connection, address = server_socket.accept() connection.setblocking(False) print(f"I got a connection from {address}") selector.register(connection, selectors.EVENT_READ) #E else: data = event_socket.recv(1024) #F print(f"I got some data: {data}") event_socket.send(data)
下面逐段解释:
import selectors import socket from selectors import SelectorKey from typing import List, Tuple
selectors:用于管理和监视多个socket对象的I/O事件。socket:用于创建和操作网络连接。SelectorKey:类型提示,表示selectors模块返回的键。List和Tuple:类型提示,用于指定列表和元组的类型。
初始化选择器和服务器套接字
selector = selectors.DefaultSelector()
- 创建一个默认的选择器对象,用于监视多个socket对象的I/O事件。
server_socket = socket.socket()
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- 创建一个TCP套接字。
- 设置套接字选项,允许重用本地地址,以便服务器在关闭后可以立即重新绑定到相同的地址和端口。
server_address = ('127.0.0.1', 8000) server_socket.setblocking(False) server_socket.bind(server_address) server_socket.listen()
- 将服务器绑定到
127.0.0.1:8000,即本地回环地址上的端口8000。 - 将套接字设置为非阻塞模式。
- 开始监听传入连接。
selector.register(server_socket, selectors.EVENT_READ)
- 注册服务器套接字,将其加入选择器进行监视,监视读事件。
while True: events: List[Tuple[SelectorKey, int]] = selector.select(timeout=1) # A # events里面到底是啥,这对于代码的理解至关重要,后面追问会详细解释
- 开始一个无限循环来处理I/O事件。
- 使用
selector.select(timeout=1)方法等待I/O事件,超时时间为1秒。返回一个事件列表。
if len(events) == 0: # B print('No events, waiting a bit more!')
- 如果没有事件发生,打印一条消息并继续等待。
for event, _ in events: event_socket = event.fileobj # C
- 遍历所有发生的事件。
event.fileobj是发生事件的套接字对象。
if event_socket == server_socket: # D connection, address = server_socket.accept() connection.setblocking(False) print(f"I got a connection from {address}") selector.register(connection, selectors.EVENT_READ) # E
- 检查事件套接字是否为服务器套接字。
- 如果是服务器套接字,调用
accept方法接受新的连接,并将其设置为非阻塞模式。 - 打印新连接的地址。
- 将新连接注册到选择器中,监视读事件。
else: data = event_socket.recv(1024) # F print(f"I got some data: {data}") event_socket.send(data)
- 如果事件套接字不是服务器套接字,表示这是一个客户端连接的读事件。
- 从客户端连接中接收数据。
- 打印接收到的数据。
- 将接收到的数据回送给客户端(回显)。
代码总结
这个服务器使用 selectors 模块实现了一个简单的非阻塞回显服务器。它能够同时处理多个客户端连接,而不会因为一个阻塞的I/O操作而阻塞整个服务器。
每当有新的客户端连接时,服务器接受连接并将其注册到选择器中进行监视。一旦有数据从客户端发送过来,服务器读取数据并将其回显给客户端。
7、events里面内容的理解
为了加深理解,让我们通过一些具体的例子来看 events 列表的内容。首先,回顾一下关键代码片段:
events: List[Tuple[SelectorKey, int]] = selector.select(timeout=1) # A if len(events) == 0: # B print('No events, waiting a bit more!') for event, _ in events: event_socket = event.fileobj # C if event_socket == server_socket: # D connection, address = server_socket.accept() connection.setblocking(False) print(f"I got a connection from {address}") selector.register(connection, selectors.EVENT_READ) # E else: data = event_socket.recv(1024) # F print(f"I got some data: {data}") event_socket.send(data)
示例:events 列表内容
示例1:没有事件发生
在没有任何客户端连接或数据传输的情况下,events 列表为空。
events = []
在这种情况下,程序会输出:
No events, waiting a bit more!
示例2:有一个新的客户端连接请求
假设有一个新的客户端尝试连接服务器,selector.select(timeout=1) 会返回一个事件,表示服务器套接字准备好了接受新的连接。
events = [(SelectorKey(fileobj=server_socket, fd=3, events=1, data=None), 1)]
SelectorKey包含了以下信息:fileobj=server_socket:发生事件的套接字对象。fd=3:文件描述符(因平台而异)。events=1:事件类型(读事件)。data=None:附加的用户数据(如果有的话)。
程序会处理这个事件,调用 accept 方法接受新的连接并注册该连接:
I got a connection from ('127.0.0.1', 54321)
示例3:客户端发送数据
假设有一个客户端已经连接并发送了一些数据,selector.select(timeout=1) 会返回一个事件,表示该客户端套接字上有数据可读。
events = [(SelectorKey(fileobj=client_socket, fd=4, events=1, data=None), 1)]
fileobj=client_socket:发生事件的套接字对象。fd=4:文件描述符(因平台而异)。events=1:事件类型(读事件)。data=None:附加的用户数据(如果有的话)。
程序会处理这个事件,调用 recv 方法读取数据并回显:
I got some data: b'Hello, server!'
小结
events是一个包含元组的列表,每个元组表示一个发生的I/O事件。- 每个元组包含一个
SelectorKey对象和一个事件掩码(整数)。 SelectorKey对象提供了发生事件的套接字对象及其相关信息。
解释每个关键点
- A:
selector.select(timeout=1)等待最多1秒钟,返回发生事件的列表。 - B:如果没有事件发生,
events列表为空,程序打印等待消息。 - C:遍历每个事件,获取发生事件的套接字对象。
- D:检查是否是服务器套接字的事件。如果是,接受新的连接。
- E:将新连接设置为非阻塞模式并注册到选择器。
- F:如果是客户端套接字的事件,读取并回显数据。
通过这些示例,便能更好地理解 events 列表的内容以及程序如何处理不同的I/O事件。
8、如何理解如下代码 [from example3-8.py]
import asyncio import socket from asyncio import AbstractEventLoop async def echo(connection: socket, loop: AbstractEventLoop) -> None: while data := await loop.sock_recv(connection, 1024): await loop.sock_sendall(connection, data) async def listen_for_connection(server_socket: socket, loop: AbstractEventLoop): while True: connection, address = await loop.sock_accept(server_socket) connection.setblocking(False) print(f"Got a connection from {address}") asyncio.create_task(echo(connection, loop)) async def main(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = ('127.0.0.1', 8000) server_socket.setblocking(False) server_socket.bind(server_address) server_socket.listen() await listen_for_connection(server_socket, asyncio.get_event_loop()) asyncio.run(main())
这段代码使用 asyncio 和 socket 模块实现了一个异步的回显服务器。下面我会逐行解释这段代码。
导入模块
import asyncio import socket from asyncio import AbstractEventLoop
asyncio:Python的异步I/O框架,用于编写异步程序。socket:用于创建和操作网络连接。AbstractEventLoop:asyncio模块中的事件循环基类。
定义异步回显函数
async def echo(connection: socket, loop: AbstractEventLoop) -> None: while data := await loop.sock_recv(connection, 1024): await loop.sock_sendall(connection, data)
echo是一个异步函数,负责处理客户端连接。connection:客户端连接的套接字对象。loop:事件循环对象。while data := await loop.sock_recv(connection, 1024):使用事件循环异步接收数据,每次最多接收1024字节。loop.sock_recv:asyncio提供的非阻塞recv方法。
await loop.sock_sendall(connection, data):将接收到的数据回送给客户端。loop.sock_sendall:asyncio提供的非阻塞sendall方法。
定义异步监听函数
async def listen_for_connection(server_socket: socket, loop: AbstractEventLoop): while True: connection, address = await loop.sock_accept(server_socket) connection.setblocking(False) print(f"Got a connection from {address}") asyncio.create_task(echo(connection, loop))
listen_for_connection是一个异步函数,负责监听新的客户端连接。server_socket:服务器套接字对象。loop:事件循环对象。while True:无限循环,持续监听新的连接。connection, address = await loop.sock_accept(server_socket):使用事件循环异步接受新的连接。loop.sock_accept:asyncio提供的非阻塞accept方法。
connection.setblocking(False):将新连接设置为非阻塞模式。print(f"Got a connection from {address}"):打印新连接的地址。asyncio.create_task(echo(connection, loop)):创建一个新的任务处理客户端连接。
定义主函数
async def main(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = ('127.0.0.1', 8000) server_socket.setblocking(False) server_socket.bind(server_address) server_socket.listen() await listen_for_connection(server_socket, asyncio.get_event_loop())
main是主异步函数,负责初始化服务器套接字并启动监听。server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM):创建一个TCP套接字。server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1):设置套接字选项,允许重用本地地址。server_address = ('127.0.0.1', 8000):服务器绑定到本地地址127.0.0.1的端口8000。server_socket.setblocking(False):将服务器套接字设置为非阻塞模式。server_socket.bind(server_address):将套接字绑定到指定地址和端口。server_socket.listen():开始监听传入连接。await listen_for_connection(server_socket, asyncio.get_event_loop()):启动监听协程,等待客户端连接。
运行主函数
asyncio.run(main())
asyncio.run(main()):运行主协程main,启动异步事件循环。
小结
- 导入必要的模块和类。
- 定义
echo协程:处理每个客户端连接,接收并回显数据。 - 定义
listen_for_connection协程:持续监听新的客户端连接并启动echo协程处理每个连接。 - 定义
main协程:初始化服务器套接字,绑定地址和端口,启动监听协程。 - 运行
main协程:启动异步事件循环。
通过这种方式,服务器能够异步处理多个客户端连接,而不会因为一个阻塞操作而影响其他连接。
9、 这里会话的概念如何理解。最后会话是不是和session一个概念?
ClientSession:aiohttp 中的一个类,表示一个"会话",用于管理和重用连接。
在网络通信中,会话(session)指的是客户端和服务器之间的一系列交互过程,从建立连接开始,到断开连接为止。
在这段时间内,双方可以进行多次数据交换。会话的主要目的是维持状态信息,以便在多次请求之间保持连续性。
会话的作用
- 管理状态:会话允许在多次请求之间保存状态信息。比如,用户登录后,会话可以保存用户的登录状态,在后续请求中识别该用户。
- 重用连接:会话可以重用底层的网络连接,从而减少连接建立和断开带来的开销,提高性能。
- 配置管理:会话可以集中管理请求的配置,如头信息(headers)、cookies、认证信息等。
通俗解释
你可以把会话想象成一次旅行。旅行的开始是你登上飞机(建立连接),结束是你下飞机(断开连接)。在这段时间内,你可以在不同的城市之间飞行(多次请求),而你的行李、机票信息、护照等(状态信息)都由航空公司帮你管理。
举例说明
假设你在网上购物:
- 开始会话:你打开购物网站,登录账户。
- 浏览商品:你浏览多个商品页面,每次点击都会发送请求。
- 添加购物车:你把喜欢的商品添加到购物车。
- 结账:你去结账页面,填写地址和支付信息。
- 结束会话:你下单并登出账户。
在这个过程中,会话帮助你保存了登录状态、购物车信息等,避免你每次请求都需要重新登录或重新添加购物车。
示例代码
下面是一个简单的示例,展示如何使用 aiohttp 的 ClientSession 进行多次请求,并保持会话状态:
import asyncio import aiohttp async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: url1 = 'https://jsonplaceholder.typicode.com/posts/1' url2 = 'https://jsonplaceholder.typicode.com/posts/2' # 第一次请求 response1 = await fetch(session, url1) print(f'URL1 Response: {response1[:100]}...') # 打印前100个字符 # 第二次请求 response2 = await fetch(session, url2) print(f'URL2 Response: {response2[:100]}...') # 打印前100个字符 # 运行主程序 asyncio.run(main())
会话与Session
在网络编程和Web开发中,“会话”(session)和“Session”通常指的是同一个概念,即在客户端和服务器之间维持状态的机制。具体到 aiohttp 中的 ClientSession:
ClientSession表示一个会话对象,管理与服务器的多个请求,并保持状态(如Cookies、连接池等)。- 在Web开发中,如Flask或Django,
Session对象用于在用户和服务器之间保持会话状态,通常通过Cookies实现。
总结
- 会话:表示客户端和服务器之间的一系列交互过程,保存状态信息。
- 作用:管理状态、重用连接、配置管理。
- 通俗解释:类似一次旅行,管理你的行李和信息。
- 举例说明:如网上购物的过程。
- 会话与Session:是同一个概念,在不同上下文中使用。
ClientSession管理HTTP请求,会话保持状态。

浙公网安备 33010602011771号