症状(SYMPTOM):
服务端tvnserver-2.0.4,客户端tvnviewer-1.5.4,采用vs2010编译,操作系统为WinXP SP2/SP3。客户端在连接并且通过验证后报Connection Closed。该问题与验证方式、服务端屏幕分辨率等无关。该症状不是必然发生在每台机器上,经测试,部分环境下不会出现,但没有得到精确地规律。
原因(CAUSE):
经查,该症状是由于服务器在验证客户端后为客户端创建桌面对象时,返回NULL,服务器主动断开了客户端的连接所致,见RfbClient.cpp文件的RfbClient::execute函数,代码如下:
void RfbClient::execute(){
...
m_desktop = m_extAuthListener->onClientAuth(this);
...
}
RfbClient是RFB协议的客户端类,服务器为每个RFB客户端创建一个RfbClient对象,每个对象有一个工作线程,RfbClient::execute是工作线程方法。m_desktop返回NULL,导致抛出异常,服务器主动关闭连接。继续跟踪,调用路径(调用栈)如下:
Thread::threadProc()
RfbClient::execute()---(rfb-sconn工程的RfbClient.cpp)
RfbClientManager::onClientAuth()---(RfbClientManager.cpp)
WinDesktop::WinDesktop()---(WinDesktop.cpp)
WindowsUserInput::WindowsUserInput(ClipboardListener *clipboardListener,bool ctrlAltDelEnabled)---(WindowsUserInput.cpp)
KeyEvent::KeyEvent(bool ctrlAltDelEnabled)---(KeyEvent.cpp)
InputInjector::InputInjector(bool ctrlAltDelEnabled)---(InputInjector.cpp)
void InputInjector::resetModifiers()---(win-system工程的InputInjector.cpp)
void InputInjector::injectKeyEvent(BYTE vkCode, bool release, bool extended)---(win-system工程的InputInjector.cpp)
最后在inputInjector::resetModifiers方法中执行injectKeyEvent(VK_LWIN, true);语句产生异常,inputInjector::resetModifiers方法代码如下:
void InputInjector::resetModifiers()
{
injectKeyEvent(VK_MENU, true);
injectKeyEvent(VK_LMENU, true);
injectKeyEvent(VK_RMENU, true);
injectKeyEvent(VK_SHIFT, true);
injectKeyEvent(VK_LSHIFT, true);
injectKeyEvent(VK_RSHIFT, true);
injectKeyEvent(VK_CONTROL, true);
injectKeyEvent(VK_LCONTROL, true);
injectKeyEvent(VK_RCONTROL, true);
injectKeyEvent(VK_LWIN, true);///<此处产生异常
injectKeyEvent(VK_RWIN, true);
injectKeyEvent(VK_DELETE, true);
}
InputInjector::injectKeyEvent代码如下:
void InputInjector::injectKeyEvent(BYTE vkCode, bool release, bool extended){
...
INPUT keyEvent = {0};
keyEvent.type = INPUT_KEYBOARD;
keyEvent.ki.wVk = vkCode;
keyEvent.ki.wScan = MapVirtualKey(vkCode, 0);
if (release) {
keyEvent.ki.dwFlags = KEYEVENTF_KEYUP;
}
if (extended) {
keyEvent.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
if (SendInput(1, &keyEvent, sizeof(keyEvent)) == 0) {///<此处执行失败,GetLastError返回1350
DWORD errCode = GetLastError();
if (errCode != ERROR_SUCCESS) {
throw SystemException(errCode);///<执行到此处抛出异常
} else {
throw Exception(_T("SendInput() function failed"));
}
}
}
...
}
SendInput函数模拟VK_LWIN(左边窗口键)失败,返回NULL。GetLastError返回1350,为“无法在与安全性无关联的对象上运行安全性操作。”。
为查明SendInput函数为何不能模拟VK_LWIN键,特用vs2010写了2个测试工程,一个是控制台应用程序,一个是MFC基于对话框的应用程序。控制台应用程序执行SendInput返回0,GetLastError返回错误码0;MFC程序执行SendInput返回0,GetLastError返回错误码127。与TightVNC中的返回码都不一致。时间关系,未进一步分析SendInput函数失败的真正原因。
解决方案(SOLUTION):
直接注释掉InputInjector::resetModifiers函数中的模拟VK_LWIN的代码,修改后的代码为:
void InputInjector::resetModifiers()
{
injectKeyEvent(VK_MENU, true);
injectKeyEvent(VK_LMENU, true);
injectKeyEvent(VK_RMENU, true);
injectKeyEvent(VK_SHIFT, true);
injectKeyEvent(VK_LSHIFT, true);
injectKeyEvent(VK_RSHIFT, true);
injectKeyEvent(VK_CONTROL, true);
injectKeyEvent(VK_LCONTROL, true);
injectKeyEvent(VK_RCONTROL, true);
//injectKeyEvent(VK_LWIN, true);///<此处产生异常
//injectKeyEvent(VK_RWIN, true);
injectKeyEvent(VK_DELETE, true);
}
重新编译win-system工程和tvnserver工程,问题得到解决。
这只是个临时解决办法,最终的解决办法是找出SendInput函数失败的原因,然后寻找解决之道。