Skip to content

Python 网络编程

Python 为网络编程提供了全面的支持,范围涵盖从低级套接字(socket)访问到常见应用协议的高级库。

  • 低级访问:socket 模块提供对 BSD 套接字(socket)接口的访问,允许使用 TCP(面向连接)和 UDP(无连接)协议直接实现客户端和服务器。
  • 高级访问:标准库提供了诸如 HTTP (http.client, urllib.request)、FTP (ftplib)、SMTP (smtplib)、POP3 (poplib)、IMAP (imaplib) 等协议的抽象。第三方库,例如 requests,进一步简化了 HTTP 交互。

本章重点介绍使用 socket 模块进行低级套接字编程的基础知识。

套接字(Socket)是网络上运行的两个程序之间双向通信链接的端点。套接字绑定到一个端口号(port number)上,以便 TCP 层能够识别数据要发送到的应用程序。

套接字促进了同一进程内、同一机器上进程间(使用 Unix 域套接字或环回接口),或互联网上不同机器间进程之间的通信。

关键套接字术语:

术语描述
域(或族)指定协议族。常见值:
AF_INET: IPv4 协议。
AF_INET6: IPv6 协议。
AF_UNIX: Unix 域套接字(用于本地进程间通信)。
类型指定通信语义。常见值:
SOCK_STREAM: 面向连接的可靠流(TCP)。
SOCK_DGRAM: 无连接的不可靠数据报(UDP)。
协议通常为 0,允许系统根据域和类型选择适当的协议(例如,SOCK_STREAM 对应 IPPROTO_TCP,SOCK_DGRAM 对应 IPPROTO_UDP)。
主机名标识一个网络接口。可以是域名(例如 ‘www.example.com’)、IPv4 地址字符串(例如 ‘192.168.1.1’)、IPv6 地址字符串、代表 INADDR_ANY 的空字符串 ('')(监听所有可用接口),或用于环回接口的 'localhost' / '127.0.0.1'。
端口一个数字 (0-65535),用于标识主机上的特定进程或服务。端口 0-1023 是众所周知端口,通常需要 root/管理员权限。

Python 中套接字编程的核心是 socket 模块。

使用 socket.socket() 创建一个新的套接字对象。

s = socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0)

常见参数:

  • family: 套接字域(例如,IPv4 使用 socket.AF_INET)。
  • type: 套接字类型(例如,TCP 使用 socket.SOCK_STREAM,UDP 使用 socket.SOCK_DGRAM)。
  • proto: 协议号(通常为 0)。

不再需要套接字时应该将其关闭。with 语句确保套接字自动关闭,类似于文件处理。

import socket
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# Use the socket 's' here
print("Socket created successfully.")
# Socket 's' will be closed automatically upon exiting the 'with' block
except socket.error as err:
print(f"Socket creation failed with error: {err}")
方法描述
s.bind((host, port))将套接字绑定到特定的网络接口 (host) 和 port 端口号上。
s.listen(backlog)使服务器能够接受连接。backlog 指定排队连接的最大数量。
s.accept()等待传入的客户端连接。返回一个元组 (conn, addr),其中 conn 是用于与客户端通信的新的套接字对象,addr 是客户端的地址元组。
方法描述
s.connect((host, port))将客户端套接字连接到位于 host 和 port 的服务器。
方法描述
s.send(bytes)发送 TCP 数据。数据必须是 bytes 类型。返回发送的字节数。
s.recv(bufsize)接收 TCP 数据。bufsize 是单次接收的最大数据量(字节)。返回接收到的数据作为 bytes。
s.sendall(bytes)发送 bytes 中的所有 TCP 数据。处理潜在的短发送问题。
s.sendto(bytes, address)将 UDP 数据报 (bytes) 发送到指定的地址元组 (host, port)。
s.recvfrom(bufsize)接收 UDP 数据报。返回一个元组 (bytes, address),其中 bytes 是数据,address 是发送方的地址。
s.close()关闭套接字。with 语句会自动处理。
socket.gethostname()返回本地机器的标准主机名。

**重要提示:**在 Python 3 中,套接字方法 send()、recv()、sendto() 和 recvfrom() 操作的是 bytes 对象,而不是字符串。发送前必须将字符串编码为字节,接收后必须将字节解码为字符串。

此服务器监听特定端口,接受客户端连接,读取来自客户端的数据,然后将相同数据发送回去(回显)。

server.py
#!/usr/bin/env python3
import socket
HOST = '127.0.0.1' # Standard loopback interface address (localhost)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)
def run_server():
# Use 'with' statement for automatic socket closing
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind((HOST, PORT))
s.listen()
print(f"Server listening on {HOST}:{PORT}")
# Wait for a connection
conn, addr = s.accept() # Blocking call
# Use 'with' for the client connection socket as well
with conn:
print(f"Connected by {addr}")
while True:
# Receive data (up to 1024 bytes)
data = conn.recv(1024)
if not data:
# If recv returns empty bytes, client closed connection
print(f"Client {addr} disconnected.")
break
# Decode received bytes to string for printing
print(f"Received from {addr}: {data.decode('utf-8')}")
# Echo data back to client (encode string to bytes)
conn.sendall(data)
print(f"Echoed back to {addr}")
except socket.error as e:
print(f"Socket error: {e}")
except KeyboardInterrupt:
print("\nServer shutting down.")
if __name__ == "__main__":
run_server()

此客户端连接到服务器,发送一条消息,然后打印回显响应。

client.py
#!/usr/bin/env python3
import socket
HOST = '127.0.0.1' # The server's hostname or IP address
PORT = 65432 # The port used by the server
def run_client(message):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
print(f"Connected to server {HOST}:{PORT}")
# Encode the message to bytes before sending
message_bytes = message.encode('utf-8')
s.sendall(message_bytes)
print(f"Sent: {message}")
# Receive the echoed data
data = s.recv(1024)
# Decode the received bytes to string
received_message = data.decode('utf-8')
print(f"Received echo: {received_message}")
except socket.error as e:
print(f"Socket error: {e}")
except ConnectionRefusedError:
print(f"Connection refused. Is the server running on {HOST}:{PORT}?")
if __name__ == "__main__":
message_to_send = "Hello, Server!"
run_client(message_to_send)
  1. 打开一个终端并运行服务器:python server.py
  2. 打开另一个终端并运行客户端:python client.py

服务器输出:

Server listening on 127.0.0.1:65432
Connected by ('127.0.0.1', <client_port>)
Received from ('172.17.0.1', <client_port>): Hello, Server!
Echoed back to ('172.17.0.1', <client_port>)
Client ('172.17.0.1', <client_port>) disconnected.

客户端输出:

Connected to server 127.0.0.1:65432
Sent: Hello, Server!
Received echo: Hello, Server!

Python 标准库包含了常见互联网协议的模块:

协议主要用途常见端口Python 模块
HTTP/HTTPS网页访问80, 443urllib.request, http.client (较低级)。requests (第三方,推荐)
FTP文件传输21ftplib
SMTP发送电子邮件25, 587, 465smtplib
POP3获取电子邮件110, 995poplib
IMAP4获取电子邮件143, 993imaplib
NNTPUsenet 新闻119, 563nntplib
Telnet远程登录23telnetlib
XML-RPC远程过程调用变化xmlrpc.client, xmlrpc.server

对于 Web 交互 (HTTP/HTTPS),第三方库 requests 被广泛使用,并且通常因其更简单的 API 而比 urllib 更受欢迎。

套接字编程是一个深入的话题。请查阅以下资源: