如何用requests监听文件变化?避免高频请求导致网站被Dos
解决方案:替换轮询为服务器推送机制
你的核心问题是短轮询导致请求量过高触发DoS,解决思路是把「客户端主动反复问服务器文件有没有变」改成「服务器主动告诉客户端文件变了」,以下是几种可行方案:
1. 用Server-Sent Events(SSE)实现单向推送
SSE是HTTP协议的扩展,客户端和服务器建立一次长连接,服务器有数据更新时主动推送给客户端,完全替代轮询。
客户端修改示例(Python)
import requests from sseclient import SSEClient # 连接到你的SSE服务器端点 messages = SSEClient('https://your-server.com/sun-updates') for msg in messages: if msg.data: try: val = int(msg.data) print(val) process.write(sun_pointer, val) except ValueError: print("无效的数值格式")
服务器端核心实现(以Flask为例)
from flask import Flask, Response import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler app = Flask(__name__) sun_value = 0 file_path = "/path/to/my_file.txt" class SunFileHandler(FileSystemEventHandler): def on_modified(self, event): global sun_value if event.src_path == file_path: with open(file_path, 'r') as f: sun_value = int(f.read().strip()) # 启动本地文件监听 event_handler = SunFileHandler() observer = Observer() observer.schedule(event_handler, path='/path/to/', recursive=False) observer.start() @app.route('/sun-updates') def sun_updates(): def generate(): prev_val = None while True: global sun_value if sun_value != prev_val: prev_val = sun_value yield f"data: {sun_value}\n\n" time.sleep(1) return Response(generate(), mimetype='text/event-stream') if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
2. 用WebSocket实现双向通信
如果需要客户端也能主动发送修改请求(比如用户调整阳光的操作),WebSocket更合适,它支持双向实时通信。
客户端示例(Python)
import asyncio import websockets async def listen_sun_updates(): async with websockets.connect('ws://your-server.com/ws') as websocket: async for message in websocket: try: val = int(message) print(val) process.write(sun_pointer, val) except ValueError: print("无效的数值格式") asyncio.get_event_loop().run_until_complete(listen_sun_updates())
服务器端核心逻辑(用websockets库)
import asyncio import websockets from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler file_path = "/path/to/my_file.txt" sun_value = 0 connected_clients = set() class SunFileHandler(FileSystemEventHandler): def on_modified(self, event): global sun_value if event.src_path == file_path: with open(file_path, 'r') as f: sun_value = int(f.read().strip()) # 推送给所有在线客户端 asyncio.create_task(broadcast(sun_value)) async def broadcast(value): if connected_clients: message = str(value) await asyncio.gather(*[client.send(message) for client in connected_clients]) async def handle_client(websocket): connected_clients.add(websocket) try: await websocket.wait_closed() finally: connected_clients.remove(websocket) # 启动文件监听 event_handler = SunFileHandler() observer = Observer() observer.schedule(event_handler, path='/path/to/', recursive=False) observer.start() start_server = websockets.serve(handle_client, "0.0.0.0", 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
3. 优化现有轮询(临时过渡方案)
如果暂时不想重构架构,至少把固定间隔轮询改成指数退避轮询,避免请求过于密集:
import requests import time val = int(requests.get("https://link.to/my_file.txt").text) prevVal = val backoff = 5 # 初始等待时间 max_backoff = 60 while True: try: new_val = int(requests.get("https://link.to/my_file.txt").text) if new_val != prevVal: prevVal = new_val print(new_val) process.write(sun_pointer, new_val) backoff = 5 # 数据更新后重置等待时间 else: time.sleep(backoff) backoff = min(backoff * 2, max_backoff) # 无更新时指数增长等待时间 except requests.exceptions.ConnectTimeout: print("连接超时") backoff = min(backoff * 2, max_backoff) continue except KeyboardInterrupt: exit()
关键原理说明
原来的短轮询每5秒发一次请求,不管文件有没有变化,高并发下服务器会被大量请求压垮。换成推送机制后:
- 每个客户端只保持1个长连接
- 只有当文件真的变化时,服务器才会发送数据
- 请求量直接从「N个客户端×每5秒1次」降到「N个长连接+仅变化时的推送包」,完全避免DoS风险
内容的提问来源于stack exchange,提问作者Reinmar Grau
相关产品推荐
相关产品推荐

