Compare commits
2
Commits
5efc608835
...
f810e712ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f810e712ec | ||
|
|
8ab3672c4e |
No files matched your search
@@ -79,13 +79,15 @@ using connector::ConnectorService;
|
||||
|
||||
- (void)refreshStatus:(id)sender {
|
||||
(void)sender;
|
||||
self.connectionButton.title = self.service && self.service->running() ? @"断开连接" : @"启动连接";
|
||||
if (self.startupError.length > 0) {
|
||||
const BOOL ownsPort = self.service && self.service->running();
|
||||
const BOOL portOccupied = ownsPort || connector::connector_port_in_use();
|
||||
self.connectionButton.title = portOccupied ? @"停止连接" : @"启动连接";
|
||||
if (self.startupError.length > 0 && !portOccupied) {
|
||||
self.statusLabel.stringValue = [@"● 连接器未启动 · " stringByAppendingString:self.startupError];
|
||||
self.statusLabel.textColor = NSColor.systemRedColor;
|
||||
return;
|
||||
}
|
||||
if (!self.service || !self.service->running()) {
|
||||
if (!portOccupied) {
|
||||
self.statusLabel.stringValue = @"● 连接器已断开";
|
||||
self.statusLabel.textColor = NSColor.secondaryLabelColor;
|
||||
return;
|
||||
@@ -93,6 +95,11 @@ using connector::ConnectorService;
|
||||
const std::string spine = connector::find_spine_executable();
|
||||
const BOOL showPathControls = spine.empty();
|
||||
for (NSView* view in self.pathControls) view.hidden = !showPathControls;
|
||||
if (!ownsPort) {
|
||||
self.statusLabel.stringValue = @"● 端口 27843 正在运行 · 可停止连接";
|
||||
self.statusLabel.textColor = NSColor.systemGreenColor;
|
||||
return;
|
||||
}
|
||||
self.statusLabel.stringValue = spine.empty() ? @"● 连接器运行中 · 未找到 Spine" : @"● 连接器运行中 · Spine 已就绪";
|
||||
self.statusLabel.textColor = spine.empty() ? NSColor.systemOrangeColor : NSColor.systemGreenColor;
|
||||
}
|
||||
@@ -102,6 +109,10 @@ using connector::ConnectorService;
|
||||
if (self.service && self.service->running()) {
|
||||
self.service->stop();
|
||||
self.startupError = @"";
|
||||
} else if (connector::connector_port_in_use()) {
|
||||
std::string error;
|
||||
connector::stop_connector_port(error);
|
||||
self.startupError = [NSString stringWithUTF8String:error.c_str()];
|
||||
} else if (self.service) {
|
||||
std::string error;
|
||||
self.service->start(error);
|
||||
|
||||
@@ -141,13 +141,18 @@ std::wstring field_text(HWND field) {
|
||||
}
|
||||
|
||||
void refresh_status() {
|
||||
const bool owns_port = service.running();
|
||||
const bool port_occupied = owns_port || connector::connector_port_in_use();
|
||||
std::wstring status;
|
||||
if (!startup_error.empty()) {
|
||||
if (!startup_error.empty() && !port_occupied) {
|
||||
status = L"● 连接器未启动 · " + wide(startup_error);
|
||||
status_color = kErrorText;
|
||||
} else if (!service.running()) {
|
||||
} else if (!port_occupied) {
|
||||
status = L"● 连接器已断开";
|
||||
status_color = kSecondaryText;
|
||||
} else if (!owns_port) {
|
||||
status = L"● 端口 27843 正在运行 · 可停止连接";
|
||||
status_color = kSuccessText;
|
||||
} else if (connector::find_spine_executable().empty()) {
|
||||
status = L"● 连接器运行中 · 未找到 Spine";
|
||||
status_color = kWarningText;
|
||||
@@ -157,7 +162,7 @@ void refresh_status() {
|
||||
}
|
||||
SetWindowTextW(status_label, status.c_str());
|
||||
InvalidateRect(status_label, nullptr, TRUE);
|
||||
SetWindowTextW(connection_button, service.running() ? L"断开连接" : L"启动连接");
|
||||
SetWindowTextW(connection_button, port_occupied ? L"停止连接" : L"启动连接");
|
||||
const bool show_path = connector::find_spine_executable().empty();
|
||||
for (HWND control : {path_label, path_field, browse_button, automatic_button, save_button}) {
|
||||
if (control) ShowWindow(control, show_path ? SW_SHOW : SW_HIDE);
|
||||
@@ -246,6 +251,9 @@ LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lp
|
||||
if (service.running()) {
|
||||
service.stop();
|
||||
startup_error.clear();
|
||||
} else if (connector::connector_port_in_use()) {
|
||||
startup_error.clear();
|
||||
connector::stop_connector_port(startup_error);
|
||||
} else {
|
||||
startup_error.clear();
|
||||
service.start(startup_error);
|
||||
|
||||
@@ -20,6 +20,10 @@ std::filesystem::path to_path(const std::string& value);
|
||||
std::string platform_name();
|
||||
std::string find_spine_executable();
|
||||
bool valid_spine_executable(const std::string& path);
|
||||
// 查询/释放连接器固定使用的本地端口。释放操作针对当前监听进程,
|
||||
// 使用户可以从新打开的连接器窗口停止遗留实例或其他占用者。
|
||||
bool connector_port_in_use();
|
||||
bool stop_connector_port(std::string& error);
|
||||
ProcessResult run_process(const std::string& executable,
|
||||
const std::vector<std::string>& arguments,
|
||||
const std::filesystem::path& working_directory,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <fcntl.h>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
@@ -12,6 +14,38 @@
|
||||
#include <unistd.h>
|
||||
|
||||
namespace connector {
|
||||
namespace {
|
||||
|
||||
constexpr unsigned short kConnectorPort = 27843;
|
||||
|
||||
std::filesystem::path lsof_path() {
|
||||
for (const char* candidate : {"/usr/sbin/lsof", "/usr/bin/lsof"}) {
|
||||
if (std::filesystem::exists(candidate)) return candidate;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::set<pid_t> connector_port_pids() {
|
||||
const std::filesystem::path executable = lsof_path();
|
||||
if (executable.empty()) return {};
|
||||
const ProcessResult result = run_process(executable.string(),
|
||||
{"-nP", "-tiTCP:" + std::to_string(kConnectorPort), "-sTCP:LISTEN"}, {}, 3);
|
||||
if (result.timed_out || (result.exit_code != 0 && result.exit_code != 1)) return {};
|
||||
std::set<pid_t> pids;
|
||||
std::istringstream lines(result.output);
|
||||
std::string line;
|
||||
while (std::getline(lines, line)) {
|
||||
try {
|
||||
const long value = std::stol(line);
|
||||
if (value > 0) pids.insert(static_cast<pid_t>(value));
|
||||
} catch (...) {
|
||||
// lsof 可能输出诊断信息;只接受纯数字 PID。
|
||||
}
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProcessResult run_process(const std::string& executable,
|
||||
const std::vector<std::string>& arguments,
|
||||
@@ -70,6 +104,46 @@ ProcessResult run_process(const std::string& executable,
|
||||
return result;
|
||||
}
|
||||
|
||||
bool connector_port_in_use() {
|
||||
return !connector_port_pids().empty();
|
||||
}
|
||||
|
||||
bool stop_connector_port(std::string& error) {
|
||||
const std::set<pid_t> pids = connector_port_pids();
|
||||
if (pids.empty()) {
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
for (const pid_t pid : pids) {
|
||||
if (pid == getpid()) {
|
||||
error = "无法从外部停止当前连接器进程";
|
||||
return false;
|
||||
}
|
||||
if (kill(pid, SIGTERM) != 0) {
|
||||
error = "无法停止占用端口 27843 的进程,请检查系统权限";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
if (!connector_port_in_use()) {
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
for (const pid_t pid : pids) {
|
||||
if (kill(pid, 0) == 0) kill(pid, SIGKILL);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (connector_port_in_use()) {
|
||||
error = "已请求停止进程,但端口 27843 仍未释放";
|
||||
return false;
|
||||
}
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace connector
|
||||
|
||||
#endif
|
||||
@@ -1,17 +1,24 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <iphlpapi.h>
|
||||
|
||||
#include "platform.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace connector {
|
||||
namespace {
|
||||
|
||||
constexpr unsigned short kConnectorPort = 27843;
|
||||
|
||||
std::wstring wide(const std::string& value) {
|
||||
if (value.empty()) return {};
|
||||
const int size = MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0);
|
||||
@@ -35,6 +42,26 @@ std::wstring quote(const std::string& value) {
|
||||
return result + L'\"';
|
||||
}
|
||||
|
||||
std::set<DWORD> connector_port_pids() {
|
||||
DWORD size = 0;
|
||||
DWORD result = GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET,
|
||||
TCP_TABLE_OWNER_PID_LISTENER, 0);
|
||||
if (result != ERROR_INSUFFICIENT_BUFFER || size == 0) return {};
|
||||
std::vector<unsigned char> storage(size);
|
||||
result = GetExtendedTcpTable(storage.data(), &size, FALSE, AF_INET,
|
||||
TCP_TABLE_OWNER_PID_LISTENER, 0);
|
||||
if (result != NO_ERROR) return {};
|
||||
const auto* table = reinterpret_cast<const MIB_TCPTABLE_OWNER_PID*>(storage.data());
|
||||
std::set<DWORD> pids;
|
||||
for (DWORD index = 0; index < table->dwNumEntries; ++index) {
|
||||
const auto& row = table->table[index];
|
||||
if (ntohs(static_cast<u_short>(row.dwLocalPort)) == kConnectorPort && row.dwOwningPid != 0) {
|
||||
pids.insert(row.dwOwningPid);
|
||||
}
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProcessResult run_process(const std::string& executable,
|
||||
@@ -93,6 +120,46 @@ ProcessResult run_process(const std::string& executable,
|
||||
return result;
|
||||
}
|
||||
|
||||
bool connector_port_in_use() {
|
||||
return !connector_port_pids().empty();
|
||||
}
|
||||
|
||||
bool stop_connector_port(std::string& error) {
|
||||
const std::set<DWORD> pids = connector_port_pids();
|
||||
if (pids.empty()) {
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
for (const DWORD pid : pids) {
|
||||
if (pid == GetCurrentProcessId()) {
|
||||
error = "无法从外部停止当前连接器进程";
|
||||
return false;
|
||||
}
|
||||
HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, pid);
|
||||
if (!process) {
|
||||
error = "无法停止占用端口 27843 的进程,请以管理员身份运行后重试";
|
||||
return false;
|
||||
}
|
||||
const BOOL stopped = TerminateProcess(process, 0);
|
||||
if (stopped) WaitForSingleObject(process, 3000);
|
||||
CloseHandle(process);
|
||||
if (!stopped) {
|
||||
error = "停止占用端口 27843 的进程失败";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
if (!connector_port_in_use()) {
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
error = "进程已停止,但端口 27843 仍未释放";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace connector
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user