目录
一. socket 基础
二. 创建一个 Socket
三. 服务器端
TCP 服务器示例:
四.客户端
TCP 客户端示例:
五. UDP 服务器和客户端
5.1 UDP 服务器示例:
5.2 UDP 客户端示例:
5.3 UDP非阻塞监听
5.3.1 服务端代码:
5.3.2 客户端代码:
5.3.3 注意事项
六. 处理异常
七.处理多个请求
7.1 UDP 服务器端
7.2 UDP 客户端示例
7.3 说明
Python 的 socket 模块提供了网络编程的基础,可以使用它创建服务器和客户端应用程序。以下是对 socket 编程的详细介绍,包括常见操作和示例。
一. socket 基础
socket 是网络通信的端点。Python 的 socket 模块提供了创建、连接和操作 socket 的方法。
二. 创建一个 Socket
可以使用 socket.socket()
函数创建一个 socket 对象。通常指定两个参数:地址族和套接字类型。
地址族:
AF_INET
:IPv4AF_INET6
:IPv6AF_UNIX
:本地通信套接字类型:
SOCK_STREAM
:TCP(面向连接的套接字)SOCK_DGRAM
:UDP(数据报套接字)
import socket# 创建一个TCP/IP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
三. 服务器端
服务器通常执行以下步骤:
- 绑定(bind):将套接字绑定到一个特定的地址和端口,使其可以监听来自客户端的连接请求。
- 监听(listen):使套接字进入监听模式,准备接受传入的连接请求。
- 接受(accept):一旦有客户端尝试连接,服务器接受连接请求并建立通信。
- 接收和发送数据(recv 和 send):服务器与客户端之间进行数据交换。
- 关闭连接(close):当通信结束时,关闭套接字以释放资源。
TCP 服务器示例:
import socket# 创建一个TCP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定套接字到地址和端口
server_address = ('localhost', 65432)
server_socket.bind(server_address)# 监听连接请求
server_socket.listen(1)print("等待连接...")
# 接受连接请求
connection, client_address = server_socket.accept()
print(f"连接来自 {client_address}")try:while True:data = connection.recv(1024)if data:print(f"接收到的数据: {data.decode()}")connection.sendall(data)else:break
finally:connection.close()server_socket.close()
四.客户端
客户端通常执行以下步骤:
- 创建套接字(socket):与服务器端类似,首先创建一个套接字对象。
- 连接(connect):使用服务器的地址和端口连接到服务器。
- 接收和发送数据(recv 和 send):与服务器进行数据通信。
- 关闭连接(close):通信结束后,关闭套接字。
TCP 客户端示例:
import socket# 创建一个TCP/IP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 连接到服务器
server_address = ('localhost', 65432)
client_socket.connect(server_address)try:message = "Hello, Server!"print(f"发送消息: {message}")client_socket.sendall(message.encode())data = client_socket.recv(1024)print(f"接收到的数据: {data.decode()}")
finally:client_socket.close()
五. UDP 服务器和客户端
UDP 是无连接的,因此不需要监听和接受连接。
5.1 UDP 服务器示例:
import socketserver_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('localhost', 65432)
server_socket.bind(server_address)print("等待接收数据...")
while True:data, address = server_socket.recvfrom(1024)print(f"接收到的数据来自 {address}: {data.decode()}")server_socket.sendto(data, address)
5.2 UDP 客户端示例:
import socketclient_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('localhost', 65432)message = "Hello, Server!"
client_socket.sendto(message.encode(), server_address)data, server = client_socket.recvfrom(1024)
print(f"接收到的数据: {data.decode()}")client_socket.close()
5.3 UDP非阻塞监听
在 UDP 套接字编程中,非阻塞模式允许套接字在没有数据到达时不阻塞,继续执行后续代码。可以通过设置套接字的非阻塞模式实现这一点。以下是如何在 Python 中实现 UDP 非阻塞监听的示例:
5.3.1 服务端代码:
import socket
import time# 创建一个UDP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 绑定套接字到地址和端口
server_address = ('localhost', 65432)
server_socket.bind(server_address)# 设置为非阻塞模式
server_socket.setblocking(False)print("等待接收数据...")while True:try:data, address = server_socket.recvfrom(1024)print(f"接收到的数据来自 {address}: {data.decode()}")server_socket.sendto(data, address)except BlockingIOError:# 如果没有数据到达,会抛出BlockingIOError异常,可以在这里进行其他处理print("没有数据到达,继续执行其他任务...")time.sleep(1) # 模拟其他任务的执行
5.3.2 客户端代码:
import socket# 创建一个UDP/IP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)server_address = ('localhost', 65432)
message = "Hello, Server!"try:print(f"发送消息: {message}")sent = client_socket.sendto(message.encode(), server_address)# 设置为非阻塞模式(仅用于演示,通常客户端不需要非阻塞模式)client_socket.setblocking(False)while True:try:data, server = client_socket.recvfrom(1024)print(f"接收到的数据: {data.decode()}")breakexcept BlockingIOError:# 如果没有数据到达,会抛出BlockingIOError异常,可以在这里进行其他处理print("等待服务器响应...")
finally:client_socket.close()
5.3.3 注意事项
- 异常处理:在非阻塞模式下,当没有数据可读时,
recvfrom
会抛出BlockingIOError
异常。需要捕获并处理这个异常。- 资源利用:在非阻塞模式下,需要确保在没有数据到达时不会无限循环占用 CPU 资源。可以使用
time.sleep()
函数或其他方式让出 CPU 时间片。- 其他处理:在没有数据到达时,可以在
except
块中处理其他任务,如日志记录、状态更新等。
通过这种方式,可以实现 UDP 套接字的非阻塞监听,确保程序在等待数据时不会阻塞,能够同时处理其他任务。
六. 处理异常
在网络编程中,处理异常是很重要的。可以使用 try
和 except
块来捕获和处理可能发生的异常。
import sockettry:sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.connect(('localhost', 65432))sock.sendall(b'Hello, World!')data = sock.recv(1024)print(f"接收到的数据: {data.decode()}")
except socket.error as e:print(f"Socket error: {e}")
finally:sock.close()
七.处理多个请求
在网络编程中,如果需要同时等待多个数据连接,可以使用 select
模块。select
模块提供了一种机制,可以监控多个文件描述符(包括套接字),等待其中任何一个变为可读、可写或发生异常。这样可以有效地管理多个客户端连接而不需要为每个连接创建一个独立的线程。以下是使用 select
模块在一个服务器上同时等待多个数据连接的示例。
- 创建并绑定一个服务器套接字。
- 将服务器套接字设置为非阻塞模式。
- 使用
select.select
方法监控多个套接字的状态。
7.1 UDP 服务器端
import socket
import select# 创建并绑定一个UDP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 65432))
server_socket.setblocking(False)# 初始化读、写、错误列表
inputs = [server_socket]
outputs = []
message_queues = {}print("服务器启动,等待连接...")while inputs:readable, writable, exceptional = select.select(inputs, outputs, inputs)for s in readable:if s is server_socket:# 处理客户端消息data, client_address = s.recvfrom(1024)if data:print(f"接收到的数据来自 {client_address}: {data.decode()}")if client_address not in message_queues:message_queues[client_address] = []message_queues[client_address].append(data)if s not in outputs:outputs.append(s)for s in writable:for client_address in message_queues:if message_queues[client_address]:next_msg = message_queues[client_address].pop(0)s.sendto(next_msg, client_address)if not message_queues[client_address]:del message_queues[client_address]outputs.remove(s)for s in exceptional:print(f"异常情况发生在 {s.getpeername()}")inputs.remove(s)if s in outputs:outputs.remove(s)s.close()
7.2 UDP 客户端示例
import socket# 创建一个UDP/IP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('localhost', 65432)try:message = "Hello, Server!"print(f"发送消息: {message}")sent = client_socket.sendto(message.encode(), server_address)while True:data, server = client_socket.recvfrom(1024)print(f"接收到的数据: {data.decode()}")break
finally:client_socket.close()
7.3 说明
服务器端:
- 创建一个 UDP 套接字并绑定到特定地址和端口。
- 将套接字设置为非阻塞模式。
- 使用
select.select
监听套接字的可读状态。- 接收到数据后,将其存储在消息队列中。
- 使用
select.select
监听套接字的可写状态,并将消息发送回客户端。
客户端:
- 创建一个 UDP 套接字并发送数据到服务器。
- 接收服务器的响应数据。
这种方法可以处理多个客户端请求,而无需为每个客户端创建单独的线程或进程。通过使用 select
模块,可以有效地管理和处理多个套接字的状态变化。