taocoding


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

boost中各种各样的timer

发表于 2010-03-31 | 分类于 C++

最近由于工作需要,大量使用boost,某天闲来无事整了个hello world版的timer实现。

#include <boost/thread.hpp>

#include <boost/date_time.hpp>

#include <iostream>

#include <boost/function.hpp>
void threadTimer( boost::posix_time::time_duration duration ,boost::function<void (void)> funCallback)
{
//使用localtime
boost::posix_time::ptime ptimeCur( boost::posix_time::second_clock::local_time() );
while ( true )
{
boost::posix_time::ptime ptimeNow( boost::posix_time::second_clock::local_time() );
//超过了预定的时间,由于下面有sleep因此这里算时间长度时减去相应的长度
//纯属学习,没啥实际用途,O(∩_∩)O~
if ( boost::posix_time::time_period( ptimeCur, ptimeNow ).length() > (duration -boost::posix_time::seconds(1)) )
{
funCallback();
ptimeCur = ptimeNow;
}
//为了不让cpu被占用,这里sleep下
boost::xtime xt;
boost::xtime_get(&xt, boost::TIME_UTC);
xt.sec += 1;
boost::thread::sleep( xt );
}
}
void timeFun()
{
std::cout << “time out…” << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
boost::thread thread(threadTimer,boost::posix_time::time_duration( boost::posix_time::seconds(3) ), timeFun );
thread.join();

return 0;
} 

虽然代码不长,但是涉及到boost好几个库:thread,data_time,function,呵呵,纯属学习了。在查找thread::sleep使用过程中,

顺带发现thread里面自带的一个例子(稍加修改)即可作为timer使用:

#include <boost/thread/thread.hpp>

#include <boost/thread/xtime.hpp>

#include <iostream>
struct thread_alarm
{
thread_alarm(int secs) : m_secs(secs) { }
void operator()()
{
while (true)
{
boost::xtime xt;
boost::xtime_get(&xt, boost::TIME_UTC);
xt.sec += m_secs;
boost::thread::sleep(xt);
std::cout << “alarm sounded…” << std::endl;
}
}
int m_secs;
};
int main(int argc, char* argv[])
{
int secs = 5;
std::cout << “setting alarm for 5 seconds…” << std::endl;
thread_alarm alarm(secs);
boost::thread thrd(alarm);
thrd.join();
return 0;
} 

这里巧妙利用sleep实现timer,值得学习。

ps:今天研究asio过程中,发现deadline_timer比较好用,另外加上还有个单独的timer库,看来timer真是无处不在呀。

长连接和Keepalive

发表于 2010-03-01 | 分类于 系统编程

原文链接:http://www.cppblog.com/zhangyq/archive/2010/02/28/108615.html

之前一个项目的服务器端好像就是在待机之后,tcp链接已经断开,但是服务器没有检测到,看了这篇文章之后就比较明了了。

 

TCP协议中有长连接和短连接之分。短连接在数据包发送完成后就会自己断开,长连接在发包完毕后,会在一定的时间内保持连接,即我们通常所说的Keepalive(存活定时器)功能。
默认的Keepalive超时需要7,200,000 milliseconds,即2小时,探测次数为5次。它的功效和用户自己实现的心跳机制是一样的。开启Keepalive功能需要消耗额外的宽带和流量,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,Keepalive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接。

keepalive并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped),(2)它们消费了不必要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。

一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。

个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。

也可以在客户端设置存活器选项,且没有不允许这样做的理由,但通常设置在服务器。如果连接两端都需要探测对方是否消失,那么就可以在两端同时设置(比如NFS)。







keepalive工作原理:

若在一个给定连接上,两小时之内无任何活动,服务器便向客户端发送一个探测段。(我们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:

1) 客户端主机依旧活跃(up)运行,并且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,如果在这两个小时到期之前,连接上发生应用程序的通信,则定时器重新为往下的两小时复位,并且接着交换数据。

2) 客户端已经崩溃,或者已经关闭(down),或者正在重启过程中。在这两种情况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,并且在75秒之后超时。服务器将总共发送10个这样的探测,每个探测75秒。如果没有收到一个响应,它就认为客户端主机已经关闭并终止连接。

3) 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。

4) 客户端主机活跃运行,但从服务器不可到达。这与状态2类似,因为TCP无法区别它们两个。它所能表明的仅是未收到对其探测的回复。

 

服务器不必担心客户端主机被关闭然后重启的情况(这里指的是操作员执行的正常关闭,而不是主机的崩溃)。当系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以允许服务器检测这种状态。

在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,通过服务器的TCP,返回给服务器应用程序错误信息。(通常服务器向网络发出一个读请求,等待客户端的数据。如果存活特征返回一个错误信息,则将该信息作为读操作的返回值返回给服务器。)在状态2,错误信息类似于“连接超时”。状态3则为“连接被对方复位”。第四种状态看起来像连接超时,或者根据是否收到与该连接相关的ICMP错误信息,而可能返回其它的错误信息。

linux

内核包含对keepalive的支持。其中使用了三个参数:tcp_keepalive_time(开启keepalive的闲置时 长)tcp_keepalive_intvl(keepalive探测包的发送间隔)和tcp_keepalive_probes (如果对方不予应答,探测包的发送次数);在liunx中,keepalive是一个开关选项,可以通过函数来使能。具体地说,可以使用以下代码:
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));


 

当tcp检测到对端socket不再可用时(不能发出探测包,或探测包没有收到ACK的响应包),select会返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT。此时TCP的状态是断开的。

keepalive参数设置代码如下:

// 开启KeepAlive
BOOL bKeepAlive = TRUE;
int nRet = ::setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));
if (nRet == SOCKET_ERROR)
{
return FALSE;
}

// 设置KeepAlive参数
tcp_keepalive alive_in                = {0};
tcp_keepalive alive_out                
= {0};
alive_in.keepalivetime                
= 5000;                // 开始首次KeepAlive探测前的TCP空闭时间
alive_in.keepaliveinterval        = 1000;                // 两次KeepAlive探测间的时间间隔
alive_in.onoff                                = TRUE;
unsigned 
long ulBytesReturn = 0;
nRet 
= WSAIoctl(socket_handle, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in),
&alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);
if (nRet == SOCKET_ERROR)
{
return FALSE;
}

开启Keepalive选项之后,对于使用IOCP模型的服务器端程序来说,一旦检测到连接断开,GetQueuedCompletionStatus函数将立即返回FALSE,使得服务器端能及时清除该连接、释放该连接相关的资源。对于使用select模型的客户端来说,连接断开被探测到时,以recv目的阻塞在socket上的select方法将立即返回SOCKET_ERROR,从而得知连接已失效,客户端程序便有机会及时执行清除工作、提醒用户或重新连接。

TCP连接非正常断开的检测(KeepAlive探测)

此处的”非正常断开”指TCP连接不是以优雅的方式断开,如网线故障等物理链路的原因,还有突然主机断电等原因

有两种方法可以检测:1.TCP连接双方定时发握手消息 2.利用TCP协议栈中的KeepAlive探测

第二种方法简单可靠,只需对TCP连接两个Socket设定KeepAlive探测。


行为模式之Observer

发表于 2010-02-28 | 分类于 设计模式

最近项目中遇到一个需求:在服务器进行一次调度以后,要给客户端一个通知,而客户端在调用服务端的调度之前会进行通知的注册。这是一个典型的Observer模式,因此顺便将观察者模式再复习了一次,这个模式其实就只涉及到主题和观察者两个类,也相对比较简单,UML类图如下:

顺序图:

有了这两个图,观察者模式就一目了然了,这里在讲HF上面的对应的设计原则贴上来:

1找出程序中会变化的方面,然后将和固定不变的方面相分离—>在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题,这就叫提前规划!

2针对接口编程,不针对实现编程–>主题和观察者都是用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。

3多用组合,少用继承–>观察者模式利用“组合”将许多观察者组合进主题中,对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的。

深圳之行

发表于 2010-01-19

      从深圳回武汉刚好一个月了,上个月19号坐的车,离开了生活了16个月的深圳,再次回到熟悉的城市,但是心情完全不一样了。发现呆了4年的武昌除了学校那块比较熟悉外,其它好多地方还是那么的陌生,这也从某种意义上反映了自己的大学生活不是很丰富。

      在深圳的一年多,虽然对之前的公司没有太多好感,但是作为自己工作的第一步,只能说既然选择了那么肯定也是也有其存在的理由,由于公司所处的行业特殊,前两个月竟然是在车间度过的,虽然当时是很郁闷的,但是后来发现这也是一段值得珍藏的经历,因为在接下来的基本上是不可能再有这样的机会了。

      从车间回来后就差不多开始开工了,工作的内容以及能力要求基本上在毕业之前就都差不多,因此让自己有惊无险的度过了那个“工作危机”,正是由于自己相对来说比较擅长界面,前几个月基本上就是和界面打交道,这个过程让自己对Windows消息有了更进一步的认识同时对当前的Windows界面开发也有了一定的研究,种种原因导致开发不是很规范,加上一些客观原因09年开年那会自己已经对当时做界面相当的反感了,这段时间基本上就是在自己写的一个Dialog基类上面进行开发,以及之前界面的移植,不过就现在来说虽然我更希望朝通讯方面发展,但是仍然对界面有很大的兴趣,不知道为什么总是有那么多人看不起做界面,甚至看不起做界面的人,这里不是指那种用现成的控件和界面库,就来一个自定义控件来说吧,我觉得这里面涉及的东西也不少,也不是一天两天就能搞定的吧。

      到了3月份,我们这个小组的几个一致认为需要重新将之前那个“项目”从头设计开发。这个过程大概是3月到6月吧,没办法自己还是不情愿的把界面这部分模块接下来了,当然这次也不完全是界面了,还有一些逻辑功能以及相关的模块设计。正是这段时间,终于算是正式的用上了第一设计模式–singleton,由于当时对设计还没有太多的想法,导致后面开发又成了东成西就,每次想修改设计最终还是是被庞大的工作量给吓回去了。这段时间主要学到了界面闪烁的解决方案,socket通讯编程的进一步学习,最突出的应该算是调试了。

      6月份,上面又给在当前项目上面加了个视频会议的功能,这段时间开始到10月份算是相当充实的一段时间,视频会议涉及的内容也是相当的多,编解码,音视频数据传输,会议管理及维护,当然还少不了界面。前面两个模块基本上都是有现成代码,编解码是用的h264,音视频数据传输用的电驴的NatSock部分代码的移植,这两个模块都是一个前辈搞定的,当然自己也从其中学到一些尤其是数据传输部分。由于人手有限,我完成了剩下的两个模块,╮(╯▽╰)╭,界面总是仍给我了,这部分界面是作为一个单独的模块来实现的,直接嵌入到主程序里面,会议管理及维护也是相当的多,包括会议创建,会议角色的管理已经会议的维护。开始设计时,把过多的责任交给了创建者,导致后面的会议维护几乎不可能,最终又加了一个会议管理服务器专门进行会议的管理,也正是这段时间,客户端和服务器同时开发把人搞晕死了,但是收获也颇多。

      10.1来了之后,基本上客户端以及视频会议模块方面都进入了相对比较稳定的一个阶段,这会又给我一个新的任务,在客户端从后台登陆公司的OA网站,获取相关的信息,很有点像QQ那个面板上面的一排小按钮的功能。虽然任务不是很重,但是实现过程却是蜿蜒曲折,最开始采用的是直接socket,后来发现量太大了而且与相关业务直接挂钩的不太多,后来用WinInet,好不容易把cook及sessionid的问题搞定了,登陆成功后发现要获取的信息在另外一个服务器上面,而且其需要相关的sessionid认证,又放弃了,最后干脆用WebBroswer,本以为这次该没问题了,谁知此控件存在内存泄露,长时间运行尤其是在xp上面会导致内存耗尽,由于这个是M$的控件也就讲究用着了,当然从真正实用的角度来看用socket和WinInet才是。

      11月份由于种种原因,准备辞职,一直到12月中下旬才办完手续,19号启程,武汉的生活中……

ATL 窗口类源码浅析

发表于 2009-12-10 | 分类于 VC

图1

对于典型的Win32应用程序而言,一般包括WinMain,窗口类注册,创建显示窗口,消息循环,消息处理。

下面是ATL Internals上面对上图的说明:

ATL对Win32窗口API的封装如上图,其中粗体子的类比较重要,而其它的则是作为一般的辅助类,当然这个图并没有包括所有的内容,比如窗口类注册,消息循环等,下面我会一一道来。

1WinMain在ATL窗口程序中没有太大的变化,并没有先MFC那样直接Link到exe中,而是暴露给程序使用。

2窗口类注册在ATL中使用了一个如下的结构:

struct _ATL_WNDCLASSINFO

{

WNDCLASSEX m_wc;

LPCSTR m_lpszOrigName;

WNDPROC pWndProc;

LPCSTR m_lpszCursorID;

BOOL m_bSystemCursor;

ATOM m_atom;

CHAR m_szAutoName[5+sizeof(void)CHAR_BIT];

ATOM Register(WNDPROC* p)

{

return AtlWinModuleRegisterWndClassInfoA(&_AtlWinModule, &_AtlBaseModule, this, p);

}

};

另外在通过typedef定义为:typedef _ATL_WNDCLASSINFOW CWndClassInfo;

此结构是通过下面的define引入的:

/////////////////////////////////////////////////////////////////////////////

// CWndClassInfo - Manages Windows class information

#define DECLARE_WND_CLASS(WndClassName) /

static ATL::CWndClassInfo& GetWndClassInfo() /

{ /

static ATL::CWndClassInfo wc = /

{ /

{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, /

  0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, /

NULL, NULL, IDC_ARROW, TRUE, 0, _T(“”) /

}; /

return wc; /

}

#define DECLARE_WND_CLASS_EX(WndClassName, style, bkgnd) /

static ATL::CWndClassInfo& GetWndClassInfo() /

{ /

static ATL::CWndClassInfo wc = /

{ /

{ sizeof(WNDCLASSEX), style, StartWindowProc, /

  0, 0, NULL, NULL, NULL, (HBRUSH)(bkgnd + 1), NULL, WndClassName, NULL }, /

NULL, NULL, IDC_ARROW, TRUE, 0, _T(“”) /

}; /

return wc; /

}

#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) /

static ATL::CWndClassInfo& GetWndClassInfo() /

{ /

static ATL::CWndClassInfo wc = /

{ /

{ sizeof(WNDCLASSEX), 0, StartWindowProc, /

  0, 0, NULL, NULL, NULL, NULL, NULL, WndClassName, NULL }, /

OrigWndClassName, NULL, NULL, TRUE, 0, _T(“”) /

}; /

return wc; /

}

在CWindowImpl中有定义DECLARE_WND_CLASS(NULL),因此任何从CWindowImpl派生的类都包含一个GetWndClassInfo函数并返回一个static CWndClassInfo用于注册窗口类,这也比较符合所有的窗口类产生的窗口对象都是一个样式的,比如你用BUTTON作为窗口类创建的肯定就是一个按钮了。上面第三个define是用来SuperClass用的,至于SuperClass和SubClass的区别可以在MSDN上面找到,主要就是前者对整个窗口类而言,而后者只对某个窗口而言。

整个窗口类的注册比较复杂,下面是我根据源码分析的流程

 

图2



3窗口创建及显示部分,这里主要由CWindow来实现,这个和MFC的CWnd不同处在于,CWnd把不仅包括对HWND形式API的封装还包括内部的CWnd和HWND之间的映射,消息映射,OLE等等,而CWindow仅仅只包括一个HWND数据成员,只对API进行了很浅的封装基本上就是一些ShowWindow,SetWindowText之类的API的一个包装,而且CWindow在析构时也不会销毁窗口,对于窗口的消息映射等方面没有做了任何处理。

4消息映射部分,这里从图1可以看到一个CMessageMap的类,它负责窗口的消息映射

class ATL_NO_VTABLE CMessageMap

{ 

public:

virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,

LRESULT& lResult, DWORD dwMsgMapID) = 0;

};

CWindowImplRoot直接从CWindow和CMessageMap派生,因此基本上具体有了窗口和处理消息的功能,但是为了减少模板带来的代码膨胀,这个类以及下面的CWindowImplBaseT只是起辅助作用,其中前者实现了对消息反射以及消息转发,消息反射是为了将子窗口中的通知消息反射回去以便于实现复用的控件,消息转发主要是在实现组合窗口中传递通知消息用的,当一个子窗口的父窗口还有父窗口,而这时子窗口消息又是要在最外层窗口中处理时就可以通过转发消息,两者的实现也不复杂:

template <class TBase>

LRESULT CWindowImplRoot< TBase >::ForwardNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

LRESULT lResult = 0;

switch(uMsg)

{

case WM_COMMAND:

case WM_NOTIFY:

case WM_PARENTNOTIFY:

case WM_DRAWITEM:

case WM_MEASUREITEM:

case WM_COMPAREITEM:

case WM_DELETEITEM:

case WM_VKEYTOITEM:

case WM_CHARTOITEM:

case WM_HSCROLL:

case WM_VSCROLL:

case WM_CTLCOLORBTN:

case WM_CTLCOLORDLG:

case WM_CTLCOLOREDIT:

case WM_CTLCOLORLISTBOX:

case WM_CTLCOLORMSGBOX:

case WM_CTLCOLORSCROLLBAR:

case WM_CTLCOLORSTATIC:

lResult = GetParent().SendMessage(uMsg, wParam, lParam);

break;

default:

bHandled = FALSE;

break;

}

return lResult;

}



template <class TBase>

LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

HWND hWndChild = NULL;



switch(uMsg)

{

case WM_COMMAND:

if(lParam != NULL) // not from a menu

hWndChild = (HWND)lParam;

break;

case WM_NOTIFY:

hWndChild = ((LPNMHDR)lParam)->hwndFrom;

break;

case WM_PARENTNOTIFY:

switch(LOWORD(wParam))

{

case WM_CREATE:

case WM_DESTROY:

hWndChild = (HWND)lParam;

break;

default:

hWndChild = GetDlgItem(HIWORD(wParam));

break;

}

break;

case WM_DRAWITEM:

if(wParam) // not from a menu

hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;

break;

case WM_MEASUREITEM:

if(wParam) // not from a menu

hWndChild = GetDlgItem(((LPMEASUREITEMSTRUCT)lParam)->CtlID);

break;

case WM_COMPAREITEM:

if(wParam) // not from a menu

hWndChild =  ((LPCOMPAREITEMSTRUCT)lParam)->hwndItem;

break;

case WM_DELETEITEM:

if(wParam) // not from a menu  

hWndChild =  ((LPDELETEITEMSTRUCT)lParam)->hwndItem;

 

break;

case WM_VKEYTOITEM:

case WM_CHARTOITEM:

case WM_HSCROLL:

case WM_VSCROLL:

hWndChild = (HWND)lParam;

break;

case WM_CTLCOLORBTN:

case WM_CTLCOLORDLG:

case WM_CTLCOLOREDIT:

case WM_CTLCOLORLISTBOX:

case WM_CTLCOLORMSGBOX:

case WM_CTLCOLORSCROLLBAR:

case WM_CTLCOLORSTATIC:

hWndChild = (HWND)lParam;

break;

default:

break;

}



if(hWndChild == NULL)

{

bHandled = FALSE;

return 1;

}



ATLASSERT(::IsWindow(hWndChild));

return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);

}



template <class TBase>

BOOL CWindowImplRoot< TBase >::DefaultReflectionHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult)

{

switch(uMsg)

{

case OCM_COMMAND:

case OCM_NOTIFY:

case OCM_PARENTNOTIFY:

case OCM_DRAWITEM:

case OCM_MEASUREITEM:

case OCM_COMPAREITEM:

case OCM_DELETEITEM:

case OCM_VKEYTOITEM:

case OCM_CHARTOITEM:

case OCM_HSCROLL:

case OCM_VSCROLL:

case OCM_CTLCOLORBTN:

case OCM_CTLCOLORDLG:

case OCM_CTLCOLOREDIT:

case OCM_CTLCOLORLISTBOX:

case OCM_CTLCOLORMSGBOX:

case OCM_CTLCOLORSCROLLBAR:

case OCM_CTLCOLORSTATIC:

lResult = ::DefWindowProc(hWnd, uMsg - OCM__BASE, wParam, lParam);

return TRUE;

default:

break;

}

return FALSE;

}

上面的DefaultReflectionHandler是在子窗口中对不感兴趣的反射消息进行默认的处理。

CWindowImplBaseT你用Thunk(这个原理也不是很懂大致就是用一些汇编方式的技巧修改内存中的某些数据吧)实现了窗口消息和对应的CWindowImplBaseT对象成员函数之间的消息映射,还有就是窗口的subclass和创建等。最后的CWindowImpl仅仅提供了一个Create函数在调用基类Create之前先进行窗口类的注册也就是图2的一系列流程。

这里还有CWinTraits和CWinTraitsOR它们主要是进行窗口样式的辅助设置。

5程序结构

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,

                   LPSTR szCmdLine, int nCmdShow)

{

CMyWindow wndMain; //这里CMyWindow派生自CWindowImpl

MSG msg;

 

    // Create & show our main window

    if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault, 

                                 T(“My First ATL Window”) ))

    {

        // Bad news, window creation failed

        return 1;

    }

 

    wndMain.ShowWindow(nCmdShow);

    wndMain.UpdateWindow();

 

    // Run the message loop

    while ( GetMessage(&msg, NULL, 0, 0) > 0 )

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

 

    return msg.wParam;

}



差不多大部分的ATL Window部分分析完了,虽然没怎么写文字,但是看一大串模板代码还是够呛的,尤其是注册窗口那部分绕来绕去的,++。



TFTP服务器接收文件

发表于 2009-12-10 | 分类于 系统编程

最近看到很多UDP可靠文件传输的文章,其中有提到TFTP协议,在CodeProject上面搜到一个客户端的,觉得很不错,http://www.codeproject.com/KB/IP/tftp_client.aspx

这里模仿者写了个服务器端,只实现了服务器端的接受文件,配合着基本上就可以作为一般的UDP文件接发例子了,大致代码如下

头文件:

#pragma comment(lib,"ws2_32.lib")

#define TFTP_DEFAULT_PORT    69

#define TFTP_OPCODE_READ     1
#define TFTP_OPCODE_WRITE    2
#define TFTP_OPCODE_DATA     3
#define TFTP_OPCODE_ACK      4
#define TFTP_OPCODE_ERROR    5

#define TFTP_DEFAULT_MODE    "octet"

#define TFTP_DATA_PKT_SIZE   512

#define TFTP_ERROR_MSG_MAX1024
#define TFTP_PACKET_DATA_MAX1024

#define TFTP_RETRY     3

#define TFTP_RESULT_ERROR1
#define TFTP_RESULT_CONTINUE    2
#define TFTP_RESULT_DONE3

#define TFTP_STATUS_NOTHING1
#define TFTP_STATUS_GET2
#define TFTP_STATUS_PUT3
#define TFTP_STATUS_ERROR4

#define TFTP_TIMEOUT5000 //ms,5Ãë

#include <fstream>

class CTFTPServer  
{
protected:
class CPacket
{
public:
CPacket();
~CPacket();
bool AddByte(BYTE c);
bool AddWord(WORD c);
bool AddString(char* str);
bool AddMem(char* mem,int len);
unsigned char* GetData(int pos=0);
WORD GetWord(int pos);
BYTE GetByte(int pos);
bool GetMem(int pos,char* mem,int len);
int  GetSize();
void SetSize(int N);
void Clear();
bool IsValid();
bool IsNull();
bool IsACK(int blocknum);
bool IsDATA(int blocknum);
bool IsError();
bool GetError(char* buf,unsigned int buflen);

void MakeACK(int blocknum);
void MakeDATA(int blocknum,char* data,int size);
bool MakeWRQ(int opcode,char* filename);

protected:
unsigned char n_dataBuf[TFTP_PACKET_DATA_MAX];
int m_nSize;
};
public:
CTFTPServer();
virtual ~CTFTPServer();
bool Start(std::string strIP,USHORT nPort = TFTP_DEFAULT_PORT);
bool Stop();
protected:
static DWORD WINAPI WorkThread(LPVOID lParam);
HANDLE m_hWorkThread;
private:
SOCKETm_sockSvr;
SOCKADDR_INm_sockAddr;
SOCKADDR_INm_clientAddr;
boolm_bRunning;
CPacketm_packetRecv;
CPacketm_packetSend;

FILE*m_fileRecv;
unsigned intm_nPackNum;
unsigned intm_nTotalBytes;
charm_chFileName[64];
std::ofstreamm_logFile;
void ProcessRecvData();
};

实现文件:

 

// TFTPServer.cpp: implementation of the CTFTPServer class.
//
//////////////////////////////////////////////////////////////////////

#include “stdafx.h”

#include “TFTPServer.h”

//////////////////////////////////////////////////////////////////////
// TFTP packet class functions
//////////////////////////////////////////////////////////////////////
CTFTPServer::CPacket::CPacket()
{
Clear();
}
CTFTPServer::CPacket::~CPacket()
{

}

unsigned char* CTFTPServer::CPacket::GetData(int pos)
{
return &(n_dataBuf[pos]);
}

int CTFTPServer::CPacket::GetSize()
{
return m_nSize;
}

void CTFTPServer::CPacket::Clear()
{
m_nSize=0;
memset(n_dataBuf,0,TFTP_PACKET_DATA_MAX);
}

bool CTFTPServer::CPacket::IsValid()
{
return (m_nSize!=-1);
}

bool CTFTPServer::CPacket::IsNull()
{
return (m_nSize == 0);
}

void CTFTPServer::CPacket::SetSize(int N)
{
if ( N > TFTP_PACKET_DATA_MAX)
{
m_nSize =-1;
}
else
{
m_nSize =N;
}
}

bool CTFTPServer::CPacket::AddByte(BYTE c)
{
if (!IsValid()) return false;
if (m_nSize >= TFTP_PACKET_DATA_MAX)
{
m_nSize=-1;
return false;
}
n_dataBuf[m_nSize] = (unsigned char)c;
m_nSize++;
return true;
}

bool CTFTPServer::CPacket::AddMem(char* mem,int len)
{
if (!IsValid()) return false;
if ( m_nSize+len>= TFTP_PACKET_DATA_MAX)
{
m_nSize=-1;
return false;
}
memcpy(&(n_dataBuf[m_nSize]),mem,len);
m_nSize+=len;
return true;
}

bool CTFTPServer::CPacket::AddWord(WORD c)
{
//一个Word等于两个Byte,先存高一个Byte,再存低Byte
if (!AddByte( (((BYTE)&c)+1) )) return false;
return (!AddByte( ((BYTE)&c) ));
}

bool CTFTPServer::CPacket::AddString(char* str)
{
if ((str==NULL)||(strlen(str)==0)) return true;
int n=strlen(str);

for (int i=0;i<n;i++)
if (!AddByte(*(str+i)))
return false;
return true;//AddByte(0);
}

bool CTFTPServer::CPacket::GetMem(int pos,char* mem,int len)
{
if (pos>m_nSize) return false;
if (len<m_nSize-pos) return false;

memcpy(mem,&(n_dataBuf[pos]),m_nSize-pos);

return true;

}

WORD CTFTPServer::CPacket::GetWord(int pos)
{
WORD hi = GetByte(pos);
WORD lo = GetByte(pos+1);
return ((hi<<8)|lo);
}

BYTE CTFTPServer::CPacket::GetByte(int pos)
{
return (BYTE)n_dataBuf[pos];
}

bool CTFTPServer::CPacket::IsACK(int blocknum)
{
if (GetSize()!=4) return false;
WORD opcode = GetWord(0);
WORD block = GetWord(2);
if (opcode != TFTP_OPCODE_ACK) return false;
if (blocknum != block) return false;

return true;
}

bool CTFTPServer::CPacket::IsDATA(int blocknum)
{
if (GetSize()<4) return false;
WORD opcode = GetWord(0);
WORD block = GetWord(2);
if (opcode != TFTP_OPCODE_DATA) return false;
if (blocknum != block) return false;

return true;
}

bool CTFTPServer::CPacket::IsError()
{
if (GetSize()<4) return false;
WORD opcode = GetWord(0);
return (opcode == TFTP_OPCODE_ERROR);
}

bool CTFTPServer::CPacket::GetError(char buf,unsigned int buflen)
{
if (!IsError()) return false;
WORD errcode = GetWord(2);
char
errmsg = (char*)GetData(4);
if (buf==NULL) return false;
if (strlen(errmsg)+10>buflen) return false;
sprintf(buf,”#%i:%s”,errcode,errmsg);
return true;
}

void CTFTPServer::CPacket::MakeACK(int blocknum)
{
Clear();
AddWord(TFTP_OPCODE_ACK);
AddWord(blocknum);
}

bool CTFTPServer::CPacket::MakeWRQ(int opcode,char* filename)
{
ATLASSERT(filename!=NULL);
if (strlen(filename)>TFTP_DATA_PKT_SIZE) return false;
Clear();
AddWord(opcode);
AddString(filename);
AddByte(0);
AddString(TFTP_DEFAULT_MODE);
AddByte(0);
return true;
}

void CTFTPServer::CPacket::MakeDATA(int blocknum,char* data,int size)
{
ATLASSERT(data!=NULL);
ATLASSERT(size<=TFTP_DATA_PKT_SIZE);
Clear();
AddWord(TFTP_OPCODE_DATA);
AddWord(blocknum);
AddMem(data,size);
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CTFTPServer::CTFTPServer():m_bRunning(false),m_sockSvr(INVALID_SOCKET)
{
::ZeroMemory(&m_sockAddr,sizeof(m_sockAddr));
::ZeroMemory(m_chFileName,64);
m_fileRecv = NULL;

m_logFile.open(“TFTPSvr.log”);
}

CTFTPServer::~CTFTPServer()
{

}

bool CTFTPServer::Start(std::string strIP,USHORT nPort/ = TFTP_DEFAULT_PORT/)
{
int nError = 0;
int nRet = 0;
m_sockSvr = ::socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(INVALID_SOCKET == m_sockSvr)
{
nError = ::WSAGetLastError();
return false;
}
//绑定到端口
m_sockAddr.sin_family = AF_INET;
m_sockAddr.sin_addr.s_addr = ::inet_addr(strIP.c_str());
m_sockAddr.sin_port = ::htons(nPort);
nRet = ::bind(m_sockSvr,(sockaddr*)&m_sockAddr,sizeof(m_sockAddr));
if (SOCKET_ERROR == nRet)
{
nError = ::WSAGetLastError();
return false;
}
m_bRunning = true;

m_hWorkThread = ::CreateThread(NULL,0,WorkThread,this,0,0);
if (m_hWorkThread != NULL)
{
::CloseHandle(m_hWorkThread);
}
return true;
}

bool CTFTPServer::Stop()
{
if (m_bRunning)
{
m_bRunning = false;
::closesocket(m_sockSvr);
}

return true;
}

DWORD WINAPI CTFTPServer::WorkThread( LPVOID lParam )
{
CTFTPServer pSvr = reinterpret_cast<CTFTPServer>(lParam);

if (pSvr != NULL)
{
while (pSvr->m_bRunning)
{
int nRecvLen;

int SenderAddrSize = sizeof(sockaddr_in);
nRecvLen = recvfrom(
pSvr->m_sockSvr,
(char)pSvr->m_packetRecv.GetData(),
TFTP_PACKET_DATA_MAX,
0,
(SOCKADDR
)&pSvr->m_clientAddr,
&SenderAddrSize
);

pSvr->m_logFile<<”CTFTPServer::WorkThread 接收到来自”<<::inet_ntoa(pSvr->m_clientAddr.sin_addr)<<”:”<<
::ntohs(pSvr->m_clientAddr.sin_port)<<std::endl;

if ( ( nRecvLen == 0) || (nRecvLen == WSAECONNRESET ) )
{
ATLTRACE(“Connection Closed./n”);
//break;
}
if ( nRecvLen == SOCKET_ERROR)
{
ATLTRACE(“recv error./n”);
//break;
}
char bufTrace[1024] = {0};
::sprintf(bufTrace,”%s/n”,pSvr->m_packetRecv.GetData());
ATLTRACE(bufTrace);
pSvr->m_packetRecv.SetSize(nRecvLen);
pSvr->ProcessRecvData();
//Sleep(1);
}
}

return 0;
}

void CTFTPServer::ProcessRecvData()
{
//处理接收数据
DWORD dwOpCode = m_packetRecv.GetWord(0);
switch (dwOpCode)
{
case TFTP_OPCODE_READ:
{
ATLTRACE(“客户端下载文件/n”);
break;
}
case TFTP_OPCODE_WRITE:
{
//获取文件名
::strcpy(m_chFileName,(const char*)m_packetRecv.GetData(2));
m_packetRecv.Clear();
char bufTrace[1024] = {0};
::sprintf(bufTrace,”客户端上传文件:%s/n”,m_chFileName);
ATLTRACE(bufTrace);
if( (m_fileRecv = fopen( m_chFileName, “wb” )) == NULL )
{
ATLTRACE(“创建文件失败!/n”);
}
//上传文件服务器确认包
m_packetSend.MakeACK(0);

//发送数据
int nLenSended = ::sendto(m_sockSvr,(char)m_packetSend.GetData(),m_packetSend.GetSize(),
0,(SOCKADDR
)&m_clientAddr,sizeof(m_clientAddr));
m_packetSend.Clear();

//下一个数据序号为1
m_nPackNum = 1;
m_nTotalBytes = 0;
break;
}
case TFTP_OPCODE_DATA:
{
DWORD dwDataNum = m_packetRecv.GetWord(2);

if (m_nPackNum != dwDataNum)
{
m_logFile<<”TFTP_OPCODE_DATA 期望数据序号:”<<m_nPackNum<<” 与发过来的不一致:”<<dwDataNum<<std::endl;
break;
}

char bufTrace[1024] = {0};
::sprintf(bufTrace,”客户端上传文件数据序号:%d/n”,dwDataNum);
ATLTRACE(bufTrace);
char dataBuf[TFTP_DATA_PKT_SIZE] = {0};
if (!m_packetRecv.GetMem(4,dataBuf,TFTP_DATA_PKT_SIZE))
{
ATLTRACE(“复制数据错误/n”);
}
//数据前面4个字节为opcode和packnum
int nDataLen = m_packetRecv.GetSize() - 4;
m_nTotalBytes += nDataLen;
m_nPackNum++;
//m_logFile<<”CTFTPServer::ProcessRecvData() 接收数据大小为:”<<nDataLen<<std::endl;

size_t nWrited = fwrite(dataBuf,1,nDataLen,m_fileRecv);

//m_logFile<<”CTFTPServer::ProcessRecvData() 写入文件大小为:”<<nWrited<<std::endl;
//发完了
if (nDataLen < TFTP_DATA_PKT_SIZE)
{
fclose(m_fileRecv);
m_logFile<<”CTFTPServer::ProcessRecvData() 文件接收完毕,关闭文件……,接收大小为:”<<m_nTotalBytes<<std::endl;
m_logFile<<”CTFTPServer::ProcessRecvData() 文件接收完毕,关闭文件……,接收数据块大小为:”<<m_nPackNum<<std::endl;
}
m_packetRecv.Clear();
m_packetSend.MakeACK(dwDataNum);
//发送数据
int nLenSended = ::sendto(m_sockSvr,(char)m_packetSend.GetData(),m_packetSend.GetSize(),
0,(SOCKADDR
)&m_clientAddr,sizeof(m_clientAddr));
m_packetSend.Clear();
break;
}
case TFTP_OPCODE_ACK:
{
ATLTRACE(“确认包/n”);
break;
}
case TFTP_OPCODE_ERROR:
{
ATLTRACE(“/n”);
break;
}
default:
{
ATLTRACE(“/n”);
break;
}
}
}

只在本机测试了下,不知道在internet上面是啥效果。

N个数组成最大整数

发表于 2009-11-23 | 分类于 数据结构/算法

 最近帮一个高中同学做其专升本的数据结构试卷,总体感觉比我们那会考试时难多了,其中最后一道算法设计题如下:

 

“请设计一个程序,其功能是:从键盘输入整数n及n个无符号数,将这n个无符号数连成一个最大的多位整数并输出之。例如,将5个无符号整数6750,67,34,345,7连成767675034534。”

 

第一次将题目看错,以为很简单,当然我基本上都是用C++写的,没有太考虑其中的数据结构(没办法c++用久了,有点不习惯c那种方式),仔细看发现,这个是要将一组数据进行某种方式的排序然后组合的,当然这里根本就没有考虑最大整数的实际意义,要不767675034534肯定会溢出了,想了会本来想用个类来包装每个输入的整数,然后比较每一位大小再组合的,后来发现似乎也不简单,主要是没有找到很有说服力的规律,google下发现这是一个南京师范大学非计算机专业本科生程序竞赛试题,寒啊,可惜那个网页打不开,在换了下关键词google,竟然在百度知道里面有个人问,下面两个回答,第一个和我想的差不多,第二个真是一语道天机啊,按照字符串大小排序,最近真是状态萎靡啊,下面是我写的一个小程序:

#include <sstream>

#include <string>

#include <iostream>

#include <vector>

#include <algorithm>

#include <functional>
using namespace std;

int main(int argc, char* argv[])
{
cout<<”sizeof(unsigned int)=”<<sizeof(unsigned int)<<endl;
cout<<”UINT_MAX = “<<UINT_MAX<<endl;
int n;
cout<<”Please In put n:”<<endl;
cin >> n;

vector<string> vecStrInteger;
for (int i = 0 ; i< n ;++i)
{
cout<<”Please In put The “<<i+1<<”‘s unsigned integer:”;
unsigned int data;
cin >> data;
stringstream strStream;
strStream << data;
vecStrInteger.push_back(strStream.str());
}

sort(vecStrInteger.begin(),vecStrInteger.end(),greater<string>());
cout<<”The bigest integer is:”<<endl;
for (vector<string>::iterator iter = vecStrInteger.begin() ; iter != vecStrInteger.end() ; ++iter)
{
cout<<*iter;
}
cout<<endl;
return 0;
}

不知道这和数据结构有啥关联!!

 

 


C/C++数组名与指针区别深入探索(转)

发表于 2009-11-06 | 分类于 C++

 **引言

**  指针是C/C++语言的特色,而数组名与指针有太多的相似,甚至很多时候,数组名可以作为指针使用。于是乎,很多程序设计者就被搞糊涂了。而许多的大学老师,他们在C语言的教学过程中也错误得给学生讲解:”数组名就是指针”。很幸运,我的大学老师就是其中之一。时至今日,我日复一日地进行着C/C++项目的开发,而身边还一直充满这样的程序员,他们保留着”数组名就是指针”的误解。

  想必这种误解的根源在于国内某著名的C程序设计教程。如果这篇文章能够纠正许多中国程序员对数组名和指针的误解,笔者就不甚欣慰了。借此文,笔者站在无数对知识如饥似渴的中国程序员之中,深深寄希望于国内的计算机图书编写者们,能以”深入探索”的思维方式和精益求精的认真态度来对待图书编写工作,但愿市面上多一些融入作者思考结晶的心血之作!

  魔幻数组名

  请看程序(本文程序在WIN32平台下编译):







1. #include <iostream.h>
2. int main(int argc, char argv[])
3. {
4.  char str[10];
5.  char
pStr = str;
6.  cout << sizeof(str) << endl;
7.  cout << sizeof(pStr) << endl;
8.  return 0;
9. }

  1、数组名不是指针

  我们先来推翻”数组名就是指针”的说法,用反证法。

  证明 数组名不是指针

  假设:数组名是指针;

  则:pStr和str都是指针;

  因为:在WIN32平台下,指针长度为4;

  所以:第6行和第7行的输出都应该为4;

  实际情况是:第6行输出10,第7行输出4;

  所以:假设不成立,数组名不是指针

  2、数组名神似指针

  上面我们已经证明了数组名的确不是指针,但是我们再看看程序的第5行。该行程序将数组名直接赋值给指针,这显得数组名又的确是个指针!

  我们还可以发现数组名显得像指针的例子:







1. #include <string.h>
2. #include <iostream.h>
3. int main(int argc, char* argv[])
4. {
5.  char str1[10] = “I Love U”;
6.  char str2[10];
7.  strcpy(str2,str1);
8.  cout << “string array 1: “ << str1 << endl;
9.  cout << “string array 2: “ << str2 << endl;
10.  return 0;
11. }

  标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针,而我们在调用中传给它的却是两个数组名!函数输出:







string array 1: I Love U
string array 2: I Love U

  数组名再一次显得像指针!

  既然数组名不是指针,而为什么到处都把数组名当指针用?于是乎,许多程序员得出这样的结论:数组名(主)是(谓)不是指针的指针(宾)。

  整个一魔鬼。

  揭密数组名

  现在到揭露数组名本质的时候了,先给出三个结论:

  (1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;

  (2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量;

  (3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!

  1、数组名指代一种数据结构:数组

  现在可以解释为什么第1个程序第6行的输出为10的问题,根据结论1,数组名str的内涵为一种数据结构,即一个长度为10的char型数组,所以sizeof(str)的结果为这个数据结构占据的内存大小:10字节。

  再看:







1. int intArray[10];
2. cout << sizeof(intArray) ;

  第2行的输出结果为40(整型数组占据的内存空间大小)。

  如果C/C++程序可以这样写:







1. int[10] intArray;
2. cout << sizeof(intArray) ;

  我们就都明白了,intArray定义为int[10]这种数据结构的一个实例,可惜啊,C/C++目前并不支持这种定义方式。

  2、数组名可作为指针常量

  根据结论2,数组名可以转换为指向其指代实体的指针,所以程序1中的第5行数组名直接赋值给指针,程序2第7行直接将数组名作为指针形参都可成立。

  下面的程序成立吗?







1. int intArray[10];
2. intArray++;

  读者可以编译之,发现编译出错。原因在于,虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改。

  而指针,不管是指向结构体、数组还是基本数据类型的指针,都不包含原始数据结构的内涵,在WIN32平台下,sizeof操作的结果都是4。
顺便纠正一下许多程序员的另一个误解。许多程序员以为sizeof是一个函数,而实际上,它是一个操作符,不过其使用方式看起来的确太像一个函数了。语句sizeof(int)就可以说明sizeof的确不是一个函数,因为函数接纳形参(一个变量),世界上没有一个C/C++函数接纳一个数据类型(如int)为”形参”。

  3、数据名可能失去其数据结构内涵

  到这里似乎数组名魔幻问题已经宣告圆满解决,但是平静的湖面上却再次掀起波浪。请看下面一段程序:







1. #include <iostream.h>
2. void arrayTest(char str[])
3. {
4.  cout << sizeof(str) << endl;
5. }
6. int main(int argc, char* argv[])
7. {
8.  char str1[10] = “I Love U”;
9.  arrayTest(str1);
10.  return 0;
11. }

  程序的输出结果为4。不可能吧?

  一个可怕的数字,前面已经提到其为指针的长度!

  结论1指出,数据名内涵为数组这种数据结构,在arrayTest函数体内,str是数组名,那为什么sizeof的结果却是指针的长度?这是因为:

  (1)数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;

  (2)很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

  所以,数据名作为函数形参时,其全面沦落为一个普通指针!它的贵族身份被剥夺,成了一个地地道道的只拥有4个字节的平民。

  以上就是结论4。

  结束语

  最后,笔者再次表达深深的希望,愿我和我的同道中人能够真正以谨慎的研究态度来认真思考开发中的问题,这样才能在我们中间产生大师级的程序员,顶级的开发书籍。每次拿着美国鬼子的开发书籍,我们不免发出这样的感慨:我们落后太远了。

简易模版栈及数制转换

发表于 2009-11-01 | 分类于 数据结构/算法

 

#pragma once

const int INIT_SIZE = 100;
const int INCRENT_SIZE = 10;

template <typename T>
class CStack
{
private:
T m_pBase;/基址*/
int m_nSize;
int m_nTop;
public:

CStack()
{
m_pBase = new T[INIT_SIZE];
m_nSize = INIT_SIZE;
m_nTop = 0;
}

~CStack(void)
{
delete [] m_pBase;
}
bool IsEmpty()const
{
return m_nTop == 0;
}
void Push(const T& val)
{
//空间已满
if (m_nTop == m_nSize)
{
//增加
T newBase = new T[m_nSize+INCRENT_SIZE];
//复制原来的数据
::memcpy(newBase,m_pBase,m_nSize
sizeof(T));
//删除原来的空间
delete []m_pBase;
//新的空间地址
m_pBase = newBase;
m_nSize += INCRENT_SIZE;
}
m_pBase[m_nTop++] = val;
}
int Pop(T& val)
{
if (m_nTop)
{
val = m_pBase[–m_nTop];
return 0;
}
return -1;
}
};

下面是个小的测试程序

// StackTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>

#include "Stack.h"
void DecToOthers(int decVal,const int x)
{
CStack<int> stack;
while (decVal)
{
stack.Push(decVal%x);
decVal = decVal / x;
}
while(!stack.IsEmpty())
{
int n;
stack.Pop(n);
std::cout<<n;
}
std::cout<<std::endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
DecToOthers(1348,8);

DecToOthers(128,2);

CStack<int> stack;
for (int i=0;i<200;i++)
{
stack.Push(i);
}
while(!stack.IsEmpty())
{
int n;
stack.Pop(n);
std::cout<<n<<"  ";
}

return 0;
}

IWebBrowser2登陆网站(无验证码的)

发表于 2009-10-20 | 分类于 VC

      忙活了快两天,第一次正儿八经的使用COM(仅仅是使用),需求是客户端登陆网站获取相关信息,比如是否有新的事务,开始通过WinInet抓包,由于是第一次接触这个,搞了两天终于登陆成功(主要是卡在SessionID上面了),进入主界面后发现获取事务的信息不是直接在HTML中,一个做Web的同事给我分析了一遍,竟然是通过JS+AJAX +_+然后再怎么直接更新(不刷新页面),反正是搞不清,而且这里事务的链接和登录后的页面不是在同一个服务器上面,直接GET那些页面不行。

 

      直到没办法了昨天开始用IWebBrowser2,开始本来是想直接用ActiveX控件的,后来发现那个也就是封装了下,反正也是学习干脆直接用IWebBrowser2,算然以前知道这个东西但是没用过,硬着头皮在CodeProject上面找+MSDN,终于了解了大致流程:

 

首先通过IWebBrowser2加载URL,为了省事没有分析HTML直接是在浏览器看源文件抠出来的关键词,通过MSHTML的COM接口设置相关的用户名和密码然后提交表单,奇怪的是IHTMLInputButtonElement 竟然没有click之类的方法,网上找到的是通过IHTMLInputButtonElement 找到对应的表单然后提交,具体可以参考源代码,代码中只试了gmail和163的,gmail可以登录成功,163的不知道怎么搞的提交后用户名和密码清空了,另外有一点需要注意的是有的网站登录“按钮”实际上是个图片,HTML代码大致是这样的

<input name=“Submit” value=“登 录” type=“image” src=“http://static.xxx.com/v3/www/images/btn_login.gif" />

这个在MSHTML对应的是IHTMLImgElement,这个接口在网上讲述的很少,而且由于不是真正的按钮,不能通过IHTMLInputButtonElement 来获取相应的表单,我这里是通过先通过IHTMLDocument2获取IHTMLElementCollection然后通过IHTMLElementCollection::item来枚举子Form,不知道为什么用IHTMLElementCollection::tags获取的Form总是不对,获取到Form后就好办了submit,这个在Demo中没写(还要洗衣服,呵呵),可以自己试试。

 

 

Demo代码:http://download.csdn.net/source/1757459

1…789…16

billowqiu

157 日志
33 分类
10 标签
GitHub E-Mail
© 2020 billowqiu
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.3