From 880a5d24ea992f8d20fffcc23fceb48be6b68b6c Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Tue, 11 Aug 2026 21:31:56 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E4=BF=AE=E6=94=B9=E7=AB=9E?= =?UTF-8?q?=E6=80=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- internal/process/manager.go | 77 +++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/Dockerfile b/Dockerfile index e8dbea6..8807dbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build \ FROM alpine:latest # 先切换到国内镜像源,再安装包 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories -RUN apk --no-cache add ca-certificates tzdata sqlite bash curl iproute2 +RUN apk --no-cache add ca-certificates tzdata sqlite bash curl iproute2 net-tools WORKDIR /app diff --git a/internal/process/manager.go b/internal/process/manager.go index 4fa4299..245508f 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -401,7 +401,9 @@ func (pm *ProcessManager) CheckPort() PortCheckResult { } conn.Close() + // 获取端口占用者信息 pid, process := pm.getPortOwner(pm.adminPort) + // 即使 pid=0,也返回 Ready=true,让调用方决定如何处理 return PortCheckResult{ Ready: true, PID: pid, @@ -410,10 +412,19 @@ func (pm *ProcessManager) CheckPort() PortCheckResult { } func (pm *ProcessManager) getPortOwner(port int) (int, string) { - if runtime.GOOS != "linux" { - return 0, "" + // 方法1: ss + if pid, name := pm.getPortOwnerBySS(port); pid > 0 { + return pid, name } + // 方法2: netstat + if pid, name := pm.getPortOwnerByNetstat(port); pid > 0 { + return pid, name + } + return 0, "" +} +func (pm *ProcessManager) getPortOwnerBySS(port int) (int, string) { + // ss -lntp | grep ':7400 ' | grep -oP 'pid=\K[0-9]+' | head -1 cmd := exec.Command("sh", "-c", fmt.Sprintf("ss -lntp | grep ':%d ' | grep -oP 'pid=\\K[0-9]+' | head -1", port)) out, err := cmd.Output() if err != nil { @@ -427,30 +438,25 @@ func (pm *ProcessManager) getPortOwner(port int) (int, string) { if err != nil || pid <= 0 { return 0, "" } - - cmd2 := exec.Command("sh", "-c", fmt.Sprintf("ps -p %d -o comm= 2>/dev/null | head -1", pid)) - out2, err2 := cmd2.Output() - if err2 != nil { - return pid, "" - } - return pid, strings.TrimSpace(string(out2)) + return pid, "" } -func (pm *ProcessManager) GetPortStatus() (*PortStatus, error) { - status := &PortStatus{Port: pm.adminPort, Occupied: false, PID: 0, IsFRPC: false} - result := pm.CheckPort() - if result.Err != nil { - return status, result.Err +func (pm *ProcessManager) getPortOwnerByNetstat(port int) (int, string) { + // netstat -tlnp | grep ':7400 ' | awk '{print $7}' | cut -d'/' -f1 | head -1 + cmd := exec.Command("sh", "-c", fmt.Sprintf("netstat -tlnp 2>/dev/null | grep ':%d ' | awk '{print $7}' | cut -d'/' -f1 | head -1", port)) + out, err := cmd.Output() + if err != nil { + return 0, "" } - if !result.Ready { - return status, nil + pidStr := strings.TrimSpace(string(out)) + if pidStr == "" { + return 0, "" } - status.Occupied = true - status.PID = result.PID - if result.Process != "" { - status.IsFRPC = strings.Contains(result.Process, "frpc") + pid, err := strconv.Atoi(pidStr) + if err != nil || pid <= 0 { + return 0, "" } - return status, nil + return pid, "" } // ================================================================ @@ -576,25 +582,33 @@ func (pm *ProcessManager) isFRPReady(pid int) bool { return false } - // 方式1: stdout 检测(快速通道) + // stdout 快速通道 if strings.Contains(pm.getLastOutput(), "start proxy success") || strings.Contains(pm.getLastOutput(), "login to server success") { log.Printf("[DEBUG] FRPReady(pid=%d): 检测到 stdout 关键字", pid) return true } - // 方式2: admin API 检测(绑定 PID) - // 首先确认端口是否被当前 PID 占用 + // 端口归属检测 portResult := pm.CheckPort() if !portResult.Ready { log.Printf("[DEBUG] FRPReady(pid=%d): 端口 %d 未就绪", pid, pm.adminPort) return false } - if portResult.PID > 0 && portResult.PID != pid { - log.Printf("[DEBUG] FRPReady(pid=%d): 端口被进程 %d 占用,与期望 PID %d 不一致", pid, portResult.PID, pid) + + // 关键修复:端口被占用但无法识别归属 → 保守返回 false + if portResult.PID == 0 { + log.Printf("[DEBUG] FRPReady(pid=%d): 端口 %d 被占用但无法识别归属进程,保守返回 false", pid, pm.adminPort) return false } + if portResult.PID != pid { + log.Printf("[DEBUG] FRPReady(pid=%d): 端口 %d 被进程 %d 占用,与期望 PID %d 不一致", + pid, pm.adminPort, portResult.PID, pid) + return false + } + + // admin API 检测 url := fmt.Sprintf("http://127.0.0.1:%d/api/status", pm.adminPort) ctx, cancel := context.WithTimeout(context.Background(), APITimeout) defer cancel() @@ -628,7 +642,7 @@ func (pm *ProcessManager) isFRPReady(pid int) bool { return false } - // 检查是否有代理处于 running 状态 + // 检查代理状态 proxyLists := [][]struct { Name string `json:"name"` Type string `json:"type"` @@ -637,13 +651,8 @@ func (pm *ProcessManager) isFRPReady(pid int) bool { LocalAddr string `json:"local_addr"` RemoteAddr string `json:"remote_addr"` }{ - status.TCP, - status.UDP, - status.HTTP, - status.HTTPS, - status.STCP, - status.XTCP, - status.SUDP, + status.TCP, status.UDP, status.HTTP, status.HTTPS, + status.STCP, status.XTCP, status.SUDP, } for _, proxies := range proxyLists {