Browse Source

自动启动 mihomo 所有,但是要很久,不知为何无法并发,而且无法停止,占用内存超1.5G,估计要考虑动态分配策略

mrh 1 year ago
parent
commit
fbae4610a4
2 changed files with 58 additions and 23 deletions
  1. 34 6
      backend/routers/mihomo.py
  2. 24 17
      backend/utils/mihomo_service.py

+ 34 - 6
backend/routers/mihomo.py

@@ -92,8 +92,8 @@ async def post_start_mihomo(request: MihomoBatchRequest) -> MihomoMetaWithURL:
             return miho_model
         mixed_port = request.port
         if not mixed_port:
-            mixed_port = find_free_port()
-            external_controller_port = find_free_port((mixed_port+1, 18000))
+            mixed_port = await find_free_port()
+            external_controller_port = await find_free_port((mixed_port+1, 18000))
         config = {}
         temp_path = settings.MIHOMO_TEMP_PATH / f"{miho_model.provider_name}_{external_controller_port}.yaml"
         config['mixed-port'] = mixed_port
@@ -193,12 +193,9 @@ async def get_mihomo_running_status():
 
 @mihomo_router.post("/proxies_reachability")
 async def get_proxies_reachability():
-    # 1. 启动所有服务商
-    await post_start_each_provider()
-    
     # 2. 获取所有运行中的代理
     db = SubscriptionManager()
-    running_proxies = db.get_running_proxies()
+    running_proxies = db.get_each_provider_running_proxies()
     
     # 3. 测试延迟并更新数据库
     results = []
@@ -229,6 +226,37 @@ async def get_proxies_reachability():
     
     return results
 
+@mihomo_router.post("/start_all_proxies_reachability")
+async def start_all_proxies_reachability():
+    await post_start_each_provider()
+    await get_proxies_reachability()
+    db = SubscriptionManager()
+    with Session(db.engine) as session:
+        proxies_reachability = session.exec(
+            select(MihomoMeta)
+           .where(MihomoMeta.delay.is_not(None))
+        ).all()
+    tasks = []
+    port_start = 9350
+    for proxy in proxies_reachability:
+        tasks.append(post_start_mihomo(MihomoBatchRequest(id=int(proxy.id), port=port_start)))
+        port_start += 2
+    # 并发执行所有任务,并允许任务抛出异常而不中断其他任务
+    results = await asyncio.gather(*tasks, return_exceptions=True)
+    
+    # 处理结果和错误
+    ret = []
+    for result in results:
+        if isinstance(result, Exception):
+            # 如果是异常,记录错误并添加到 ret 中
+            logger.error(f"Failed to start mihomo: {str(result)}")
+            ret.append({"error": str(result)})
+        else:
+            # 如果是正常结果,直接添加到 ret 中
+            ret.append(result)
+    
+    return ret
+
 @mihomo_router.get("/external-controller")
 async def get_controller_urls():
     running_list = await get_mihomo_running_status()

+ 24 - 17
backend/utils/mihomo_service.py

@@ -4,31 +4,38 @@ from pathlib import Path
 import os
 from contextlib import closing
 from socket import socket, AF_INET, SOCK_STREAM
-def find_free_port(scope=(9350, 18000)):
+import asyncio
+from contextlib import closing
+from socket import AF_INET, SOCK_STREAM
+
+async def find_free_port(scope=(9350, 18000)):
     """
-    查找一个可用端口
+    异步查找一个可用端口
     :param scope: 指定端口范围,为None时使用默认范围(9600-19600)
     :return: 可以使用的端口号
     """
-
     for port in range(*scope):
-        with closing(socket(AF_INET, SOCK_STREAM)) as sock:
-            try:
-                # 尝试绑定端口,如果成功则说明端口空闲
-                sock.bind(('127.0.0.1', port))
-                return port
-            except OSError:
-                # 端口已被占用,继续尝试下一个
-                continue
+        try:
+            # 创建异步套接字
+            reader, writer = await asyncio.open_connection('127.0.0.1', port)
+            writer.close()
+            await writer.wait_closed()
+        except (ConnectionRefusedError, OSError):
+            # 端口未被占用,返回该端口
+            return port
 
     raise OSError('未找到可用端口。')
 
-def port_is_using(ip, port):
-    """检查端口是否被占用"""
-    with closing(socket(AF_INET, SOCK_STREAM)) as sock:
-        sock.settimeout(.1)
-        result = sock.connect_ex((ip, int(port)))
-        return result == 0
+async def port_is_using(ip, port):
+    """异步检查端口是否被占用"""
+    try:
+        reader, writer = await asyncio.open_connection(ip, port)
+        writer.close()
+        await writer.wait_closed()
+        return True
+    except (ConnectionRefusedError, OSError):
+        return False
+
 
 async def download_mihomo():
     """下载mihomo可执行文件"""