跳到主要内容

Python Socket编程

什么是 Socket 编程?

Socket(套接字)是网络编程中的一个核心概念,它提供了计算机网络中端点间通信的抽象。通过 Socket,不同计算机上运行的程序可以相互发送和接收数据,实现网络通信。

在 Python 中,socket 模块提供了对底层网络接口的访问,让我们能够创建不同类型的网络应用,如 Web 客户端、服务器、聊天程序等。

备注

Socket 编程是实现网络通信的基础,掌握它将帮助你理解许多高级网络协议和框架的工作原理。

Socket 的基本概念

在深入代码示例之前,让我们了解一些基础概念:

  1. IP 地址:用于在网络中标识一台计算机
  2. 端口号:在一台计算机上标识特定应用程序
  3. 协议:定义数据如何在网络中传输(如 TCP、UDP)
  4. 客户端-服务器模型:最常见的网络应用模型,服务器等待连接,客户端发起连接

创建第一个 Socket 程序

服务器端

以下是一个简单的 TCP 服务器示例,它会监听指定端口并回显接收到的消息:

python
import socket

# 创建一个 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = socket.gethostname()
port = 9999

# 绑定端口
server_socket.bind((host, port))

# 设置最大连接数
server_socket.listen(5)

print(f"服务器启动,监听端口 {port}...")

while True:
# 建立客户端连接
client_socket, addr = server_socket.accept()
print(f"获取到来自 {addr} 的连接")

# 向客户端发送消息
message = "欢迎访问 Python Socket 服务器!"
client_socket.send(message.encode('utf-8'))

# 接收客户端消息
client_msg = client_socket.recv(1024).decode('utf-8')
print(f"客户端消息: {client_msg}")

# 发送回显消息
echo_msg = f"服务器回声: {client_msg}"
client_socket.send(echo_msg.encode('utf-8'))

# 关闭连接
client_socket.close()

客户端

与服务器对应的客户端代码:

python
import socket

# 创建 socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = socket.gethostname()
port = 9999

# 连接服务器
client_socket.connect((host, port))

# 接收服务器欢迎消息
msg = client_socket.recv(1024)
print(msg.decode('utf-8'))

# 发送消息到服务器
message = "你好,服务器!"
client_socket.send(message.encode('utf-8'))

# 接收服务器回显
echo_msg = client_socket.recv(1024).decode('utf-8')
print(echo_msg)

# 关闭连接
client_socket.close()

运行结果

服务器端输出:

服务器启动,监听端口 9999...
获取到来自 ('192.168.1.5', 52431) 的连接
客户端消息: 你好,服务器!

客户端输出:

欢迎访问 Python Socket 服务器!
服务器回声: 你好,服务器!

Socket 基本函数详解

服务器端函数

  1. socket():创建一个新的 socket 对象

    python
    socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
    • family: 地址族,通常是 AF_INET (IPv4) 或 AF_INET6 (IPv6)
    • type: socket 类型,常用 SOCK_STREAM (TCP) 或 SOCK_DGRAM (UDP)
  2. bind():将 socket 绑定到特定地址和端口

    python
    socket_object.bind((host, port))
  3. listen():开始监听连接

    python
    socket_object.listen(backlog)
    • backlog: 等待连接队列的最大长度
  4. accept():接受连接,返回新 socket 和客户端地址

    python
    client_socket, client_address = socket_object.accept()

客户端函数

  1. connect():连接到远程服务器
    python
    socket_object.connect((host, port))

共用函数

  1. send()recv():发送和接收数据

    python
    socket_object.send(bytes)    # 返回发送的字节数
    socket_object.recv(bufsize) # 返回接收到的数据
  2. close():关闭 socket 连接

    python
    socket_object.close()

UDP Socket 编程

TCP 提供可靠的连接,但有时我们需要更轻量级的 UDP 协议。下面是 UDP 的客户端和服务器示例:

UDP 服务器

python
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
host = socket.gethostname()
port = 12345

server_socket.bind((host, port))

print(f"UDP 服务器已启动,监听端口 {port}...")

while True:
data, addr = server_socket.recvfrom(1024)
print(f"接收来自 {addr} 的消息: {data.decode('utf-8')}")

# 发送响应
message = f"已收到你的消息: {data.decode('utf-8')}"
server_socket.sendto(message.encode('utf-8'), addr)

UDP 客户端

python
import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
host = socket.gethostname()
port = 12345

message = "你好,UDP 服务器!"
client_socket.sendto(message.encode('utf-8'), (host, port))

data, server = client_socket.recvfrom(1024)
print(f"收到服务器 {server} 的响应: {data.decode('utf-8')}")

client_socket.close()
提示

UDP 不建立连接,也不保证消息一定会送达,但它的开销较小,适合对实时性要求高、允许少量丢包的应用,如视频流或游戏。

实际应用案例:多客户端聊天服务器

下面是一个简单的聊天服务器,支持多客户端同时连接并相互通信:

聊天服务器

python
import socket
import threading

class ChatServer:
def __init__(self, host, port):
self.host = host
self.port = port
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)

# 存储所有客户端连接
self.clients = {}

print(f"聊天服务器已在 {host}:{port} 启动")

def broadcast(self, message, exclude_client=None):
"""向所有客户端广播消息,可选择排除某个客户端"""
for client_socket, client_name in self.clients.items():
if client_socket != exclude_client:
try:
client_socket.send(message)
except:
# 客户端可能已断开连接
client_socket.close()
del self.clients[client_socket]

def handle_client(self, client_socket, client_address):
"""处理单个客户端连接的线程函数"""
try:
# 获取客户端名称
client_name = client_socket.recv(1024).decode('utf-8')
welcome_message = f"欢迎 {client_name} 加入聊天室!".encode('utf-8')
self.clients[client_socket] = client_name

# 通知所有人新客户端加入
self.broadcast(f"{client_name} 已加入聊天室!".encode('utf-8'))

# 持续接收该客户端的消息
while True:
try:
message = client_socket.recv(1024)
if message:
# 格式化消息并广播
broadcast_message = f"{client_name}: {message.decode('utf-8')}".encode('utf-8')
self.broadcast(broadcast_message)
else:
# 空消息,客户端可能已断开
break
except:
# 出错,退出循环
break

except Exception as e:
print(f"处理客户端 {client_address} 时出错: {e}")
finally:
# 客户端离开
if client_socket in self.clients:
client_name = self.clients[client_socket]
del self.clients[client_socket]
self.broadcast(f"{client_name} 已离开聊天室!".encode('utf-8'))
client_socket.close()

def run(self):
"""运行服务器主循环"""
try:
while True:
client_socket, client_address = self.server_socket.accept()
print(f"接受来自 {client_address} 的连接")

# 为每个客户端创建新线程
client_thread = threading.Thread(
target=self.handle_client,
args=(client_socket, client_address)
)
client_thread.daemon = True
client_thread.start()

except KeyboardInterrupt:
print("服务器关闭")
finally:
self.server_socket.close()

if __name__ == "__main__":
server = ChatServer('0.0.0.0', 8888)
server.run()

聊天客户端

python
import socket
import threading
import sys

class ChatClient:
def __init__(self, host, port, username):
self.host = host
self.port = port
self.username = username
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

def connect(self):
"""连接到服务器"""
try:
self.client_socket.connect((self.host, self.port))
# 发送用户名
self.client_socket.send(self.username.encode('utf-8'))
return True
except Exception as e:
print(f"连接失败: {e}")
return False

def receive_messages(self):
"""从服务器接收消息的线程函数"""
while True:
try:
message = self.client_socket.recv(1024).decode('utf-8')
if message:
print(message)
else:
# 空消息表示连接已关闭
print("与服务器的连接已关闭")
self.client_socket.close()
break
except Exception as e:
print(f"接收消息时出错: {e}")
self.client_socket.close()
break

def send_message(self, message):
"""向服务器发送消息"""
try:
self.client_socket.send(message.encode('utf-8'))
except Exception as e:
print(f"发送消息时出错: {e}")
self.client_socket.close()

def run(self):
"""运行客户端"""
if not self.connect():
return

# 创建接收消息的线程
receive_thread = threading.Thread(target=self.receive_messages)
receive_thread.daemon = True
receive_thread.start()

# 主线程处理用户输入
try:
while True:
message = input()
if message.lower() == 'quit':
break
self.send_message(message)
except KeyboardInterrupt:
pass
finally:
self.client_socket.close()
print("已断开连接")

if __name__ == "__main__":
# 用法: python chat_client.py <username>
username = sys.argv[1] if len(sys.argv) > 1 else f"User_{hash(threading.get_ident()) % 1000}"
client = ChatClient('localhost', 8888, username)
client.run()

这个聊天应用展示了如何:

  • 使用线程处理多个客户端
  • 管理多个客户端之间的通信
  • 处理客户端连接和断开
  • 实现广播消息功能

处理错误和超时

在网络编程中,错误和超时是常见问题。以下是如何处理这些情况:

设置超时

python
import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置 5 秒超时
client_socket.settimeout(5)

try:
client_socket.connect(('example.com', 80))
except socket.timeout:
print("连接超时")
except socket.error as e:
print(f"连接错误: {e}")

错误处理最佳实践

python
try:
# 尝试接收数据
data = sock.recv(1024)
if not data: # 检测连接关闭
print("连接已关闭")
sock.close()
except ConnectionResetError:
print("连接被对方重置")
sock.close()
except ConnectionAbortedError:
print("连接被中止")
sock.close()
except socket.timeout:
print("接收数据超时")
except Exception as e:
print(f"发生未知错误: {e}")
sock.close()

非阻塞 Socket

默认情况下,socket 操作是阻塞的,但我们可以使用非阻塞模式:

python
import socket
import select

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 8000))
server_socket.listen(5)

# 设置为非阻塞模式
server_socket.setblocking(False)

# 要监控的套接字列表
inputs = [server_socket]

try:
while inputs:
# select 函数监控多个 socket 的 I/O
readable, writable, exceptional = select.select(inputs, [], inputs)

for sock in readable:
if sock is server_socket:
# 处理新连接
client, address = sock.accept()
print(f"接收到新连接: {address}")
client.setblocking(False)
inputs.append(client)
else:
# 处理客户端数据
try:
data = sock.recv(1024)
if data:
print(f"接收: {data.decode('utf-8')}")
sock.send(f"Echo: {data.decode('utf-8')}".encode('utf-8'))
else:
# 空数据表示客户端关闭连接
print("客户端关闭连接")
inputs.remove(sock)
sock.close()
except:
inputs.remove(sock)
sock.close()

except KeyboardInterrupt:
print("服务器关闭")
finally:
server_socket.close()

这个例子展示了如何使用 select 模块创建一个简单的事件驱动服务器,它可以同时处理多个连接而无需创建线程。

总结

通过本文,我们学习了:

  1. Socket 编程的基本概念和工作原理
  2. 创建 TCP 和 UDP 客户端与服务器
  3. 多客户端通信的实现方法
  4. 错误处理和超时设置
  5. 非阻塞 Socket 编程

Socket 编程是更高级网络应用的基础,掌握这些技能后,你可以进一步学习 HTTP 服务器、WebSockets、异步网络编程等更复杂的主题。

练习项目

为了巩固所学知识,尝试完成以下项目:

  1. 文件传输服务器:创建一个服务器,允许客户端上传和下载文件
  2. 简单的 HTTP 客户端:使用 socket 实现一个能发送 GET 请求并解析响应的程序
  3. 在线游戏:创建一个简单的多人游戏,如井字棋或猜数字游戏
  4. 聊天室增强:为我们的聊天应用添加私聊、用户列表、文件共享等功能

附加资源

通过掌握 Socket 编程,你已经拥有了构建任何网络应用所需的基础知识。无论是创建简单的客户端-服务器应用,还是复杂的分布式系统,这些知识都将为你提供坚实的基础。