一、Python 网络编程概述
1.1 什么是网络编程
网络编程又叫套接字编程,Socket编程,通信双方都有自己的Socket对象,数据在俩个Socket之间通过数据报包或者IO流的方式传输
网络编程是指通过编写代码,让两台或多台设备(计算机、手机等)通过网络进行数据传输和通信的过程。在 Python 中,我们可以利用内置的 socket 模块快速实现网络通信。
1.2 Python 中的网络编程支持
Python 提供了丰富的网络编程库,其中最核心、最基础的是 socket模块 ,它封装了底层的网络通信 API,让我们可以用简洁的代码实现 TCP、UDP 等协议的通信。此外,还有 http、urllib 等高级模块用于特定场景的网络开发。
二、网络编程核心三要素
要实现网络通信,必须明确三个核心要素:IP 地址 、端口号 和传输协议。
2.1 IP 地址:标识网络中的设备
- 作用:IP 地址是网络中设备的"身份证号",用于唯一标识一台网络设备。
- 分类:
-
- IPv4 :如
127.0.0.1(本地回环地址,用于本机测试)、192.168.1.1(局域网地址)。 - IPv6 :更长的地址格式,用于解决 IPv4 地址耗尽问题(如
::1是 IPv6 的本地回环地址)。
- IPv4 :如
2.2 端口号:标识设备上的应用程序
- 作用:一台设备上可能运行多个网络程序(如浏览器、微信、QQ),端口号用于区分不同的应用程序。
- 范围:0 ~ 65535
-
- 知名端口(0 ~ 1023):固定分配给常用服务,如 HTTP(80)、HTTPS(443)、SSH(22)。
- 注册端口(1024 ~ 49151):可由用户程序使用。
- 动态端口(49152 ~ 65535):通常由操作系统临时分配给客户端程序。
2.3 传输协议:TCP 与 UDP
传输协议规定了数据在网络中传输的规则,最常用的是 TCP 和 UDP,两者对比如下:
|----------|----------------------------|---------------------------|
| 特性 | TCP(传输控制协议) | UDP(用户数据报协议) |
| 连接性 | 面向连接(需三次握手建立连接) | 无连接(无需建立连接,直接发数据) |
| 可靠性 | 可靠传输(保证数据不丢失、不重复、按序到达) | 不可靠传输(不保证数据到达,也不保证顺序) |
| 传输效率 | 较低(因需维护连接状态和可靠性机制) | 较高(无需额外开销) |
| 适用场景 | 文件传输、网页浏览、邮件等对可靠性要求高的场景 | 视频通话、直播、游戏等对实时性要求高的场景 |
三、Socket 编程基础
3.1 什么是 Socket
Socket(套接字)是网络通信的"端点",是两台设备之间进行数据传输的接口。可以把 Socket 想象成一个"插座",服务器和客户端通过各自的"插座"建立连接并传输数据。
3.2 Python 的 socket 模块
使用前需导入模块:
import socket
3.3 Socket 的创建与基本参数
通过 socket.socket() 函数创建 Socket 对象,语法如下:
socket_obj = socket.socket(family=socket.AF_INET,
type=socket.SOCK_STREAM,
proto=0)
family(地址族):
-
socket.AF_INET:使用 IPv4 地址(最常用)。socket.AF_INET6:使用 IPv6 地址。
type(Socket 类型):
-
socket.SOCK_STREAM:面向连接的 TCP 协议(默认)。socket.SOCK_DGRAM:无连接的 UDP 协议。
proto(协议):通常填 0,由系统自动选择合适的协议。
四、TCP 网络编程(可靠传输)
4.1 TCP 协议简介与三次握手
TCP 是面向连接的协议,通信前需通过"三次握手"建立连接:
- 客户端向服务器发送 SYN 包(请求建立连接)。
- 服务器回复 SYN+ACK 包(确认请求,并同步序列号)。
- 客户端回复 ACK 包(确认连接建立)。
连接建立后,双方才能进行数据传输;传输结束后,还需通过"四次挥手"断开连接。
4.2 TCP 服务器实现步骤与代码
TCP 服务器的核心流程:创建 Socket → 绑定 IP 和端口 → 监听连接 → 接受连接 → 收发数据 → 关闭连接。
完整代码示例
import socket
def tcp_server():
# 1. 创建 TCP Socket(AF_INET: IPv4, SOCK_STREAM: TCP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 IP 地址和端口号
# '127.0.0.1' 表示仅本机可访问,若要局域网访问可填本机局域网 IP
# 端口号建议选 1024 以上的注册端口
server_addr = ('127.0.0.1', 8888)
server_socket.bind(server_addr)
# 3. 开始监听连接
# 参数 5 表示最大等待连接数(即最多允许 5 个客户端在队列中等待)
server_socket.listen(5)
print(f"[TCP 服务器] 已启动,监听地址:{server_addr}")
# 4. 等待客户端连接(accept() 会阻塞,直到有客户端连接)
# accept() 返回两个值:
# - client_socket:专门用于与该客户端通信的新 Socket
# - client_addr:客户端的(IP, 端口)地址
client_socket, client_addr = server_socket.accept()
print(f"[TCP 服务器] 客户端 {client_addr} 已连接")
try:
# 5. 接收客户端发送的数据
# recv(1024) 表示一次最多接收 1024 字节(1KB)的数据
# 注意:recv() 返回的是 bytes 类型,需要用 decode() 解码为字符串
recv_data = client_socket.recv(1024)
if recv_data:
print(f"[TCP 服务器] 收到客户端数据:{recv_data.decode('utf-8')}")
# 6. 向客户端发送数据
# 注意:send() 只能发送 bytes 类型,需要用 encode() 编码
send_data = "你好,客户端!我是 TCP 服务器。".encode('utf-8')
client_socket.send(send_data)
print(f"[TCP 服务器] 已向客户端发送数据")
finally:
# 7. 关闭与客户端通信的 Socket
client_socket.close()
# 8. 关闭服务器监听 Socket
server_socket.close()
print("[TCP 服务器] 已关闭")
if __name__ == "__main__":
tcp_server()
4.3 TCP 客户端实现步骤与代码
TCP 客户端的核心流程:创建 Socket → 连接服务器 → 收发数据 → 关闭连接。
完整代码示例
import socket
def tcp_client():
# 1. 创建 TCP Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务器(指定服务器的 IP 和端口)
server_addr = ('127.0.0.1', 8888)
client_socket.connect(server_addr)
print(f"[TCP 客户端] 已连接到服务器 {server_addr}")
try:
# 3. 向服务器发送数据
send_data = "你好,服务器!我是 TCP 客户端。".encode('utf-8')
client_socket.send(send_data)
print(f"[TCP 客户端] 已向服务器发送数据")
# 4. 接收服务器回复的数据
recv_data = client_socket.recv(1024)
if recv_data:
print(f"[TCP 客户端] 收到服务器回复:{recv_data.decode('utf-8')}")
finally:
# 5. 关闭 Socket
client_socket.close()
print("[TCP 客户端] 已关闭")
if __name__ == "__main__":
tcp_client()
4.4 实战:TCP 双向循环通信
上面的代码只能收发一次数据,我们可以用 while 循环实现双向持续通信(直到一方输入"exit"退出)。
改进后的服务器代码
import socket
def tcp_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('127.0.0.1', 8888))
server_socket.listen(5)
print("[TCP 服务器] 等待客户端连接...")
client_socket, client_addr = server_socket.accept()
print(f"[TCP 服务器] 客户端 {client_addr} 已连接")
try:
while True:
# 接收客户端消息
recv_data = client_socket.recv(1024)
if not recv_data: # 客户端断开连接时,recv() 返回空 bytes
break
recv_msg = recv_data.decode('utf-8')
print(f"[客户端] {recv_msg}")
if recv_msg == "exit":
print("[TCP 服务器] 客户端请求退出")
break
# 服务器输入回复
send_msg = input("[服务器] 请输入回复:")
client_socket.send(send_msg.encode('utf-8'))
if send_msg == "exit":
break
finally:
client_socket.close()
server_socket.close()
print("[TCP 服务器] 已关闭")
if __name__ == "__main__":
tcp_server()
改进后的客户端代码
import socket
def tcp_client():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 8888))
print("[TCP 客户端] 已连接到服务器")
try:
while True:
# 客户端输入消息
send_msg = input("[客户端] 请输入消息:")
client_socket.send(send_msg.encode('utf-8'))
if send_msg == "exit":
break
# 接收服务器回复
recv_data = client_socket.recv(1024)
if not recv_data:
break
recv_msg = recv_data.decode('utf-8')
print(f"[服务器] {recv_msg}")
if recv_msg == "exit":
break
finally:
client_socket.close()
print("[TCP 客户端] 已关闭")
if __name__ == "__main__":
tcp_client()
4.5 进阶:TCP 文件/图片传输
TCP 可靠传输的特性非常适合传输文件(如图片、文档、视频)。核心思路是:先发送文件名,再分块发送文件内容。
TCP 文件传输服务器(接收端)
import socket
def tcp_file_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('127.0.0.1', 8888))
server_socket.listen(5)
print("[TCP 文件服务器] 等待客户端连接...")
client_socket, client_addr = server_socket.accept()
print(f"[TCP 文件服务器] 客户端 {client_addr} 已连接")
try:
# 1. 先接收文件名(假设文件名不超过 1024 字节)
file_name = client_socket.recv(1024).decode('utf-8')
print(f"[TCP 文件服务器] 准备接收文件:{file_name}")
# 2. 分块接收文件内容并写入本地
# 文件名前加 'recv_' 前缀,避免覆盖原文件
with open(f'recv_{file_name}', 'wb') as f:
while True:
# 每次接收 1024 字节(1KB)
file_data = client_socket.recv(1024)
if not file_data: # 数据接收完毕
break
f.write(file_data)
print(f"[TCP 文件服务器] 文件 {file_name} 接收完成!")
finally:
client_socket.close()
server_socket.close()
print("[TCP 文件服务器] 已关闭")
if __name__ == "__main__":
tcp_file_server()
TCP 文件传输客户端(发送端)
import socket
import os
def tcp_file_client():
# 要发送的文件路径(请确保该文件存在,例如同目录下的 'test.jpg')
file_path = 'test.jpg'
# 检查文件是否存在
if not os.path.exists(file_path):
print(f"[TCP 文件客户端] 错误:文件 {file_path} 不存在")
return
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 8888))
print("[TCP 文件客户端] 已连接到服务器")
try:
# 1. 先发送文件名(从路径中提取文件名)
file_name = os.path.basename(file_path)
client_socket.send(file_name.encode('utf-8'))
print(f"[TCP 文件客户端] 准备发送文件:{file_name}")
# 2. 分块读取文件并发送
with open(file_path, 'rb') as f:
while True:
# 每次读取 1024 字节
file_data = f.read(1024)
if not file_data: # 文件读取完毕
break
client_socket.send(file_data)
print(f"[TCP 文件客户端] 文件 {file_name} 发送完成!")
finally:
client_socket.close()
print("[TCP 文件客户端] 已关闭")
if __name__ == "__main__":
tcp_file_client()
五、UDP 网络编程(不可靠传输)
5.1 UDP 协议简介
UDP 是无连接的协议,通信前不需要建立连接,直接向指定的(IP, 端口)发送数据即可。虽然不可靠,但传输速度快,适合实时性要求高的场景。
5.2 UDP 服务器实现步骤与代码
UDP 服务器的核心流程:创建 Socket → 绑定 IP 和端口 → 直接收发数据(无需监听/接受连接)。
完整代码示例(带详细注释)
import socket
def udp_server():
# 1. 创建 UDP Socket(SOCK_DGRAM: UDP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定 IP 和端口
server_addr = ('127.0.0.1', 8888)
server_socket.bind(server_addr)
print(f"[UDP 服务器] 已启动,监听地址:{server_addr}")
while True:
# 3. 接收数据(recvfrom() 会阻塞)
# recvfrom() 返回两个值:
# - recv_data:接收到的 bytes 数据
# - client_addr:发送方的(IP, 端口)地址
recv_data, client_addr = server_socket.recvfrom(1024)
recv_msg = recv_data.decode('utf-8')
print(f"[UDP 服务器] 收到来自 {client_addr} 的消息:{recv_msg}")
if recv_msg == "exit":
print("[UDP 服务器] 收到退出指令")
break
# 4. 向发送方回复数据(需指定对方地址)
send_msg = f"已收到你的消息:{recv_msg}".encode('utf-8')
server_socket.sendto(send_msg, client_addr)
# 5. 关闭 Socket
server_socket.close()
print("[UDP 服务器] 已关闭")
if __name__ == "__main__":
udp_server()
5.3 UDP 客户端实现步骤与代码
UDP 客户端的核心流程:创建 Socket → 直接向服务器发送数据 → 接收回复。
完整代码示例(带详细注释)
import socket
def udp_client():
# 1. 创建 UDP Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 服务器地址
server_addr = ('127.0.0.1', 8888)
while True:
# 2. 输入并发送消息
send_msg = input("[UDP 客户端] 请输入消息:")
client_socket.sendto(send_msg.encode('utf-8'), server_addr)
if send_msg == "exit":
break
# 3. 接收服务器回复
recv_data, _ = client_socket.recvfrom(1024)
print(f"[UDP 客户端] 收到服务器回复:{recv_data.decode('utf-8')}")
# 4. 关闭 Socket
client_socket.close()
print("[UDP 客户端] 已关闭")
if __name__ == "__main__":
udp_client()
六、网络编程常见问题与注意事项
6.1 编码与解码(str ↔ bytes)
- 关键点 :Python 3 中,
socket的send()和recv()方法只能处理bytes类型,不能直接处理字符串。 - 转换方法:
-
- 字符串 → bytes:
"你好".encode('utf-8') - bytes → 字符串:
b'\xe4\xbd\xa0\xe5\xa5\xbd'.decode('utf-8')
- 字符串 → bytes:
- 建议 :统一使用
utf-8编码,避免乱码。
- 细节:
- 编码:把我们看懂的 转成 我们看不懂的.
'字符串'.encode(码表) - 解码:把我们看不懂的 转成 我们看懂的.
二进制.decode(码表) - 只要乱码了, 原因只有1个, 编解码不同.
- 英文字母, 数字, 特殊符号无论什么码表都只占1个字节, 中文在gbk占2个字节, 在utf-8中占3个字节.
- 二进制数据特殊写法, 即: b'字母 数字 特殊符号', 该方式针对于中文无效.
- 编码:把我们看懂的 转成 我们看不懂的.
6.2 端口占用问题
- 现象 :运行服务器时提示
OSError: [Errno 48] Address already in use。 - 解决方法:
-
- 更换端口号:将代码中的端口号(如 8888)改为其他未被占用的端口。
- 杀掉占用端口的进程:
-
-
- Windows:打开命令行,输入
netstat -ano | findstr "8888"找到进程 PID,再输入taskkill /PID <PID> /F杀掉进程。 - Linux/Mac:输入
lsof -i:8888找到进程 PID,再输入kill -9 <PID>杀掉进程。
- Windows:打开命令行,输入
-
6.3 数据缓冲区与 recv() 参数
recv(1024)中的1024表示一次最多接收 1024 字节,不是必须每次都收满 1024 字节。- 如果数据量较大(如大文件),应使用
while循环分块接收,直到数据为空。
6.4 文件传输的完整性
- 上面的 TCP 文件传输代码是基础版本,实际应用中建议:
-
- 先发送文件大小:接收端根据文件大小判断是否接收完毕。
- MD5 校验:发送端发送文件的 MD5 值,接收端接收后计算 MD5 并对比,确保文件未损坏。
七、总结与回顾
- 网络编程三要素:IP 地址(标识设备)、端口号(标识应用)、传输协议(TCP/UDP)。
- Socket:网络通信的端点,是实现网络编程的核心工具。
- TCP 编程:面向连接、可靠传输,流程为"服务器 bind → listen → accept,客户端 connect",适合文件传输等场景。
- UDP 编程:无连接、不可靠传输,流程为"直接 sendto/recvfrom",适合视频通话等实时场景。
- 注意事项:编码解码、端口占用、分块收发数据。