pin_drop当前位置:知识文库 ❯ 图文
Python TCP客户端与服务端编程 - 网络通信实战教程
一、TCP协议概述
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在Python网络编程中,TCP是最常用的通信方式之一,广泛应用于Web服务器、文件传输、即时通讯等场景。
TCP的核心特性包括:面向连接、可靠传输、流量控制、拥塞控制和全双工通信。这些特性确保了数据在网络传输中的完整性和有序性。
小贴士
互联网上超过90%的流量使用TCP协议。我们日常使用的HTTP、HTTPS、FTP、SMTP、SSH等协议都运行在TCP之上。了解TCP编程是掌握网络编程的基石。
二、TCP三次握手与四次挥手
TCP连接的建立和断开都遵循严格的过程。理解这些过程有助于编写更健壮的网络程序。
三次握手(建立连接)
-
第一次:客户端发送SYN包到服务器,进入SYN_SENT状态
-
第二次:服务器收到SYN后回复SYN+ACK,进入SYN_RECV状态
-
第三次:客户端收到SYN+ACK后发送ACK,双方进入ESTABLISHED状态
四次挥手(断开连接)
-
第一次:主动关闭方发送FIN,进入FIN_WAIT_1
-
第二次:被动关闭方回复ACK,进入CLOSE_WAIT
-
第三次:被动关闭方发送FIN,进入LAST_ACK
-
第四次:主动关闭方回复ACK,进入TIME_WAIT
三、服务端开发步骤
TCP服务端的基本开发流程包括创建Socket、绑定地址、监听连接、接受连接和数据处理五个步骤。
代码示例
import socket
def create_tcp_server(host='localhost', port=8080):
"""创建TCP服务端的基本流程"""
# 步骤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:绑定IP地址和端口
server_socket.bind((host, port))
print(f"服务器绑定到 {host}:{port}")
# 步骤4:开始监听(参数为最大等待连接数)
server_socket.listen(5)
print("服务器正在监听...")
# 步骤5:接受客户端连接
client_socket, client_address = server_socket.accept()
print(f"客户端连接: {client_address}")
# 步骤6:接收和发送数据
data = client_socket.recv(1024)
print(f"收到数据: {data.decode('utf-8')}")
response = "Hello from server!"
client_socket.sendall(response.encode('utf-8'))
# 步骤7:关闭连接
client_socket.close()
server_socket.close()四、客户端开发步骤
相比服务端,TCP客户端的开发更为简单,只需要创建Socket并连接到服务端即可开始通信。
代码示例
import socket
def create_tcp_client(server_host='localhost', server_port=8080):
"""创建TCP客户端的基本流程"""
# 步骤1:创建TCP Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 步骤2:连接到服务器
client_socket.connect((server_host, server_port))
print(f"已连接到服务器 {server_host}:{server_port}")
# 步骤3:发送数据
message = "Hello, Server!"
client_socket.sendall(message.encode('utf-8'))
print(f"发送: {message}")
# 步骤4:接收服务器响应
response = client_socket.recv(1024)
print(f"收到响应: {response.decode('utf-8')}")
# 步骤5:关闭连接
client_socket.close()
print("连接已关闭")五、多客户端并发处理
实际应用中,服务端需要同时处理多个客户端的连接。Python提供了多种并发处理方式,包括多线程、多进程和异步I/O。
多线程方式处理并发
代码示例
import socket
import threading
def handle_client(client_socket, client_address):
"""处理单个客户端连接"""
print(f"客户端 {client_address} 已连接")
try:
while True:
data = client_socket.recv(1024)
if not data:
break
message = data.decode('utf-8')
print(f"来自 {client_address}: {message}")
# 回显消息
response = f"服务器收到: {message}"
client_socket.sendall(response.encode('utf-8'))
except ConnectionResetError:
print(f"客户端 {client_address} 断开连接")
finally:
client_socket.close()
print(f"客户端 {client_address} 连接已关闭")
def multi_client_server(host='localhost', port=8080):
"""支持多客户端的TCP服务器"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(5)
print(f"服务器启动,监听 {host}:{port}")
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()小贴士
对于高并发场景,建议使用select、epoll或Python的asyncio模块,它们比多线程更高效,能处理数万个并发连接。
六、完整实战代码示例
示例1:简易聊天服务器
代码示例
# chat_server.py - 简易聊天服务器
import socket
import threading
class ChatServer:
def __init__(self, host='localhost', port=8080):
self.host = host
self.port = port
self.clients = {} # {client_address: client_socket}
self.server_socket = None
def start(self):
"""启动聊天服务器"""
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
print(f"聊天服务器启动,监听 {self.host}:{self.port}")
while True:
client_socket, client_address = self.server_socket.accept()
self.clients[client_address] = client_socket
print(f"新客户端加入: {client_address}")
# 广播消息
self.broadcast(f"[系统] {client_address} 加入了聊天室")
# 启动线程处理客户端
thread = threading.Thread(
target=self.handle_client,
args=(client_socket, client_address)
)
thread.daemon = True
thread.start()
def handle_client(self, client_socket, client_address):
"""处理客户端消息"""
try:
while True:
data = client_socket.recv(1024)
if not data:
break
message = data.decode('utf-8')
self.broadcast(f"[{client_address}] {message}")
except ConnectionResetError:
pass
finally:
del self.clients[client_address]
client_socket.close()
self.broadcast(f"[系统] {client_address} 离开了聊天室")
def broadcast(self, message):
"""广播消息给所有客户端"""
for addr, sock in list(self.clients.items()):
try:
sock.sendall(message.encode('utf-8'))
except:
pass
if __name__ == '__main__':
server = ChatServer()
server.start()示例2:聊天客户端
代码示例
# chat_client.py - 聊天客户端
import socket
import threading
def receive_messages(client_socket):
"""接收消息的线程函数"""
while True:
try:
data = client_socket.recv(1024)
if not data:
break
print(f"\n{data.decode('utf-8')}")
print("请输入消息: ", end='', flush=True)
except:
break
def start_client(host='localhost', port=8080):
"""启动聊天客户端"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host, port))
print("已连接到聊天服务器!")
# 启动接收消息线程
recv_thread = threading.Thread(target=receive_messages, args=(client_socket,))
recv_thread.daemon = True
recv_thread.start()
# 主线程发送消息
while True:
message = input("请输入消息: ")
if message.lower() == 'quit':
break
client_socket.sendall(message.encode('utf-8'))
client_socket.close()
print("已断开连接")
if __name__ == '__main__':
start_client()示例3:HTTP服务器简化版
代码示例
# simple_http_server.py - 简易HTTP服务器
import socket
import threading
def handle_http_request(client_socket):
"""处理HTTP请求"""
request = client_socket.recv(4096).decode('utf-8')
print(f"收到请求:\n{request}")
# 构建HTTP响应
html_content = """
<html>
<head><title>Python TCP服务器</title></head>
<body>
<p>这是一个使用Python Socket实现的简易HTTP服务器</p>
</body>
</html>
"""
response = f"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: {len(html_content)}
{html_content}"""
client_socket.sendall(response.encode('utf-8'))
client_socket.close()
def start_http_server(host='localhost', port=8000):
"""启动HTTP服务器"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host, port))
server.listen(5)
print(f"HTTP服务器启动: http://{host}:{port}")
while True:
client, addr = server.accept()
thread = threading.Thread(
target=handle_http_request, args=(client,)
)
thread.start()
if __name__ == '__main__':
start_http_server()七、注意事项与最佳实践
注意1:使用
sendall()而不是send(),确保所有数据都被发送出去,避免部分发送导致的数据丢失。
注意2:始终设置合理的超时时间
settimeout(),防止连接长时间阻塞导致程序无响应。
注意3:处理粘包问题。TCP是字节流协议,没有消息边界,需要通过长度前缀、分隔符或固定长度来区分消息。
注意4:生产环境应使用
with语句或try-finally确保Socket正确关闭,防止文件描述符泄漏。
八、小结与练习题
小结
-
服务端流程:socket() → bind() → listen() → accept() → recv()/send() → close()
-
客户端流程:socket() → connect() → send()/recv() → close()
-
并发处理:使用多线程、多进程或异步I/O实现多客户端并发
练习题
练习1
编写一个TCP文件传输服务器,支持客户端上传和下载文件。要求实现文件大小确认、传输进度显示和断点续传功能。
练习2
使用asyncio模块重写聊天服务器,实现异步高并发处理。对比多线程版本,分析两者的性能差异。
常见问题
TCP粘包问题是什么?如何解决?
TCP是字节流协议,发送方多次send的数据可能被接收方一次recv收到(粘包),或一次send的数据被分多次recv(半包)。解决方法:①使用固定长度消息;②在消息头添加长度字段;③使用特殊分隔符(如\n)标记消息边界。
listen()参数5是什么意思?
listen(5)中的5指定了等待连接队列(backlog)的最大长度。当队列满时,新连接会被拒绝。实际值可能被系统限制,Linux默认通常为128。高并发服务器应适当增大此值,但更重要的是快速accept()处理连接。
如何优雅地关闭TCP连接?
①发送方先调用shutdown(socket.SHUT_WR)关闭写端,通知对方数据发送完毕;②等待对方确认并关闭;③最后调用close()释放资源。使用with语句可以自动处理关闭操作,但异常情况下仍需手动shutdown。
为什么要设置SO_REUSEADDR?
当TCP连接关闭后,端口会进入TIME_WAIT状态(通常持续1-4分钟),在此期间无法重新绑定该端口。设置SO_REUSEADDR允许立即重用处于TIME_WAIT状态的端口,方便开发调试时频繁重启服务器。
accept()会阻塞吗?如何避免阻塞?
accept()默认是阻塞的,会一直等待直到有新连接到来。避免阻塞的方法:①设置socket.settimeout();②使用select模块监控socket可读状态;③使用非阻塞模式socket.setblocking(False);④使用asyncio异步框架。
本文涉及AI创作
内容由AI创作,请仔细甄别