粒子连接器修改
This commit is contained in:
1 parent
bd0771747d
commit
8ab3672c4e
5 files changed
+170
-6
No files matched your search
@@ -79,13 +79,15 @@ using connector::ConnectorService;
|
|||||||
|
|
||||||
- (void)refreshStatus:(id)sender {
|
- (void)refreshStatus:(id)sender {
|
||||||
(void)sender;
|
(void)sender;
|
||||||
self.connectionButton.title = self.service && self.service->running() ? @"断开连接" : @"启动连接";
|
const BOOL ownsPort = self.service && self.service->running();
|
||||||
if (self.startupError.length > 0) {
|
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.stringValue = [@"● 连接器未启动 · " stringByAppendingString:self.startupError];
|
||||||
self.statusLabel.textColor = NSColor.systemRedColor;
|
self.statusLabel.textColor = NSColor.systemRedColor;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!self.service || !self.service->running()) {
|
if (!portOccupied) {
|
||||||
self.statusLabel.stringValue = @"● 连接器已断开";
|
self.statusLabel.stringValue = @"● 连接器已断开";
|
||||||
self.statusLabel.textColor = NSColor.secondaryLabelColor;
|
self.statusLabel.textColor = NSColor.secondaryLabelColor;
|
||||||
return;
|
return;
|
||||||
@@ -93,6 +95,11 @@ using connector::ConnectorService;
|
|||||||
const std::string spine = connector::find_spine_executable();
|
const std::string spine = connector::find_spine_executable();
|
||||||
const BOOL showPathControls = spine.empty();
|
const BOOL showPathControls = spine.empty();
|
||||||
for (NSView* view in self.pathControls) view.hidden = !showPathControls;
|
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.stringValue = spine.empty() ? @"● 连接器运行中 · 未找到 Spine" : @"● 连接器运行中 · Spine 已就绪";
|
||||||
self.statusLabel.textColor = spine.empty() ? NSColor.systemOrangeColor : NSColor.systemGreenColor;
|
self.statusLabel.textColor = spine.empty() ? NSColor.systemOrangeColor : NSColor.systemGreenColor;
|
||||||
}
|
}
|
||||||
@@ -102,6 +109,10 @@ using connector::ConnectorService;
|
|||||||
if (self.service && self.service->running()) {
|
if (self.service && self.service->running()) {
|
||||||
self.service->stop();
|
self.service->stop();
|
||||||
self.startupError = @"";
|
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) {
|
} else if (self.service) {
|
||||||
std::string error;
|
std::string error;
|
||||||
self.service->start(error);
|
self.service->start(error);
|
||||||
|
|||||||
@@ -141,13 +141,18 @@ std::wstring field_text(HWND field) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void refresh_status() {
|
void refresh_status() {
|
||||||
|
const bool owns_port = service.running();
|
||||||
|
const bool port_occupied = owns_port || connector::connector_port_in_use();
|
||||||
std::wstring status;
|
std::wstring status;
|
||||||
if (!startup_error.empty()) {
|
if (!startup_error.empty() && !port_occupied) {
|
||||||
status = L"● 连接器未启动 · " + wide(startup_error);
|
status = L"● 连接器未启动 · " + wide(startup_error);
|
||||||
status_color = kErrorText;
|
status_color = kErrorText;
|
||||||
} else if (!service.running()) {
|
} else if (!port_occupied) {
|
||||||
status = L"● 连接器已断开";
|
status = L"● 连接器已断开";
|
||||||
status_color = kSecondaryText;
|
status_color = kSecondaryText;
|
||||||
|
} else if (!owns_port) {
|
||||||
|
status = L"● 端口 27843 正在运行 · 可停止连接";
|
||||||
|
status_color = kSuccessText;
|
||||||
} else if (connector::find_spine_executable().empty()) {
|
} else if (connector::find_spine_executable().empty()) {
|
||||||
status = L"● 连接器运行中 · 未找到 Spine";
|
status = L"● 连接器运行中 · 未找到 Spine";
|
||||||
status_color = kWarningText;
|
status_color = kWarningText;
|
||||||
@@ -157,7 +162,7 @@ void refresh_status() {
|
|||||||
}
|
}
|
||||||
SetWindowTextW(status_label, status.c_str());
|
SetWindowTextW(status_label, status.c_str());
|
||||||
InvalidateRect(status_label, nullptr, TRUE);
|
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();
|
const bool show_path = connector::find_spine_executable().empty();
|
||||||
for (HWND control : {path_label, path_field, browse_button, automatic_button, save_button}) {
|
for (HWND control : {path_label, path_field, browse_button, automatic_button, save_button}) {
|
||||||
if (control) ShowWindow(control, show_path ? SW_SHOW : SW_HIDE);
|
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()) {
|
if (service.running()) {
|
||||||
service.stop();
|
service.stop();
|
||||||
startup_error.clear();
|
startup_error.clear();
|
||||||
|
} else if (connector::connector_port_in_use()) {
|
||||||
|
startup_error.clear();
|
||||||
|
connector::stop_connector_port(startup_error);
|
||||||
} else {
|
} else {
|
||||||
startup_error.clear();
|
startup_error.clear();
|
||||||
service.start(startup_error);
|
service.start(startup_error);
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ std::filesystem::path to_path(const std::string& value);
|
|||||||
std::string platform_name();
|
std::string platform_name();
|
||||||
std::string find_spine_executable();
|
std::string find_spine_executable();
|
||||||
bool valid_spine_executable(const std::string& path);
|
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,
|
ProcessResult run_process(const std::string& executable,
|
||||||
const std::vector<std::string>& arguments,
|
const std::vector<std::string>& arguments,
|
||||||
const std::filesystem::path& working_directory,
|
const std::filesystem::path& working_directory,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
@@ -12,6 +14,38 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace connector {
|
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,
|
ProcessResult run_process(const std::string& executable,
|
||||||
const std::vector<std::string>& arguments,
|
const std::vector<std::string>& arguments,
|
||||||
@@ -70,6 +104,46 @@ ProcessResult run_process(const std::string& executable,
|
|||||||
return result;
|
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
|
} // namespace connector
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,17 +1,24 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <winsock2.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <set>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace connector {
|
namespace connector {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr unsigned short kConnectorPort = 27843;
|
||||||
|
|
||||||
std::wstring wide(const std::string& value) {
|
std::wstring wide(const std::string& value) {
|
||||||
if (value.empty()) return {};
|
if (value.empty()) return {};
|
||||||
const int size = MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0);
|
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'\"';
|
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
|
} // namespace
|
||||||
|
|
||||||
ProcessResult run_process(const std::string& executable,
|
ProcessResult run_process(const std::string& executable,
|
||||||
@@ -93,6 +120,46 @@ ProcessResult run_process(const std::string& executable,
|
|||||||
return result;
|
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
|
} // namespace connector
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
Reference in new issue
Block a user