第 1 部分:最简单的 TCP 服务器
这个例子将实现最基本的功能:启动一个服务器,监听一个端口,接收一个客户端的连接,接收一条消息,打印出来,然后关闭连接。

代码示例
# server_simple.py
import socket
# 1. 创建一个 socket 对象
# socket.AF_INET 表示使用 IPv4 地址
# socket.SOCK_STREAM 表示使用 TCP 协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 IP 地址和端口号
# '0.0.0.0' 表示监听本机所有可用的网络接口
# 8888 是我们选择的端口号
server_address = ('0.0.0.0', 8888)
server_socket.bind(server_address)
# 3. 开始监听,等待客户端连接
# 5 是连接队列的长度,表示最多有多少个等待连接的客户端
server_socket.listen(5)
print(f"服务器启动,正在监听 {server_address[0]}:{server_address[1]}...")
# 4. 接受客户端连接
# accept() 是一个阻塞函数,程序会在这里等待,直到有客户端连接
# 它返回一个 (client_socket, client_address) 元组
# client_socket 是一个新的 socket 对象,用于与这个特定的客户端通信
# client_address 是客户端的 IP 地址和端口号
client_socket, client_address = server_socket.accept()
print(f"已接受来自 {client_address} 的连接!")
# 5. 接收客户端发送的数据
# recv(1024) 表示最多接收 1024 字节的数据
# 它也是一个阻塞函数,会等待客户端发送数据
data = client_socket.recv(1024).decode('utf-8')
print(f"收到来自客户端的消息: {data}")
# 6. 向客户端发送响应
response = "你好,客户端!你的消息已收到。"
client_socket.send(response.encode('utf-8'))
# 7. 关闭连接
client_socket.close()
server_socket.close()
print("连接已关闭,服务器退出。")
如何运行
-
保存代码:将上面的代码保存为
server_simple.py。 -
运行服务器:在终端中执行
python server_simple.py。$ python server_simple.py 服务器启动,正在监听 0.0.0.0:8888...
服务器会阻塞在
server_socket.accept()等待客户端连接。 -
使用客户端测试:打开另一个终端,使用
telnet或nc(netcat) 作为客户端来连接服务器。
(图片来源网络,侵删)# 使用 telnet $ telnet 127.0.0.1 8888 # 或者使用 nc (netcat) $ nc 127.0.0.1 8888
连接成功后,在客户端终端输入一些消息,
hello server,然后按回车。 -
观察服务器输出:回到服务器的终端,你会看到:
服务器启动,正在监听 0.0.0.0:8888... 已接受来自 ('127.0.0.1', 54321) 的连接! # 端口号可能不同 收到来自客户端的消息: hello server 连接已关闭,服务器退出。客户端终端也会收到服务器的响应:
hello server 你好,客户端!你的消息已收到。
第 2 部分:改进服务器 - 处理多个客户端(多线程)
上面的服务器只能处理一个客户端连接,一旦连接关闭,服务器就退出了,在实际应用中,服务器需要能够同时处理多个客户端的请求,最简单的方法是使用多线程。

当一个客户端连接进来时,我们创建一个新的线程来处理这个客户端的所有通信,主线程则继续回到 accept() 状态,等待下一个客户端。
代码示例
# server_threaded.py
import socket
import threading
# 定义一个函数,用于处理与单个客户端的通信
def handle_client(client_socket, client_address):
print(f"[新连接] {client_address} 已连接。")
try:
while True:
# 接收数据
data = client_socket.recv(1024).decode('utf-8')
if not data:
# recv() 返回空数据,表示客户端已关闭连接
break
print(f"[来自 {client_address}] {data}")
# 发送响应
response = f"服务器已收到你的消息: {data}"
client_socket.send(response.encode('utf-8'))
except ConnectionResetError:
print(f"[连接断开] {client_address} 异常断开。")
finally:
# 确保连接被关闭
client_socket.close()
print(f"[连接关闭] {client_address} 的连接已关闭。")
# --- 主服务器逻辑 ---
if __name__ == "__main__":
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('0.0.0.0', 8888)
server_socket.bind(server_address)
server_socket.listen(5)
print(f"服务器启动,正在监听 {server_address[0]}:{server_address[1]}...")
try:
while True:
# 接受新的客户端连接
client_socket, client_address = server_socket.accept()
# 为每个客户端连接创建一个新的线程
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address)
)
# 设置为守护线程,这样当主线程退出时,子线程也会随之退出
client_thread.daemon = True
client_thread.start()
print(f"[活动连接] 当前有 {threading.active_count() - 1} 个客户端连接。")
except KeyboardInterrupt:
print("\n服务器正在关闭...")
finally:
server_socket.close()
print("服务器已关闭。")
如何运行和测试
- 保存代码:保存为
server_threaded.py。 - 运行服务器:
$ python server_threaded.py 服务器启动,正在监听 0.0.0.0:8888...
- 测试:
- 打开两个或三个终端,分别运行
telnet 127.0.0.1 8888。 - 在每个
telnet窗口中输入不同的消息,"client 1", "client 2"。 - 你会看到服务器终端能够同时打印出来自不同客户端的消息,并且活动连接数在增加。
- 关闭一个
telnet窗口,服务器终端会打印出对应的连接关闭信息。
- 打开两个或三个终端,分别运行
第 3 部分:关键概念和最佳实践
socket.socket() - 创建套接字
socket.AF_INET: 用于 IPv4 网络。socket.AF_INET6: 用于 IPv6 网络。socket.SOCK_STREAM: 面向连接的 TCP 协议。socket.SOCK_DGRAM: 无连接的 UDP 协议。
socket.bind() - 绑定地址
- 服务器必须绑定一个特定的 IP 地址和端口号。
- IP 地址
'0.0.0.0'是一个特殊地址,表示“所有可用的网络接口”,这意味着服务器可以从本机的任何 IP 地址(如0.0.1或局域网 IP)接收连接。 - 端口号注意:端口号范围是 0-65535,0-1023 是系统保留端口(如 HTTP 80, HTTPS 443),普通用户程序应使用 1024 以上的端口。
socket.listen() - 开始监听
listen(backlog):backlog参数指定了连接队列的长度,当服务器繁忙时,新的连接请求会被放入这个队列中等待,如果队列满了,新的连接请求可能会被拒绝。
socket.accept() - 接受连接
- 这是服务器端的关键阻塞点,它等待客户端发起连接。
- 成功后,它会返回一个新的 客户端套接字 (
client_socket) 和 客户端地址 (client_address)。 - 重要:
server_socket只负责接受连接,之后与客户端的所有数据收发(send,recv)都应该使用client_socket。
socket.recv() 和 socket.send() - 数据收发
recv(buffer_size): 从套接字中接收数据。buffer_size指定了最多接收多少字节,它是阻塞的,如果没有数据到达,它会等待。- 返回值:如果连接正常关闭,
recv()会返回一个空字节对象 `
