网络编程
指通过计算机网络实现程序间通信的技术。Python提供了丰富的库支持各种网络协议和编程模式
套接字
是网络通信的基本操作单元,是应用层与TCP/IP协议族通信的中间软件抽象层。它提供了一组接口,允许不同主机或同一主机的不同进程之间进行通信。分为面向连接套接字(TCP)和无连接套接字(UDP)
面向连接套接字(传输控制协议TCP)
在进行通信之前,先建立一个连接,该连接的通信是序列化的、可靠的、不重复的数据交付,意味着每条信息可以拆分成多个片段,并且每一条消息片段都能确保能够到达目的地,然后按顺序组合起来(SOCK_STREAM 流套接字之一)
无连接套接字(用户数据报协议UDP)
在通讯之前无需建立连接,数据传输无法保证它的顺序性、可靠性、重复性,信息是以整体发送的,而并非分成多个片段(SOCK_DGRAM)
一、socket函数介绍
socket(套接字)
是网络通信的端点,是应用层与传输层之间的接口。它允许不同主机或同一主机的不同进程之间进行通信
socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None)
family/type:参数见下表
proto(通常省略):协议号,通常为0,表示使用默认协议 socket.IPPROTO_TCP:6 socket.IPPROTO_UDP:17 socket.IPPROTO_ICMP :1
fileno:文件描述符,可选参数,如果指定,将从指定的文件描述符创建一个套接字对象
通常使用如下简洁方法创建
TCP/IP套接字:
socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
UDP/IP套接字:socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
family参数 | 描述 | 常见用途 |
---|---|---|
socket.AF_INET | IPv4网络协议 (默认值) | 大多数互联网应用 |
socket.AF_INET6 | IPv6网络协议 | 新一代互联网应用 |
socket.AF_UNIX | Unix域套接字(本地通信) | 同一台主机上的进程间通信 |
type参数 | 协议 | 特点 | 适用场景 |
---|---|---|---|
socket.SOCK_STREAM (流式) | TCP | 可靠、面向连接、按序到达 | Web、文件传输、数据库连接 |
socket.SOCK_DGRAM (数据报) | UDP | 不可靠、无连接、可能丢失或乱序 | 视频流、DNS查询、在线游戏 |
socket.SOCK_RAW (原始) | ICMP等底层协议 | 直接访问底层协议如IP、ICMP | 网络探测、协议开发 |
socket.socket()会返回一个套接字对象,该套接字对象常用方法如下
函数 | 描述 |
---|---|
服务器方法 | |
bind() | 将地址(主机名、端口号)绑定到套接字上 |
listen() | 设置并启动TCP监听器 |
accept() | 被动接收TCP客户端连接,也一直等待直到连接到达(阻塞) |
客户端方法 | |
connect() | 主动发起TCP服务器连接 |
connect_ex() | connect()的扩散版本,此时会以错误码的形式放回问题,二不是抛出异常 |
通用方法 | |
recv() | 接收TCP信息 |
recv_into() | 接收TCP信息到指定缓冲区 |
send() | 发送TCP信息 |
sendall() | 完整的发生TCP信息 |
recvfrom() | 接收UDP信息 |
recvfrom_into() | 接收UDP信息到指定缓冲区 |
sendto() | 发送UDP信息 |
getpeername() | 连接到TCP套接字的远程地址 |
getsockname() | 当前套接字地址 |
getsockopt() | 返回给定套接字选项的值 |
setsockopt() | 设置给定套接字的值 |
shutdown() | 关闭连接 |
close() | 关闭套接字 |
detach() | 在未关闭文件描述符的情况下关闭套接字,返回文件描述符 |
ioctl() | 控制套接字的模式(仅支持Windows) |
面向阻塞的方法 | |
setblocking() | 设置套接字的阻塞或非阻塞模式 |
settimeout() | 设置阻塞套接字操作的超时时间 |
gettimeout() | 获取阻塞套接字操作的超时时间 |
面向文件的方法 | |
fileno() | 套接字的文件描述符 |
makefile() | 创建与套接字关联的文件对象 |
数据属性 | |
family | 套接字家族 |
type | 套接字类型 |
proto | 套接字协议 |
二、TCP/IP服务端/客户端
TCP (传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议。具有特点如下:
面向连接:通信前需要建立连接
可靠传输:保证数据顺序和完整性
全双工通信:双方可以同时发送和接收数据
流量控制:防止发送方过快导致接收方来不及处理
服务端:
import socket
def tcp_server():
# 1. 创建TCP socket(服务器套接字)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 设置地址重用(可选,即关闭后可重新启动)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3. 绑定地址和端口
server_socket.bind(('0.0.0.0', 8888))
# 4. 开始监听,设置最大等待连接数(注:是等待连接数,并非最大连接数)
server_socket.listen(5)
print("TCP服务器启动,等待连接...")
try:
while True:
# 5. 接受客户端连接
client_socket, addr = server_socket.accept()
print(f"客户端 {addr} 已连接")
try:
while True:
# 6. 接收数据(1024表示接收数据的缓冲区大小)
data = client_socket.recv(1024)
if not data: # 客户端关闭连接
break
print(f"收到消息: {data.decode('utf-8')}")
# 7. 发送响应
response = "服务端已收到消息"
client_socket.send(response.encode('utf-8'))
except ConnectionResetError:
print("客户端异常断开")
finally:
# 8. 关闭客户端连接
client_socket.close()
print(f"客户端 {addr} 已断开")
except KeyboardInterrupt:
print("服务器正在关闭...")
finally:
# 9. 关闭服务器socket
server_socket.close()
if __name__ == '__main__':
tcp_server()
客户端:
import socket
def tcp_client():
# 1. 创建TCP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# 2. 连接服务器
client_socket.connect(('127.0.0.1', 8888))
print("已连接到服务器")
while True:
# 3. 获取用户输入
message = input("请输入消息(输入quit退出): ")
if message.lower() == 'quit':
break
# 4. 发送数据
client_socket.send(message.encode('utf-8'))
# 5. 接收响应
response = client_socket.recv(1024)
print(f"服务器响应: {response.decode('utf-8')}")
except ConnectionRefusedError:
print("无法连接到服务器")
except Exception as e:
print(f"发生错误: {e}")
finally:
# 6. 关闭连接
client_socket.close()
print("连接已关闭")
if __name__ == '__main__':
tcp_client()
对于需要长时间保持连接的网络,并需要持续首发信息的情况,上方代码结构是完全不够的,上方只是一个简单的举例
三、UDP/IP服务端/客户端
UDP/IP 是互联网协议套件中的核心组合之一,由 UDP(用户数据报协议) 和 IP(网际协议) 共同构成。它是一种无连接的、轻量级的传输层协议,适用于对实时性要求高但允许少量数据丢失的场景
无连接:通信前无需建立连接,直接发送数据。
不可靠:不保证数据包的顺序、完整性或可达性(无重传机制)。
高效:头部开销小(仅8字节),传输延迟低。
支持广播/多播:可同时向多个目标发送数据
服务端:
import socket
def udp_server():
# 1. 创建UDP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 设置地址重用(可选,即关闭后可重新启动)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3. 绑定地址和端口
server_socket.bind(('127.0.0.1', 8888))
print("UDP服务器启动,等待消息...")
try:
while True:
# 4. 接收数据和客户端地址
data, addr = server_socket.recvfrom(1024)
print(f"收到来自 {addr} 的消息: {data.decode('utf-8')}")
# 5. 发送响应
response = "UDP消息已收到"
server_socket.sendto(response.encode('utf-8'), addr)
except KeyboardInterrupt:
print("服务器正在关闭...")
finally:
# 6. 关闭socket
server_socket.close()
if __name__ == '__main__':
udp_server()
客户端:
import socket
def udp_client():
# 1. 创建UDP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 8888)
try:
while True:
# 2. 获取用户输入
message = input("请输入消息(输入quit退出): ")
if message.lower() == 'quit':
break
# 3. 发送数据
client_socket.sendto(message.encode('utf-8'), server_address)
# 4. 接收响应
response, _ = client_socket.recvfrom(1024)
print(f"服务器响应: {response.decode('utf-8')}")
except Exception as e:
print(f"发生错误: {e}")
finally:
# 5. 关闭socket
client_socket.close()
print("客户端已关闭")
if __name__ == '__main__':
udp_client()
四、多线程服务器(threading)
多线程服务器是一种常见的并发服务器模型,它通过 Socket 实现网络通信,并利用 Threading(线程) 处理多个客户端请求,提高服务器的并发能力。
多线程服务器通常由以下部分组成:
主线程(Main Thread):负责监听客户端连接(accept())。
工作线程(Worker Thread):每个客户端连接由一个独立线程处理(recv(), send())。
工作流程如下
- 服务器启动,绑定IP和端口(bind()),并监听(listen())。
- 客户端发起连接(connect()),服务器接受连接(accept())。
- 为每个客户端创建一个新线程,处理该客户端的请求。
- 线程完成任务后关闭连接(close()),线程终止。
多线程服务端
import socket
import threading
def handle_client(client_socket, addr):
try:
while True:
data = client_socket.recv(1024)
if not data:
break
print(f"来自 {addr} 的消息: {data.decode('utf-8')}")
client_socket.send("已收到".encode('utf-8'i))
except Exception as e:
print(f"处理 {addr} 时出错: {e}")
finally:
client_socket.close()
print(f"{addr} 已断开")
def multi_thread_server():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8888))
server.listen(5)
print("多线程服务器启动...")
try:
while True:
client, addr = server.accept()
print(f"新连接: {addr}")
thread = threading.Thread(target=handle_client, args=(client, addr))
thread.start()
finally:
server.close()
if __name__ == '__main__':
multi_thread_server()
同理客户端复杂的时候,如果需要持续收发信息,客户端可能也需要创建一个线程持续接收服务端的数据
五、网络编程常见问题(地址复用、粘包、数据长度)
地址占用
启动服务器后,接着关闭在次重启时,可能会出现地址占用问题,即服务端无法启动,需要换个地址才能使用,创建套接字时使用
setsockopt
方法设置地址复用即可解决
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
数据粘包问题(TCP粘包)
TCP是流式协议,数据无明确边界,多次发送可能被合并接收。可以采取如下方式解决
固定长度:每次发送固定长度的信息(效率低)
分隔符:用特殊字符分割信息(如\n
)
消息头+长度:发送信息前先传长度
# 客户端(在末尾添加\n分隔符)
message = "hello\n"
client_socket.send(message.encode('utf-8'))
# 服务端(收到消息后按\n分隔后一项一项分别处理)
rece_message = data.decode('utf-8')
for message in rece_message.split("\n"):
print(message)
数据长度问题
在发送消息过长或多次接收的信息合并到一起,导致信息长度超过接收缓冲区的长度时,可能会导致信息无法正常处理,这时需要对信息接收模块进行处理
# 服务端接收信息处理(客户端同理)
data = client_socket.recv(1024)
whilelen(data) % 1024 != 0:
data += client_socket.recv(1024)