第9章 高级网络编程API 9.1 MFC提供的Winsock类 9.2 信报API(MAPI) 9.3 WinInet API 习题
9.1 MFC提供的Winsock类 9.1.1 CAsyncSocket类 CAsyncSocket类封装了一些基本的Windows Sockets API函数,它提供了与较低层Windows Sockets对话的接口,一般适合于有一定基础的网络编程者使用,可方便地进行底层的网络事件通知及信息回叫控制等操作。 CAsyncSocket类定义了一组非常有用的成员函数,这些函数的功能和格式与前几章介绍的函数相同或类似,故下面只进行一些简单的说明。
1.Accept( )函数 Accept( )函数格式如下: virtual BOOL Accept(CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL ); 该函数用于在一个套接口上接受连接请求。rConnectedSocket参数为监听套接口;lpSockAddr参数为指向地址的指针,其初始值为空,在连接建立后保存远程套接口的IP地址;lpSockAddrLen参数为指向客户套接口地址长度的指针。
2.AsyncSelect( )函数 AsyncSelect( )函数格式如下: BOOL AsyncSelect(long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE ); 该函数用于在套接口准备好之后请求事件通知。事件的含义见表8-5,其对应的数值如表9-1所示。
表9-1 网络事件的数值
3.Attach( )函数 Attach( )函数格式如下: BOOL Attach(SOCKET hSocket, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE ); 该函数的作用是将一个套接口句柄连接到一个CAsyncSocket对象实例上,即建立两者的关联,这样就可以实现与另一台计算机的连接了。hSocket参数为套接口句柄。
4.Bind( )函数 Bind( )函数格式如下: BOOL Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL ); 或 BOOL Bind ( const SOCKADDR* lpSockAddr, int nSockAddrLen ); 该函数用于建立一个本地地址与一个套接口的关联,即绑定。它有两种调用格式:第一种调用格式提供套接口、端口号和地址,使用的是字符串形式的地址,一般常用这种调用方法;第二种调用格式提供SOCKADDR结构类型的地址参数。
5.Close( )函数 Close( )函数格式如下: virtual void Close( ); 该函数用于关闭套接口,释放与套接口有关的系统资源。要注意的是,Close( )函数是在对象被删除时由CAsyncSocket的析构函数自动调用的。Close( )函数的行为取决于套接口的SO_LINGER和SO_DONTLINGER选项。
6.Connect( )函数 Connect( )函数格式如下: BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort ); 或 BOOL Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen ); 该函数用于建立与远程套接口的连接。它有两种调用格式:第一种调用格式使用套接口、端口号和地址,且地址使用参数为字符串型的地址参数(如“ftp.microsoft.com”)或点分十进制的地址(如“128.56.22.8”);第二种调用格式提供SOCKADDR结构类型的地址参数。
7.Create( )函数 Create( )函数格式如下: BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL ); 在调用CAsyncSocket的构造函数创建了CAsyncSocket对象后,需要调用Create( )函数来创建底层的套接口。因为CAsyncSocket的构造函数没有带参数,所以它只创建一个新的空套接口对象,创建底层套接口的真正工作是由Create( )函数来完成的。
Create( )函数中,第一个参数nSocketPort用于指定一个分配给套接口的端口号,其默认值为0,其真正的含义是告诉系统可以由系统给该套接口分配一个端口号。在开发服务器程序时,应该明确地分配一个端口号(即前面介绍的“众所周知”的端口号),这样客户就可以方便地使用该端口号与服务器连接。在开发客户程序时,可以使用默认的0值,即由系统自动分配一个端口号。 函数的第二个参数nSocketType用来指定是流式(SOCK_STREAM)还是数据报式(SOCK_DGRAM)套接口。
第三个参数lEvent用来指定一个将为应用程序生成通知的事件。在默认情况下,所有的事件都会生成通知。 最后一个参数lpszSocketAddress允许我们为套接口指定一个网络地址,其默认值为0,表示套接口的地址将限定为本地机。该地址以点分十进制的形式给定,如“128.56.22.8”。
8.Detach( )函数 Detach( )函数格式如下: SOCKET Detach( ); 该函数的作用是将上一次建立关联的套接口句柄断开,并返回此套接口句柄。 9.FromHandle( )函数 FromHandle( )函数格式如下: static * PASCAL FromHandle( SOCKET hSocket ); 该函数的hSocket参数指定一个套接口的句柄,其作用是返回与此套接口关联的CAsyncSocket对象的指针。如果没有与此套接口关联的CAsyncSocket对象,则返回值为空(NULL)。
10.GetLastError( )函数 GetLastError( )函数格式如下: static int GetLastError( ); 在实际应用中,当调用的操作失败后使用该函数返回套接口的错误代码。 11.GetPeerName( )函数 GetPeerName( )函数格式如下: BOOL GetPeerName(CString& rPeerAddress, UINT& rPeerPort );或 BOOL GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen );
对于指定的套接口调用GetPeerName( )函数,就可以返回与之对应的远程套接口的IP地址和端口号。远程套接口的IP地址和端口号分别由rPeerAddress和rPeerPort参数返回,也可以由指向SOCKADDR结构的参数lpSockAddr返回。 12.GetSockName( )函数 GetSockName( )函数格式如下: BOOL GetSockName(CString& rSocketAddress, UINT& rSocketPort );或 BOOL GetSockName(SOCKADDR* lpSockAddr, int* lpSockAddrLen );
该函数的作用与GetPeerName( )函数类似,对于给定的套接口,就可以返回其IP地址和端口号。IP地址和端口号分别由rSocketAddress和rSocketPort参数返回,也可以由指向SOCKADDR结构的参数lpSockAddr返回。 13.GetSockOpt( )函数 GetSockOpt( )函数格式如下: BOOL GetSockOpt( int nOptionName, void* lpOptionValue, int* lpOptionLen, int nLevel = SOL_SOCKET ); 该函数用于返回一个套接口的选项。获取的选项值由参数lpOptionValue确定,其长度由lpOptionLen指针指向的值确定;nOptionName参数指出要获取的选项名,nLevel参数说明选项级别,默认的选项级别是SOL_SOCKET。该函数仅支持SOL_SOCKET和IPPROTO_TCP两个选项级别。
14.SetSockOpt( )函数 SetSockOpt( )函数格式如下: BOOL SetSockOpt( int nOptionName, const void* lpOptionValue, int nOptionLen, int nLevel = SOL_SOCKET ); 该函数的作用与GetSockOpt( )函数相反,用于设置一个套接口的选项。要设置的选项值由参数lpOptionValue确定,其他参数的含义与GetSockOpt( )函数相同。 15.IOCtl()函数 IOCtl( )函数格式如下: BOOL IOCtl( long lCommand, DWORD* lpArgument ); 该函数用于设置套接口的工作模式。
16.Listen()函数 Listen( )函数格式如下: BOOL Listen( int nConnectionBacklog = 5 ); 该函数用于连接的监听,当调用成功时返回一个非0的值。参数nConnectionBacklog的取值区间为1~5,默认值为5,它指出正在等待连接的最大队列长度。 17.OnAccept( )函数 OnAccept( )函数格式如下: virtual void OnAccept( int nErrorCode ); 该函数是一个需要重载的回调函数,当一个套接口可能需要与另一端建立连接时,可以调用此函数处理相应的消息。参数nErrorCode指出最新的错误代码。
18.OnClose( )函数 OnClose( )函数格式如下: virtual void OnClose( int nErrorCode ); 该函数是一个需要重载的回调函数,当一个套接口关闭时,可以调用此函数处理相应的消息。 19.OnConnect()函数 OnConnect( )函数格式如下: virtual void OnConnect( int nErrorCode ); 该函数是一个需要重载的回调函数,当一个套接口成功建立连接或连接失败时,可以调用此函数处理相应的消息。
20.OnOutOfBandData( )函数 virtual void OnOutOfBandData( int nErrorCode ); 该函数是一个需要重载的回调函数,如果一些非常急需的数据已经准备好,就会发出相应的消息,这一类消息的处理需要由该函数完成。 21.OnReceive( )函数 OnReceive( )函数格式如下: virtual void OnReceive( int nErrorCode ); 该函数是一个需要重载的回调函数,当一个套接口已经准备好数据,并可以由函数Receive( )接收时,就可以调用此函数处理相应的消息。举例说明如下:
// 下面的CMyAsyncSocket是CAsyncSocket类的派生类 void CMyAsyncSocket::OnReceive(int nErrorCode) { static int i=0; i++; TCHAR buff[4096]; int nRead; nRead = Receive(buff, 4096); switch (nRead)
{ case 0: Close( ); break; case SOCKET_ERROR: if (GetLastError( ) != WSAEWOULDBLOCK) AfxMessageBox ("Error occurred"); }
break; default: buff[nRead] = 0; //terminate the string CString szTemp(buff); m_strRecv += szTemp; // m_strRecv 是一个在CMyAsyncSocket中说明的字符串 if (szTemp.CompareNoCase("bye") == 0 ) ShutDown( ); } CAsyncSocket::OnReceive(nErrorCode);
22.OnSend( )函数 OnSend( )函数格式如下: virtual void OnSend( int nErrorCode ); 该函数是一个需要重载的回调函数,当一个套接口已经准备好数据,并可以由Send( )函数发送时,就可以调用该函数处理相应的消息。举例说明如下: // CMyAsyncSocket是CAsyncSocket派生类并定义如下变量 // CString m_sendBuffer; // int m_nBytesSent; // int m_nBytesBufferSize;
void CMyAsyncSocket ::OnSend(int nErrorCode) { while (m_nBytesSent < m_nBytesBufferSize) int dwBytes; if ((dwBytes = Send((LPCTSTR)m_sendBuffer + m_nBytesSent, m_nBytesBufferSize - m_nBytesSent)) == SOCKET_ERROR) if (GetLastError( ) == WSAEWOULDBLOCK) break; else
{ TCHAR szError[256]; wsprintf(szError, "Server Socket failed to send: %d", GetLastError( )); Close( ); AfxMessageBox (szError); } else m_nBytesSent += dwBytes;
if (m_nBytesSent == m_nBytesBufferSize) { m_nBytesSent = m_nBytesBufferSize = 0; m_sendBuffer = ""; } CAsyncSocket::OnSend(nErrorCode);
23.Receive( )函数 Receive( )函数格式如下: virtual int Receive( void* lpBuf, int nBufLen, int nFlags = 0 ); 该函数用于获得已经与当前套接口建立连接的远程套接口发送的数据,该数据存入lpBuf缓冲区中。 24.ReceiveFrom( )函数 ReceiveFrom( )函数格式如下:
int ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 ); 或int ReceiveFrom( void* lpBuf, int nBufLen, SOCKADDR* lpSockAddr, int* lpSockAddrLen, int nFlags = 0 ); 该函数用于从无连接的远程套接口上读取数据报信息。 25.Send( )函数 Send( )函数格式如下: virtual int Send( const void* lpBuf, int nBufLen, int nFlags = 0 ); 该函数用于向与当前套接口已经建立连接的远程套接口发送数据。
26.SendTo( )函数 SendTo( )函数格式如下: int SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 ); 或 int SendTo( const void* lpBuf, int nBufLen, const SOCKADDR* lpSockAddr, int nSockAddrLen, int nFlags = 0 ); 该函数用于向无连接的远程套接口发送数据。
27.ShutDown( )函数 ShutDown( )函数格式如下: BOOL ShutDown( int nHow = sends ); 调用该函数并不影响套接口的打开状态,它不关闭套接口,只可以控制是否能够继续发送或接收数据,即是否允许继续调用Send( )和Receive( )函数。因此,可以将ShutDown( )的功能理解为对接收或发送功能的关闭,具体的控制模式有三种,如表9-2所示。
表9-2 控制模式的值
9.1.2 CSocket类 CSocket类派生于CAsyncSocket类,它继承了父类中一些常用、易懂的Windows Sockets API函数,并对CAsyncSocket底层中较难控制的一些API函数或成员函数进行了处理,为Winsock API提供了一个更高级别的编程接口。它通过MFC CArchive对象进行信息的接收和发送操作,使得套接口数据输入和输出如同使用MFC的文档一样简捷、易用。同时,它支持模块化的后台信息处理,解决了CAsyncSocket中较难克服的多线程处理问题。 CSocket类提供了如下的一些常用函数。
1.Attach( )函数 Attach( )函数格式如下: BOOL Attach( SOCKET hSocket ); 该函数将一个套接口句柄连接到一个CSocket对象实例上,从而可以建立与另一端的连接。举例说明如下。 // ... class CSockThread : public CWinThread { // ... Other function and member declarations protected: CSocket m_sConnected; };
SOCKET hConnected; BOOL CSockThread::InitInstance( ) { // Attach the socket object to the socket handle // in the context of this thread. m_sConnected.Attach(hConnected); return TRUE; } // This listening socket has been constructed // in the primary thread.
void CListeningSocket::OnAccept(int nErrorCode) { // This CSocket object is used just temporarily // to accept the incoming connection. CSocket sConnected; Accept(sConnected); // Detach the newly accepted socket and save // the SOCKET handle.
hConnected = sConnected.Detach( ); // After detaching it, it should no longer be // used in the context of this thread. // Start the other thread. AfxBeginThread(RUNTIME_CLASS(CSockThread)); }
2.Create( )函数 Create( )函数格式如下: BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR lpszSocketAddress = NULL ); 在调用CSocket的构造函数(该构造函数不带参数)创建了一个空的CSocket后,再调用Create( )函数完成具体的初始化工作。该函数的三个参数都有默认值,对客户机套接口来说,使用默认参数就可以了。但对服务器套接口来说,通常还要为套接口指定一个端口号。
该函数的默认套接口类型是SOCK_STREAM,即流式套接口,当然在程序中我们还可以使用数据报套接口SOCK_DGRAM。但要注意,如果我们打算使用CArchive对象,则必须使用默认的套接口类型SOCK_DGRAM。 当一个服务器有多个IP地址时,可以为套接口指定一个特定的地址。地址的默认值为NULL,指出套接口被创建时使用INADDR_ANY。 3.FromHandle( )函数 FromHandle( )函数格式如下: static CSocket* PASCAL FromHandle( SOCKET hSocket ); 该函数的参数指出一个套接口句柄,在调用后返回与此套接口关联的CSocket对象指针。如果没有与此套接口关联的对象,则返回NULL。
4.IsBlocking( )函数 IsBlocking( )函数格式如下: BOOL IsBlocking( ); IsBlocking( )函数调用后返回一个布尔值,作用为判断在当前时刻套接口是否处于阻塞状态,即是否在等待某种事件的发送。 5.CancelBlockingCall( )函数 CancelBlockingCall( )函数格式如下: void CancelBlockingCall( ); 如果套接口处于阻塞状态,那么调用CancelBlockingCall( )函数后,所有套接口的阻塞请求被删除。
6.OnMessagePending( )函数 virtual BOOL OnMessagePending( ); 该函数是一个需要重载的虚函数,当套接口处于阻塞状态时,调用OnMessagePending( )函数可以处理应用中的其他消息。
9.2 信报API(MAPI) 9.2.1 MAPI的结构 MAPI的组件如图9-1所示。
图9-1 MAPI组件结构图
9.2.2 MFC对MAPI的支持 1.使用VC++提供的AppWizard 利用AppWizard创建一个MAPI应用的过程非常简单,但要注意在MFC AppWizard-Step 4 of 6对话框中要确认对MAPI项的选择,如图9-2所示。
图9-2 创建一个MAPI应用
如果选择了MAPI选项,则将为应用程序的File菜单增加一个传送命令,并将为CDocument MAPI功能增加相应的消息映射项。若在创建工程时没有选择MAPI选项,则可以通过手工的方式增加MAPI功能。 以上创建应用程序的过程完成后,如果执行“文件”菜单中的“传送…”命令,则弹出如图9-3所示的邮件处理窗口,用户就可以根据邮件的具体情况输入收件人、抄送、主题等内容进行邮件处理工作。但一定要注意,如果没有设置默认的邮件处理程序,则在运行“传送…”命令时将弹出有关邮件设置的提示信息,要求设置默认邮件处理程序。
图9-3 邮件处理窗口
2.使用MAPI组件 如果已经创建好了一个项目,但在项目中并没有使用MAPI,则可以使用VC++的Project | Add to Project | Commonents and Controls菜单命令(在中文环境下为“工程”|“添加工程”|Commonents and Controls),弹出如图9-4所示的对话框,双击Visual C++ Components,从弹出的对话框中选择MAPI组件即可。然后执行程序,在“文件”菜单中加入“Send…”菜单命令。
图9-4 插入MAPI对话框
3.手工添加MAPI 我们也可以通过在程序中直接加入代码的方法添加MAPI,操作方法如下: (1) 在“文件”菜单下添加“传送…”菜单项(也可以放在其他菜单下)。该菜单项的ID可以从属性中选择ID_FILE_SEND_MAIL(当然用户也可以输入其他ID,但该ID是一个标准的ID命令)。 (2) 为“传送…”命令增加消息映射。对于每一个CDocument类,应该增加如下所示的消息映射项:
ON_COMMAND(ID_FILE_SEND_MAIL,OnFileSendMail) ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL,OnUpdateFileSendMail) 增加后的程序为 BEGIN_MESSAGE_MAP(CMapi1Doc, CDocument) //{{AFX_MSG_MAP(CMapi1Doc) // NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail) ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail) END_MESSAGE_MAP( )
9.2.3 MAPI的高级应用 目前存在以下4种MAPI的客户接口。 ● 简单MAPI: ● 通用信报调用(Common Messaging Calls,CMS): ● 扩展MAPI: ● OLE信报:
9.2.4 通用信报调用CMC 1.CMC定义的基本数据类型 CMC定义的基本数据类型有: typedef char CMC_sint8; typedef short CMC_sint16; typedef long int CMC_sint32; typedef unsigned short int CMC_uint16; typedef unsigned long int CMC_uint32; typedef void far * CMC_buffer; typedef char far * CMC_string;
typedef CMC_uint16 CMC_boolean; typedef CMC_sint32 CMC_enum; typedef CMC_uint32 CMC_return_code; typedef CMC_uint32 CMC_flags; typedef CMC_string CMC_object_identifier; #define CMC_FALSE ((CMC_boolean)0) #define CMC_TRUE ((CMC_boolean)1) CMC中定义的其他数据类型还有: typedef CMC_uint32 CMC_session_id; typedef CMC_uint32 CMC_ui_id; typedef CMC_counted_string CMC_message_reference;
2.CMC定义的标志 在CMC中定义了各种类型的标志,这些标志广泛使用在后面介绍的CMC函数中。 (1) CMC定义的扩展标志有: #define CMC_EXT_REQUIRED ((CMC_flags) 0x00010000) #define CMC_EXT_OUTPUT ((CMC_flags) 0x00020000) #define CMC_EXT_LAST_ELEMENT ((CMC_flags) 0x80000000)
#define CMC_EXT_RSV_FLAG_MASK ((CMC_flags) 0xFFFF0000) #define CMC_EXT_ITEM_FLAG_MASK ((CMC_flags) 0x0000FFFF) (2) CMC定义的附件标志有: #define CMC_ATT_APP_OWNS_FILE ((CMC_flags) 1) #define CMC_ATT_LAST_ELEMENT ((CMC_flags) 0x80000000) #define CMC_ATT_OID_BINARY "? ? ? ? ? ?" #define CMC_ATT_OID_TEXT
(3) CMC定义的名称类型标志有: #define CMC_TYPE_UNKNOWN ((CMC_enum) 0) #define CMC_TYPE_INDIVIDUAL ((CMC_enum) 1) #define CMC_TYPE_GROUP ((CMC_enum) 2)
(4) CMC定义的角色标志有: #define CMC_ROLE_TO ((CMC_enum) 0) #define CMC_ROLE_CC ((CMC_enum) 1) #define CMC_ROLE_BCC ((CMC_enum) 2) #define CMC_ROLE_ORIGINATOR ((CMC_enum) 3) #define CMC_ROLE_AUTHORIZING_USER ((CMC_enum) 4)
(5) CMC定义的接收标志有: #define CMC_RECIP_IGNORE ((CMC_flags) 1) #define CMC_RECIP_LIST_TRUNCATED ((CMC_flags) 2) #define CMC_RECIP_LAST_ELEMENT ((CMC_flags) 0x80000000)
(6) CMC定义的消息标志有: #define CMC_MSG_READ ((CMC_flags) 1) #define CMC_MSG_TEXT_NOTE_AS_FILE ((CMC_flags) 2) #define CMC_MSG_UNSENT ((CMC_flags) 4) #define CMC_MSG_LAST_ELEMENT ((CMC_flags) 0x80000000)
(7) CMC定义的消息概要标志有: #define CMC_SUM_READ ((CMC_flags) 1) #define CMC_SUM_UNSENT ((CMC_flags) 2) #define CMC_SUM_LAST_ELEMENT ((CMC_flags) 0x80000000)
(8) CMC定义的功能标志有: #define CMC_ERROR_UI_ALLOWED ((CMC_flags) 0x01000000) #defineCMC_LOGON_UI_ALLOWED ((CMC_flags) 0x02000000) #define CMC_COUNTED_STRING_TYPE ((CMC_flags) 0x04000000)
(9) CMC定义的信报发送标志有: #define CMC_SEND_UI_REQUESTED ((CMC_flags) 1) //cmc_send( )函数使用 #define CMC_FIRST_ATTACH_AS_TEXT_NOTE ((CMC_flags) 2) //cmc_send_documents( )函数使用 (10) CMC定义的信报操作标志(cmc_act_on( )函数使用)有: #define CMC_ACT_ON_EXTENDED ((CMC_enum) 0) #define CMC_ACT_ON_DELETE ((CMC_enum) 1)
(11) CMC定义的信报列表标志(cmc_list( )函数使用)有: #define CMC_LIST_UNREAD_ONLY ((CMC_flags) 1) #define CMC_LIST_MSG_REFS_ONLY ((CMC_flags) 2) #define CMC_LIST_COUNT_ONLY ((CMC_flags) 4) #define CMC_LENGTH_UNKNOWN 0xFFFFFFFF
(12) CMC定义的读信报标志(cmc_read( )函数使用)有: #define CMC_DO_NOT_MARK_AS_READ ((CMC_flags) 1) #define CMC_MSG_AND_ATT_HDRS_ONLY ((CMC_flags) 2) #define CMC_READ_FIRST_UNREAD_MESSAGE ((CMC_flags) 4)
(13) CMC定义的信报查找标志(cmc_look_up( )函数使用)有: #defineCMC_LOOKUP_RESOLVE_PREFIX_SEARCH ((CMC_flags) 1) #defineCMC_LOOKUP_RESOLVE_IDENTITY ((CMC_flags) 2) #defineCMC_LOOKUP_RESOLVE_UI ((CMC_flags) 4) #defineCMC_LOOKUP_DETAILS_UI ((CMC_flags) 8) #defineCMC_LOOKUP_ADDRESSING_UI ((CMC_flags) 16)
(14) CMC定义的断开连接标志(cmc_logoff( )函数使用)有: #define CMC_LOGOFF_UI_ALLOWED ((CMC_flags) 1) (15) CMC定义的连接标志(cmc_logon( )函数使用)有: #define CMC_VERSION ((CMC_uint16) 100) (16) CMC定义的查询配置标志有: #define CMC_CONFIG_CHARACTER_SET ((CMC_enum) 1) #define CMC_CONFIG_LINE_TERM ((CMC_enum) 2) #define CMC_CONFIG_DEFAULT_SERVICE ((CMC_enum) 3)
#define CMC_CONFIG_DEFAULT_USER ((CMC_enum) 4) #define CMC_CONFIG_REQ_PASSWORD ((CMC_enum) 5) #define CMC_CONFIG_REQ_SERVICE ((CMC_enum) 6) #define CMC_CONFIG_REQ_USER ((CMC_enum) 7) #define CMC_CONFIG_UI_AVAIL ((CMC_enum) 8)
#define CMC_CONFIG_SUP_NOMKMSGREAD ((CMC_enum) 9) #define CMC_CONFIG_SUP_COUNTED_STR ((CMC_enum) 10) #define CMC_CONFIG_VER_IMPLEM ((CMC_enum) 11) #define CMC_CONFIG_VER_SPEC ((CMC_enum) 12)
(17) CMC定义的行标志有: #define CMC_LINE_TERM_CRLF ((CMC_enum) 0) #define CMC_LINE_TERM_CR ((CMC_enum) 1) #define CMC_LINE_TERM_LF ((CMC_enum) 2) (18) CMC定义的配置需求标志有: #define CMC_REQUIRED_NO #define CMC_REQUIRED_YES #define CMC_REQUIRED_OPT
(19) CMD定义的字符集标志有: #define CMC_CHAR_CP437 "1 2 840 113556 3 2 437" #define CMC_CHAR_CP850 "1 2 840 113556 3 2 850" #define CMC_CHAR_CP1252 "1 2 840 113556 3 2 1252" #define CMC_CHAR_ISTRING "1 2 840 113556 3 2 0" #define CMC_CHAR_UNICODE "1 2 840 113556 3 2 1"
(20) CMC定义的返回代码标志有: #define CMC_ERROR_DISPLAYED ((CMC_return_code) 0x00008000) #define CMC_ERROR_RSV_MASK ((CMC_return_code) 0x0000FFFF) #define CMC_ERROR_IMPL_MASK ((CMC_return_code) 0xFFFF0000) #define CMC_SUCCESS ((CMC_return_code) 0)
#define CMC_E_AMBIGUOUS_RECIPIENT ((CMC_return_code) 1) #define CMC_E_ATTACHMENT_NOT_FOUND ((CMC_return_code) 2) #define CMC_E_ATTACHMENT_OPEN_FAILURE ((CMC_return_code) 3) #define CMC_E_ATTACHMENT_READ_FAILURE ((CMC_return_code) 4)
#define CMC_E_ATTACHMENT_WRITE_FAILURE ((CMC_return_code) 5) #define CMC_E_COUNTED_STRING_UNSUPPORTED ((CMC_return_code) 6) #define CMC_E_DISK_FULL ((CMC_return_code) 7) #define CMC_E_FAILURE ((CMC_return_code) 8) #define CMC_E_INSUFFICIENT_MEMORY ((CMC_return_code) 9) #define CMC_E_INVALID_CONFIGURATION ((CMC_return_code) 10)
#define CMC_E_INVALID_ENUM ((CMC_return_code) 11) #define CMC_E_INVALID_FLAG ((CMC_return_code) 12) #define CMC_E_INVALID_MEMORY ((CMC_return_code) 13) #define CMC_E_INVALID_MESSAGE_PARAMETER ((CMC_return_code) 14) #define CMC_E_INVALID_MESSAGE_REFERENCE ((CMC_return_code) 15) #define CMC_E_INVALID_PARAMETER ((CMC_return_code) 16)
#define CMC_E_INVALID_SESSION_ID ((CMC_return_code) 17) #define CMC_E_INVALID_UI_ID ((CMC_return_code) 18) #define CMC_E_LOGON_FAILURE ((CMC_return_code) 19) #define CMC_E_MESSAGE_IN_USE ((CMC_return_code) 20) #define CMC_E_NOT_SUPPORTED ((CMC_return_code) 21)
#define CMC_E_PASSWORD_REQUIRED ((CMC_return_code) 22) #define CMC_E_RECIPIENT_NOT_FOUND ((CMC_return_code) 23) #define CMC_E_SERVICE_UNAVAILABLE ((CMC_return_code) 24) #define CMC_E_TEXT_TOO_LARGE ((CMC_return_code) 25)
#define CMC_E_TOO_MANY_FILES ((CMC_return_code) 26) #define CMC_E_TOO_MANY_RECIPIENTS ((CMC_return_code) 27) #define CMC_E_UNABLE_TO_NOT_MARK_AS_READ ((CMC_return_code) 28)
#define CMC_E_UNRECOGNIZED_MESSAGE_TYPE ((CMC_return_code) 29) #define CMC_E_UNSUPPORTED_ACTION ((CMC_return_code) 30) #define CMC_E_UNSUPPORTED_CHARACTER_SET ((CMC_return_code) 31) #define CMC_E_UNSUPPORTED_DATA_EXT ((CMC_return_code) 32) #define CMC_E_UNSUPPORTED_FLAG ((CMC_return_code) 33)
#defineCMC_E_UNSUPPORTED_FUNCTION_EXT ((CMC_return_code) 34) #defineCMC_E_UNSUPPORTED_VERSION ((CMC_return_code) 35) #defineCMC_E_USER_CANCEL ((CMC_return_code) 36) #defineCMC_E_USER_NOT_LOGGED_ON ((CMC_return_code) 37)
3.CMC处理信报的过程 1) 开始对话 在CMC中执行的所有信报操作都是在一个信报对话中进行的。调用cmc_logon( )函数可以创建一个CMC对话,cmc_logon( )函数将利用信报服务程序建立与邮件服务器的连接,检查用户的登录号(即标识用户),设置邮件服务器的属性参数,并返回一个对话句柄。cmc_logon( )函数的调用格式如下:
CMC_return_code cmc_logon( CMC_string service, CMC_string user, CMC_string password, CMC_object_identifier character_set, CMC_ui_id ui_id, CMC_uint16 caller_cmc_version, CMC_flags logon_flags, CMC_session_id FAR *session, CMC_extension FAR *logon_extensions );
该函数的参数很简单,从各参数的名称就可以看出其含义。最后一个参数logon_extensions是一个指向CMC_extension结构数组的指针,该结构在下面的很多函数中都要用到,它包含了使用CMC API函数或结构时的扩展数据。 CMC_extension结构的定义如下: typedef struct { CMC_uint32 item_code; CMC_uint32 item_data; CMC_buffer item_reference; CMC_flags extension_flags; } CMC_extension;
该结构的最后一个字段extension_flags使用时要注意,它设置为CMC_EXT_LAST_ELEMENT时,表示是结构数据中的最后一个结构。 用户可以通过使用cmc_query_configuration( )函数来获取CMC当前的安装信息,包括缺省的用户、信报服务和用户入网条件及其他的一些信息。 cmc_query_configuration( )函数的调用格式如下: CMC_return_code cmc_query_configuration( CMC_session_id session, CMC_enum item, CMC_buffer reference, CMC_extension FAR *config_extensions );
当要结束一个对话时,应该调用cmc_logoff( )函数来关闭一个对话。该函数的调用格式如下: CMC_return_code cmc_logoff( CMC_session_id session, CMC_ui_id ui_id, CMC_flags logoff_flags, CMC_extension FAR *logoff_extensions );
开始对话的过程就是初始化CMC的过程,下面我们举一个实例来说明对话过程的开始和结束。 #include ''xcmc.h'' …… CMC_return_code cmcStatus; CMC_Boolean cmcUiAvail; CMC_session_id cmcSession; //查询该实现中UI(user interface)的可用性
cmcStatus=cmc_query_configuration( 0, //没有会话句柄 CMC_CONFIG_UI_AVAIL, //该参数用来查询UI的可用性 (void*)& cmcUiAvail, //返回的值 NULL //无扩展 ); if(cmcStatus!=CMC_SUCCESS) { //进行错误处理 …… }
else if(cmcUiAvail) printf(''Logon UI is available''); //下面创建一个会话 cmcStatus=cmc_logon( NULL, //使用默认服务 NULL, //用户名 NULL, //用户口令 NULL, //使用默认字符集 0, //使用默认UI标识
CMC_VERSION, //Version 1 CMC CMC_LOGON_UI_ALLOWED | CMC_ERROR_UI_ALLOWED &cmcSession, //返回对话 NULL //无扩展 ); if(cmcStatus!=CMC_SUCCESS) { //进行错误处理 …… }
CMC_VERSION, //Version 1 CMC CMC_LOGON_UI_ALLOWED | CMC_ERROR_UI_ALLOWED &cmcSession, //返回对话 NULL //无扩展 ); if(cmcStatus!=CMC_SUCCESS) { //进行错误处理 …… }
2) 发送信报 当创建好一个CMC对话后,就可以调用cmc_send( )函数或cmc_send_documents( )函数发送信报了。 CMC API提供的cmc_send_documents( )函数功能强大, 几乎包括了发送信报时所需的所有功能,如它可以创建一个对话,而不必使用cmc_logon( )函数。下面是该函数的原型:
CMC_return_code cmc_send_documents( CMC_string recipient_addresses, CMC_string subject, CMC_string text_note, CMC_flags send_doc_flags, CMC_string file_paths, CMC_string file_names, CMC_string delimiter, CMC_ui_id ui_id );
该函数的大多数参数是字符串型的。recipient_addresses参数表示收件人地址;subject为邮件主题;file_paths和file_names参数指出要发送邮件的目录和文件名;可以在同一个字符串参数中定义多个值,使用delimiter参数中定义的定界符将它们分开。 接收者的地址可以是确定的地址,例如user@somewhere.com,也可以是地址手册、其他程序中接收者的名字。ui_id表示发送对话框的父对话框的句柄,只有当使用的服务提供程序具有UI时才可以使用。要使用信报服务提供的对话框,必须把send_doc_flags参数的值设置为CMC_SEND_UI_REQUESTED,这样才允许用户利用普通目的新信报对话框来选择地址,并进入信报的文本和附属文件中,此时为消息确定的任何参数将出现在对话框中成为默认值,这些参数也可以为空。
下面是该函数一种最简单的调用方法: cmcStatus=(*lpfncmc_send_documents)(NULL,NULL,NULL, CMC_SEND_UI_REQUESTED | CMC_LOGON_UI_ ALLOWED| CMC_ERROR_UI_ALLOWED NULL, NULL, NULL, 0); 该调用中的标志CMC_LOGON_UI_ALLOWED和CMC_ERROR_UI_ALLOWED表示允许信报服务程序利用本身的UI进行登录和错误处理等工作。当不使用信报服务程序提供的UI时,要在信报发送时设置地址等参数,例如:
cmcStatus=(*lpfncmc_send_documents)(''to:Zhang Hong,cc:Li Ping'', ''Important Info'', ''We have a date…'', 0, ''c:, c:temp'', ''file1.txt, file2.txt'', '', '', 0);
cmc_send( )函数可以提供更强的发送信报控制功能,它的调用格式如下: CMC_return_code cmc_send( CMC_session_id session, CMC_message FAR *message, CMC_flags send_flags, CMC_ui_id ui_id, CMC_extension FAR *send_extensions );
该函数使用了一个CMC_message结构,以确定要发送的信报和有关信报的信息,这些信息包括接收者、信报内容、附件等。 typedef struct { CMC_message_reference FAR *message_reference; CMC_string message_type; CMC_string subject;
CMC_time time_sent; CMC_string text_note; CMC_recipient FAR *recipients; CMC_attachment FAR *attachments; CMC_flags message_flags; CMC_extension FAR *message_extensions; } CMC_message;
CMC_message结构包括一个指向CMC_recipient结构数组的指针recipients,该结构的定义如下: typedef struct { CMC_string name; CMC_enum name_type; CMC_string address; CMC_enum role; CMC_flags recip_flags; CMC_extension FAR *recip_extensions; } CMC_recipient;
要注意,当填充这些长度可变的每一个数组时,我们必须把最后一个CMC_recipient结构的recip_flags字段指定为CMC_RECIP_LAST_ELEMENT。 attachments字段是一个指向附件文件的CMC_attachment结构数组的指针。该结构的定义如下: typedef struct { CMC_string attach_title; CMC_object_identifier attach_type; CMC_string attach_filename; CMC_flags attach_flags; CMC_extension FAR *attach_extensions; } CMC_attachment;
同上面一样,最后一个CMC_attachment元素的attach_flags也必须指定为CMC_ATT_LAST_ELEMENT。 在CMC_message结构中用到了一个CMC_time结构类型的字段,用来表示发送时间。CMC API中用CMC_time结构类型的量表示时间,它的定义如下:
typedef struct { CMC_sint8 second; CMC_sint8 minute; CMC_sint8 hour; CMC_sint8 day; CMC_sint8 month; CMC_sint8 year; CMC_sint8 isdst; CMC_sint8 unused1; CMC_sint16 tmzone; CMC_sint16 unused2; } CMC_time;
下面是一个使用cmc_send( )函数发送信报的实例: …… CMC_recipient Recip[2]; CMC_attachment Attach; CMC_session_id Session; CMC_message Message; CMC_return_code Status; CMC_time t_now;
//信报接收者(to) Recip[0].name=''Zhang Hua''; Recip[0].name_type=CMC_TYPE_INDIVIDUAL; Recip[0].address=NULL; Recip[0].role=CMC_ROLE_TO; Recip[0].recip_flags=0; Recip[0].recip_extensions=NULL;
//信报抄送者(cc) Recip[1].name=''Li Ping''; Recip[1].name_type=CMC_TYPE_INDIVIDUAL; Recip[1].address=NULL; Recip[1].role=CMC_ROLE_CC; Recip[1].recip_flags=CMC_RECIP_LAST_ELEMENT; //是最后一个 Recip[1].recip_extensions=NULL;
//附件文件 Attach.attach_title=''stock.wks''; Attach.attach_type=NULL; Attach.attach_filename=''file1.tmp''; Attach.attach_flags=CMC_ATT_LAST_ELEMENT; Attach.attach_extensions=NULL;
//信报放入message结构 Message.message_reference=NULL; Message.message_type=NULL; Message.subject=''Stock''; Message.text_note=''Time to buy''; s Message.recipients=Recip; Message.attachments=&Attach; Message.message_flags=0; Message.message_extensions=NULL;
Status=cmc_send(Session, //由cmc_logon调用返 回&Message,0,0,NULL); if(Status!=CMC_SUCCESS) { //进行错误处理 …… }
//下面是用cmc_send_documents( )函数完成上述内容时的调用方法 Status= cmc_send_documents(''to: Zhang Hua,cc: Li Ping'', ''Stock”, ''Time to buy'', CMC_SEND_UI_REQUESTED| CMC_LOGON_UI_ALLOWED| CMC_ERROR_UI_ALLOWED, ''stock.wks'' ''file1.tmp'' '','', 0);
if(Status!=CMC_SUCCESS) { //进行错误处理 …… } 3) 分辨信报的地址 可以使用cmc_look_up( )函数查找与信报相关的姓名和地址,其函数格式为:
CMC_return_code cmc_look_up( CMC_session_id session, CMC_recipient FAR *recipient_in, CMC_flags look_up_flags, CMC_ui_id ui_id, CMC_uint32 FAR *count, CMC_recipient FAR * FAR *recipient_out, CMC_extension FAR *look_up_extensions );
其中,look_up_flags字段控制该函数的操作行为,其常用的标志有: ● CMC_LOOKUP_ADDRESSING_UI: ● CMC_LOOKUP_DETAILS_UI: ● CMC_LOOKUP_RESOLVE_IDENTITY: ● CMC_LOOKUP_RESOLVE_PREFIX_SEARCH: cmc_free( )函数的调用格式很简单,如下所示: CMC_return_code cmc_free( CMC_buffer memory ); 该函数的作用是释放参数memory指出的由CMC分配的内存。
下面是一个如何使用cmc_look_up( )函数查找一个确定地址的实例。 …… CMC_session_id Session; CMC_recipient *pRecipOut; CMC_recipient RecipIn; CMC_return_code Status; CMC_uint32 cCount;
RecipIn.name=''Li Ping''; RecipIn.name_type=CMC_TYPE_INDIVIDUAL; RecipIn.address=NULL; RecipIn.role=0; RecipIn.recip_flags=0; RecipIn.recip_extensions=NULL; Status= cmc_look_up(Session,&RecipIn, MC_LOOKUP_RESOLVE_UI|CMC_ERROR_UI_ALLOWED, 0, &cCount; &pRecipOut, NULL);
//对pRecipOut进行处理 ….. cmc_free(pRecipOut); …… 4) 接收信报 CMC提供了三个用于接收用户收件箱中信报的函数,它们是cmc_list( )、cmc_read( )和cmc_act_on( )。 (1) 列表函数cmc_list( )可按一定的选择标准来获取用户收件箱中的信报列表,其函数调用格式如下:
CMC_return_code cmc_list( CMC_session_id session, CMC_string message_type, CMC_flags list_flags, CMC_message_reference FAR *seed, CMC_uint32 FAR *count, CMC_ui_id ui_id, CMC_message_summary FAR * FAR *result, CMC_extension FAR *list_extensions );
该函数参数较多,其中message_type参数可用来选择列出特定类型的信报,如果要选择所有类型的信报,该参数的值应为NULL;seed参数指向一个CMC_message_reference结构,用于通知地址服务程序在seed所确定的信报被引用后开始查询。 结构CMC_message_reference的定义如下: typedef struct { CMC_uint32 length; char string[1]; } CMC_counted_string; typedef CMC_counted_string CMC_message_reference; 其中,length是string字符串的长度。
cmc_list( )函数的count参数指出用户要获取的最大记录数,函数返回时,它包含实际返回的记录数。如果将list_flags标志设置为CMC_LIST_COUNT_ONLY,则只有count参数被修改,否则,cmc_list( )函数返回一个关于已被发现信报的CMC_message_summary结构数组,该结构的定义如下: typedef struct { CMC_message_referenceFAR *message_reference; CMC_string message_type; CMC_string subject; CMC_time time_sent;
CMC_uint32 byte_length; CMC_recipient FAR *originator; CMC_flags summary_flags; CMC_extension FAR *message_summary_extensions; } CMC_message_summary; 如果将list_flags标志设置为CMC_LIST_MSG_REFS_ONLY,则用来返回有关信报的参考信息。当list_flags标志设置为CMC_LIST_UNREAD_ONLY时,将返回未读取的信报列表。
(2) 信报读取函数cmc_read( )读取一个信报的具体内容,其调用格式如下: CMC_return_code cmc_read( CMC_session_id session, CMC_message_reference FAR *message_reference, CMC_flags read_flags, CMC_message FAR * FAR *message, CMC_ui_id ui_id, CMC_extension FAR *read_extensions );
其中,message_reference参数可能包含一个有效的CMC_message_reference结构类型的值,例如由cmc_list( )函数返回的在CMC_message_reference结构的message_reference成员中的值。当收件箱中的第一个信报被读取时,它的值为NULL。在程序中可以通过指定read_flags的值为CMC_READ_FIRST_UNREAD_MESSAGE来指出cmc_read( )函数读出第一个未能读取的信报。在默认情况下,由cmc_read( )函数返回的信报在邮箱中的标记为已读,可以通过指定read_flags的值为CMC_DO_NOT_MARK_AS_READ来禁止对信报标记。
当cmc_read( )函数调用成功时,函数的返回值为CMC_SUCCESS,并且指向信报的指针被修改为指向CMC_message结构,cmc_read( )将为该结构分配内存,并把已读信报的信息填充到该结构中。使用完CMC_message结构后, 应该调用cmc_free( )函数释放内存。 信报的文本内容包含在CMC_message结构的text_note字段中,但如果该结构的message_flags字段为CMC_MSG_TEXT_NOTE_AS_FILE,则信报的文本存放在第一个附加文件中。
有关信报的附加信息包含在CMC_message结构的attachments字段中,访问CMC_attachment结构数组中的最后一个附件,要将message_flags字段设置为CMC_ATT_LAST_ELEMENT标记。当使用cmc_read( )函数读取一个信报时,所有的附加文件都以暂存磁盘文件的形式被保存。附件的文件名被存放在CMC_attachment结构的attach_filename字段中,我们可以通过文件的I/O操作来处理这些文件。
(3) 处理信报函数cmc_act_on( )可以在收件箱的信报上执行除读取以外的各种操作,该函数的调用格式如下: CMC_return_code cmc_act_on( CMC_session_id session, CMC_message_reference FAR *message_reference, CMC_enum operation, CMC_flags act_on_flags, CMC_ui_id ui_id, CMC_extension FAR *act_on_extensions );
该函数的message_reference参数必须指向一个有效的CMC_message_reference结构值,通常它是调用cmc_list( )函数时在CMC_message_summary结构中的message_reference字段的返回值,或者是在调用cmc_read( )时在CMC_message结构中的message_reference字段的返回值。 通过给该函数的operation字段设置不同的值,使该函数完成不同的操作。当设置的值为CMC_ACT_ON_DELETE时,可用来删除一个信报;当设置的值为CMC_ACT_ON_EXTENDED时,可完成由act_on_extensions参数定义的操作。
下面是一个综合实例,在该实例中列出、读取和删除了收件箱中的信报。 …… CString strTmp; CMC_message_summary *pMsgSummary; CMC_message *pMessage; CMC_uint32 iCount; CMC_session_id Session; CMC_return_code Status;
iCount=5; Status=cmc_list (Session,NULL, CMC_LIST_UNREAD_ONLY, NULL, &iCount, 0, &pMsgSummary, NULL);
if(Status!=CMC_SUCCESS) { //进行错误处理 …… } Status=cmc_read(Session, PMsgSummary->message_reference, CMC_MSG_AND_ATT_HDRS_ONLY, &pMessage, 0, NULL);
if(Status!=CMC_SUCCESS) { //进行错误处理 …… } Status=cmc_act_on(Session, pMsgSummary->message_reference, CMC_ACT_ON_DELETE, 0, 0, NULL);
{ //进行错误处理 …… } Status=cmc_free(PmsgSummary); Status=cmc_free(pMessage);
9.3 WinInet API 9.3.1 WinInet概述 1.WinInet中句柄的概念 句柄的概念对于使用MFC进行程序设计的人员来说应该是非常熟悉的了,在WinInet中(包括后面要介绍的ISAPI和TAPI)同样要经常使用句柄的概念。
HINTERNET句柄与其他Win32句柄的重要区别在于,Internet句柄被安排在一个树型体系结构中,由InternetOpen( )返回的对话句柄是该树型体系的主干,由InternetConnect( )返回的连接句柄是该体系的分支,指向格式文件的句柄和查询结果构成该树型体系的树叶。 句柄可以从派生出它的句柄继承属性,例如在异步方法下,我们只需调用一次InternetCloseHandle( )来关闭所有的分支句柄,由它派生出的所有句柄都将关闭。 根据句柄的具体类型,HINTERNET句柄可有许多不同的选项与之相联系,这些选项可以通过InternetQueryOption( )和InterSetOption( )来访问。它们可以用来访问如下信息:句柄的具体类型、超时设置、回调、环境变量、缓冲区大小以及其他设置。
2.WinInet中错误的处理 WinInet函数在调用时,如果调用成功则返回一个有效的句柄,如果调用失败则函数返回FALSE或NULL。在函数调用失败时可以调用GetLastError( )函数得到具体的错误信息。当GetLastError( )函数返回的错误信息是ERROR_INTERNET_EXTENDED_ERROR时,对于FTP或Gopher服务器,可进一步通过调用InternetGetLastResponseInfo( )函数得到更多的信息。对于HTTP操作,可以使用InternetErrorDlg( )函数来显示一个错误信息对话框,允许用户对如何处理错误做出选择。
3.WinInet函数缓冲区参数的含义 许多WinInet API函数使用指针(lpszBuffer)参数和缓冲区长度(lpdwBufferLength)参数返回一个可变长度的字符串。在传送的缓冲区大小不能容纳返回的字符串时(或指针为NULL),函数调用将失败。如果使用GetLastError( )函数,将返回ERROR_INSUFFICIENT_BUFFER错误。 4.WinInet函数的异步I/O操作 WinInet函数的默认操作是同步操作,但这对于完成时间不确定的一些操作是不合适的,因此如果要使用异步操作,可以通过调用InterOpen( )函数来打开一个对话,设置一个叫INTERNET_FLAG_ASYNC的标志来完成。
9.3.2 基本WinInet函数 1.打开一个WinInet对话 大多数的WinInet函数在使用之前,必须首先调用InternetOpen( )函数,以打开一个新的对话,然后在调用其他函数时以该函数返回的句柄为输入参数,才可以进行其他操作。该函数的调用格式如下:
HINTERNET InternetOpen( IN LPCTSTR lpszAgent, IN DWORD dwAccessType, IN LPCTSTR lpszProxyName, IN LPCTSTR lpszProxyBypass, IN DWORD dwFlags );
该函数在WinInet应用程序中是第一个被调用的函数,它的功能是初始化WinInet库和数据结构,为其他WinInet调用做好准备。在WinInet中,当对话操作完成以后,应该使用InternetCloseHandle( )函数关闭该对话句柄,以释放与该对话句柄相关的资源。尽管在一个WinInet应用中可能使用一次InternetOpen( )函数调用就足够了,但WinInet允许多次调用该函数。
lpszAgent参数在HTTP协议中是用户代理的名字,例如Microsoft® Internet Explorer。 dwAccessType参数表示存取类型,它可设置为: ● INTERNET_OPEN_TYPE_DIRECT,表示在本地解决所有的主机名问题。 ● INTERNET_OPEN_TYPE_PRECONFIG,表示获取代理或从注册表直接配置。 ●INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY,表示获取代理或从注册表直接配置并防止 startup的使用。 ● INTERNET_OPEN_TYPE_PROXY,表示用代理服务器发送请求。
lpszProxyName参数表示代理服务器名称。LpszProxyBypass参数定义一个字符串,可以表示在本地机上决定地址列表,该字符串中包含的每个地址请求将在本地机进行处理,而不被送到代理服务器。 DwFlags是一个无符号的长整型量,该标志的不同取值影响函数的操作行为。当它的取值为INTERNET_FLAG_ASYNC时,表示该对话句柄或由它派生出的句柄启动异步操作;为INTERNET_FLAG_FROM_CACHE时(等同INTERNET_FLAG_OFFLINE),表示不进行网络请求,所有操作从高速缓冲返回,如果请求不在高速缓冲中,则返回适当的错误,如ERROR_FILE_NOT_FOUND。要注意这些取值可以进行适当的组合。
2.与服务器建立连接 为了与FTP、HTTP或Gopher服务器进行通信,我们首先应该与服务器建立连接。与服务器建立连接的函数是InternetConnect( ),它的原型如下: HINTERNET InternetConnect( IN HINTERNET hInternet, IN LPCTSTR lpszServerName, IN INTERNET_PORT nServerPort, IN LPCTSTR lpszUserName, IN LPCTSTR lpszPassword, IN DWORD dwService, IN DWORD dwFlags, IN DWORD_PTR dwContext );
该函数调用成功后返回一个对FTP、HTTP或Gopher会话的HINTERNET型连接句柄,该连接句柄是由调用InternetOpen( )函数时所创建的对话句柄派生出来的,它继承了对话句柄的属性。 对于FTP协议,调用InternetConnect( )函数时将与服务器建立一条真正的连接;而对于HTTP和Gopher协议来说,到指定的服务器请求进行时才建立连接。 hInternet参数是先前InternetOpen( )函数调用返回的一个有效的对话句柄。
lpszServerName和nServerPort参数指出要连接服务器的名称和端口号。服务器名称可以是点分十进制表示的IP地址,如11.0.1.45。nServerPort参数如果是INTERNET_INVALID_PORT_NUMBER,则使用由dwService确定的默认端口,也可以是以下的一些常数: ● NTERNET_DEFAULT_FTP_PORT,表示使用FTP服务器的默认端口21。 ● INTERNET_DEFAULT_GOPHER_PORT,表示使用Gopher服务器的默认端口70。
● INTERNET_DEFAULT_HTTP_PORT,表示使用HTTP服务器的默认端口80。 ● INTERNET_DEFAULT_SOCKS_PORT, 表示使用SOCKS防火墙服务器的默认端口1080。 lpszUserName和lpszPassword参数表示入网时的用户名和口令字,如果lpszUserName参数为NULL,除HTTP外,函数将使用一个合适的默认值。如对FTP服务器,则使用anonymous。如果lpszPassword参数为NULL,则使用anonymous做为口令,但对FTP服务器,一般使用用户的电子邮件地址作为口令。FTP服务器中这两个参数的各种取值如表9-3所示。
表9-3 lpszUserName和lpszPassword参数的取值
dwService参数指出连接的服务类型,它可以取如下的值: ● INTERNET_SERVICE_FTP,表示FTP服务。 ● INTERNET_SERVICE_GOPHER,表示Gopher服务。 ● INTERNET_SERVICE_HTTP,表示HTTP服务。 dwFlags参数当前只能取INTERNET_SERVICE_FTP值,表示对于FTP连接采用被动方式。该函数调用失败时返回NULL,可以调用GetLastError( )函数或InternetGetLast-ResponseInfo( )函数取得进一步的错误信息。
3.句柄选项的设置与查询 我们可以对一个Internet的句柄设置一些选项,以控制对句柄的操作行为。对句柄设置选项要调用InternetSetOption( )函数,其函数原型如下: BOOL InternetSetOption( IN HINTERNET hInternet, IN DWORD dwOption, IN LPVOID lpBuffer, IN DWORD dwBufferLength );
hInternet参数表示将要被设置选项的句柄;lpBuffer和dwBufferLength参数分别指缓冲区和缓冲区的长度;lpBuffer中存有选项;dwOption是要设置的选项,它可以取下列值: INTERNET_OPTION_BYPASS_EDITED_ENTRY INTERNET_OPTION_CACHE_TIMESTAMPS INTERNET_OPTION_CALLBACK INTERNET_OPTION_CLIENT_CERT_CONTEXT INTERNET_OPTION_CONNECT_RETRIES
INTERNET_OPTION_CONNECT_TIMEOUT INTERNET_OPTION_CONNECTED_STATE INTERNET_OPTION_CONTEXT_VALUE INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT INTERNET_OPTION_CONTROL_SEND_TIMEOUT INTERNET_OPTION_DATAFILE_NAME INTERNET_OPTION_DIGEST_AUTH_UNLOAD INTERNET_OPTION_END_BROWSER_SESSION
INTERNET_OPTION_ERROR_MASK INTERNET_ERROR_MASK_COMBINED_SEC_CERT INTERNET_ERROR_MASK_INSERT_CDROM INTERNET_ERROR_MASK_LOGIN_FAILURE_DISPLAY_ENTITY_BODY INTERNET_OPTION_EXTENDED_ERROR INTERNET_OPTION_FROM_CACHE_TIMEOUT
INTERNET_OPTION_HANDLE_TYPE:该选项只能用于下面要介绍的选项查询函数。句柄类型可能的返回值如下: ● INTERNET_HANDLE_TYPE_CONNECT_FTP ● INTERNET_HANDLE_TYPE_CONNECT_GOPHER ● INTERNET_HANDLE_TYPE_CONNECT_HTTP ● INTERNET_HANDLE_TYPE_FILE_REQUEST ● INTERNET_HANDLE_TYPE_FTP_FILE
● INTERNET_HANDLE_TYPE_FTP_FILE_HTML ● INTERNET_HANDLE_TYPE_FTP_FIND ● INTERNET_HANDLE_TYPE_FTP_FIND_HTML ● INTERNET_HANDLE_TYPE_GOPHER_FILE ● INTERNET_HANDLE_TYPE_GOPHER_FILE_HTML ● INTERNET_HANDLE_TYPE_GOPHER_FIND ● INTERNET_HANDLE_TYPE_GOPHER_FIND_HTML
● INTERNET_HANDLE_TYPE_HTTP_REQUEST ● INTERNET_HANDLE_TYPE_INTERNET INTERNET_OPTION_HTTP_VERSION INTERNET_OPTION_IGNORE_OFFLINE INTERNET_OPTION_MAX_CONNS_PER_SERVER INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER INTERNET_OPTION_PARENT_HANDLE
INTERNET_OPTION_PASSWORD INTERNET_OPTION_PER_CONNECTION_OPTION INTERNET_OPTION_PROXY INTERNET_OPTION_PROXY_PASSWORD INTERNET_OPTION_PROXY_USERNAME INTERNET_OPTION_READ_BUFFER_SIZE INTERNET_OPTION_RECEIVE_TIMEOUT INTERNET_OPTION_REFRESH INTERNET_OPTION_RESET_URLCACHE_SESSION
INTERNET_OPTION_REQUEST_FLAGS:该选项只能用于下面要介绍的选项查询函数,可取的值如下: ● INTERNET_REQFLAG_CACHE_WRITE_DISABLED ● INTERNET_REQFLAG_FROM_CACHE ● INTERNET_REQFLAG_NET_TIMEOUT ● INTERNET_REQFLAG_NO_HEADERS ● INTERNET_REQFLAG_VIA_PROXY INTERNET_OPTION_SECONDARY_CACHE_KEY INTERNET_OPTION_SECURITY_CERTIFICATE INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT
INTERNET_OPTION_SECURITY_FLAGS:该选项只能用于下面要介绍的选项查询函数。它可以是下列值的组合: ● SECURITY_FLAG_128BIT ● SECURITY_FLAG_STRENGTH_STRONG ● SECURITY_FLAG_40BIT ● SECURITY_FLAG_STRENGTH_WEAK ● SECURITY_FLAG_56BIT
● SECURITY_FLAG_STRENGTH_MEDIUM ● SECURITY_FLAG_FORTEZZA ● SECURITY_FLAG_IETFSSL4 ● SECURITY_FLAG_IGNORE_CERT_CN_INVALID ●SECURITY_FLAG_IGNORE_CERT_DATE_INVALID ● SECURITY_FLAG_IGNORE_REDIRECT_TO_HTTP ● SECURITY_FLAG_IGNORE_REDIRECT_TO_HTTPS ● SECURITY_FLAG_IGNORE_REVOCATION
● SECURITY_FLAG_IGNORE_UNKNOWN_CA ● SECURITY_FLAG_IGNORE_WRONG_USAGE ● SECURITY_FLAG_NORMALBITNESS ● SECURITY_FLAG_STRENGTH_WEAK ● SECURITY_FLAG_PCT ● SECURITY_FLAG_PCT4 ● SECURITY_FLAG_SECURE ● SECURITY_FLAG_SSL
● SECURITY_FLAG_SSL3 ● SECURITY_FLAG_UNKNOWNBIT INTERNET_OPTION_SECURITY_KEY_BITNESS INTERNET_OPTION_SEND_TIMEOUT INTERNET_OPTION_SETTINGS_CHANGED INTERNET_OPTION_URL INTERNET_OPTION_USER_AGENT INTERNET_OPTION_USERNAME INTERNET_OPTION_VERSION INTERNET_OPTION_WRITE_BUFFER_SIZE
这些选项的含义在MSDN或WinInet Help中有详细的说明。以上选项绝大部分对于选项设置和选项查询函数都可以使用,但也要注意有少部分的选项只能用于选项查询函数。 当然,对于设置的选项也可以使用InternetQueryOption( )函数进行查询,该函数的原型如下: BOOL InternetQueryOption( IN HINTERNET hInternet, IN DWORD dwOption, OUT LPVOID lpBuffer, IN OUT LPDWORD lpdwBufferLength );
该函数的参数与InternetSetOption( )函数的参数含义是相同的,只是lpBuffer参数是输出参数; 该函数的参数与InternetSetOption( )函数的参数含义是相同的,只是lpBuffer参数是输出参数; lpdwBufferLength参数调用时是输入参数,返回时是输出参数,返回实际需要的缓冲区长度。 4.回调函数 前面说过,WinInet函数在默认情况下是同步操作的,如果要异步使用,在对话句柄由InternetOpen( )创建时,dwFlags参数必须设置为INTERNET_FLAG_ASYN,还要指定一个环境变量,并为句柄指定一个返回函数,而该返回函数通过InternetSetStatusCallback( )函数调用与一个句柄连接,InternetSetStatusCallback( )的原型如下:
INTERNET_STATUS_CALLBACK InternetSetStatusCallback( IN HINTERNET hInternet, IN INTERNET_STATUS_CALLBACK lpfnInternetCallback );
该函数的hInternet参数指定一个将在由lpfnInternetCallback指定的回调函数中使用的句柄。由该句柄派生出的所有的句柄在呼叫被连接之后,将继续使用该回调函数。 VOID(CALLBACK*INTERNET_STATUS_CALLBACK) ( IN HINTERNET hInternet, IN DWORD_PTR dwContext, IN DWORD dwInternetStatus, IN LPVOID lpvStatusInformation, IN DWORD dwStatusInformationLength );
当调用回调函数返回异步操作的状态时,hInternet参数指出将被调用的回调函数的句柄;dwContext是与hInternet相关的应用上下文参数;dwInternetStatus参数指出调用回调函数的原因;lpvStatusInformation参数是与回调函数相关的信息;而dwStatusInformation-Length参数中存放lpvStatusInformation参数中信息的长度。lpvStatusInformation参数一般指向一个INTERNET_ASYNC_RESULT结构,该结构的定义如下: typedef struct { DWORD dwResult; DWORD dwError; } INTERNET_ASYNC_RESULT, * LPINTERNET_ASYNC_RESULT; 其中,dwResult字段是一个操作返回值,dwError字段包含一个错误代码。
dwInternetStatus参数可取的值如下: INTERNET_STATUS_CLOSING_CONNECTION INTERNET_STATUS_CONNECTED_TO_SERVER INTERNET_STATUS_CONNECTING_TO_SERVER INTERNET_STATUS_CONNECTION_CLOSED INTERNET_STATUS_CTL_RESPONSE_RECEIVED INTERNET_STATUS_DETECTING_PROXY INTERNET_STATUS_HANDLE_CLOSING INTERNET_STATUS_HANDLE_CREATED INTERNET_STATUS_INTERMEDIATE_RESPONSE
INTERNET_STATUS_NAME_RESOLVED INTERNET_STATUS_RECEIVING_RESPONSE INTERNET_STATUS_REDIRECT INTERNET_STATUS_REQUEST_COMPLETE INTERNET_ASYNC_RESULT INTERNET_STATUS_REQUEST_SENT INTERNET_STATUS_RESOLVING_NAME INTERNET_STATUS_RESPONSE_RECEIVED INTERNET_STATUS_SENDING_REQUEST INTERNET_STATUS_STATE_CHANGE
5.通用文件操作 在WinInet API中定义的大部分函数,在调用之前先要与服务器建立连接。然而WinInet API中定义的InternetOpenUrl( )函数提供了一种从FTP、HTTP或Gopher服务器获取数据的简单方法,即以一种通用的方法调用InternetOpenUrl( )函数,该函数的原型如下: HINTERNET InternetOpenUrl( IN HINTERNET hInternet, IN LPCTSTR lpszUrl, IN LPCTSTR lpszHeaders, IN DWORD dwHeadersLength, IN DWORD dwFlags, IN DWORD_PTR dwContext );
该函数被调用时将建立它自己与服务器的连接,因此不需要调用InternetConnect( )函数来建立连接。 hInternet参数是由先前InternetOpen( )函数调用返回的一个有效的句柄。 lpszUrl参数包含一个开始读时的URL,该URL只能由“ftp:”、“gopher:”、“http:”、或“https:”开始。 lpszHeaders参数包含将被发送到HTTP服务器的内容的头信息,该信息的长度由dwHeadersLength参数指出,当无附加的头信息时,该字段可取NULL。dwFlags参数是标志字段,它可以取下面的任何一个值:
INTERNET_FLAG_EXISTING_CONNECT INTERNET_FLAG_HYPERLINK INTERNET_FLAG_IGNORE_CERT_CN_INVALID INTERNET_FLAG_IGNORE_CERT_DATE_INVALID INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS INTERNET_FLAG_KEEP_CONNECTION INTERNET_FLAG_NEED_FILE INTERNET_FLAG_NO_AUTH INTERNET_FLAG_NO_AUTO_REDIRECT INTERNET_FLAG_NO_CACHE_WRITE INTERNET_FLAG_NO_COOKIES
INTERNET_FLAG_NO_UI INTERNET_FLAG_PASSIVE INTERNET_FLAG_PRAGMA_NOCACHE INTERNET_FLAG_RAW_DATA INTERNET_FLAG_RELOAD INTERNET_FLAG_RESYNCHRONIZE INTERNET_FLAG_SECURE 如果连接被成功建立,该函数将返回一个对于FTP、Gopher或HTTP URL的有效句柄,否则返回NULL,可以调用GetLastError( )和InternetGetLastResponseInfo( )函数以获取其错误说明。
6.数据读取操作 对于一个已经打开的句柄,可以调用InternetReadFile( )函数来读取数据。读取数据的句柄可以是由InternetOpenUrl( )、FtpOpenFile( )、GopherOpenFile( )或HttpOpenRequest( )函数创建的。该函数的原型如下: BOOL InternetReadFile( IN HINTERNET hFile, IN LPVOID lpBuffer, IN DWORD dwNumberOfBytesToRead, OUT LPDWORD lpdwNumberOfBytesRead );
hFile参数是一个由InternetOpenUrl( )、FtpOpenFile( )、GopherOpenFile( )或HttpOpen- Request( )函数返回的有效句柄。所读取的数据存入lpBuffer指向的缓冲区中;参数dwNumberOfBytesToRead确定读取的字节数;lpdwNumberOfBytesRead参数返回实际读取的字节数,调用之前它的值应为0。 函数调用成功则返回TRUE,否则返回FALSE。如果函数返回TRUE,并且读取的字节数为0,则文件传输完成,表示读到了文件结束标志(EOF)。
7.向Internet文件写数据 我们可以调用InternetWriteFile( )函数向一个打开的Internet文件写数据,该函数的原型如下: BOOL InternetWriteFile( IN HINTERNET hFile, IN LPCVOID lpBuffer, IN DWORD dwNumberOfBytesToWrite, OUT LPDWORD lpdwNumberOfBytesWritten );
其中,hFile参数表示要进行写操作的文件句柄,该句柄由先前的FtpOpenFile( )调用返回;lpBuffer缓冲区中保存要写入文件的数据,其长度由dwNumberOfBytesToWrite参数确定;lpdwNumberOfBytesWritten参数返回实际写入数据的字节数。 当该函数调用成功时,返回TRUE,否则返回FALSE。当应用程序在发送数据时,只有使用InternetCloseHandle( )关闭句柄,数据的发送才终止。
8.移动Internet文件指针 调用InternetReadFile( )读取文件数据时,可以调用InternetSetFilePointer( )函数来设置一个文件的指针位置,用来控制读取数据的位置。该函数的原型如下: DWORD InternetSetFilePointer( IN HINTERNET hFile, IN LONG lDistanceToMove, IN PVOID pReserved, IN DWORD dwMoveMethod, IN DWORD dwContext );
这是一个同步调用,然而当缓冲区没有可操作的数据或服务器不支持随机存取方式时,在InternetSetFilePointer( )函数之后的其他调用可能被阻塞或挂起。 hFile参数是一个先前由InternetOpenUrl( )或HttpOpenRequest( )函数调用返回的句柄,该句柄在建立时不能使用INTERNET_FLAG_DONT_CACHE或INTERNET_FLAG_NO_CACHE_WRITE设置标志。 lDistanceToMove参数指出文件指针要移动的字节数,如果是一个正数,则表示指针向前移动;如果是一个负数,则表示指针向后移动。pReserved和dwContext是保留参数,但前者必须为NULL,后者必须为0。
dwMoveMethod参数指出指针开始移动的位置,它的取值如下: FILE_BEGIN FILE_CURRENT FILE_END 9.数据可用性查询 在调用InternetReadFile( )函数读取数据前,我们可以调用InternetQueryDataAvailable( )函数,以查询服务器有多少可读数据。该函数的原型如下:
BOOL InternetQueryDataAvailable( IN HINTERNET hFile, OUT LPDWORD lpdwNumberOfBytesAvailable, IN DWORD dwFlags, IN DWORD dwContext );
其中,hFile参数是由InternetOpenUrl( )、FtpOpenFile( )、GopherOpenFile( )或HttpOpenRequest( )函数调用返回的一个有效的句柄;lpdwNumberOfBytesAvailable参数返回可用数据的字节数;dwFlags和dwContext参数都是保留参数,但要注意它们的取值一定要设置为0。 如果该函数调用成功,则返回TRUE,否则返回FALSE。如果返回FALSE,则可以调用GetLastError( )函数获取进一步的错误信息。如果函数调用时发现没有匹配的文件,GetLastError( )函数将返回ERROR_NO_MORE_FILES错误信息。
10.URL处理操作 WinInet API提供了多个对URL进行处理的函数,这些函数的用法比较简单,主要有对URL各个组成部分进行语法分析的InternetCrackUrl( )函数,用于由各个组件创建URL的InternetCreateUrl( )函数,用于把URL转化成正规格式的InterCanonicalizeUrl( )函数和用于把基本URL和相对URL合并的InternetCombinUrl( )函数。
9.3.3 FTP客户机WinInet函数 FTP客户机WinInet函数专门在与FTP服务器相连时使用。FTP客户机编程的步骤一般是先建立与FTP服务器的连接,再进行目录操作,查找FTP服务器上的文件,最后进行文件的下载或上载操作。 以下各函数中使用到的FTP连接句柄hConnect由调用InternetConnect( )函数时返回。
1.获取FTP目录 可以使用FtpGetCurrentDirectory( )函数获取特定FTP的当前目录。该函数的原型如下: BOOL FtpGetCurrentDirectory( IN HINTERNET hConnect, OUT LPTSTR lpszCurrentDirectory, IN OUT LPDWORD lpdwCurrentDirectory ); 其中,参数hConnect是一个对FTP会话的有效句柄;lpszCurrentDirectory参数存放返回的当前目录的绝对路径;lpdwCurrentDirectory参数中存放lpszCurrentDirectory参数的长度。
2.设置FTP当前目录 设置FTP当前工作目录的函数原型如下: BOOL FtpSetCurrentDirectory( IN HINTERNET hConnect, IN LPCTSTR lpszDirectory ); 其中,参数hConnect是一个对FTP会话的有效句柄;lpszDirectory参数指出要设置的当前工作目录。
3.创建FTP目录 在一个FTP服务器上创建一个新目录的函数原型如下: BOOL FtpCreateDirectory( IN HINTERNET hConnect, IN LPCTSTR lpszDirectory ); 其中,参数hConnect是一个对FTP会话的有效句柄;lpszDirectory参数指出要创建的目录。
4.删除FTP目录 删除FTP目录的函数原型如下: BOOL FtpRemoveDirectory( IN HINTERNET hConnect, IN LPCTSTR lpszDirectory ); 其中,参数hConnect是一个对FTP会话的有效句柄;lpszDirectory参数指出要删除的目录。
5.查找文件 使用FtpFindFirstFile( )函数可以从指定的FTP服务器上查找文件或目录,该函数原型如下: HINTERNET FtpFindFirstFile( IN HINTERNET hConnect, IN LPCTSTR lpszSearchFile, OUT LPWIN32_FIND_DATA lpFindFileData, IN DWORD dwFlags, IN DWORD_PTR dwContext );
其中,参数lpszSearchFile中存有一个要查找的有效的文件或目录名。查找结果保存在lpFindFileData参数中,该参数是一个指向WIN32_FIND_DATA结构的指针,它的定义如下: typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow;
DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[ MAX_PATH ]; TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA; 此定义中的dwFileAttributes是文件的属性描述字段,它的取值如下: FILE_ATTRIBUTE_ARCHIVE FILE_ATTRIBUTE_COMPRESSED FILE_ATTRIBUTE_DIRECTORY FILE_ATTRIBUTE_ENCRYPTED FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_NORMAL FILE_ATTRIBUTE_OFFLINE FILE_ATTRIBUTE_REPARSE_POINT FILE_ATTRIBUTE_SPARSE_FILE FILE_ATTRIBUTE_TEMPORARY 此定义中其他字段的含义很简单,FILETIME是一个表示时间的结构,它的定义如下: typedef struct _FILETIME { DWORD dwLowDateTime;
DWORD dwHighDateTime; } FILETIME, *PFILETIME; 函数中,dwFlags参数控制函数的操作行为,它可以是下列值的组合: INTERNET_FLAG_HYPERLINK INTERNET_FLAG_NEED_FILE INTERNET_FLAG_NO_CACHE_WRITE INTERNET_FLAG_RELOAD INTERNET_FLAG_RESYNCHRONIZE
6.下载文件 使用FtpGetFile( )函数可以从指定的FTP服务器上下载文件,该函数原型如下: BOOL FtpGetFile( IN HINTERNET hConnect, IN LPCTSTR lpszRemoteFile, IN LPCTSTR lpszNewFile, IN BOOL fFailIfExists, IN DWORD dwFlagsAndAttributes, IN DWORD dwFlags, IN DWORD_PTR dwContext );
其中,lpszRemoteFile参数是要下载的远程文件的名称;lpszNewFile参数是在本地机上要创建的文件名称。如果已经存在同名的本地文件,则fFailIfExists取值为TRUE,本次FtpGetFile( )函数的调用失败。dwFlags参数用于文件的控制传输模式和缓冲使用方式,它可以是下列取值的组合: FTP_TRANSFER_TYPE_ASCII FTP_TRANSFER_TYPE_BINARY INTERNET_FLAG_HYPERLINK INTERNET_FLAG_NEED_FILE INTERNET_FLAG_RELOAD INTERNET_FLAG_RESYNCHRONIZE
7.上载文件 使用FtpPutFile( )函数可以把指定文件上载到FTP服务器,该函数原型如下: BOOL FtpPutFile( IN HINTERNET hConnect, IN LPCTSTR lpszLocalFile, IN LPCTSTR lpszNewRemoteFile, IN DWORD dwFlags, IN DWORD_PTR dwContext ; 其中,lpszLocalFile参数是要上载的本地文件的名称;lpszNewRemoteFile参数是在远程服务器上的文件名称。dwFlags参数的含义与FtpGetFile( )函数中该参数的含义相同。
8.文件更名 使用FtpRenameFile( )函数可以更改远程FTP服务器上指定文件的名称,该函数原型如下: BOOL FtpRenameFile( IN HINTERNET hConnect, IN LPCTSTR lpszExisting, IN LPCTSTR lpszNew ); 其中,lpszExisting参数是远程服务器上将要更改的文件的名称;lpszNew参数是文件的新名称。
9.3.4 HTTP客户机WinInet函数 HTTP客户机WinInet函数专门在客户机与HTTP服务器相连时使用。从客户机向HTTP服务器发出一个请求到接收到相应的结果要经过多个操作,但首先要创建一个HTTP请求,因此下面我们先学习HTTP请求的创建方法。同样,各函数中使用到的连接句柄hConnect由调用InternetConnect( )函数时返回。
1.HTTP请求 创建一个HTTP请求可以调用HttpOpenRequest( )函数,该函数的原型如下: HINTERNET HttpOpenRequest( IN HINTERNET hConnect, IN LPCTSTR lpszVerb, IN LPCTSTR lpszObjectName, IN LPCTSTR lpszVersion, IN LPCTSTR lpszReferer, IN LPCTSTR FAR * lpszAcceptTypes, IN DWORD dwFlags, IN DWORD_PTR dwContext );
其中,lpszVerb是一个非常重要的参数,它表示请求所使用的动词,这些动词可以是: ● GET:表示取得数据(默认取值); ● HEAD:接收服务器的响应标题; ● POST:为服务器发送所执行操作的信息; ● PUT:为服务器发送信息; ● DELETE:从服务器上删除一个资源; ● LINK:在URL之间建立连接。
lpszObjectName参数包含lpszVerb动词所操作的目标对象名称;lpszVersion参数包含有HTTP的版本信息;lpszReferer参数指定页的URL;lpszAcceptTypes参数指出客户机应用程序接收的信息类型,它是一个字符串数组,如“image/jpeg”等。如果lpszAcceptTypes参数为NULL,则表示只能接收文本文件,即类型为“text/*”。dwFlags的取值可以为: INTERNET_FLAG_CACHE_IF_NET_FAIL INTERNET_FLAG_HYPERLINK INTERNET_FLAG_IGNORE_CERT_CN_INVALID INTERNET_FLAG_IGNORE_CERT_DATE_INVALID INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS INTERNET_FLAG_KEEP_CONNECTION
INTERNET_FLAG_NEED_FILE INTERNET_FLAG_NO_AUTH INTERNET_FLAG_NO_AUTO_REDIRECT INTERNET_FLAG_NO_CACHE_WRITE INTERNET_FLAG_NO_COOKIES INTERNET_FLAG_NO_UI INTERNET_FLAG_PRAGMA_NOCACHE INTERNET_FLAG_RELOAD INTERNET_FLAG_RESYNCHRONIZE INTERNET_FLAG_SECURE
如果调用该函数时请求成功,则函数的返回值为一个有效的句柄,使用该句柄就可以调用HTTP的其他操作了。 2.HTTP请求标题 HTTP请求有各种不同的标题与之相联系,对标题进行操作要使用HttpAddRequest- Headers( )函数,该函数的原型如下: BOOL HttpAddRequestHeaders( IN HINTERNET hConnect, IN LPCTSTR lpszHeaders, IN DWORD dwHeadersLength, IN DWORD dwModifiers );
其中,lpszHeaders和dwHeadersLength参数表示标题和标题的长度;dwModifiers参数的值决定对标题的操作,它的取值如下: ● HTTP_ADDREQ_FLAG_ADD:如果标题不存在,则增加标题; ● HTTP_ADDREQ_FLAG_ADD_IF_NEW:仅在标题不存在时增加标题,否则返回错误; ● HTTP_ADDREQ_FLAG_COALESCE:相同名称的标题合并; ●HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA:使用逗号将相同名称的标题合并。如“Accept: text/*”后跟“Accept: audio/*”,则合并后为“Accept: text/*, audio/*”;
●HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON:使用引号将相同名称的标题合并; ● HTTP_ADDREQ_FLAG_REPLACE:替换或移去一个标题。 3.发送HTTP请求 可以调用HttpSendRequest( )函数向HTTP服务器发送指定的请求,该函数的原型如下: BOOL HttpSendRequest( IN HINTERNET hRequest, IN LPCTSTR lpszHeaders,
IN DWORD dwHeadersLength, IN LPVOID lpOptional, DWORD dwOptionalLength ); 其中,hRequest参数是一个由HttpOpenRequest( )函数调用返回的句柄;lpszHeaders参数指定一个长度由dwHeadersLength参数确定的附加标题和请求一起发送;lpOptional参数确定的任意数据被放在标题后面发送,这通常在向服务器发送数据的PUT或POST操作中使用。
4.HTTP信息查询 可以调用HttpQueryInfo( )函数向HTTP服务器查询与HTTP请求相关的标题信息,该函数的原型如下: BOOL HttpQueryInfo( IN HINTERNET hRequest, IN DWORD dwInfoLevel, IN LPVOID lpvBuffer, IN LPDWORD lpdwBufferLength, IN OUT LPDWORD lpdwIndex );
其中,hRequest参数是一个HINTERNET型的由HttpOpenRequest( )函数或InternetOpenUrl( )函数调用返回的句柄;dwInfoLevel参数指定要返回的信息和返回方式,它可以是下列值的组合: HTTP_QUERY_ACCEPT HTTP_QUERY_ACCEPT_CHARSET HTTP_QUERY_ACCEPT_ENCODING HTTP_QUERY_ACCEPT_LANGUAGE HTTP_QUERY_ACCEPT_RANGES
HTTP_QUERY_AGE HTTP_QUERY_ALLOW HTTP_QUERY_AUTHORIZATION HTTP_QUERY_CACHE_CONTROL HTTP_QUERY_CONNECTION HTTP_QUERY_CONTENT_BASE HTTP_QUERY_CONTENT_DESCRIPTION HTTP_QUERY_CONTENT_DISPOSITION HTTP_QUERY_CONTENT_ENCODING HTTP_QUERY_CONTENT_ID
HTTP_QUERY_CONTENT_LANGUAGE HTTP_QUERY_CONTENT_LENGTH HTTP_QUERY_CONTENT_LOCATION HTTP_QUERY_CONTENT_MD5 HTTP_QUERY_CONTENT_RANGE HTTP_QUERY_CONTENT_TRANSFER_ENCODING HTTP_QUERY_CONTENT_TYPE HTTP_QUERY_COOKIE HTTP_QUERY_CUSTOM HTTP_QUERY_DATE HTTP_QUERY_ETAG HTTP_QUERY_EXPECT
HTTP_QUERY_EXPIRES HTTP_QUERY_FORWARDED HTTP_QUERY_FROM HTTP_QUERY_HOST HTTP_QUERY_IF_MATCH HTTP_QUERY_IF_MODIFIED_SINCE HTTP_QUERY_IF_NONE_MATCH HTTP_QUERY_IF_RANGE HTTP_QUERY_IF_UNMODIFIED_SINCE HTTP_QUERY_LINK
HTTP_QUERY_LAST_MODIFIED HTTP_QUERY_LOCATION HTTP_QUERY_MAX HTTP_QUERY_MAX_FORWARDS HTTP_QUERY_MIME_VERSION HTTP_QUERY_ORIG_URI HTTP_QUERY_PRAGMA HTTP_QUERY_PROXY_AUTHENTICATE HTTP_QUERY_PROXY_AUTHORIZATION HTTP_QUERY_PROXY_CONNECTION
HTTP_QUERY_PUBLIC HTTP_QUERY_RANGE HTTP_QUERY_RAW_HEADERS HTTP_QUERY_RAW_HEADERS_CRLF HTTP_QUERY_REFERER HTTP_QUERY_REFRESH HTTP_QUERY_REQUEST_METHOD HTTP_QUERY_RETRY_AFTER HTTP_QUERY_SERVER HTTP_QUERY_SET_COOKIE
HTTP_QUERY_STATUS_CODE HTTP_QUERY_STATUS_TEXT HTTP_QUERY_TITLE HTTP_QUERY_TRANSFER_ENCODING HTTP_QUERY_UNLESS_MODIFIED_SINCE HTTP_QUERY_UPGRADE HTTP_QUERY_URI HTTP_QUERY_USER_AGENT HTTP_QUERY_VARY
HTTP_QUERY_VERSION HTTP_QUERY_VIA HTTP_QUERY_WARNING HTTP_QUERY_WWW_AUTHENTICATE HTTP_QUERY_FLAG_COALESCE HTTP_QUERY_FLAG_NUMBER HTTP_QUERY_FLAG_REQUEST_HEADERS HTTP_QUERY_FLAG_SYSTEMTIME lpvBuffer参数存放接收的信息,长度由lpdwBufferLength参数确定;lpdwIndex参数用于返回有关具有相同名字的多个标题信息。
5.使用Cookies WinInet提供了两个用于Cookies操作的函数:InternetSetCookie( )和InternetGetCookie( )。使用InternetSetCookie( )函数可以建立一个与特定URL相关的Cookies,该函数的原型如下: BOOL InternetSetCookie( IN LPCTSTR lpszUrl, IN LPCTSTR lpszCookieName, IN LPCTSTR lpszCookieData ); 其中,lpszUrl是一个指向URL字符串的指针;lpszCookieName参数中存放Cookies名称;lpszCookieData参数中存放Cookies数据。
InternetGetCookie( )函数用于获得指定URL的Cookies,该函数的原型如下: BOOL InternetGetCookie( IN LPCTSTR lpszUrlName, IN LPCTSTR lpszCookieName, OUT LPTSTR lpszCookieData, IN OUT LPDWORD l pdwSize ); 其中,前三个参数与InternetSetCookie( )函数类似,pdwSize参数用于返回Cookie数据的长度。 这两个函数由于没有使用句柄,所以在调用之前不需要调用InternetOpen( )函数。
9.3.5 MFC WinInet类及其应用方法 MFC对WinInet的封装向类库开发人员提供了熟悉的上下文。MFC提供三个由CStdioFile派生的类:CInternetFile、CHttpFile和CGopherFile。对使用过CStdioFile类操作和处理本地文件的开发人员来说,这些类不仅使得获取和处理Internet数据驾轻就熟,而且使得处理本地数据和Internet数据的方式一致、透明,因此,数据的存储位置已经不重要了,不论是本地还是在远程的数据,处理方式都是一样的。
MFC WinInet类有如下功能: ● 缓冲器输入/输出; ● 安全的数据处理; ● 众多函数带缺省参数; ● 普通Internet错误的异常处理; ● 自动清除打开的句柄和连接。 1.WinInet类和全程函数 MFC提供了下列类和全程函数用来创建Internet客户端程序: ● CInternetSession(父类CObject)。 ● CInternetConnection(父类CObject), 它有三个派生类:CFtpConnection、CGopherConnection和CHttpConnection。
● CInternetFile(父类CStdioFile),它有两个派生类:CGopherFile和CHttpFile。 ● CFileFind(父类CObject),它有两个派生类:CFtpFileFind和CGopherFileFind。 ● CGopherLocator(父类CObject)。 ● CInternetException(父类CException)。 全程函数有: ● AfxParseURL。 ● AfxGetInternetHandleType。 ● AfxThrowInternetException。 注意:这些类和全程函数除CFileFind在头文件afx.h中声明之外,其余都在afxinet.h头文件中声明。
2.使用WinInet类实现常用操作 客户端的用户要实现某些操作,必须要满足一定的条件。例如,读取文件时,就必须先建立一个Internet连接。表9-4列出了常用操作对应的方法。 表9-5和表9-6列出了客户端要进行FTP操作和HTTP操作时,实现每一个操作所必须要使用的各种方法。
表9-4 常用操作对应的方法
表9-5 常用FTP操作对应的方法
表9-6 常用HTTP操作对应的方法
有两种方式来创建CInternetFile对象: (1) 使用CInternetSession::OpenURL建立服务器连接,这个调用返回一个CInternetFile对象。 (2) 使用CInternetSession::GetFtpConnection、GetGopherConnection或GetHttpConnection建立服务器连接,这时必须调用CFtpConnection::OpenFile、CGopherConnection::OpenFile或CHttpConnection::OpenRequest,相应地返回一个CInternetFile、CGopherFile或CHttpFile对象。 表9-7给出了实现一个典型的Internet客户应用程序的步骤。
表9-7 典型客户机应用程序的实现步骤
3.HTTP应用程序的实现步骤 在表9-8中,列出了实现一个典型的HTTP客户应用程序的步骤。
表9-8 HTTP客户机应用程序的实现步骤
4.FTP应用程序的实现步骤 表9-9给出了实现一个典型的FTP客户应用程序的步骤。
表9-9 FTP客户机应用程序的实现步骤
9.4 其他网络程序设计API 9.4.1 ISAPI简介 1.什么是ISAPI ISAPI是Internet服务器应用程序接口(Internet Server Application Program Interface)的缩写,它是微软公司提供的一套面向Internet服务的API接口。开发ISAPI应用程序一般使用VC++、VB或Delphi开发工具。 2.ISAPI程序的功能 Internet提供信息最主要的方式是由用户通过客户端浏览器(如Netscape或Explorer等)与WWW服务器连接,通过网页给用户提供所需要的信息。
实现一个可与用户交互信息的服务系统需要两个条件:一是在客户端要有交互式主页(HTML);二是要有处理用户输入信息的程序。交互式主页应该有输入信息的编辑框、选择框、按钮之类的组件,以供用户输入信息,用户输入的信息则由用户信息处理程序处理。这种处理程序有多种实现方法,它可以放在服务器端,也可以放在客户机端,前者如CGI、ISAPI等,后者如JAVA Applet等。 ISAPI提供了扩展支持WWW服务器的简单而有效的方法。
一个ISAPI的DLL可以在被用户请求激活后长驻内存,等待用户的另一个请求;还可以在一个DLL里设置多个用户请求处理函数。此外,ISAPI的DLL应用程序和WWW服务器处于同一个进程中,因此它的效率较高。 ISAPI的不足之处是平台兼容性较差,目前只能用于微软自己的Windows操作系统,服务器平台也一般只用微软的IIS(Internet Information Server)。 3.ISAPI程序的分类 ISAPI有两种WWW服务器的扩展方法:一种是Internet服务器扩展应用程序ISA(Internet Server Applications,ISAs);另一种是被称为ISAPI过滤器的应用程序。
服务器扩展应用程序能作为对WEB服务器功能的扩展以完成用户所请求的一些特殊功能。使用ISAPI开发ISA DLL程序时,为实现与HTTP服务器的交互,在程序中要实现三个入口:第一个入口是GetExtensionVersion( )函数,该函数在首次装载扩展DLL时被服务器调用,它可以向服务器报告ISA所支持的ISAPI版本。第二个入口是TerminateExtension( )函数,它在扩展DLL程序被卸载时被调用。第三个入口是HttpExtensionProc( )函数,这里包含有程序员为完成对服务器的扩展而编写的大量代码,当客户发出向ISA的请求时,服务器将调用该函数。这类程序的调用方式很简单,可以通过要处理的网页调用,如在输入客户数据网页的表单中加入如下内容: <form action="/isapi1/debug/isapi1.dll?RegisterUser" method=post>
这里指明了对应处理该网页输入信息的ISA程序是isapi1 这里指明了对应处理该网页输入信息的ISA程序是isapi1.dll,它是ISAPI的应用程序动态链接库,该程序可以处理用户从网页中输入的注册信息等。当然也可以直接在客户端的浏览器中调用ISA,例如: http://www.mysite.edu/isapi1.dll?name=fisherman&id=12345 对ISAPI过滤器程序来说,它可以定制以下处理:接收HTTP协议头预处理,发送HTTP协议头预处理,发送原始数据(Raw Data)预处理,获得原始数据(Raw Data)预处理,HTTP会话结束信息处理,自定义的安全认证机制,URL映射信息处理,日志记录处理等。灵活利用这些定制处理,可以在程序中实现许多复杂的功能。
ISAPI过滤器的开发非常简单,只需要完成三个接口DLL函数即可,它们是GetFilterVersion( )、HttpFilterProc( )和TerminateFilter( ),其用法类似于ISA中对应的函数,详细内容可以查看MSDN。ISAPI过滤器是DLL文件,为使ISAPI过滤器能够运行,需要在注册表的如下项中建立一个字符串项: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters 字符串项的名称为“Filter Dlls”,值为ISAPI过滤器文件的全路径名称。若这个字符串项已经存在,则只需把它的全路径名称加入即可。不同的ISAPI过滤器文件之间用“;”分隔,可以根据执行的优先顺序加在适当的位置。设置好后重新启动IIS服务,ISAPI过滤器就发挥作用了。
4.MFC对ISAPI的支持 VC++6.0中提供了对Internet服务器扩展应用程序ISA和ISAPI过滤器程序的支持。MFC中支持ISAPI的类有: ● CHttpServer: ● CHttpServerContext: ● CHttpFilter: ● CHttpFilterContext: ● CHtmlStream:
9.4.2 TAPI简介 1.什么是TAPI TAPI是电话应用程序编程接口(Telephony Application Programming Interface)的缩写,它是由Microsoft公司、Intel公司以及一些电信公司合作开发的一套用来编写与电信业务相关的应用程序的编程接口。TAPI提供了处理话音和数据传输的功能,如可以用来实现电话的自动拨号、会议呼叫、传输文件、拨号鉴定、传真处理和语音邮件等功能。 2.TAPI程序的工作原理 TAPI程序的工作原理可以用图9-5表示。
图9-5 TAPI程序工作原理图
3.TAPI程序的通信过程 (1) 初始化通信线路。 (2) 进行线路配置。 (3) 发出呼叫。 (4) 呼叫应答。 (5) 进行通信。 (6) 挂机。 (7) 关闭线路。
习题 1.本章介绍了哪些用于网络编程的API?它们各有什么特点? 2.MFC定义的Winsock类是什么?它们封装了哪些主要函数? 3.使用CMC构建一个信报程序的过程中要用到哪些主要函数? 4.使用WinInet构建一个FTP客户程序和HTTP客户程序时要用到哪些主要程序? 5.简述ISAPI的用途和ISAPI程序的执行过程。 6.简述TAPI的功能和TAPI程序的工作原理及过程。