公司的考勤系統有兩套,一套是門卡打卡上班,另一套是電腦登陸考勤網站上班。像我這樣成天乘公交上班的人,不知道什麼時候一堵車就遲到了。因此無聊加無奈,才寫了下面的程式。。。。 首先考察一下門卡系統。因為要刷卡才能上班,刷卡資料儲存在公司linux伺服器上,沒啥手腳可以動。於是轉向網站考勤系統。這個網站是用jsp寫得,於是打算用EffeTech HTTP Sniffer偵查一下。本來以為需要很費勁的,其實嗅探下來結果非常簡單,登陸請求就是用URL rewrite技術封裝的。把這個url請求截獲之後,就可以開始類比請求了。用啥語言呢,java中可以直接用socket發請求,但是win32的Platform Sdk中有更高層的HTTP協議封裝——winhttp。於是就簡單地設計了下面的請求發送類 //-----------------------------------------------------------------------// Name: class CMyWinHttp// Desc: Connect to the web server, process the interaction based on http//-----------------------------------------------------------------------class CMyWinHttp{ HINTERNET m_hSession; HINTERNET m_hConnect; HINTERNET m_hRequest; public: CMyWinHttp(); INT Init(); VOID Close(); BOOL ProcessOneRequest(LPCWSTR pwszObjectName); }; 建構函式就不說了,反正是初始化成員變數為NULL。Init函數比較簡單,其中HOST和PORT就是伺服器IP和80連接埠。///---------------------------------------------------------------/// DESC: Init the connection/// Unless the host/port/user-agent changes...///---------------------------------------------------------------INT CMyWinHttp::Init(){ // Use WinHttpOpen to obtain a session handle. m_hSession = WinHttpOpen( USER_AGENT, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 ); // Specify an HTTP server. if( m_hSession ) m_hConnect = WinHttpConnect( m_hSession, HOST, PORT, 0 ); if ( m_hConnect ) return 0; return GetLastError();} Close()函數也不說了,關閉一些控制代碼。對產生和發送Get請求的封裝處理如下:BOOL CMyWinHttp::ProcessOneRequest(LPCWSTR pwszObjectName){ DWORD dwSize = 0; DWORD dwDownloaded = 0; LPSTR pszOutBuffer; BOOL bResults = FALSE; // Create an HTTP request handle. if( m_hConnect ) m_hRequest = WinHttpOpenRequest( m_hConnect, L"GET", pwszObjectName, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0 ); // Send a request. if( m_hRequest ) bResults = WinHttpSendRequest( m_hRequest, ADDITIONAL, -1L, WINHTTP_NO_REQUEST_DATA, 0, 0, 0 ); // End the request. if( bResults ) bResults = WinHttpReceiveResponse( m_hRequest, NULL ); // Keep checking for data until there is nothing left. if( bResults ) { do { // Check for available data. dwSize = 0; if( !WinHttpQueryDataAvailable( m_hRequest, &dwSize ) ) printf( "Error %u in WinHttpQueryDataAvailable./n", GetLastError( ) ); // Allocate space for the buffer. pszOutBuffer = new char[dwSize+1]; if( !pszOutBuffer ) { printf( "Out of memory/n" ); dwSize=0; } else { // Read the data. ZeroMemory( pszOutBuffer, dwSize+1 ); if( !WinHttpReadData( m_hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded ) ) printf( "Error %u in WinHttpReadData./n", GetLastError( ) ); else printf( "%s", pszOutBuffer ); // Free the memory allocated to the buffer. delete [] pszOutBuffer; } } while( dwSize > 0 ); } // Report any errors. if( !bResults ) printf( "Error %d has occurred./n", GetLastError( ) ); return bResults;} 主函數只要這麼寫就可以了,其中SUBMIT是解析出來的GET請求。int _tmain(int argc, _TCHAR* argv[]){ CMyWinHttp* myWinHttp = new CMyWinHttp(); if ( myWinHttp->Init() != 0 ) return -1; if ( !myWinHttp->ProcessOneRequest(SUBMIT) ) return -1; myWinHttp->Close(); return 0;} 到此九陽神功略有小成,下面要關注的就是怎麼讓它每天都可以自己跑。可以選擇放到計劃任務中,可是總有設定的麻煩。於是打算寫個服務,只有在系統開機的時候運行一次,然後再把機器BIOS設定為定時啟動就可以了(俺rp不錯,正好機器的BIOS中有這個功能,而且還能選擇autoboot only weekday,暈一個)。網上介紹編寫系統服務的鋪天蓋地,我把那個NTService修改了一下就可以用了。類設計如下://-----------------------------------------------------------------------------// Name: class CService// Desc: System service registration//-----------------------------------------------------------------------------class CService{ DWORD dwAttendantTime; TCHAR* lpServiceName;public: CService(); //~CService(); static void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] ); static void WINAPI ServiceControlHandler( DWORD controlCode ); void InstallService(); void UninstallService(); void RunService(); SERVICE_STATUS serviceStatus; SERVICE_STATUS_HANDLE serviceStatusHandle; static CService* m_This; HANDLE stopServiceEvent; }; 其中有兩個靜態成員函數,用來做win32api的回呼函數。哎,沒有c#的delegate爽。順便提一下,網上那篇高效能c++委託的實現看起來不太實用,如果不強調高效能的話使用靜態成員函數作Callback函數就可以了。注意有一個靜態成員指標m_This指向類執行個體,在回呼函數中可以使用它訪問類的成員(這個事情在java中是完全不可思議的,因為Java中沒有指標,所以Java只能使用內部類的技術來完成圖形介面的事件相應)。跑題了,下面回過來看ServiceMain()函數
//-----------------------------------------------------------------------------/// @brief Static Function ServiceMain for CallBack/// @note Entry point of the service//-----------------------------------------------------------------------------void CService::ServiceMain( DWORD argc, TCHAR* argv[] ){ CService& ThisService = *m_This; struct tm* tm; time_t now; DWORD minnow; CMyWinHttp* myWinHttp; ThisService.dwAttendantTime = GenerateRandomTime(); // initialise service status ThisService.serviceStatus.dwServiceType = SERVICE_WIN32; ThisService.serviceStatus.dwCurrentState = SERVICE_STOPPED; ThisService.serviceStatus.dwControlsAccepted = 0; ThisService.serviceStatus.dwWin32ExitCode = NO_ERROR; ThisService.serviceStatus.dwServiceSpecificExitCode = NO_ERROR; ThisService.serviceStatus.dwCheckPoint = 0; ThisService.serviceStatus.dwWaitHint = 0; ThisService.serviceStatusHandle = RegisterServiceCtrlHandler( ThisService.lpServiceName, ServiceControlHandler ); if ( ThisService.serviceStatusHandle ) { // Service is starting ThisService.serviceStatus.dwCurrentState = SERVICE_START_PENDING; SetServiceStatus( ThisService.serviceStatusHandle, &(ThisService.serviceStatus) ); // Do initialization ThisService.stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0); // Running ThisService.serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); ThisService.serviceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus( ThisService.serviceStatusHandle, &(ThisService.serviceStatus) ); do { //Beep( 1000, 100 ); //Sleep(100); time(&now); tm = localtime(&now); minnow = tm->tm_min; // only exectue at 8:00~9:00 on weekday if ( (tm->tm_wday == 0) || (tm->tm_wday == 6) || (tm->tm_hour != 8) ) { Sleep(2000); SetEvent( ThisService.stopServiceEvent ); break; } if (ThisService.dwAttendantTime+38<minnow) { // for example: now is 8:45, min is 26, then do following: myWinHttp = new CMyWinHttp(); myWinHttp->Init(); myWinHttp->ProcessOneRequest(QIANZ_SUBMIT); myWinHttp->Close(); SetEvent( ThisService.stopServiceEvent ); } else { Sleep(1000); } } while ( WaitForSingleObject( ThisService.stopServiceEvent, 1000 ) == WAIT_TIMEOUT ); // Service was stopped ThisService.serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus( ThisService.serviceStatusHandle, &(ThisService.serviceStatus) ); // do cleanup here CloseHandle( ThisService.stopServiceEvent ); ThisService.stopServiceEvent = 0; // service is now stopped ThisService.serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); ThisService.serviceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus( ThisService.serviceStatusHandle, &(ThisService.serviceStatus) ); }} 看明白了嗎?使用引用CService& ThisService = *m_This來訪問成員變數,並且只有在早上八點到九點的時候向伺服器發送考勤上班請求,然後自動關閉服務。這樣以後就不會遲到了。