公司的考勤系统有两套,一套是门卡打卡上班,另一套是计算机登陆考勤网站上班。像我这样成天乘公交上班的人,不知道什么时候一堵车就迟到了。因此无聊加无奈,才写了下面的程序。。。。
首先考察一下门卡系统。因为要刷卡才能上班,刷卡数据保存在公司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来访问成员变量,并且只
有在早上八点到九点的时候向服务器发送考勤上班请求,然后自动关闭服务。这样以后就不会
迟到了。