一、 ISAPI 简介
通用网关接口 CommonGatewayInterface(CGI) 很早就作为交互式的 Web 应用程序的一个标准广泛应用在 Internet 之中。 CGI 脚本允许人们用多种编程语言 ( 如 Basic 、 C 、 Perl 、 Shell 等等 ) 来编写简单的应用程序。这些脚本运行在 Web 服务器上, 而在客户的 Web 浏览器上输出运行结果。客户的输入通过环境变量或者标准输入设备来进行传递, 然后 CGI 程序根据需要完成特定的操作,并通过 HTML 格式显示在客户的浏览器中。 CGI 的这一特性给互联网带来了生机,网站的建设也从此从沉默的处子变为热烈的少女,随着时间的推移,这位曾经轰动一时的白雪公主也日益疲乏起来。
人们在长期的使用中还是发现了 CGI 应用程序的一个很大的缺点:性能不高。 我每请求一次 CGE 程序时, CGI 执行文件(或者脚本的解释器)都要为每一个请求创建一个新的进程。对于一个信息量比较大的站点来说,这无疑给服务器增加了一个沉重的负担。在以后的岁月里,热闹的互联网又推出了 ASP , JSP 等好多提高安全性及服务器性能的措施,可其安全性及开发工具的成熟度远远不及 VC , Delphi 等成功的工具,而作为一个熟悉软件编程的程序员来说,利用现成的开发工具及现有的资源开发一套网站系统更是得心应手。
ISAPI ( Internet Server Aplication Programing Interfase )就是一种用编程工具进行开发,、运行于 NT 服务的一种标准的编程接口, Web 开发者可用它编写交互式应用程序。 ISAPI 扩展( Extension )应用程序具有 CGI 脚本同样的功能,但它比 CGI 具有更快、更有效的性能。
ISAPI for Windows NT 编写的应用程序, Web 用户可通过填写 HTML 表单( Form )或单击 Web 节点上 HTML 页面中的链接来激活该应用程序。服务器端获取用户提供的信息后,被激活的 ISAPI 应用程序对获取的信息作出处理。根据 ISAPI 应用程序的功能或把获取的信息存入数据库,或以获取的信息为条件访问数据库中的数据,然后把结果以 HTML 页面的形式送回到客户端。
ISAPI 的应用程序被编译为动态链接库( DLL, Dynamic Linked Library ),该库在 WWW 服务( Service )启动时装载入内存。它要求较少的系统开销,性能明显优于 CGI 应用程序。因为每个请求并不启动单独的进程。而如用 CGI 作交互程序, 每个请求需要启动单独的进程。 ISAPI 有明显的性能优势,但其调试不方面,开发资料较少, 一直成为开发人员不优选的原因。为了更广泛地运用 ISAPI ,特作此文,以供广大开发者更快地进入开发过程。
二、一个简单的 ISAPI 程序
New,Object,ISAPI Extension Wizard,Object Name 就起个 FirstISAPI,OK, 第二步什么也不要变,点 Finshed 完成。按下 F7 把生成的 DLL 放到 C:inetpubwwwrootfirstisapi 目录下 ( 建文件夹不用我教吧? ), 打开浏览器,在地址栏内输入
http://127.0.0.1/firstisapi/FirstISAPI.dll
OK, 我们立马可以看到如下内容: This default message was produced by the Internet Server DLL Wizard. Edit your CFirstISAPIExtension::Default() implementation to change it.
你看到了吗?
如果你看到了上面这一串英文字符,说明你的服务器可以用啦。
如果你看到了一个下载文件对话框,别哭!说明你的服务器还没有配置好,下面由我来一步步教你,会用的朋友也别错过这个好机会!
开始,程序,管理工具, Internet 服务管理器,打开左边树,找到 默认 Web 站点下的 firstisapi, 把鼠标入在上面单击右键,属性,在 执行许可 里面下拉列表,选中 脚本和可执行程序 其他什么也别动(如果你觉得动着好玩的话就动吧, 不过服务器配置不好了可别怪我没提前 “ 声明:) ” ) .
下面我们要做什么呢 ? 忘了 , 等等 ! 哦 , 对了 , 点击 OK, 如果没有呢 , 你的是中文版吧 , 那就点确定吧 !
好了 ,http://127.0.0.1/firstisapi/FirstISAPI.dll-->>>
This default message was produced by the Internet Server DLL Wizard. Edit
your CFirstISAPIExtension::Default() implementation to change it.
这回看到了吧 ?
还没有 ?
算了 , 别学 ISAPI 了 , 多烦 , 你不烦我都烦了 , 臭机子 ! ( 想了想 , 这种可能还是有的 , 我还是为人为到底吧 , 免得你又骂娘了 , 好 , 把开光驱 , 把 2000 的盘放进去啊 , 还愣着干么 ? 添加组件 , 重装一遍你的 INTERNET 服务 !......)
这回不会不行吧 ? 不行 ?
如此 , 我们的第一个 ISAPI 就做成了 , 你可以放心的说 , 我会做 ISAPI 了 ........---- 屁 ! 这也叫会 ?
对了 , 忘了 WIN98 的朋友 , 对不起啊 , 你有没有装 PWS 啊
没有 ?
那装一个吧 !
其他的和 2000 大同小异 , 不用我教吧 ?:):)
三、 ISAPI 实现 客户 / 服务器 交互
我们现在已经可以做一个简单的 ISAPI 程序了,但是,它什么功能也没有实现, 毕竟我们还没有加让它动起来的代码嘛!
在 CFirstISAPIExtension 类里象做一般程序一样加入一个成员函数
void Getdata(CHttpServerContext *pCtxt, char *data);
然后在 BEGIN_PARSE_MAP(CFirstISAPIExtension, CHttpServer) 和
END_PARSE_MAP(CFirstISAPIExtension) 之间加入
ON_PARSE_COMMAND(Getdata, CFirstISAPIExtension,ITS_PSTR) 和
ON_PARSE_COMMAND_PARAMS("data") ,如下所示:
BEGIN_PARSE_MAP(CFirstISAPIExtension, CHttpServer)
// TODO: insert your ON_PARSE_COMMAND() and
// ON_PARSE_COMMAND_PARAMS() here to hook up your commands.
// For example:
ON_PARSE_COMMAND(Getdata, CFirstISAPIExtension,ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("data")
ON_PARSE_COMMAND(Default, CFirstISAPIExtension, ITS_EMPTY)
DEFAULT_PARSE_COMMAND(Default, CFirstISAPIExtension)
END_PARSE_MAP(CFirstISAPIExtension)
ON_PARSE_COMMAND 和 ON_PARSE_COMMAND_PARAMS 是参数映射宏, ON_PARSE_COMMAND 的第
一个参数 Getdata 是要处理事件的函数名,第二个参数 CFirstISAPIExtension 为以 CHttpServer 为父类的应用程序子类,后面的参数要传给 Getdata 函数的参数列表,它们可以是: ITS_EMPTY : 没 有 数 据; ITS_PSTR : 字 符 串; ITS_I2 : short 整 型; ITS_I4 : long 整 型; ITS_R4 : float 浮 点 数; ITS_R8 : double 浮 点 数。 参数的个数一般与下面 ON_PARSE_COMMAND_PARAMS 解析的参数相对应。我们在这里可以 data 参数赋给一个初值,当我们在浏览器里没有传上来这个参数的实际数值时就是它了。如 data=www.ourcode.net; 这里 data 就是我们定义函数 Getdata 里所要传入的参数名。
有了上面这些准备工作,下面我们就可以添加代码了,因为它和平常编程时没什么两样,因此我们就简单地加上一点代码看看我们劳动了半天到底做了些什么?
如下:
void CFirstISAPIExtension::Getdata(CHttpServerContext *pCtxt, char *data)
{
StartContent(pCtxt);// 写 HTML 文件头
WriteTitle(pCtxt);// 写标题
*pCtxt << _T(" 这是你提交上来的数据: rn");// 向浏览器发送数据
*pCtxt <<data <<"rn";// 写上一个换行和回车
EndContent(pCtxt)
}
CHttpServerContext *pCtxt 是一个服务器上下文标志, 我们向浏览器传输数据就全靠
它。 *pCtxt << 后的字串就是浏览器里的 HTML 源码,因此,你可以把 " 这是你提交上来的数据:
rn" ,改成你想要的 HTML ,想怎么改随你吧!:)
现在是不是要按下 F7 了?哈哈,好, F7 ,马上把 FirstISAPI.dll Ctrl+C,Ctrl+V........
" 无法创建或替换 FirstISAPI: 指定的文件正被 Windows 使用 " ,没法了?我就重起计算机啦,谁教它装入内存呢!(其实只要重启 IIS 或结束进程 Win2003 下 w3wp.exe , Win2000 下 aspnet_wp.exe )
下面 ISAPI 调试的文章,不爱老重起计算机的朋友可以看一下。
ISAPI 在运行时是 IIS 的一部分,而 IIS 又作为 NT 的一个服务而运行。这一事实使用调试过程变得复杂了,因为在 IIS 运行时, VC++ 的调试器不能够接管 ISA 。为了解决这个问 题,微软公司以两种形式发行了 IIS :作为一项服务,以及作为一个单独的可执行程序。 对于后一种情况,我们就可以在命令行上来控制服务器。虽然这样可以解决上述问题并使得开发过程变得容易一些,但实现起来显得很繁琐。下面我们来介绍这个过程。
当用户处于 debug 调试模式时, VC++( 以及 IIS) 将在用户的帐号和权限下运行。由于通常 IIS 完成的一些工作是不允许大多数用户有相应的权限的,因此用户(或用户的系统管理员)需要做以下工作:
在桌面上选择 “ 开始程序管 理工具(公用)域用户管理器 ”, 打开域用户管理器;
在 “ 规则 ” 菜单中选择 “ 用户 权限 ” ;
选择 “ 显示高级用户权限 ” 检 查框;
在 “ 权限 ” 下拉列表中选择 “ 以操作系统方式操作 ” ;
选择 “ 添加 ” 按钮得到 “ 添加 用户及组 ” 对话框,选择 “ 显示用户 ” 按钮,并在 “ 名 称 ” 列表中选择用户使用的帐号,然后选择 “ 添加 ” 按钮;
选择 “ 确定 ” 按钮;
对 “ 产生安全审核 ” 权限重复 上述步骤。
为了使这些设置生效,用户必 须先退出登录,然后再登录回来。
IIS 中包含了三项服务: FTPPublishing Service,GopherPublishingService 和 WorldWideWeb 。由于调试器要在命令行上运行 IIS ,所以所有这三项服务都必须停止。 这可以通过 “ 控制面板 ” 中的 “ 服务 ” 程序或者使用 IIS 的 “Internet 服务管理器 ” 来实现。如果需要进行大量的调试工作, 我们建议用户通过 “ 控制面板 ” 中的 “ 服务 ” 程序来关闭 IIS 服务并禁止它们自动启动,这样可以避免用户每次启动计算机时都要进行关闭服务的操作。
接下来就必须对工程进行一些 配置了:
在 Project 菜单中选择 Settings 菜单 项;
选择 Debug 面板,并在 Category 下拉 列表中选择 General ;
在 Executablefordebugsession 框中输入 或者寻找 IIS 执行文件的路径 (通常情况下位于 WINNTsystem32inetsrvinetinfo.exe );
在 Programarguments 框中输入 -ew3svc ,如 图 3 所示;图3 Debug 面板设置选择 Link 面板;
在 Outputfilename 框中输入被编译 后的 DLL 将被放置的路径和文件名。这个路径必须位于 Web 服务器的根目录下或者某个虚拟目标下,以便客户可 以通过 URL 来访问。例如,我们的 Web 服务器的目录是 c:InetPubwwwrootfirstisapi ,我们把 firstisapi.dll 放置在该目录下,这样客户就可以使用 下面的 URL 来访问它:
http://127.0.0.1/firstisapi/firstisapi.dll
如果用户现在还没有退出登录 以改变权限,请现在行动,然后再登录回来。
如此,我们的服务器 DLL 就大功告成啦,赶快在浏览器里输入 http://127.0.0.1/firstisapi/firstisapi.dll?Getdata&data= 这是我发给服务器的信息!
OK ?
这是你提交上来的数据:
这是我发给服务器的信息!
OK !
要是我们想以 FORM 的表单形式向服务器提交数据呢?
把下面这段 HTML 源码存为一个 firstisapi.htm 文件,放在 DLL 相同目录下。
<form method="post" action="firstisapi.dll?">
<p align="center"> </p>
<input type="hidden" name="MfcISAPICommand" VALUE="Getdata">
<p align="center"> 要提交的数据: <input type="text" name="data" size="20"></p>
<p align="center"><input type="submit" value=" 提交 " > </p></form> </body>
四、 ISAPI 操作数据库
上面我们定义了一个 Getdata 函数 , 在函数里我们实现了这样一个功能 : 把客户传送上的数据发给客户 . 要是操作数据库呢 ? 一样的道理 , 我们只要在自己定义的函数里添加上操作数据库的代码 , 就可以实现存取数据库 . 输入的数据在变量 data 里,输出给客户机用 *pCtxt 发给浏览器。
现在我们建立一个数据库,用什么库由你选取吧,可以是 ACCESS,SQL Server ,Sbase 等,我们只要用数据源来操作它就行了,我们在建好库后建立系统数据源(不要建成用户数据源啊?为什么?问比尔 . 盖茨去吧) FirstODBC, 用下面的 SQL 建立一个简单的表。
表 : 用户资料
create table user_info (
id int (4) not null,
friend_id int(4) not null,
user_name varchar (30) not null,
user_sex varchar (2) not null,
address varchar (20),
age int (4),
zip char (6),
phone varchar (20) )
有了数据源,我们就可以添加代码实现数据库的查询,修改等。同上一节一样,在 CFirstISAPIExtension 里添加一个成员函数
void Reg(CHttpServerContext *pCtxt,char *user_name,char *sex,char*address,int age,
char*zip,char*phone)
参数分别是 : 用户名 , 性别 , 地址 , 年龄 , 邮编和电话 . 我们要它实现的功能是把用户通过表单提交的数据写到 FirstODBC 数据库 . 然后同样在
BEGIN_PARSE_MAP(CFirstISAPIExtension, CHttpServer) 和
END_PARSE_MAP(CFirstISAPIExtension) 之间加入
ON_PARSE_COMMAND(Reg, CFirstISAPIExtension,
ITS_PSTR ITS_PSTR ITS_PSTR ITS_I2 ITS_PSTR ITS_PSTR)
和 ON_PARSE_COMMAND_PARAMS("user_name sex address='' age=20 zip='' phone='')
,如下所示:
BEGIN_PARSE_MAP(CFirstISAPIExtension, CHttpServer)
// TODO: insert your ON_PARSE_COMMAND() and
// ON_PARSE_COMMAND_PARAMS() here to hook up your commands.
// For example:
ON_PARSE_COMMAND(Reg, CFirstISAPIExtension,ITS_PSTR ITS_PSTR ITS_PSTR ITS_I2 I
ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("user_name sex address age zip phone)
ON_PARSE_COMMAND(Getdata, CFirstISAPIExtension,ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("data")
ON_PARSE_COMMAND(Default, CFirstISAPIExtension, ITS_EMPTY)
DEFAULT_PARSE_COMMAND(Default, CFirstISAPIExtension)
END_PARSE_MAP(CFirstISAPIExtension)
下面我们定义自己的函数并进行数据库的操作 , 单击左边 FirstISAPI classes,New Class,
Name:CUserSet,Base class:CRecordset---------------OK,ODBC:FirstODBC,
Table Name:user_info,OK.
( 请先看看 stdafx.h 里有没有 #include )
void Reg(CHttpServerContext *pCtxt,char *user_name,char *sex,char*address,
int age,char*zip,char*phone)
{// 打开数据库源代码
StartContent(pCtxt); // 打印 <HTML> 和 <BODY> 标记
WriteTitle(pCtxt); // 打印 <TITLE> 、 “ 标题内容 ” 和 </TITLE> 标记等
CDatabase db;
if(!db.Open(_T("FirstODBC"), // (系统源名)
FALSE, //bExclusive
FALSE, //bReadOnly
_T("ODBC; UID=sa; PWD=;"), //lpszConnect (与数据库的连接方式)
FALSE))
{ //bUseCursorLib
*pCtxt << "Could not open database.";
return;
}
CRecordset set(&db);
try
{
set.Open();
}
catch (CDBException* pEx)
{
*pCtxt << _T("Error Selecting from table:");
TCHAR szErrorMessage[1024];
if (pEx->GetErrorMessage(szErrorMessage, sizeof(szErrorMessage)))
{
*pCtxt << szErrorMessage;
*pCtxt << _T("rn");
}
return;
}
set.AddNew();
set.Edit();
set.m_id=0;
set.m_user_name=user_name;
set.m_sex=sex;
set.m_address=address;
set.m_age=age;
set.m_zip=zip;
set.m_phone=phone;
set.Update();
set.Close();
db.Close();
*pCtxt << _T(" 你已成功注册 !rn");
// 打印 </BODY> 和 </HTML> 标记
EndContent(pCtxt);
}
把下面的 HTML 存为 reg.htm
<HTML>
<BODY>
<CENTER>
<FONT COLOR='#FF0000'><H3> 请在下面输入你的注册信息 ( 含 * 的为必填内容 )</H3>
</FONT>
</CENTER>
<HR>
<CENTER>
<form method="post" action="firstisapi.dll?">
<p align="center"> </p>
<input type="hidden" name="MfcISAPICommand" VALUE="Reg">
<p align="center"> 姓名: <input type="text" name="user_name" size="20"></p>
<p align="center"> 性别:
<select name="sex" size="1">
<option selected value="1"> 男 </option>
<option value="0"> 女 </option>
</select></p>
<p align="center"> 地址: <input type="text" name="address" size="20"></p>
<p align="center"> 年龄: <input type="text" name="age" size="20"></p>
<p align="center"> 邮编: <input type="text" name="zip" size="20"></p>
<p align="center"> 电话: <input type="text" name="phone" size="20"></p>
<p align="center"><input type="submit" value=" 提交 " > </p></form>
</center>
</BODY>
</HTML>
然后放到 firstisapi 目录里 ,..................
http://127.0.0.1/firstisapi/reg.htm 这样在浏览器的表单里输入你的数据后提交 , 看看你我服务器数据库 , 哈 , 有了 -----------> 你已成功注册 !! 本想再做一个查询程序 , 可是时间太紧 , 还是自己想想吧 , 和注册程序没什么两样 , 只要再加一个 Chaxun(...) 就 OK That is all right! 程序还有一些不完美的地方就是还不能检验用户名是否已被注册,还没有到得 ID 的最大值,不过在此只是为了演示如何操作数据库,笔者因此没有深虑,有兴趣的朋友自己可以加上这方面的代码。
五、利用 ISAPI 进行网络数据传送
所谓网络数据传送 , 简单地说 , 就是把数据从一台计算机传输到网络上的另外一台计算机 . 说起数据传输 , 大家最熟的不过 socket 套接字了 , 无论在 UNIX,WINDOWS, 它一贯都是公认的传输方案 . 笔者在研究了 ISAPI 之后 , 成功地实现了用 ISAPI 进行网络数据传输 , 并且解决了局域网防火墙内数据的有效传输 .
首先 , 我们要建立服务器数据接收 ISAPI 程序 . 在第三节 <ISAPI 实现 客户 / 服务器 交互 > 里我们已经建立了一个 Getdata(..char *data) 函数 , 其中 data 里存放的就是我们从客户机传上来的数据 , 在这个函数里 , 我们就可以对它进行处理 , 如写文件 , 写数据库等 , 再通过 *pCtxt 向客户发送回执 . 关于如何写库写文件已超出本节范围 , 在此不再赘述。
在第三节里,我们是通过 FORM 表单通过浏览器把数据发送给服务器,在这里,我们要通过我们客户机的程序向服务器发送数据。在你的程序里添加一个成员函数:
BOOL SendData(char *ip,char *request, char *buffer, unsigned int bufferlen) ,
其中参数 ip 为服务器 IP 地址,如果是本机调,传入 127.0.0.1;request 为要发送到服务器的数据,最终由 Getdata ()接收,存放在 data 里供服务器处理程序处理 ;buffer 为服务器传回回执存放绶冲区,即 *pCtxt 串行回的数据 bufferlen 为 buffer 绶冲区长度。 SendData 代码如下:
BOOL SendData(char *ip,char *request, char *buffer, unsigned int bufferlen)
{
int ret=0;
CString URL;
URL="http://";
URL+=(CString)ip;
URL+="/firstisapi/firstisapi.dll?Getdata&data=";
URL+=(CString)request;
//
//AfxMessageBox(URL);
CInternetSession session;
CInternetFile *cf;
//----------------
try
{
cf = (CInternetFile * )session.OpenURL (URL);
}
catch (CInternetException * Exp)
{
char err[1024];
Exp->GetErrorMessage (err, 1024, NULL);
AfxMessageBox(err);
cf = NULL;
Exp->Delete ();
}
if (cf)
{
ret=cf->Read(buffer, bufferlen);
cf->Close ();
}
session.Close ();
return(ret);
}
这样我们在程序里可以这样调用它:
void CSassDlg::OnButton1()
{
// TODO: Add your control notification handler code here
CString ip="127.0.0.1",data=" 这是我要发给服务器的数据 ",buffer;
BOOL b=SendData((char *)LPCTSTR(ip),(char *)LPCTSTR(data),
buffer.GetBuffer(1204), 1024);
if(b)
{
MessageBox(" 数据发送成功 ");
}
}
这样,我们只要把要发送给服务器的数据传给 SendData 的第二个参数,服务器 data 就可收到,你想怎么处理就怎么处理吧。
六、 ISAPI 传输数据的加密
为了提高数据传的安全性,可靠性,我们在数据传输之前都要对数据进行加密,但是无论哪种加密算法,进行加密的数据都有可能出现诸如 "@#$%^&*-+" 及不可字符,可是用 ISAPI 这种方法传输时, 服务器经过 ON_PARSE_COMMAND 和 ON_PARSE_COMMAND_PARAMS 参数映射宏进行处理后,如 "%,&" 等字符将会进行自动转换或作为分隔字符处理 , 这样, 服务器收到的数据将会是错误的。为了保证数据传输的可靠性,笔者经过多方面研究,自己定义了下面两个函数,分别作为客户机加密后上传前数据处理和服务器接收到数据后解密前处理。客户机加密后上传前数据处理函数:
void code(char *buf, int len, char *outstr)// 加密后处理
{
CString str;
int outstr_len;
outstr_len=0;
char *tmp;
tmp=new char[16];
outstr[0]=0;
for (int i=0; i<len ;i++)
{
tmp[0]=0;
if(buf[i]=='%'||buf[i]=='&'||
buf[i]=='!'||buf[i]=='?'||
buf[i]=='+'||buf[i]==''||
buf[i]=='/')
{
str.Format("%%%x",buf[i]);
strcpy(tmp,str);
outstr_len+=3;
}
else
{
tmp[0]=buf[i];
tmp[1]=0;
outstr_len++;
}
if (buf[i]=='$')
{
strcpy(tmp,"$$");outstr_len+=2;}
if (buf[i]==0)
{
strcpy(tmp,"$0");outstr_len+=2;
}
strcat(outstr,tmp);
}
return;
}
其中 buf 为传入数据绶冲区指针, len 为 buf 数据长度, outbuf 为处理后数据存放绶冲区 . 处理时将诸如 "%,&,+" 等特殊字符及 0 字符进行处理,这样, 数据传送到服务器后将会是正确的,之后经过服务器处理函数进行还原,再解密,那我们的数据就不会有错了。
服务器接收到数据后解密前处理函数:
int decode(char *buff, int len, char *outstr)
// 上传后处理
{
int i, p;
char v;
p=0;
for (i=0; i<len; i++)
{
if (buff[i]=='$')
{
if (buff[i+1]=='$')
{
v='$';
}
else
{
if(buff[i+1]=='0')
v=0;
}
i++;
}
else
{
v = buff[i];
}
outstr[p]=v;
p++;
}
return(p);
}
其中 buff 为传入数据绶冲区指针, len 为 buff 数据长度, outstr 为处理后数据存放绶冲区 .
处理流程:对 Data 进行加密 -->code 函数进行处理 --> 服务器接到 data-- >decode 进行上传后处理 --> 解密
七、 ISAPI 网络数据传送进阶(网络大数据包传送)
笔者曾尝试用上述方法传输大数据包,可是发现这种方法只可传输小于 2K 的数据包,这无疑成为它一个缺点。经过多方面的研究,发现这种方法是用 GET 方法发送数据, 操作系统本身就限制了它的数据量。以后经过测试,如用 POST 方法传输,发送数据包的大小将不受限制。下节将讲解如何用 POST 方法向服务器发送数据。
BOOL PostData(char *ip,char *request,char *out_buf,int buf_len)
{
BOOL ret=FALSE;
char *buffer,*buff,*out;
buffer=new char[2048];
int bb_len=strlen(request);
buff=new char[bb_len];
out=new char[bb_len+1000];
CInternetSession session;
CHttpFile* pFile=NULL;
CHttpConnection* pConnection=NULL;
CString m_ip,sql;
m_ip=(CString)ip;
//---------------------------- 对 request 加密
strcpy(buff,request);
for(int i=0;i<bb_len;i++)//
buff[i]=buff[i]^(char)((i+0x56)%255);
code (buff,bb_len,out);// 上传前处理
//-----------------------------------
sql=(CString)out;
CString strHeaders =_T("Content-Type: application/x-www-form-urlencoded");
CString str,strFormData = _T("data=");
strFormData+=sql;
try
{
pConnection =
session.GetHttpConnection(m_ip);
pFile =
pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,
_T("/firstisapi/firstisapi.dll?Getdata"));
BOOL result = pFile->SendRequest(strHeaders,
(LPVOID)(LPCTSTR)strFormData, strFormData.GetLength());
DWORD dwRet;
pFile->QueryInfoStatusCode(dwRet);
//--------------
if (dwRet == HTTP_STATUS_OK)
{
buf_len=pFile->Read(buffer,2048);
str=(CString)buffer;
str=str.Left(buf_len);
ret=TRUE;
}
else
str=" 服务器处理错误! ";
pFile->Close ();
delete pFile;
pConnection->Close();
delete pConnection;
}
catch(CInternetException* pEx)
{
TCHAR szErr[1024];
pEx->GetErrorMessage(szErr, 1024);
str=szErr;
pEx->Delete();
}
catch (...)
{
str=" 和服务器通信时发生意外! ";
}
session.Close();
strcpy(out_buf,str);
buf_len=str.GetLength ();
MessageBox(NULL, str, " 系统提示 ", MB_OK);
delete buffer;
delete buff;
delete out;
return ret;
}
其中参数 ip 为服务器 IP 地址,如果是本机调,传入 127.0.0.1; request 为要发送到服务器的数据,最终由 Getdata ()接收,存放在 data 里供服务器处理程序处理 ;buffer 为服务器传回回执存放绶冲区,即 *pCtxt 串行回的数据 bufferlen 为 buffer 绶冲区长度。服务器接收程序同前,这样,我们就可以发送大于 2K 的数据了。