下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

如何用C++写CGI的程序

作者:课课家教育     来源: http://www.kokojia.com点击数:921发布时间: 2016-02-29 20:00:08

标签: java程序java开发java创建类

大神带你学编程,欢迎选课

  经过前面的学习,大家应该能够根据例子用ANSI C为自己的服务器写出CGI程序。之所以选用ANSI C,是因为它几乎随处可见,是最流行的C语言标准。当然,现在的C++也非常流行了,特别是采用GNU C++编译器形式的那一些。可从网上许多地方免费下载g++,而且可选用几乎所有平台的版本(通常与Linux那样的操作系统配套提供,且已预先安装好)。正如大家即将看到的那样,从CGI程序可获得面向对象程序设计的许多好处。

如何用C++写CGI的程序_java程序_java开发_课课家

  GNU的全称是“Gnu's Not Unix”。这最早是由“自由软件基金会”(FSF)负责开发的一个项目,致力于用一个免费的版本取代原有的Unix操作系统。现在的Linux似乎正在做前人没有做到的事情。但GNU工具在Linux的开发中扮演了至关重要的角色。事实上,Linux的整套软件包附带了数量非常多的GNU组件。

  为避免第一次就提出过多的新概念,这个程序并未打算成为一个“纯”C++程序;有些代码是用普通C写成的——尽管还可选用C++的一些替用形式。但这并不是个突出的问题,因为该程序用C++制作最大的好处就是能够创建类。在解析CGI信息的时候,由于我们最关心的是字段的“名称/值”对,所以要用一个类(Pair)来代表单个名称/值对;另一个类(CGI_vector)则将CGI字串自动解析到它会容纳的Pair对象里(作为一个vector),这样即可在有空的时候把每个Pair(对)都取出来。

  这个程序同时也非常有趣,因为它演示了C++与java相比的许多优缺点。大家会看到一些相似的东西;比如class关键字。访问控制使用的是完全相同的关键字public和private,但用法却有所不同。它们控制的是一个块,而非单个方法或字段(也就是说,如果指定private:,后续的每个定义都具有private属性,直到我们再指定public:为止)。另外在创建一个类的时候,所有定义都自动默认为private。

  在这儿使用C++的一个原因是要利用C++“标准模板库”(STL)提供的便利。至少,STL包含了一个vector类。这是一个C++模板,可在编译期间进行配置,令其只容纳一种特定类型的对象(这里是Pair对象)。和Java的Vector不同,如果我们试图将除Pair对象之外的任何东西置入vector,C++的vector模板都会造成一个编译期错误;而Java的Vector能够照单全收。而且从vector里取出什么东西的时候,它会自动成为一个Pair对象,毋需进行造型处理。所以检查在编译期进行,这使程序显得更为“健壮”。此外,程序的运行速度也可以加快,因为没有必要进行运行期间的造型。vector也会过载operator[],所以可以利用非常方便的语法来提取Pair对象。vector模板将在CGI_vector创建时使用;在那时,大家就可以体会到如此简短的一个定义居然蕴藏有那么巨大的能量。

  若提到缺点,就一定不要忘记Pair在下列代码中定义时的复杂程度。与我们在Java代码中看到的相比,Pair的方法定义要多得多。这是由于C++的程序员必须提前知道如何用副本构建器控制复制过程,而且要用过载的operator=完成赋值。正如第12章解释的那样,我们有时也要在Java中考虑同样的事情。但在C++中,几乎一刻都不能放松对这些问题的关注。

  这个项目首先创建一个可以重复使用的部分,由C++头文件中的Pair和CGI_vector构成。从技术角度看,确实不应把这些东西都塞到一个头文件里。但就目前的例子来说,这样做不会造成任何方面的损害,而且更具有Java风格,所以大家阅读理解代码时要显得轻松一些:

  //: CGITools.h

  // Automatically extracts and decodes data

  // from CGI GETs and POSTs. Tested with GNU C++

  // (available for most server machines).

  #include

  #include // STL vector

  using namespace std;

  // A class to hold a single name-value pair from

  // a CGI query. CGI_vector holds Pair objects and

  // returns them from its operator[].

  class Pair {

  char* nm;

  char* val;

  public:

  Pair() { nm = val = 0; }

  Pair(char* name, char* value) {

  // Creates new memory:

  nm = decodeURLString(name);

  val = decodeURLString(value);

  }

  const char* name() const { return nm; }

  const char* value() const { return val; }

  // Test for "emptiness"

  bool empty() const {

  return (nm == 0) || (val == 0);

  }

  // Automatic type conversion for boolean test:

  operator bool() const {

  return (nm != 0) && (val != 0);

  }

  // The following constructors & destructor are

  // necessary for bookkeeping in C++.

  // Copy-constructor:

  Pair(const Pair& p) {

  if(p.nm == 0 || p.val == 0) {

  nm = val = 0;

  } else {

  // Create storage & copy rhs values:

  nm = new char[strlen(p.nm) + 1];

  strcpy(nm, p.nm);

  val = new char[strlen(p.val) + 1];

  strcpy(val, p.val);

  }

  }

  // Assignment operator:

  Pair& operator=(const Pair& p) {

  // Clean up old lvalues:

  delete nm;

  delete val;

  if(p.nm == 0 || p.val == 0) {

  nm = val = 0;

  } else {

  // Create storage & copy rhs values:

  nm = new char[strlen(p.nm) + 1];

  strcpy(nm, p.nm);

  val = new char[strlen(p.val) + 1];

  strcpy(val, p.val);

  }

  return *this;

  }

  ~Pair() { // Destructor

  delete nm; // 0 value OK

  delete val;

  }

  // If you use this method outide this class,

  // you're responsible for calling 'delete' on

  // the pointer that's returned:

  static char*

  decodeURLString(const char* URLstr) {

  int len = strlen(URLstr);

  char* result = new char[len + 1];

  memset(result, len + 1, 0);

  for(int i = 0, j = 0; i <= len; i++, j++) {

  if(URLstr[i] == '+')

  result[j] = ' ';

  else if(URLstr[i] == '%') {

  result[j] =

  translateHex(URLstr[i + 1]) * 16 +

  translateHex(URLstr[i + 2]);

  i += 2; // Move past hex code

  } else // An ordinary character

  result[j] = URLstr[i];

  }

  return result;

  }

  // Translate a single hex character; used by

  // decodeURLString():

  static char translateHex(char hex) {

  if(hex >= 'A')

  return (hex & 0xdf) - 'A' + 10;

  else

  return hex - '0';

  }

  };

  // Parses any CGI query and turns it

  // into an STL vector of Pair objects:

  class CGI_vector : public vector {

  char* qry;

  const char* start; // Save starting position

  // Prevent assignment and copy-construction:

  void operator=(CGI_vector&);

  CGI_vector(CGI_vector&);

  public:

  // const fields must be initialized in the C++

  // "Constructor initializer list":

  CGI_vector(char* query) :

  start(new char[strlen(query) + 1]) {

  qry = (char*)start; // Cast to non-const

  strcpy(qry, query);

  Pair p;

  while((p = nextPair()) != 0)

  push_back(p);

  }

  // Destructor:

  ~CGI_vector() { delete start; }

  private:

  // Produces name-value pairs from the query

  // string. Returns an empty Pair when there's

  // no more query string left:

  Pair nextPair() {

  char* name = qry;

  if(name == 0 || *name == '\\0')

  return Pair(); // End, return null Pair

  char* value = strchr(name, '=');

  if(value == 0)

  return Pair(); // Error, return null Pair

  // Null-terminate name, move value to start

  // of its set of characters:

  *value = '\\0';

  value++;

  // Look for end of value, marked by '&':

  qry = strchr(value, '&');

  if(qry == 0) qry = ""; // Last pair found

  else {

  *qry = '\\0'; // Terminate value string

  qry++; // Move to next pair

  }

  return Pair(name, value);

  }

  }; ///:~

  在#include语句后,可看到有一行是:

  using namespace std;

  C++中的“命名空间”(Namespace)解决了由Java的package负责的一个问题:将库名隐藏起来。std命名空间引用的是标准C++库,而vector就在这个库中,所以这一行是必需的。

  Pair类表面看异常简单,只是容纳了两个(private)字符指针而已——一个用于名字,另一个用于值。默认构建器将这两个指针简单地设为零。这是由于在C++中,对象的内存不会自动置零。第二个构建器调用方法decodeURLString(),在新分配的堆内存中生成一个解码过后的字串。这个内存区域必须由对象负责管理及清除,这与“破坏器”中见到的相同。name()和value()方法为相关的字段产生只读指针。利用empty()方法,我们查询Pair对象它的某个字段是否为空;返回的结果是一个bool——C++内建的基本布尔数据类型。operator bool()使用的是C++“运算符过载”的一种特殊形式。它允许我们控制自动类型转换。如果有一个名为p的Pair对象,而且在一个本来希望是布尔结果的表达式中使用,比如if(p){//...,那么编译器能辨别出它有一个Pair,而且需要的是个布尔值,所以自动调用operator bool(),进行必要的转换。

  接下来的三个方法属于常规编码,在C++中创建类时必须用到它们。根据C++类采用的所谓“经典形式”,我们必须定义必要的“原始”构建器,以及一个副本构建器和赋值运算符——operator=(以及破坏器,用于清除内存)。之所以要作这样的定义,是由于编译器会“默默”地调用它们。在对象传入、传出一个函数的时候,需要调用副本构建器;而在分配对象时,需要调用赋值运算符。只有真正掌握了副本构建器和赋值运算符的工作原理,才能在C++里写出真正“健壮”的类,但这需要需要一个比较艰苦的过程(注释⑤)。

  ⑤:我的《Thinking in C++》(Prentice-Hall,1995)用了一整章的地方来讨论这个主题。若需更多的帮助,请务必看看那一章。

  只要将一个对象按值传入或传出函数,就会自动调用副本构建器Pair(const Pair&)。也就是说,对于准备为其制作一个完整副本的那个对象,我们不准备在函数框架中传递它的地址。这并不是Java提供的一个选项,由于我们只能传递句柄,所以在Java里没有所谓的副本构建器(如果想制作一个本地副本,可以“克隆”那个对象——使用clone(),参见第12章)。类似地,如果在Java里分配一个句柄,它会简单地复制。但C++中的赋值意味着整个对象都会复制。在副本构建器中,我们创建新的存储空间,并复制原始数据。但对于赋值运算符,我们必须在分配新存储空间之前释放老存储空间。我们要见到的也许是C++类最复杂的一种情况,但那正是Java的支持者们论证Java比C++简单得多的有力证据。在Java中,我们可以自由传递句柄,善后工作则由垃圾收集器负责,所以可以轻松许多。

  但事情并没有完。Pair类为nm和val使用的是char*,最复杂的情况主要是围绕指针展开的。如果用较时髦的C++ string类来代替char*,事情就要变得简单得多(当然,并不是所有编译器都提供了对string的支持)。那么,Pair的第一部分看起来就象下面这样:

  class Pair {

  string nm;

  string val;

  public:

  Pair() { }

  Pair(char* name, char* value) {

  nm = decodeURLString(name);

  val = decodeURLString(value);

  }

  const char* name() const { return nm.c_str(); }

  const char* value() const {

  return val.c_str();

  }

  // Test for "emptiness"

  bool empty() const {

  return (nm.length() == 0)

  || (val.length() == 0);

  }

  // Automatic type conversion for boolean test:

  operator bool() const {

  return (nm.length() != 0)

  && (val.length() != 0);

  }

  (此外,对这个类decodeURLString()会返回一个string,而不是一个char*)。我们不必定义副本构建器、operator=或者破坏器,因为编译器已帮我们做了,而且做得非常好。但即使有些事情是自动进行的,C++程序员也必须了解副本构建以及赋值的细节。

  Pair类剩下的部分由两个方法构成:decodeURLString()以及一个“帮助器”方法translateHex()——将由decodeURLString()使用。注意translateHex()并不能防范用户的恶意输入,比如“%1H”。分配好足够的存储空间后(必须由破坏器释放),decodeURLString()就会其中遍历,将所有“+”都换成一个空格;将所有十六进制代码(以一个“%”打头)换成对应的字符。

  CGI_vector用于解析和容纳整个CGI GET命令。它是从STL vector里继承的,后者例示为容纳Pair。C++中的继承是用一个冒号表示,在Java中则要用extends。此外,继承默认为private属性,所以几乎肯定需要用到public关键字,就象这样做的那样。大家也会发现CGI_vector有一个副本构建器以及一个operator=,但它们都声明成private。这样做是为了防止编译器同步两个函数(如果不自己声明它们,两者就会同步)。但这同时也禁止了客户程序员按值或者通过赋值传递一个CGI_vector。

  CGI_vector的工作是获取QUERY_STRING,并把它解析成“名称/值”对,这需要在Pair的帮助下完成。它首先将字串复制到本地分配的内存,并用常数指针start跟踪起始地址(稍后会在破坏器中用于释放内存)。随后,它用自己的nextPair()方法将字串解析成原始的“名称/值”对,各个对之间用一个“=”和“&”符号分隔。这些对由nextPair()传递给Pair构建器,所以nextPair()返回的是一个Pair对象。随后用push_back()将该对象加入vector。nextPair()遍历完整个QUERY_STRING后,会返回一个零值。

  现在基本工具已定义好,它们可以简单地在一个CGI程序中使用,就象下面这样:

  //: Listmgr2.cpp

  // CGI version of Listmgr.c in C++, which

  // extracts its input via the GET submission

  // from the associated applet. Also works as

  // an ordinary CGI program with HTML forms.

  #include

  #include "CGITools.h"

  const char* dataFile = "list2.txt";

  const char* notify = "Bruce@EckelObjects.com";

  #undef DEBUG

  // Similar code as before, except that it looks

  // for the email name inside of '<>':

  int inList(FILE* list, const char* emailName) {

  const int BSIZE = 255;

  char lbuf[BSIZE];

  char emname[BSIZE];

  // Put the email name in '<>' so there's no

  // possibility of a match within another name:

  sprintf(emname, "

赞(27)
踩(2)
分享到:
华为认证网络工程师 HCIE直播课视频教程