Python Socket网络通信详解

1.1 Socket通信核心概念

Socket又称"套接字",是实现网络通信的核心接口,本质是操作系统提供的网络通信端点,负责在不同设备的进程之间建立数据传输通道。无论是本地进程通信还是跨网络的远程通信,Socket都能通过统一的接口完成数据收发,是TCP/IP协议栈的编程实现载体。
在Python中,socket模块是实现Socket通信的核心工具,无需额外安装第三方库,直接导入即可使用。Socket通信遵循"客户端-服务端"架构,服务端负责监听指定端口、接收客户端连接请求并处理数据,客户端负责主动发起连接、与服务端交互数据,两者通过IP地址和端口号定位彼此,确保数据精准传输。
Socket通信主要分为两种类型:TCP Socket和UDP Socket。TCP(传输控制协议)是面向连接的可靠通信协议,通信前需建立三次握手连接,通信过程中保证数据有序、无丢失、无重复,适用于文件传输、登录认证等对数据可靠性要求高的场景;UDP(用户数据报协议)是无连接的不可靠通信协议,通信前无需建立连接,直接发送数据报,传输速度快但可能出现数据丢失、乱序,适用于视频直播、实时语音等对时效性要求高于可靠性的场景。


1.2 Socket通信基础原理

1.2.1 核心通信要素

***Socket通信的实现依赖三个核心要素,缺少任意一个都无法完成正常通信:

  1. 协议类型:选择TCP或UDP协议,决定通信的可靠性和传输方式;
  2. IP地址:定位网络中的目标设备,IPv4地址格式为xxx.xxx.xxx.xxx(如127.0.0.1为本地回环地址,用于本机内部通信);
  3. 端口号:定位设备中的目标进程,范围为0-65535,其中0-1023为系统保留端口(如80端口用于HTTP服务、22端口用于SSH服务),开发时建议使用1024-65535之间的未占用端口。***

1.2.2 TCP Socket通信流程

TCP是面向连接的通信模式,流程严谨且固定,分为服务端和客户端两个维度,核心步骤缺一不可:
服务端流程:创建套接字→绑定IP和端口→监听连接→接收客户端连接→收发数据→关闭连接→关闭套接字;
客户端流程:创建套接字→连接服务端IP和端口→收发数据→关闭连接→关闭套接字。
TCP的三次握手发生在"客户端连接"与"服务端接收连接"阶段,确保服务端和客户端都具备收发数据的能力;四次挥手发生在"关闭连接"阶段,确保双方数据都传输完毕后再断开连接,避免数据丢失。

1.2.3 UDP Socket通信流程

UDP是无连接的通信模式,流程相对简单,无需建立和断开连接,核心步骤如下:
服务端流程:创建套接字→绑定IP和端口→收发数据→关闭套接字;
客户端流程:创建套接字→收发数据→关闭套接字。
UDP通信时,客户端无需确认服务端是否在线,直接发送数据报;服务端绑定端口后即可接收任意客户端的数据,无需建立专属连接,每个数据报都包含目标IP和端口,独立传输。


1.3 Python Socket模块核心函数与参数

Python的socket模块封装了所有Socket通信所需的函数,核心函数的参数和用法直接决定通信是否能正常实现,以下是开发中高频使用的核心函数详解。

1.3.1 套接字创建:socket.socket()

功能:创建一个Socket对象,是所有通信的基础,语法格式如下:

sock = socket.socket(family, type, proto)

参数说明:

  1. family:地址族,指定IP类型,常用值为socket.AF_INET(IPv4地址,开发中最常用)、socket.AF_INET6(IPv6地址);

  2. type:套接字类型,TCP通信使用socket.SOCK_STREAM(流式套接字,面向连接、可靠传输),UDP通信使用socket.SOCK_DGRAM(数据报套接字,无连接、不可靠传输);

  3. proto:协议编号,默认值为0,无需手动指定,由系统根据family和type自动匹配。

示例(创建TCP套接字): tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

示例(创建UDP套接字): udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

1.3.2 地址绑定:bind()

功能:将套接字与指定的IP地址和端口号绑定,仅服务端需要调用,客户端无需绑定(由系统自动分配随机端口),语法格式如下:

sock.bind((host, port))

参数说明:

  1. host:IP地址,字符串类型,绑定本机所有网卡使用空字符串""或0.0.0.0,绑定本地回环地址使用127.0.0.1(仅本机内部可访问),绑定具体网卡IP使用设备的实际IP(如192.168.1.100,局域网内其他设备可访问);

  2. port:端口号,整数类型,需在1024-65535之间选择未被占用的端口,避免端口冲突。

注意:bind()的参数必须是元组格式(host, port),缺少括号会报错;绑定已占用的端口时,会抛出OSError异常,需更换端口后重新运行。

1.3.3 监听连接:listen()

功能:仅TCP服务端使用,将套接字设置为监听状态,等待客户端连接请求,语法格式如下:

sock.listen(backlog)

参数说明:

backlog:指定最大等待连接数,即允许同时有多少个客户端处于等待连接的状态,超过该数量的连接请求会被拒绝,常用值为5、10,无需设置过大(Python3.5+后该参数为可选,默认值由系统决定)。

示例: tcp_sock.listen(5) # 允许最多5个客户端等待连接

1.3.4 接收连接:accept()

功能:仅TCP服务端使用,阻塞等待客户端的连接请求,当有客户端连接时,返回一个新的套接字(用于与该客户端单独通信)和客户端的地址信息,语法格式如下:

conn, addr = sock.accept()

返回值说明:

  1. conn:新的套接字对象,专门用于与当前连接的客户端收发数据,每个客户端连接都会生成一个独立的conn对象;

  2. addr:元组类型,包含客户端的IP地址和端口号,格式为(client_ip, client_port)。

注意:accept()是阻塞函数,程序运行到该语句时会暂停,直到有客户端连接才会继续执行,后续需通过conn对象与客户端通信,而非原始的监听套接字。

1.3.5 发起连接:connect()

功能:仅TCP客户端使用,主动向服务端发起连接请求,语法格式如下:

sock.connect((host, port))

参数说明:

host为服务端的IP地址,port为服务端绑定的端口号,参数格式必须是元组;若服务端未启动、IP或端口错误,会抛出ConnectionRefusedError异常。

示例: tcp_sock.connect(("127.0.0.1", 8888)) # 连接本地8888端口的TCP服务端

1.3.6 数据收发:recv()、send()、sendto()、recvfrom()

1. recv():TCP通信专用,用于接收数据,阻塞等待对方发送数据,语法: data = conn.recv(bufsize)
参数bufsize:单次接收的最大字节数,常用值为1024、4096,返回值为bytes类型的二进制数据,需解码为字符串(如data.decode("utf-8"));
2. send():TCP通信专用,用于发送数据,语法: conn.send(data.encode("utf-8"))
注意:发送的数据必须是bytes类型,字符串需通过encode()编码,若发送数据过大,可能需要分多次发送;
3. recvfrom():UDP通信专用,接收数据并获取发送方地址,语法: data, addr = sock.recvfrom(bufsize)
返回值data为bytes类型数据,addr为发送方的(IP, 端口)元组;
4. sendto():UDP通信专用,发送数据到指定目标,语法: sock.sendto(data.encode("utf-8"), (host, port))
无需提前建立连接,直接指定目标IP和端口即可发送。

1.3.7 关闭套接字:close()

功能:关闭套接字,释放占用的端口资源,语法: sock.close() 或 conn.close()
注意:TCP通信中,服务端需关闭两个套接字(与客户端通信的conn对象和监听的sock对象),客户端仅需关闭自身的sock对象;通信结束后必须关闭套接字,否则会导致端口被长期占用。


1.4 TCP Socket完整实战(一对一通信)

TCP是开发中最常用的Socket通信方式,以下实现一对一的TCP通信案例:服务端绑定端口监听,客户端发起连接,双方可互相发送文本消息,输入"exit"即可断开连接。

1.4.1 TCP服务端代码实现

导入socket模块

import socket

def tcp_server():

1. 创建TCP套接字对象(IPv4+流式套接字)

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

2. 绑定IP和端口:绑定本机所有网卡,端口8888

server_addr = ("", 8888)

server_sock.bind(server_addr)

3. 开启监听,最大等待连接数5

server_sock.listen(5)

print("TCP服务端已启动,监听端口8888,等待客户端连接...")

4. 阻塞等待客户端连接,获取通信套接字和客户端地址

conn, client_addr = server_sock.accept()

print(f"成功连接客户端:{client_addr[0]}:{client_addr[1]}")

try:

循环收发数据,实现持续通信

while True:

5. 接收客户端数据,单次最多接收1024字节,解码为字符串

recv_data = conn.recv(1024).decode("utf-8")

若客户端发送exit或断开连接,退出循环

if not recv_data or recv_data == "exit":

print(f"客户端{client_addr[0]}:{client_addr[1]}已断开连接")

break

print(f"收到客户端消息:{recv_data}")

6. 向客户端发送回复消息

send_data = input("请输入回复客户端的消息(输入exit断开):")

conn.send(send_data.encode("utf-8"))

if send_data == "exit":

print("服务端主动断开连接")

break

finally:

7. 关闭与客户端的通信套接字

conn.close()

8. 关闭服务端监听套接字,释放端口

server_sock.close()

print("TCP服务端已关闭")

启动服务端

if name == "main":

tcp_server()

1.4.2 TCP客户端代码实现

导入socket模块

import socket

def tcp_client():

1. 创建TCP套接字对象

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

2. 向服务端发起连接请求,指定服务端IP和端口(本地测试用127.0.0.1)

server_addr = ("127.0.0.1", 8888)

try:

client_sock.connect(server_addr)

print("成功连接TCP服务端,可开始发送消息(输入exit断开)")

except ConnectionRefusedError:

print("服务端未启动或IP/端口错误,连接失败")

return

循环收发数据

while True:

3. 向服务端发送消息

send_data = input("请输入发送给服务端的消息(输入exit断开):")

client_sock.send(send_data.encode("utf-8"))

if send_data == "exit":

print("客户端主动断开连接")

break

4. 接收服务端回复的数据

recv_data = client_sock.recv(1024).decode("utf-8")

if not recv_data or recv_data == "exit":

print("服务端已断开连接")

break

print(f"收到服务端回复:{recv_data}")

5. 关闭客户端套接字

client_sock.close()

print("TCP客户端已关闭")

启动客户端

if name == "main":

tcp_client()

1.4.3 运行说明与注意事项

1. 运行顺序:必须先启动服务端,再启动客户端,否则客户端会提示连接失败;
2. 本地测试:服务端绑定""或127.0.0.1,客户端连接127.0.0.1即可实现本机内部通信;若需局域网通信,服务端绑定本机实际IP(如192.168.1.100),客户端连接该IP即可;
3. 编码解码:网络传输的是二进制数据,字符串必须通过encode("utf-8")编码为bytes,接收后通过decode("utf-8")解码为字符串,否则会出现乱码;
4. 异常处理:服务端未启动、端口被占用、网络中断等情况会抛出异常,实际开发中需添加异常捕获,避免程序崩溃。


1.5 UDP Socket完整实战(无连接通信)

UDP无需建立连接,通信流程更简单,以下实现UDP通信案例:服务端绑定端口接收任意客户端的消息并回复,客户端直接发送消息到服务端,无需提前连接。

1.5.1 UDP服务端代码实现

import socket

def udp_server():

1. 创建UDP套接字对象(IPv4+数据报套接字)

server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

2. 绑定IP和端口,UDP服务端必须绑定端口才能接收数据

server_addr = ("", 9999)

server_sock.bind(server_addr)

print("UDP服务端已启动,绑定端口9999,等待接收消息(输入exit退出)")

while True:

3. 接收客户端数据和客户端地址,单次接收1024字节

recv_data, client_addr = server_sock.recvfrom(1024)

recv_msg = recv_data.decode("utf-8")

print(f"收到客户端{client_addr[0]}:{client_addr[1]}的消息:{recv_msg}")

若接收exit,服务端退出

if recv_msg == "exit":

print("UDP服务端主动退出")

break

4. 向客户端回复消息,需指定客户端地址

send_msg = input("请输入回复客户端的消息:")

server_sock.sendto(send_msg.encode("utf-8"), client_addr)

5. 关闭套接字

server_sock.close()

print("UDP服务端已关闭")

if name == "main":

udp_server()

1.5.2 UDP客户端代码实现

import socket

def udp_client():

1. 创建UDP套接字对象

client_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

UDP客户端无需绑定端口,系统自动分配

server_addr = ("127.0.0.1", 9999)

print("UDP客户端已启动,可发送消息(输入exit退出)")

while True:

2. 向服务端发送消息,指定服务端地址

send_msg = input("请输入发送给服务端的消息:")

client_sock.sendto(send_msg.encode("utf-8"), server_addr)

if send_msg == "exit":

print("UDP客户端主动退出")

break

3. 接收服务端回复的数据

recv_data, _ = client_sock.recvfrom(1024)

recv_msg = recv_data.decode("utf-8")

print(f"收到服务端回复:{recv_msg}")

4. 关闭套接字

client_sock.close()

print("UDP客户端已关闭")

if name == "main":

udp_client()

1.5.3 UDP通信核心特点

1. 无连接特性:客户端无需调用connect(),直接通过sendto()发送数据,服务端通过recvfrom()获取客户端地址并回复,无需建立专属连接;
2. 不可靠性:若服务端未启动,客户端发送的数据会直接丢失,无报错提示;数据传输过程中可能出现丢失、乱序,需自行实现重传、排序机制(如需可靠传输);
3. 高效性:无需三次握手和四次挥手,传输速度快,适合小数据量、实时性要求高的场景。


1.6 Socket通信进阶技巧

1.6.1 解决TCP粘包问题

***TCP是流式传输,数据会被合并为字节流发送,导致"粘包":多次发送的小数据被一次性接收(如连续发送"a"和"b",接收方可能收到"ab")。解决粘包的核心思路是明确数据边界,常用方案:

  1. 固定长度:发送方每次发送固定长度的数据,接收方按固定长度接收,不足则补空字符,超过则截断;
  2. 分隔符:在数据末尾添加特殊分隔符(如"\n"),接收方按分隔符拆分数据;
  3. 数据头+数据体:数据头存储数据体的长度,接收方先接收数据头获取长度,再按长度接收数据体,是最常用的方案。
    示例(数据头+数据体解决粘包):
    发送方:先计算数据长度→编码长度为4字节→发送长度+数据体***

data = "hello world".encode("utf-8")
length = len(data).to_bytes(4, byteorder="big") # 4字节存储长度
conn.send(length + data)

接收方:先接收4字节数据头→解析长度→按长度接收数据体

length_data = conn.recv(4)
length = int.from_bytes(length_data, byteorder="big")
data = conn.recv(length).decode("utf-8")

1.6.2 实现TCP多客户端通信

基础TCP服务端只能与一个客户端通信,要实现多客户端同时连接,需结合多线程:服务端每接收一个客户端连接,就创建一个新线程专门处理该客户端的通信,主线程继续监听新的连接请求。

核心代码片段(多线程TCP服务端)

import socket

import threading

def handle_client(conn, client_addr):

"""处理单个客户端的通信"""

print(f"新客户端连接:{client_addr}")

try:

while True:

recv_data = conn.recv(1024).decode("utf-8")

if not recv_data or recv_data == "exit":

print(f"客户端{client_addr}断开连接")

break

print(f"[{client_addr}]:{recv_data}")

conn.send(f"已收到:{recv_data}".encode("utf-8"))

finally:

conn.close()

def tcp_multi_server():

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_sock.bind(("", 8888))

server_sock.listen(10)

print("多客户端TCP服务端已启动,监听8888端口")

while True:

conn, addr = server_sock.accept()

创建线程处理客户端通信

t = threading.Thread(target=handle_client, args=(conn, addr))

t.daemon = True # 守护线程,主线程退出时子线程也退出

t.start()

if name == "main":

tcp_multi_server()

1.6.3 超时设置

默认情况下,recv()、accept()等函数是阻塞的,程序会一直等待,直到有数据或连接。通过settimeout()设置超时时间,超时后会抛出socket.timeout异常,避免程序长期阻塞。

示例:
sock.settimeout(5) # 设置超时时间为5秒,单位为秒
若5秒内没有数据接收或连接请求,直接抛出异常,可通过try-except捕获并处理。


1.7 Socket通信常见问题与排查

1. 端口被占用:报错OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次,解决方案:更换未被占用的端口,或通过命令杀死占用端口的进程(Windows:netstat -ano | findstr 端口号,taskkill /pid 进程号 /f;Linux:lsof -i:端口号,kill -9 进程号);
2. 连接被拒绝:报错ConnectionRefusedError,原因是服务端未启动、IP地址错误、端口号不匹配,解决方案:先启动服务端,核对IP和端口是否一致;
3. 数据乱码:原因是编码解码格式不一致,解决方案:发送方和接收方统一使用utf-8编码解码;
4. 多客户端连接失败:原因是listen()的backlog值过小,或未使用多线程/多进程处理连接,解决方案:增大backlog值,结合多线程/多进程实现多客户端处理;
5. 防火墙拦截:通信双方的防火墙可能拦截端口,解决方案:关闭防火墙,或在防火墙中放行对应的端口。


1.8 本章总结

Socket通信是Python网络编程的基础,核心是基于客户端-服务端架构,通过socket模块实现TCP或UDP协议的通信。TCP适用于可靠传输场景,流程为创建套接字→绑定→监听→连接→收发数据→关闭;UDP适用于高效传输场景,无需建立连接,直接通过sendto()和recvfrom()收发数据。
开发中需注意端口绑定、编码解码、异常处理等细节,针对TCP粘包、多客户端通信等需求,可通过数据边界标识、多线程等进阶技巧解决。掌握Socket通信后,可进一步实现文件传输、聊天室、远程控制等复杂网络应用,为后续Python网络开发打下基础。

相关推荐
sun0077001 小时前
androd和qnx判断实网卡还是虚网卡
运维·服务器·网络
郝学胜-神的一滴1 小时前
Python数据封装与私有属性:保护你的数据安全
linux·服务器·开发语言·python·程序人生
智航GIS2 小时前
11.7 使用Pandas 模块中describe()、groupby()进行简单分析
python·pandas
Pyeako2 小时前
机器学习--矿物数据清洗(六种填充方法)
人工智能·python·随机森林·机器学习·pycharm·线性回归·数据清洗
口嗨农民工2 小时前
live555 sample基本解读
运维·服务器
小宇的天下2 小时前
Synopsys Technology File and Routing Rules Reference Manual (1)
java·服务器·前端
lph0092 小时前
mqtt broker (mosquitto)创建服务器、订阅与发布
运维·服务器
ScilogyHunter2 小时前
SCons:Python驱动的智能构建系统
python·构建系统·scons