程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> VC++實現對遠程計算機屏幕的監視

VC++實現對遠程計算機屏幕的監視

編輯:vc教程

  前言

  在實際工程中,經常有施工現場和控制中心不在一起的情況,在這種情況一般多由工程技術人員往返穿梭其間來實現對遠程施工現場的情況了解和對控制中心的矯正控制。顯然這種工作方式的效率是很低下的,沒有充分發揮計算機網絡的強大優勢,其實通過網絡編程完全可以使技術人員在控制中心對位於工程現場的遠程計算機實施監視和控制。雖然互聯網上有不少遠程終端控制軟件如"超級間諜"、"冰河"等,但由於其帶有黑軟的性質,不能保證其在編程時沒有留有其他後門,因此從計算機安全的角度出發應當自行開發此類軟件。為避免本文所述技術被用於制造黑客類軟件,本文將不准備對遠程終端的控制部分做進一步的介紹,而將重點放在對遠程計算機屏幕界面的監視上。 1 數據信息在網絡上的傳送

  由於本地計算機是通過網絡來對遠程計算機實施監控,因此需要對網卡進行編程以實現往來於雙方的數據信息在網絡上的順暢通訊。可供選擇的方案有套接字、郵槽、命名管道等多種,本文在此選用開發和應用都比較靈活的流式套接字作為網絡通訊的基礎。考慮到實際情況,遠程被監視主機隨時為本地監控主機提供屏幕信息的服務,因此整個系統可以劃分為兩大模塊--服務器端和客戶機端,分別運行於遠程主機和本地監控主機,由客戶機向服務器發出連接請求,在建立連接後由服務器定時發送遠程屏幕信息給客戶機,客戶機接收到服務器發來的數據後將其顯示在本地主機。

  至於用流式套接字對網絡進行編程的主要過程可用下圖來表示。服務器方在使用套接字之前,首先必須擁有一個Socket,可用socket()函數創建之:

sock=socket(AF_INET,SOCK_STREAM,0);

  其中AF_INET 和SOCK_STREAM指定了創建的是采用了TCP/IP地址族的流式套接字。該套接字實際上是提供了一個通信端口,通過這個端口可與任何一個具有套接字端口的計算機實施通信。一旦獲取了新的套接字,應立即通過bind()將該套接字與本機上的一個端口建立關聯。需要預先對一個指向包含有本機IP地址和端口信息的sockaddr_in結構填充一些必要的信息,如本地端口號和本地主機地址等,並通過bind()將服務器進程在網絡上標識出來:

sockin_s.sin_family=AF_INET;
sockin_s.sin_addr.s_addr=0;
sockin_s.sin_port=htons(PORT);
bind(sock,(LPSOCKADDR)&sockin_s,sizeof(sockin_s));

  在完成接下來的listen()偵聽後,需要用accept()等待接收客戶端的連接,由於該函數在沒有客戶端進行申請連接之前會處於阻塞狀態,因此需要為其單獨開辟一個線程,以免影響到程序整體:

AfxBeginThread(Server,NULL);//創建一個新的線程
……
UINT Server(LPVOID lpVoid)
{
 CSurveillant_ServerView* pView=((CSurveillant_ServerVIEw*)((CFrameWnd*)
 AfxGetApp()->m_pMainWnd)->GetActiveVIEw());
 int nLen=sizeof(SOCKADDR);
 pView->newskt_s= accept(pView->sock,(LPSOCKADDR)& pVIEw->sockin_s,(LPINT)& nLen);
 WSAAsyncSelect(pView->newskt_s,pVIEw->m_hWnd,WM_MSG,FD_CLOSE);
 pVIEw->SetTimer(0,2500,NULL);
 return 1;
}

  在這裡通過WSAAsyncSelect()異步選擇函數來以異步的形式響應關心的網絡事件FD_CLOSE,並在該事件發生時發出自定義WM_MSG消息,通過響應這個消息可以得之當前與服務器聯系的客戶機程序已關閉退出,由於服務器部分是運行於遠程工程現場的,為了使控制中心的監控程序(客戶)在下次發出監控請求時能為其提供服務需要在WM_MSG的消息響應函數裡關閉由accept() 所產生的新的套接字newskt_s,並重新啟動該線程等待監控程序的再次連接。在accept()函數成功返回後,就可以在定時器響應函數裡用send() 函數與之建立了連接的監控主機定時發送捕獲的遠程屏幕信息了。

  作為客戶的監控程序,其實現過程要比服務器簡單許多。由於需要接收數據,因此在異步選擇函數中需要設定待監測的網絡事件為FD_CLOSE和FD_READ。在消息響應函數中可以通過對消息參數的低位字節進行判斷而區分出具體發生是何種網絡事件,並對其做出響應的反應。下面是監控端程序網絡部分的主要代碼:

……
IPaddr=inet_addr(strIP);
sock=socket(AF_INET,SOCK_STREAM,0); //創建套接字
sockin_c.sin_family=AF_INET;
sockin_c.sin_addr.S_un.S_addr=IPaddr;
sockin_c.sin_port=m_Port;
connect(sock,(LPSOCKADDR)&sockin_c,sizeof(sockin_c));//連接服務器
……
WSAAsyncSelect(sock,m_hWnd,WM_MSG,FD_READ|FD_CLOSE);
……

  通過異步選擇函數的設定,在有數據到達時會由FD_READ觸發WM_MSG消息,並在處理函數中通過調用recv ()將遠程主機的屏幕信息從網絡接收到緩存,並完成在本地機的重顯。通過以上幾步,已經初步具備了在遠程服務器和本地客戶機之間的網絡通訊能力,可以完成屏幕信息的網絡傳送任務。

  對遠程計算機屏幕的捕捉和顯示

  前面部分的工作只是為整個監控系統提供一個低層的網絡數據通訊的能力,也可以說是為現場主機和監控中心提供一個通信用信道。至於本文的中心議題--遠程監視工作則需要分別在現場主機和監控中心中完成對屏幕的捕捉和信息的再現。屏幕的捕捉可以采取先獲取桌面窗口指針並建立一個與之兼容的設備環境,然後創建一個與桌面窗口指針相兼容的內存位圖並以位圖的形式將屏幕圖像拷貝到新創建的位圖之中:

char dot[1572864]; //1024*768*2
CBitmap bmp; //內存位圖
CDC wdc; //設備環境
CDC* pDC; //指向桌面窗口的設備環境指針
……
static CWindowDC ddc(GetDesktopWindow()); //引用桌面窗口指針定義對象ddc
pDC=&ddc; //將指針pdc指向ddc
wdc.CreateCompatibleDC(pDC); //建立與ddc兼容的設備環境
bmp.CreateCompatibleBitmap(pDC,1024,768); //建立與ddc兼容的位圖
wdc.SelectObject(&bmp); //選擇bmp
……
wdc.BitBlt(0,0,1024,768,pDC,0,0,SRCCOPY); //把桌面圖像復制到wdc的bmp中

  這時雖以獲取到了屏幕的信息,並將其復制到內存位圖之中,但此時還不能直接將其發送出去,需要調用CBitmap 類的成員函數GetBitmapBits()來將圖像信息從內存位圖拷貝到緩存,並通過套接字的send()函數將緩存中存放的屏幕信息通過網絡從現場主機發送到控制中心。

  現場主機的屏幕信息在控制中心的再現,基本上是屏幕截取的逆過程:先建立一個同客戶區相關的設備環境並建立一個與之兼容的設備環境,然後按位圖格式在內存中創建一個與之兼容的內存位圖。在從網絡接收完一屏信息後,通過CBitmap的成員函數SetBitmapBits()把緩存中的屏幕信息按位圖格式拷貝到內存位圖,最後完成對內存位圖的顯示。其主要過程如下:

CDC* pDC=GetDC(); //引用用戶窗口指針定義對象pDC
wdc.CreateCompatibleDC(pDC); //建立與pDC兼容的device context
bmp.CreateCompatibleBitmap(pDC,1024,768); //建立與pDC兼容的位圖
wdc.SelectObject(&bmp);
……
iReadLen = recv(sock,buffer,60000,0); //從網絡接收數據
for(i=0;i{
 dot[pointer]=buffer[i];
 pointer++;
 if(pointer==1572864) //判斷接收到的信息是否已滿一屏
 {
  GetClIEntRect(&rect);
  bmp.SetBitmapBits(1572864,(LPVOID)dot); //把內存數據復制到bmp中
  //把bmp中圖像復制到用戶窗口中
  pDC->StretchBlt(0,0,rect.Width(),rect.Height(),&wdc,0,0,1024,768,SRCCOPY);
  pointer=0; //接收完一屏後指針復位,准備接收下一屏
 }
}

  服務程序的自動加載及擴展

  從功能上看,服務端程序只負責為遠程客戶提供服務,在全部運行期間根本不需要人為的外來干預,因此可以隱藏其界面並將其作成後台服務程序:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
 ……
 cs.cx=200;
 cs.cy=10;
 cs.style=WS_POPUP;
 cs.dwExStyle|=WS_EX_TOOLWINDOW;
 return TRUE;
}

  另外,由於現在計算機多具有通過Modem實現遠程喚醒的功能,因此如能使服務程序具備自啟動功能將實現遠程現場主機的無人值守。自啟動有多種方式:在Autoexec.bat、win.ini等文件中加入啟動命令、在"啟動"菜單裡加入指向程序的快捷方式、修改注冊表等。其中由於注冊表通常被人為改動的機會要小的多,因此通過修改注冊表實現自啟動是一種比較安全的方法。本文采取的方法是:先通過API函數CopyFile()將服務程序復制到系統目錄,然後對HKEY_LOCAL_MacHINE 的SoftwareMicrosoftWindowsCurrentVersionRun寫入一個字符串鍵值,該鍵值的內容是服務程序在系統目錄下的全路徑:

DWord type=REG_SZ;
DWord size=MAX_PATH;
LPCTSTR Rgspath="Software\Microsoft\Windows\CurrentVersion\Run" ;
……
GetSystemDirectory(SysPath,size); //獲取系統目錄
GetModuleFileName(NULL,CurrentPath,size); //獲取程序路徑
FileCurrentName = CurrentPath;
FileNewName = lstrcat(SysPath,"\Surveillant.exe");
ret = CopyFile(FileCurrentName,FileNewName,TRUE); //拷貝程序到系統目錄
……
//打開注冊表
ret=RegOpenKeyEx(HKEY_LOCAL_MacHINE,Rgspath,0,KEY_WRITE, &hKEY);
……
//寫入注冊表
ret=RegSetValueEx(hKEY,"Surveillant",NULL,type, FileNewName,size);
……
//關閉注冊表
RegCloseKey(hKEY);

  至於監控中心對現場主機的遠程控制,則主要是通過向對方程序發送用以標識消息的數據並在遠程主機接收完畢後用SendMessage()向指定窗口發送消息來完成的,可用CreateProcess();來啟動現場主機的程序以響應消息。由於該部分技術亦可用來編寫黑客軟件,故本文在此不便作進一步的描述。

  小結:

  本文主要針對基於流式套接字的低層網絡通訊模塊和建立在該模塊基礎之上的屏幕截取和復原技術的設計、實現作了較為詳細的介紹。本文所述監控系統在實際應用中取得了較好的效果。使工程技術人員能在控制中心及時了解到位於工程現場的計算機屏幕上的指示圖表的動態顯示,並根據監視結果作出及時的決策。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved