文章目录
- 分离的 TCP 服务器与客户端实现
-
- 服务器端代码 (`tcp_server.py`)
- 客户端代码 (`tcp_client.py`)
- 使用说明
-
- 1. 启动服务器 (192.168.80.254)
- 2. 启动客户端 (连接到192.168.80.254)
- 实现特点
-
- 1. 服务器端特点
- 2. 客户端特点
- 3. 网络交互流程
- 测试场景
-
- 1. 基本通信测试
- 2. 压力测试
- 3. 异常情况测试
分离的 TCP 服务器与客户端实现
以下是分离的TCP服务器和客户端代码,服务器绑定到192.168.80.254地址:
服务器端代码 (tcp_server.py)
import socket
import threading
import logging
import argparse
import selectors
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(threadName)-15s] %(levelname)-8s: %(message)s',
datefmt='%H:%M:%S'
)
logger = logging.getLogger('tcp_server')
logger.setLevel(logging.DEBUG)
class TCPServer:
"""绑定到192.168.80.254的高效TCP服务器"""
def __init__(self, host: str = '192.168.80.254', port: int = 12345, max_connections: int = 10):
self.host = host
self.port = port
self.max_connections = max_connections
self.running = False
self.connections = {}
self.connection_counter = 0
self.server_socket = None
self.selector = selectors.DefaultSelector()
def start(self):
"""启动服务器"""
try:
# 确保绑定到指定IP
logger.info(f"🛠️ 配置服务器绑定到 {self.host}:{self.port}")
# 创建服务器套接字
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定到指定IP地址
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(self.max_connections)
self.server_socket.setblocking(False)
self.running = True
# 注册服务器套接字
self.selector.register(self.server_socket, selectors.EVENT_READ, data=None)
logger.info(f"🚀 服务器启动,监听 {self.host}:{self.port},状态: LISTEN")
logger.info(f"⚙️ 服务器配置: 最大连接数={self.max_connections}")
# 主事件循环
while self.running:
events = self.selector.select(timeout=1)
for key, mask in events:
if key.data is None:
# 服务器套接字有新连接
self._accept_connection()
else:
# 客户端套接字有事件
self._service_client(key, mask)
logger.info("🛑 服务器已停止")
except Exception as e:
logger.exception(f"服务器异常: {e}")
finally:
self._cleanup()
def _accept_connection(self):
"""接受新连接并处理三次握手"""
try:
client_socket, addr = self.server_socket.accept()
client_socket.setblocking(False)
client_id = self.connection_counter = self.connection_counter + 1
logger.info(f"🔁 收到来自 {addr} 的SYN,状态: SYN_RCVD")
# 存储客户端信息
self.connections[client_id] = {
'socket': client_socket,
'addr': addr,
'state': 'SYN_RCVD',
'buffer': b''
}
# 注册客户端套接字
self.selector.register(
client_socket,
selectors.EVENT_READ,
data={'client_id': client_id, 'action': 'handshake'}
)
# 发送SYN-ACK (第二次握手)
logger.info(f"📤 向 {addr} 发送SYN-ACK")
# 模拟接收ACK (第三次握手)
logger.info(f"⏳ 等待来自 {addr} 的ACK…")
except BlockingIOError:
pass
except Exception as e:
logger.error(f"接受连接时出错: {e}")
def _service_client(self, key, mask):
"""处理客户端事件"""
client_info = key.data
client_id = client_info['client_id']
action = client_info.get('action', 'data')
client_data = self.connections.get(client_id)
if not client_data:
logger.warning(f"找不到客户端 #{client_id}")
return
client_socket = client_data['socket']
addr = client_data['addr']
try:
if action == 'handshake':
# 完成三次握手
if client_data['state'] == 'SYN_RCVD':
# 模拟接收ACK并完成连接
client_data['state'] = 'ESTABLISHED'
# 更新选择器注册
self.selector.modify(
client_socket,
selectors.EVENT_READ | selectors.EVENT_WRITE,
data={'client_id': client_id, 'action': 'data'}
)
logger.info(f"✅ 与 {addr} 完成三次握手,状态: ESTABLISHED")
elif mask & selectors.EVENT_READ:
# 可读事件 – 接收数据
try:
data = client_socket.recv(1024)
if not data:
# 客户端关闭连接
self._initiate_close(client_id)
return
client_data['buffer'] += data
logger.info(f"📥 收到来自 {addr} 的数据: {data.decode()[:50]}{'…' if len(data) > 50 else ''}")
# 处理完整请求 (模拟行协议)
if b'\\n' in client_data['buffer']:
request, _, client_data['buffer'] = client_data['buffer'].partition(b'\\n')
self._process_request(client_id, request)
except ConnectionResetError:
logger.info(f"❌ {addr} 连接重置")
self._close_client(client_id)
elif mask & selectors.EVENT_WRITE and client_data['state'] == 'ESTABLISHED':
# 可写事件 – 发送数据
# 这个示例中不主动发送数据,等待客户端请求
pass
except Exception as e:
logger.error(f"处理客户端 {addr} 时出错: {e}")
self._close_client(client_id)
def _process_request(self, client_id: int, request: bytes):
"""处理客户端请求"""
client_data = self.connections.get(client_id)
if not client_data:
return
client_socket = client_data['socket']
addr = client_data['addr']
try:
# 记录请求
logger.info(f"🔍 处理来自 {addr} 的请求: {request.decode()[:30]}{'…' if len(request) > 30 else ''}")
# 创建响应
response = f"Server response to: {request.decode()[:30]}…\\n".encode()
# 发送响应
client_socket.sendall(response)
logger.info(f"📤 向 {addr} 发送响应")
except OSError as e:
logger.error(f"发送数据到 {addr} 失败: {e}")
self._close_client(client_id)
def _initiate_close(self, client_id: int):
"""服务器发起关闭连接(四次挥手)"""
client_data = self.connections.get(client_id)
if not client_data:
return
client_socket = client_data['socket']
addr = client_data['addr']
if client_data['state'] == 'ESTABLISHED':
try:
logger.info(f"🟡 服务器状态: CLOSE_WAIT")
logger.info(f"⬇️ 服务器发送FIN给 {addr}")
# 发送 FIN (第一次挥手)
client_socket.shutdown(socket.SHUT_WR)
# 更新状态
client_data['state'] = 'LAST_ACK'
logger.info(f"🛑 服务器状态: LAST_ACK")
# 修改选择器注册以等待ACK
self.selector.modify(
client_socket,
selectors.EVENT_READ,
data={'client_id': client_id, 'action': 'close'}
)
except OSError as e:
logger.error(f"关闭连接时出错: {e}")
self._close_client(client_id)
def _close_client(self, client_id: int):
"""关闭客户端连接并清理资源"""
if client_id not in self.connections:
return
client_data = self.connections.pop(client_id)
client_socket = client_data['socket']
addr = client_data['addr']
try:
# 从选择器注销
self.selector.unregister(client_socket)
# 关闭套接字
client_socket.close()
logger.info(f"🔒 已关闭与 {addr} 的连接")
except Exception as e:
logger.error("关闭客户端连接时出错: {e}")
def _cleanup(self):
"""清理所有资源"""
logger.info("🧹 清理服务器资源…")
# 关闭所有客户端连接
for client_id in list(self.connections.keys()):
self._close_client(client_id)
# 关闭服务器套接字
if self.server_socket:
try:
self.selector.unregister(self.server_socket)
self.server_socket.close()
logger.info("🔒 服务器套接字已关闭")
except:
pass
# 关闭选择器
self.selector.close()
def stop(self):
"""停止服务器"""
self.running = False
logger.info("🛑 正在停止服务器…")
def main():
"""服务器主函数"""
parser = argparse.ArgumentParser(description='TCP服务器 – 绑定到192.168.80.254')
parser.add_argument('–host', default='192.168.80.254', help='服务器绑定地址')
parser.add_argument('–port', type=int, default=12345, help='服务器监听端口')
parser.add_argument('–connections', type=int, default=10, help='最大连接数')
args = parser.parse_args()
logger.info(f"🔧 服务器配置: host={args.host}, port={args.port}, max_connections={args.connections}")
try:
server = TCPServer(args.host, args.port, args.connections)
logger.info("✅ 服务器初始化完成")
# 启动服务器
server_thread = threading.Thread(
target=server.start,
name="TCPServerMain"
)
server_thread.daemon = True
server_thread.start()
logger.info("🚦 服务器已在后台运行,按Ctrl+C停止")
# 保持主线程运行
server_thread.join()
except KeyboardInterrupt:
logger.info("\\n🛑 用户终止,正在停止服务器…")
server.stop()
except Exception as e:
logger.exception(f"服务器启动失败: {e}")
if __name__ == "__main__":
main()
客户端代码 (tcp_client.py)
import socket
import time
import logging
import argparse
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)-8s: %(message)s',
datefmt='%H:%M:%S'
)
logger = logging.getLogger('tcp_client')
logger.setLevel(logging.DEBUG)
class TCPClient:
"""连接到192.168.80.254的TCP客户端"""
def __init__(self, host: str = '192.168.80.254', port: int = 12345, timeout: int = 5):
self.host = host
self.port = port
self.timeout = timeout
self.client_socket = None
self.state = 'CLOSED'
def connect(self):
"""建立连接(三次握手)"""
try:
# 显示连接信息
logger.info(f"🔗 连接到服务器 {self.host}:{self.port}")
# 创建套接字
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 禁用Nagle算法
self.client_socket.settimeout(self.timeout)
logger.info(f"🆕 客户端创建套接字,状态: CLOSED")
# 第一次握手 (SYN)
logger.info(f"🚦 客户端发送SYN给服务器 {self.host}:{self.port},状态: SYN_SENT")
# 连接到服务器
self.client_socket.connect((self.host, self.port))
# 更新状态
self.state = 'ESTABLISHED'
logger.info(f"✅ 完成三次握手,状态: ESTABLISHED")
return True
except socket.timeout:
logger.error("⌛ 连接超时,服务器未响应")
return False
except ConnectionRefusedError:
logger.error("❌ 连接被拒绝,服务器可能未运行")
return False
except Exception as e:
logger.error(f"连接失败: {e}")
self.close()
return False
def send_data(self, data: bytes) –> bool:
"""发送数据到服务器"""
if not self.client_socket or self.state != 'ESTABLISHED':
logger.warning("⚠️ 无法发送数据: 连接未建立或已关闭")
return False
try:
# 发送数据
self.client_socket.sendall(data)
logger.info(f"📤 客户端发送数据: {data.decode()[:30]}{'…' if len(data) > 30 else ''}")
return True
except Exception as e:
logger.error(f"发送数据失败: {e}")
return False
def receive_data(self, buffer_size: int = 1024) –> Optional[bytes]:
"""接收服务器响应"""
if not self.client_socket or self.state != 'ESTABLISHED':
logger.warning("⚠️ 无法接收数据: 连接未建立或已关闭")
return None
try:
# 接收数据
data = self.client_socket.recv(buffer_size)
if data:
logger.info(f"📥 客户端收到响应: {data.decode()[:50]}{'…' if len(data) > 50 else ''}")
return data
except socket.timeout:
logger.warning("⌛ 接收数据超时")
return None
except ConnectionResetError:
logger.error("❌ 连接被服务器重置")
self.close()
return None
except Exception as e:
logger.error(f"接收数据失败: {e}")
return None
def close(self) –> bool:
"""关闭连接(四次挥手)"""
if not self.client_socket or self.state == 'CLOSED':
return True
try:
if self.state == 'ESTABLISHED':
# 第一次挥手 (FIN)
logger.info(f"⏹️ 客户端发送FIN给服务器,状态: FIN_WAIT_1")
self.client_socket.shutdown(socket.SHUT_WR)
self.state = 'FIN_WAIT_1'
# 接收ACK (第二次挥手)
ack = self.client_socket.recv(1)
if ack:
logger.info(f"🟢 收到服务器ACK,状态: FIN_WAIT_2")
self.state = 'FIN_WAIT_2'
# 接收FIN (第三次挥手)
fin = self.client_socket.recv(1)
if fin:
logger.info(f"🟡 收到服务器FIN,状态: TIME_WAIT")
self.state = 'TIME_WAIT'
# 第四次挥手 (ACK)
logger.info(f"⬇️ 客户端发送ACK给服务器")
self.client_socket.send(b'A')
# 等待2MSL (模拟TIME_WAIT状态)
logger.info(f"⏳ 客户端进入TIME_WAIT状态 (等待2秒)…")
time.sleep(2)
logger.info(f"🔚 客户端连接关闭,状态: CLOSED")
self.state = 'CLOSED'
return True
except Exception as e:
logger.error(f"关闭连接时出错: {e}")
return False
finally:
try:
if self.client_socket:
self.client_socket.close()
except:
pass
self.client_socket = None
def main():
"""客户端主函数"""
parser = argparse.ArgumentParser(description='TCP客户端 – 连接到192.168.80.254')
parser.add_argument('–host', default='192.168.80.254', help='服务器地址')
parser.add_argument('–port', type=int, default=12345, help='服务器端口')
parser.add_argument('–message', default='Hello from TCP Client!', help='要发送的消息')
args = parser.parse_args()
logger.info(f"🔧 客户端配置: host={args.host}, port={args.port}, message={args.message}")
try:
# 创建客户端
client = TCPClient(args.host, args.port)
# 连接服务器
if not client.connect():
logger.error("❌ 无法连接到服务器")
return
# 发送数据
if not client.send_data(args.message.encode()) or args.message == 'exit':
logger.info("ℹ️ 没有发送数据或请求退出")
else:
# 接收响应
response = client.receive_data()
if response:
logger.info(f"💡 完整响应: {response.decode()}")
# 关闭连接
client.close()
logger.info("✅ 客户端操作完成")
except KeyboardInterrupt:
logger.info("\\n🛑 用户终止操作")
client.close()
except Exception as e:
logger.exception(f"客户端操作失败: {e}")
if __name__ == "__main__":
main()
使用说明
1. 启动服务器 (192.168.80.254)
python tcp_server.py –host 192.168.80.254 –port 54321 –connections 20
参数说明:
- –host: 服务器绑定的IP地址 (默认192.168.80.254)
- –port: 服务器监听的端口 (默认12345)
- –connections: 最大并发连接数 (默认10)
服务器日志示例:
15:30:22 🔧 服务器配置: host=192.168.80.254, port=54321, max_connections=20
15:30:22 ✅ 服务器初始化完成
15:30:22 🚀 服务器启动,监听 192.168.80.254:54321,状态: LISTEN
15:30:22 ⚙️ 服务器配置: 最大连接数=20
15:30:22 🚦 服务器已在后台运行,按Ctrl+C停止
15:30:45 🔁 收到来自 ('192.168.80.100', 54216) 的SYN,状态: SYN_RCVD
15:30:45 📤 向 ('192.168.80.100', 54216) 发送SYN-ACK
15:30:45 ⏳ 等待来自 ('192.168.80.100', 54216) 的ACK…
1530:45 ✅ 与 ('192.168.80.100', 54216) 完成三次握手,状态: ESTABLISHED
2. 启动客户端 (连接到192.168.80.254)
python tcp_client.py –host 192.168.80.254 –port 54321 –message "Hello Server!"
参数说明:
- –host: 要连接的服务器IP (默认192.168.80.254)
- –port: 服务器端口 (默认12345)
- –message: 要发送的消息文本
客户端日志示例:
15:30:45 🔗 连接到服务器 192.168.80.254:54321
15:30:45 🆕 客户端创建套接字,状态: CLOSED
15:30:45 🚦 客户端发送SYN给服务器 192.168.80.254:54321,状态: SYN_SENT
15:30:45 ✅ 完成三次握手,状态: ESTABLISHED
15:30:45 📤 客户端发送数据: Hello Server!
15:30:45 📥 客户端收到响应: Server response to: Hello Server!…
15:30:45 💡 完整响应: Server response to: Hello Server!…
15:30:45 ⏹️ 客户端发送FIN给服务器,状态: FIN_WAIT_1
15:30:45 🟢 收到服务器ACK,状态: FIN_WAIT_2
15:30:45 🟡 收到服务器FIN,状态: TIME_WAIT
15:30:45 ⬇️ 客户端发送ACK给服务器
15:30:45 ⏳ 客户端进入TIME_WAIT状态 (等待2秒)…
15:30:47 🔚 客户端连接关闭,状态: CLOSED
15:30:47 ✅ 客户端操作完成
实现特点
1. 服务器端特点
- 指定IP绑定:专门绑定到192.168.80.254地址
- 高效I/O模型:使用selectors模块处理多连接
- 完整状态管理:实现TCP三次握手和四次挥手状态转换
- 健壮的错误处理:全面处理各种网络异常
- 请求处理:支持处理客户端消息并返回响应
2. 客户端特点
- 清晰的连接流程:完整展示TCP连接生命周期
- 交互式控制:支持命令行参数自定义消息
- 超时处理:所有网络操作都有超时控制
- 详细日志:记录所有关键状态变化
3. 网络交互流程
#mermaid-svg-NdeogteyNTn27QEU {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NdeogteyNTn27QEU .error-icon{fill:#552222;}#mermaid-svg-NdeogteyNTn27QEU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NdeogteyNTn27QEU .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-NdeogteyNTn27QEU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NdeogteyNTn27QEU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NdeogteyNTn27QEU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NdeogteyNTn27QEU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NdeogteyNTn27QEU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NdeogteyNTn27QEU .marker.cross{stroke:#333333;}#mermaid-svg-NdeogteyNTn27QEU svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NdeogteyNTn27QEU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NdeogteyNTn27QEU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-NdeogteyNTn27QEU .actor-line{stroke:grey;}#mermaid-svg-NdeogteyNTn27QEU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-NdeogteyNTn27QEU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-NdeogteyNTn27QEU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-NdeogteyNTn27QEU .sequenceNumber{fill:white;}#mermaid-svg-NdeogteyNTn27QEU #sequencenumber{fill:#333;}#mermaid-svg-NdeogteyNTn27QEU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-NdeogteyNTn27QEU .messageText{fill:#333;stroke:#333;}#mermaid-svg-NdeogteyNTn27QEU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NdeogteyNTn27QEU .labelText,#mermaid-svg-NdeogteyNTn27QEU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-NdeogteyNTn27QEU .loopText,#mermaid-svg-NdeogteyNTn27QEU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-NdeogteyNTn27QEU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NdeogteyNTn27QEU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NdeogteyNTn27QEU .noteText,#mermaid-svg-NdeogteyNTn27QEU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-NdeogteyNTn27QEU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NdeogteyNTn27QEU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NdeogteyNTn27QEU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NdeogteyNTn27QEU .actorPopupMenu{position:absolute;}#mermaid-svg-NdeogteyNTn27QEU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-NdeogteyNTn27QEU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NdeogteyNTn27QEU .actor-man circle,#mermaid-svg-NdeogteyNTn27QEU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-NdeogteyNTn27QEU :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Client
Server(192.168.80.254)
三次握手
SYN
SYN-ACK
ACK
数据传输
数据请求
响应数据
四次挥手
FIN
ACK
FIN
ACK
Client
Server(192.168.80.254)
测试场景
1. 基本通信测试
# 服务器
python tcp_server.py
# 客户端1
python tcp_client.py –message "Hello from Client 1"
# 客户端2
python tcp_client.py –message "Another message"
2. 压力测试
# 服务器(提高连接数)
python tcp_server.py –connections 50
# 使用多客户端并发测试
for i in {1..20}; do
python tcp_client.py –message "Request $i" &
done
3. 异常情况测试
# 服务器未启动时的客户端
python tcp_client.py # 显示连接拒绝
# 发送退出命令
python tcp_client.py –message "exit"
# 服务器重启测试
# 1. 启动服务器
# 2. 客户端连接并发送数据
# 3. 重启服务器
# 4. 客户端再次尝试操作(应显示连接错误)
这个分离实现严格遵循TCP协议规范,服务器绑定到指定IP地址192.168.80.254,客户端可以独立运行并连接到该服务器。代码包含详细的日志记录和错误处理,适合教学演示和实际网络编程参考。
评论前必须登录!
注册