Python Requests Session运行6小时后持续抛出ConnectionError求助
问题:Python Requests Session运行6小时后持续报ConnectionError
环境与场景
使用Raspberry Pi 3 Model B V1.2搭配BME688传感器,持续向openSenseMap传输环境数据。代码初期运行正常,可稳定传输约6小时,之后突然持续收到ConnectionError,已排除速率限制、网络问题(启动第二个应用实例可正常连接)。
代码实现
main.py
RATE_LIMITS_TIME = 20 def main() -> None: """ Runs main process of recording and sending measurements to the OpenSenseMap. live sensor measurements every ten seconds due to rate limits """ # create a single TCP connection session = requests.Session() while True: try: response = send_measurements_to_server(session) except requests.exceptions.ConnectionError: # renew session session = requests.Session() response = "-- CONNECTION FAILURE --" except requests.exceptions.Timeout: print("Request timed out, skipping this cycle") response = "-- TIMEOUT --" print(response,datetime.datetime.now()) time.sleep(RATE_LIMITS_TIME)
send_to_server.py
def send_measurements_to_server(session: requests.Session) -> int: opensensemap_url = "https://api.opensensemap.org/boxes/" adjusted_url = opensensemap_url + SENSE_BOX_ID + "/data" measurement = record_measurement() body = {HUMIDITY_SENSOR_ID: [measurement.humidity], TEMPERATURE_SENSOR_ID: [measurement.temperature], AIR_PRESSURE_SENSOR_ID: [measurement.pressure], GAS_RESISTANCE_SENSOR_ID: [measurement.gas_resistance] } response = session.post(adjusted_url, json=body, timeout=10) return response
return_measurement.py
def record_measurement() -> measurementClass: """Records and returns the measurement of the bme680 sensor""" try: sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) new_measurement = measurementClass( sensor.data.temperature, sensor.data.pressure, sensor.data.humidity, sensor.data.gas_resistance) print(sensor.data.temperature, sensor.data.pressure, sensor.data.humidity, sensor.data.gas_resistance) except (RuntimeError, IOError): # if the sensor fails during data collection we use replacement values print("Sensor connection disrupted -- Using replacement values") new_measurement = create_artificial_measurement() return new_measurement
关键日志
28.2 962.51 33.837 102400000.0 <Response [201]> 2025-05-14 00:38:49.823012 28.15 962.51 33.847 102400000.0 <Response [201]> 2025-05-14 00:39:09.983847 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:39:30.056937 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:39:50.175899 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:40:10.262512 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:40:30.330664 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:40:50.450122 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:41:10.604180 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:41:30.702610 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:41:50.801585 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:42:10.918886 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:42:31.072819 Sensor connection disrupted -- Using replacement values <Response [201]> 2025-05-14 00:42:51.151268 Sensor connection disrupted -- Using replacement values Request timed out, skipping this cycle -- TIMEOUT -- 2025-05-14 00:43:21.174376 28.02 962.53 34.613 102400000.0 -- CONNECTION FAILURE -- 2025-05-14 00:43:41.249583 $ From here on out only Connection Failures $
已尝试的方案
- 每次请求创建新Session
- 全程使用单个Session
- 仅在收到ConnectionError时生成新Session
以上方案均无法解决问题,目前怀疑是否与树莓派的垃圾回收机制有关。
分析与解决方案
可能原因
- TCP连接被服务器主动关闭:服务器通常会对闲置连接设置超时策略,即使每20秒发送一次请求,若中间出现超时,Session持有的连接可能已失效,但现有逻辑未完全覆盖连接异常的清理场景。
- 传感器资源泄漏:每次采集数据都重新初始化BME680实例,频繁创建销毁传感器对象可能导致I2C总线资源占用异常,间接影响网络请求的底层资源分配。
- 连接池耗尽:Requests Session默认维护连接池,若多次异常未正确释放连接,会导致后续请求无法获取可用连接,引发ConnectionError。
修复建议
- 完善Session异常清理逻辑:捕获ConnectionError时,先关闭旧Session的所有连接再重建:
except requests.exceptions.ConnectionError: session.close() session = requests.Session() response = "-- CONNECTION FAILURE --"
- 优化传感器初始化逻辑:全局初始化一次传感器,避免频繁创建实例导致资源泄漏:
修改return_measurement.py:
# 全局初始化传感器 try: sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) except (RuntimeError, IOError): sensor = None def record_measurement() -> measurementClass: """Records and returns the measurement of the bme680 sensor""" global sensor try: if not sensor: sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) sensor.get_sensor_data() # 触发数据采集 new_measurement = measurementClass( sensor.data.temperature, sensor.data.pressure, sensor.data.humidity, sensor.data.gas_resistance) print(sensor.data.temperature, sensor.data.pressure, sensor.data.humidity, sensor.data.gas_resistance) except (RuntimeError, IOError): print("Sensor connection disrupted -- Using replacement values") sensor = None # 标记失效,下次尝试重连 new_measurement = create_artificial_measurement() return new_measurement
- 定时主动重建Session:每隔一段时间(比如1小时)主动关闭并重建Session,避免长时间持有的连接出现不可预知问题:
修改main.py:
import time from datetime import datetime RATE_LIMITS_TIME = 20 SESSION_RENEW_INTERVAL = 3600 # 1小时 def main() -> None: session = requests.Session() last_renew_time = datetime.now() while True: # 定时重建Session if (datetime.now() - last_renew_time).total_seconds() >= SESSION_RENEW_INTERVAL: session.close() session = requests.Session() last_renew_time = datetime.now() try: response = send_measurements_to_server(session) except requests.exceptions.ConnectionError: session.close() session = requests.Session() response = "-- CONNECTION FAILURE --" except requests.exceptions.Timeout: print("Request timed out, skipping this cycle") response = "-- TIMEOUT --" print(response,datetime.datetime.now()) time.sleep(RATE_LIMITS_TIME)
- 配置连接池与重试策略:显式设置Session的连接池参数和重试规则,提升连接稳定性:
from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session(): session = requests.Session() # 设置重试策略 retry = Retry( total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504] ) adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=10) session.mount("https://", adapter) session.mount("http://", adapter) return session # 在main中使用 session = create_session()
内容的提问来源于stack exchange,提问作者ölkjamin
相关产品推荐
相关产品推荐

