1.AfxParseURL函数,该函数解析URL字符串并返回服务的类型及组件,包含在 afxinet.h 头文件中。
BOOL AFXAPI AfxParseURL(LPCTSTR pstrURL,DWORD& dwServiceType,CString& strServer,CString& strObject,INTERNET_PORT& nPort);
pstrURL : 一个字符串指针,指向要解析的URL。
dwServiceType:指示互联网服务的类型。
可以取下列值: AFX_INET_SERVICE_FTP
AFX_INET_SERVICE_HTTP
AFX_INET_SERVICE_HTTPS
AFX_INET_SERVICE_GOPHER
AFX_INET_SERVICE_FILE
AFX_INET_SERVICE_MAILTO
AFX_INET_SERVICE_NEWS
AFX_INET_SERVICE_NNTP
AFX_INET_SERVICE_TELNET
AFX_INET_SERVICE_WAIS
AFX_INET_SERVICE_MID
AFX_INET_SERVICE_CID
AFX_INET_SERVICE_PROSPERO
AFX_INET_SERVICE_AFS
AFX_INET_SERVICE_UNK
strServer :接收待解析的URL服务器名,服务类型后的第一个部分。
strObject: 接收待解析的URL 涉及的对象(可能为空)。
nPort: 如果存在,则从URL的服务器或对象部分搜索出来
2.CInternetSession类 继承自CObject,使用类CInternetSession 创建并初始化一个或多个同时的Internet 会话。如果需要,还可描述与代理服务器的连接。如果Internet连接必须在应用过程中保持着,可创建一个类CWinApp的CInternetSession成员。一旦已建立起Internet 会话,就可调用OpenURL。CInternetSession会通过调用全局函数AfxParseURL来为分析映射URL。无论协议类型如何,CInternetSession 解释URL并管理它。它可处理由URL资源“file://”标志的本地文件的请求。如果传给它的名字是本地文件,OpenURL 将返回一个指向CStdioFile对象的指针。 如果使用OpenURL在Internet服务器上打开一个URL,你可从此处读取信息。如果要执行定位在服务器上的指定的服务(例如,HTTP,FTP或Gopher)行为,必须与此服务器建立适当的连接。直接打开与指定的服务器的指定的类型的连接,请使用下列成员函数:
GetGopherConnection 打开与Gopher服务的连接。
GetHttpConnection 打开与HTTP服务的连接。
GetFtpConnection 打开与FTP服务的连接。
QueryOption和SetOption允许设置会话的查询选项,如超时值、再试次数等等。
Internet会话过程中,象查找或数据下载这样的事务处理会占用一定的时间。使用者可能想继续工作,或获得事务处理进程的状态信息。为解决这个问题,CInternetSession可以让查找和数据传输异步发生,允许使用者在传输结束时进行其它任务。如果要为使用者提供状态信息,或异步处理任意操作,必须设置三个条件:
①.在构造函数中,dwFlags必须包括INTERNET_FLAG_ASYNC。
②.在构造函数中,dwContext必须设置为1。
③.必须通过调用EnableStatusCallback来建立回调函数。
使用覆盖成员函数OnStatusCallback来获得异步获取的状态信息。使用此覆盖成员函数,必须从CInternetSession派生你自己的类。
注意: CInternetSession将为不支持的服务类型产生一个AfxThrowNotSupportedException。当前只支持下列服务类型:FTP,HTTP,Gopher和文件。所在头文件#include <afxinet.h>
CInternetSession类成员:
QueryOption 为错误检查提供可能的断言
SetOption 为Internet会话设置选项
OpenURL 文法分析映射并打开一个URL
GetFtpConnection 打开一个与服务器的FTP会话。写入用户日志
GetHttpConnection 为试图打开连接的应用打开一个HTTP服务器
GetGopherConnection 为试图打开连接的应用打开一个Gopher服务器
EnableStatusCallback 建立一个状态回调例程。异步操作需要EnableStatusCallback
ServiceTypeFromHandle 从Internet句柄中得到服务器类型
GetContext 为Internet或应用会话获得上下文的值
Close 当Internet会话终止时关闭Internet连接
SetCookie 为指定的URL设置小程序
GetCookie 返回指定的URL的小程序及其所有父URL
GetCookieLength 获取确定存储在缓冲区的小程序的长度的变量
可覆盖的函数OnStatusCallback 当状态回调有效时,更新操作状态
操作符operator HINTERNET 当前Internet会话的句柄
3.CHttpConnection类,MFC类CHttpConnection管理与HTTP服务器的连接。HTTP是用MFCWinInet类实现的三个Internet服务器协议之中的一个。 类CHttpConnection包含一个构造函数和一个成员函数OpenRequest,使用HTTP协议来管理与服务器的连接。 要与一个HTTP服务器通讯,必须先构造一个CInternetSession的实例,然后构造一个CHttpConnection对象。不能直接构造一个CHttpConnection对象,而是调用CInternetSession::GetHttpConnection,创建CHttpConnection对象并返回其指针。
4.CFtpConnection类,MFC类FtpConnection管理与Internet服务器的FTP连接并允许直接操纵服务器中的目录和文件。FTP是由MFC WinInet类识别的三种Internet服务器之一。 为了与FTP Internet服务器通讯,必须先创建一个CInternetSession实例,然后创建CFtpConnection对象。创建CFtpConnection对象不采用直接方式,而是调用CInternetSession::GetFtpConnertion来创建并返回一个指向它的指针。
成员函数:
SetCurrentDirectory 设置当前FTP目录
GetCurrentDirectory 获取此次连接的当前目录
GetCurrentDirectoryAsURL 获取作为URL的此次连接的当前目录
RemoveDirectory 从服务器移去指定目录
CreateDirectory 在服务器上构造一个目录
Rename 将服务器上的文件改名
Remove 从服务器上移去一个文件
PutFile 将一个文件放到服务器上
GetFile 从连接的服务器上获取一个文件
OpenFile 在连接的服务器上打开一个文件
Close 关闭与服务器的连接
5.CGopherConnection类,MFC类的CGopherConnection管理与Gopher Internet服务器的连接。Gopher服务器是由MFC WinInet类识别的三种 Internet服务器之一。CGopherConnection包含一个构造函数和三个附加成员函数,用于管理Gopher服务器:OpenFile,CreateLocator和GetAttribute。 为与一个Gopher Internet服务器相连接,必须首先建立一个CInternetSession实例,然后调用CInternetSession::GetGophConnection来构造一个CGopherConnection对象并返回一个指向它的指针。不可直接创建一个CGopherConnection对象。所在头文件#include <afxinet.h>
CString GetHttpFileForProxy(const char *url,const char*ProxyServerIPAndPort, const char *ProxyUserName ,const char *strProxyPassWD)
{
CString strContent;
char strProxyList[MAX_PATH], strUsername[64], strPassword[64];
//in this case "proxyserver" is the proxy server name, "8080" is its port
//ProxyList输入的格式为ProxyServerName:ProxyServerPort 210.45.242.8:8080
strcpy(strProxyList, strProxyServerIPAndPort); //代理服务器和端口
strcpy(strUsername, strProxyUserName); //代理用户名
strcpy(strPassword, strProxyPassWD); //代理用户名对应密码
DWORD dwServiceType = AFX_INET_SERVICE_HTTP;
CString strServer, strObject;
INTERNET_PORT nPort;
AfxParseURL(url, dwServiceType, strServer, strObject, nPort);
CInternetSession mysession;
CHttpConnection* pConnection;
CHttpFile* pHttpFile;
pConnection = mysession.GetHttpConnection(strServer,INTERNET_FLAG_KEEP_CONNECTION,INTERNET_INVALID_PORT_NUMBER,NULL, NULL);
pHttpFile = pConnection->OpenRequest("GET", strObject,NULL, 0, NULL, NULL,INTERNET_FLAG_KEEP_CONNECTION);
//here for proxy //这里设置代理服务器和端口、用户名和密码
INTERNET_PROXY_INFO proxyinfo;
proxyinfo.dwAccessType = INTERNET_OPEN_TYPE_PROXY;
proxyinfo.lpszProxy = strProxyList;
proxyinfo.lpszProxyBypass = NULL;
mysession.SetOption(INTERNET_OPTION_PROXY, (LPVOID)&proxyinfo, sizeof(INTERNET_PROXY_INFO));
pHttpFile->SetOption(INTERNET_OPTION_PROXY_USERNAME, strUsername, strlen(strUsername)+1);
pHttpFile->SetOption(INTERNET_OPTION_PROXY_PASSWORD, strPassword, strlen(strPassword)+1);
pHttpFile->SendRequest(NULL);
//输出string信息 分行放到m_SiteInfo
CString myData;
while(pHttpFile->ReadString(myData))
{
strContent=strContent+"\r\n";
strContent+=myData;
}
pHttpFile->Close();
delete pHttpFile;
pConnection->Close();
delete pConnection;
mysession.Close();
return strContent;
}
1 MFC 处理 HTTP 请求的基本方法
1.1 配置本地的 HTTP 服务器
为方便测试,可以先配置一个本地的 HTTP 服务器,根据各种需要进行定制。
我在这里,用 JSP 定制了一个基本的 HTML 表单程序,分为 index.jsp 和 RequestObjectInJSP.jsp 两个文件。其中,index.jsp 用来提供表单程序,方便测试 RequestObjectInJSP.jsp 这个表单处理文件。
为了减少在测试时期网络通信的影响,强烈建议搭建一个本地的 Web 服务器。
1.2 MFC 发起 HTTP 请求的基本方法
用 CInternetSession 来发起 Http 请求,需要包含头文件:
#include <afxinet.h>
MFC 发起 HTTP 请求的逻辑,和用 WinINet 函数集 的整体过程类似,主要的步骤在 Steps in a Typical HTTP Client Application 有详细的描述。
Retrieving a file via. HTTP 一文也对 MFC 发起 HTTP 请求有着非常详细的介绍。
1.3 用 MFC 发起 HTTP GET 请求
Get 服务类别,估计是 HTML 里最常用的,平时浏览网页用的就是这种。下面是用 GET 的方法来请求某个网页的内容,代码如下:
//通过 http GET 协议来获取并保存文件 CInternetSession session; session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1000 * 20); session.SetOption(INTERNET_OPTION_CONNECT_BACKOFF, 1000); session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); CHttpConnection* pConnection = session.GetHttpConnection(TEXT("localhost"),(INTERNET_PORT)8080); CHttpFile* pFile = pConnection->OpenRequest( CHttpConnection::HTTP_VERB_GET, TEXT("/Practice/index.jsp")); CString szHeaders = L"Accept: audio/x-aiff, audio/basic, audio/midi,\ audio/mpeg, audio/wav, image/jpeg, image/gif, image/jpg, image/png,\ image/mng, image/bmp, text/plain, text/html, text/htm\r\n"; pFile->AddRequestHeaders(szHeaders); pFile->SendRequest(); DWORD dwRet; pFile->QueryInfoStatusCode(dwRet); if(dwRet != HTTP_STATUS_OK) { CString errText; errText.Format(L"POST出错,错误码:%d", dwRet); AfxMessageBox(errText); } else { int len = pFile->GetLength(); char buf[2000]; int numread; CString filepath; CString strFile = L"response.txt"; filepath.Format(L".\\%s", strFile); CFile myfile( filepath, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary); while ((numread = pFile->Read(buf,sizeof(buf)-1)) > 0) { buf[numread] = '\0'; strFile += buf; myfile.Write(buf, numread); } myfile.Close(); } session.Close(); pFile->Close(); delete pFile;
调试上面这段代码的时候,特别要注意以下几点:
- CHttpConnection::GetHttpConnection() 里第一参数,填写的应该是类似 www.yahoo.com 这样的根域名,如果带上 http:// 或是子路径,好像均会出错。
- CHttpFile::OpenRequest() 的第一个和第二个参数很重要,会影响是否能连接,尤其是第二个参数,要输入正确的 URI 路径;
- 在 CHttpFile::SendRequest() 之后,一定要用 CHttpFile::QueryInfoStatusCode() 来获得请求的状态码,从而判断是否正确获得了 http 数据;
Http 的状态码主要有以下几类:
Group Meaning 200-299 Success 300-399 Information 400-499 Request error 500-599 Server error
更详细的代码参数:
Status code Meaning 200 URL located, transmission follows 400 Unintelligible request 404 Requested URL not found 405 Server does not support requested method 500 Unknown server error 503 Server capacity reached
1.4 用 MFC 发起 HTTP Post 请求
用 MFC 发起 HTTP Post 请求,主要流程和 MFC HTTP Get 代码一样,以下是示例代码:
//通过 http POST 协议来发送命令给服务器 CInternetSession session; session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1000 * 20); session.SetOption(INTERNET_OPTION_CONNECT_BACKOFF, 1000); session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); CHttpConnection* pConnection = session.GetHttpConnection( TEXT("localhost"), (INTERNET_PORT)8080); CHttpFile* pFile = pConnection->OpenRequest( CHttpConnection::HTTP_VERB_POST, TEXT("/Practice/RequestObjectInJSP.jsp"), NULL, 1, NULL, TEXT("HTTP/1.1"), INTERNET_FLAG_RELOAD); //需要提交的数据 CString szHeaders = L"Content-Type: application/x-www-form-urlencoded;"; //下面这段编码,则是可以让服务器正常处理 CHAR* strFormData = "username=WaterLin&password=TestPost"; pFile->SendRequest( szHeaders, szHeaders.GetLength(), (LPVOID)strFormData, strlen(strFormData)); DWORD dwRet; pFile->QueryInfoStatusCode(dwRet); if(dwRet != HTTP_STATUS_OK) { CString errText; errText.Format(L"POST出错,错误码:%d", dwRet); AfxMessageBox(errText); } else { int len = pFile->GetLength(); char buf[2000]; int numread; CString filepath; CString strFile = L"result.html"; filepath.Format(L".\\%s", strFile); CFile myfile(filepath, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary); while ((numread = pFile->Read(buf,sizeof(buf)-1)) > 0) { buf[numread] = '\0'; strFile += buf; myfile.Write(buf, numread); } myfile.Close(); } session.Close(); pFile->Close(); delete pFile;
以上的代码,与 Get 对比起来,唯一的不同在于,提交 CHttpFile::SendRequest() 数据的时候,把表单的数据也带上了。
2 疑难杂症
2.1 字符编码,可恨的字符编码
对于 C/C++ 程序来说,最可恨的事情之一,莫过于字符集的问题了,尤其是在网络通信的时候,这一问题就显得更加让人恶心了。
如果在用 MFC 发起 HTTP Post 请求时,你用的是宽字符集的编码,比如说,我把用 MFC 发起 HTTP Post 请求里同样的几行代码,替换成下面这几句:
CString szHeaders = L"Content-Type: application/x-www-form-urlencoded;charset=UTF-8"; //下面这句,因为字符集的原因,是无法让服务器正常处理 CString strFormData = L"username=WaterLin&password=TestPost"; pFile->SendRequest( szHeaders, szHeaders.GetLength(), (LPVOID)(LPCTSTR)strFormData, lstrlen(strFormData));
如果是,在服务器端会解析为如下这样:
<br>Parameters:u s e r n a m e
当你用文本编辑器打开返回的文件时,会显示如下的错误提示:
这个时候,虽然上面的 JSP 代码会输出
Character Encoding: null
这样的值,但是服务器却会把表单内容当成 ISO-8859-1 字符集来处理,从而把表单参数解析为类似下面的怪胎:
看,这就是把字符集弄混了的下场!
则需要在 HTTP 报头里,一定要显式加上 charset=UTF-8 这样的约束,比如,在上面的代码,我就是直接这样写的:
CString szHeaders = L"Content-Type: application/x-www-form-urlencoded;charset=UTF-8";
这样,服务器在收到你的报文时,就知道,你的 Form 表单内容,是用 UTF-8 来编码的,它也会用 UTF-8 字符集来解码你的 request,从而保证收到的消息一样。
3 用 MFC 来发起其它网络请求
3.1 用 MFC 来发起 FTP 请求
用 MFC 来发起 FTP 请求也非常方便,有兴趣的话,可以读一读这篇专门的文章。