socket
TCP网络编程学习笔记(完整版)
一、网络基础概念
1. IP地址
- 作用:在网络中唯一标识一台设备
- 查看方式:
- Windows系统使用命令:
ipconfig
- Linux/Mac系统使用命令:
ifconfig
- Windows系统使用命令:
- 测试网络连通性:使用
ping [IP地址]
命令 - 本机回环地址:
127.0.0.1
,用于本地测试
💡 示例:
ping 192.168.1.1 ipconfig ifconfig
2. 端口
- 作用:标识一台设备上运行的某个具体程序
- 端口号范围:共65536个(0 ~ 65535)
- 知名端口号:0 ~ 1023(如HTTP默认80、FTP默认21)
- 动态/私有端口号:1024 ~ 65535(用户自定义)
⚠️ 注意事项:
- 同一个端口不能被多个程序同时占用
- 服务器程序通常绑定知名端口或固定端口供客户端访问
二、TCP协议概述
- TCP全称:Transmission Control Protocol
- 特点:
- 面向连接(三次握手建立连接)
- 可靠传输(数据无差错、有序到达)
- 基于字节流(数据没有边界)
- 适用场景:
- 要求数据准确性和顺序性的通信
- 如网页请求、文件下载、邮件发送等
三、编码与解码
- 网络传输要求:只能传输二进制数据(bytes类型)
- 字符串转二进制:使用
.encode("utf-8")
- 二进制转字符串:使用
.decode("utf-8")
- 注意事项:
- 编码和解码必须使用相同字符集,否则会乱码
- 推荐统一使用 UTF-8 编码
📌 示例:
msg = "你好,世界!"
data = msg.encode("utf-8") # 编码为二进制
print(data) # 输出: b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81'
decoded_msg = data.decode("utf-8")
print(decoded_msg) # 输出: 你好,世界!
四、TCP客户端基本流程
实现步骤:
-
创建客户端套接字对象
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.AF_INET
: 使用IPv4协议socket.SOCK_STREAM
: 使用TCP协议
-
连接服务端
tcp_client_socket.connect(("127.0.0.1", 8080))
- 参数是元组格式
(服务器IP地址, 服务器端口号)
- 如果服务器在本机,可使用
127.0.0.1
- 参数是元组格式
-
发送数据(需先编码)
data = "Hello Server".encode("utf-8") tcp_client_socket.send(data)
-
接收数据(需解码)
recv_data = tcp_client_socket.recv(1024) # 每次最多接收1024字节 print(recv_data.decode("utf-8"))
recv()
是阻塞方法,等待数据到来才继续执行
-
关闭客户端套接字
tcp_client_socket.close()
✅ 完整示例:
import socket
# 创建客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务端
client_socket.connect(("127.0.0.1", 8080))
# 发送数据
send_data = "你好,服务器!".encode("utf-8")
client_socket.send(send_data)
# 接收响应
recv_data = client_socket.recv(1024)
print("收到回复:", recv_data.decode("utf-8"))
# 关闭连接
client_socket.close()
五、TCP服务端实现
1. 简单版本流程
实现步骤:
-
创建服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
绑定IP和端口号
tcp_server_socket.bind(("", 8080)) # "" 表示监听本机所有网卡
-
设置监听队列
tcp_server_socket.listen(128)
- 参数表示最大排队数量
-
等待接受客户端连接请求
client_socket, client_ip = tcp_server_socket.accept()
- 返回值是一个元组,第一个元素是新的通信socket,第二个是客户端地址信息
-
接收数据
data = client_socket.recv(1024) print("收到消息:", data.decode())
-
发送数据
client_socket.send(b"Hello Client")
-
关闭套接字
client_socket.close() tcp_server_socket.close()
✅ 完整示例:
import socket
# 创建服务端套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址和端口
server_socket.bind(("", 8080))
# 设置监听
server_socket.listen(128)
# 等待客户端连接
client_socket, client_addr = server_socket.accept()
# 接收数据
recv_data = client_socket.recv(1024)
print("客户端说:", recv_data.decode())
# 发送回复
client_socket.send(b"Server received your message")
# 关闭连接
client_socket.close()
server_socket.close()
2. 复杂版本(多进程并发处理)
功能说明:
- 支持多个客户端同时连接
- 使用 multiprocessing 模块创建子进程处理每个客户端请求
核心代码结构:
import socket
import multiprocessing
def handler_client_request(client_socket):
"""处理客户端请求"""
while True:
data = client_socket.recv(1024)
if len(data) == 0:
print("客户端已断开连接")
break
print("收到消息:", data.decode("utf-8"))
# 发送回应
client_socket.sendall("服务端已收到消息".encode("utf-8"))
client_socket.close()
def main():
# 创建服务端套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定地址和端口
server_socket.bind(("", 8080))
# 设置监听
server_socket.listen(128)
print("服务端启动成功,等待连接...")
while True:
# 等待客户端连接
client_socket, client_addr = server_socket.accept()
print(f"新连接来自:{client_addr}")
# 创建子进程处理该客户端
p = multiprocessing.Process(target=handler_client_request, args=(client_socket,))
p.start()
# 不会执行到这,但保留关闭语句作为规范
server_socket.close()
特点说明:
- multiprocessing.Process:每个客户端连接分配一个独立进程处理
- setsockopt():设置端口复用,避免服务重启时出现“Address already in use”错误
- while True循环:持续等待客户端连接,实现多客户端支持
- 异常处理:检测客户端是否关闭连接(收到空数据)
六、总结要点(补充细节)
内容 | 说明 |
---|---|
socket模块 | Python标准库中的网络通信核心模块 |
客户端和服务端 | 都需要经历创建套接字、连接、收发数据、关闭连接的基本流程 |
TCP通信 | 必须先建立连接,通信可靠、有序 |
数据传输 | 必须使用二进制格式,注意编解码一致性 |
多客户端处理 | 可以使用多线程或多进程技术实现并发处理 |
端口复用设置 | 使用 setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) |
异常处理 | 应对客户端突然断开、超时等情况 |
recv阻塞 | 默认是阻塞模式,可通过设置非阻塞进行优化 |
七、常见问题解答
Q1: 为什么使用 127.0.0.1
?
- 这是本机回环地址,用于测试本机网络应用,不经过物理网络接口。
Q2: recv()
方法为什么会阻塞?
- 因为它是同步阻塞IO,默认情况下会一直等待直到有数据到达或连接关闭。
Q3: 为什么要设置 SO_REUSEADDR
?
- 避免服务端意外崩溃后端口处于 TIME_WAIT 状态导致无法立即重启。
Q4: 为什么最大监听数设为128?
- 一般开发环境下足够使用。生产环境可根据需求调整,但受限于系统配置。