chapter 13:TCP/IP 和网络编程

发布时间 2023-11-14 09:55:17作者: 20211108俞振阳

学习笔记:TCP/IP和网络编程

13.0 摘要

  • 本章分为两个部分:
    1. TCP/IP协议与应用

      • 包括TCP/IP协议栈、IP地址、主机名、DNS、IP数据包和路由器。
      • 描述了UDP和TCP协议、端口号以及TCP/IP网络中的数据流。
      • 解释了服务器-客户端计算模型和套接字编程接口。
      • 通过使用UDP和TCP套接字的示例演示了网络编程。
      • 第一个编程项目是实现一对TCP服务器-客户端,可通过互联网进行文件操作,并允许用户定义可靠传输文件内容的通信协议。
    2. Web和CGI编程

      • 包括HTTP编程模型、网页和Web浏览器。
      • 指导如何配置Linux HTTPD服务器以支持用户网页、PHP和CGI编程。
      • 解释了客户端和服务器端动态网页。
      • 第二个编程项目涉及在Linux HTTPD服务器上使用CGI编程实现服务器端动态网页。

13.1 网络编程简介

  • 如今,访问互联网已成为日常生活的必需品。虽然大多数人可能只将互联网用作信息收集、在线购物和社交媒体等工具,但计算机科学学生必须对互联网技术有一定了解,以及具备一些网络编程技能。在本章中,我们将涵盖TCP/IP网络和网络编程的基础知识,包括TCP/IP协议、UDP和TCP协议、服务器-客户端计算、HTTP和网页,以及用于动态网页的PHP和CGI编程。

13.2 TCP/IP协议

  • TCP/IP互联网的支柱:
    • TCP:传输控制协议。
    • IP:互联网协议。
    • 版本:IPv4(32位地址)和IPv6(128位地址)。
  • TCP/IP层次结构:
    • 顶层的应用程序使用TCP/IP。
    • 传输层处理可靠的数据传输(TCP用于可靠性,UDP用于效率)。
    • 实际数据传输发生在互联网(IP)和链路层。
  • 数据流路径:
    • 在传输层或以上进行逻辑传输;实际传输发生在IP和链路层。

13.3 IP主机和IP地址

  • 主机和IP标识:
    • 主机:支持TCP/IP的计算机或设备。
    • 通过32位IP地址进行标识。
    • IP地址以点分表示法表示。
    • 主机名等效于IP地址。
  • IP地址组成部分:
    • 分为网络ID和主机ID两部分。
    • 根据划分分为A到E类。
    • 特例:localhost(127.0.0.1)用于在同一台计算机上运行TCP/IP应用程序而无需连接到互联网。

13.4 IP协议

  • 最佳尽力协议:
    • IP在IP主机之间发送/接收数据包。
    • 不保证数据包将被传递到目的地,也不保证按顺序传递。
    • 如果需要,可在IP层以上实施可靠性。

13.5 IP数据包格式

  • 结构:
    • IP数据包由IP头部、发送者/接收者IP地址和数据组成。
    • 每个IP数据包的最大大小为64KB。
    • IP头部包含有关数据包的更多信息(例如总长度、是否使用TCP或UDP、生存时间(TTL)计数、用于错误检测的校验和等)。

学习笔记:路由器、UDP和TCP、端口号、网络字节顺序、TCP/IP网络数据流和网络编程

13.6 路由器

  • IP主机可能相隔甚远,直接从一个主机发送数据包到另一个主机通常是不可能的。

  • 路由器是特殊的IP主机,用于接收和转发数据包。

  • 数据包可能通过多个路由器或跳数到达目的地。图显示了TCP/IP网络的拓扑结构。
    TCP/IP network topology

  • 每个IP数据包的IP头部有一个8位的生存时间(TTL)计数,最大值为255。在每个路由器处,TTL减1。如果TTL减至0且数据包仍未到达目的地,则数据包被丢弃,防止数据包在IP网络中无限循环。

13.7 UDP 用户数据报协议

  • UDP(RFC 768 1980; Comer 1988)位于IP之上,用于发送/接收数据报。
  • 与IP类似,UDP不保证可靠性,但速度快且高效。
  • 用于不需要可靠性的情况,例如使用ping命令探测目标主机。
  • UDP的应用:ping是一个应用程序,向目标主机发送带有时间戳的UDP数据包,目标主机接收到后会回显带有时间戳的UDP数据包,以计算和显示往返时间。

13.8 TCP 传输控制协议

  • TCP是一种面向连接的协议,用于发送/接收数据流。
  • 也位于IP之上,但保证可靠的数据传输。
  • 比喻:UDP类似于邮政服务(发送邮件),TCP类似于电话连接。

13.9 端口号

  • 每个主机上可能同时有许多应用程序(进程)使用TCP/UDP。
  • 每个应用程序由三元组唯一标识:
    • 应用程序 = (主机IP,协议,端口号)
  • 端口号是分配给应用程序的唯一无符号短整数。
  • 前1024个端口号保留,其他端口号供一般使用。
  • 应用程序必须选择或获取一个端口号才能使用UDP或TCP。

13.10 网络和主机字节顺序

  • 计算机可能使用大端序或小端序。
  • 在互联网上,数据始终以网络字节顺序(大端序)存在。
  • 针对小端序机器(如Intel x86系列PC),有一组库函数(htons()、htonl()、ntohs()、ntohl())可在主机字节顺序和网络字节顺序之间转换。

13.11 TCP/IP网络中的数据流

  • 图13.6显示了TCP/IP网络中各层数据格式及它们之间的数据流路径。
    Data format in TCP/IP layers
  • 数据从应用层传递到传输层,在传输层添加TCP或UDP头以标识传输协议。然后传递到IP互联网层,在该层添加包含IP地址的IP头以标识发送和接收主机。最后传递到网络链路层,在该层将数据分割成帧,添加发送和接收网络的地址以进行跨物理网络的传输。

13.12 网络编程

  • 所有Unix/Linux系统都提供网络编程的TCP/IP支持。

13.12.1 网络编程平台

  • 为了进行网络编程,读者必须访问支持网络编程的平台。有两种方式:
    1. 在服务器机器上拥有用户帐户:几乎所有教育机构都提供网络访问,通常以无线连接的形式提供给教职员工和学生。机构的每个成员应能够登录服务器机器以访问互联网。
    2. 单独的个人计算机或笔记本电脑:即使读者无法访问服务器机器,仍然可以在独立计算机上通过使用计算机的localhost进行网络编程。

13.12.2 服务器-客户端计算模型

  • 大多数网络编程任务基于服务器-客户端计算模型。
  • 在服务器-客户端计算模型中,首先在服务器主机上运行服务器进程,然后从客户端主机上运行客户端。
  • 在UDP中,服务器等待来自客户端的数据报,处理数据报并生成响应给客户端。
  • 在TCP中,服务器等待客户端连接。客户端首先连接到服务器,以在客户端和服务器之间建立虚拟电路。连接建立后,服务器和客户端可以交换连续的数据流。

13.13 Socket 编程

  • 在网络编程中,对TCP/IP的用户接口是通过一组C库函数和系统调用实现的,这被称为 sockets API。
  • 使用 sockets API 需要 socket 地址结构,用于标识服务器和客户端,该结构定义在 netdb.h 和 sys/socket.h 中。

13.13.1 Socket 地址

struct sockaddr_in {
    sa_family_t sin_family;   // AF_INET for TCP/IP
    in_port_t sin_port;       // port number
    struct in_addr sin_addr;  // IP address
};

struct in_addr {             // internet address
    uint32_t s_addr;         // IP address in network byte order
};
  • 在 socket 地址结构中:
    • sin_family 始终设置为 AF_INET,用于 TCP/IP 网络。
    • sin_port 包含网络字节顺序的端口号。
    • sin_addr 是主机 IP 地址,以网络字节顺序存储。

13.13.2 Socket API

  • 服务器必须创建一个 socket 并将其与包含服务器的 IP 地址和端口号的 sockaddr 绑定。
  • 客户端必须创建一个 socket。对于 UDP sockets,将 socket 绑定到服务器地址是可选的。

1. 创建 Socket

int socket(int domain, int type, int protocol);

// Examples:
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);  // 创建用于发送/接收 UDP 数据报的 socket
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0); // 创建用于发送/接收数据流的连接导向的 TCP socket

2. 绑定 Socket

int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);

3. UDP Sockets

  • 使用 sendto() / recvfrom() 来发送/接收数据报。

4. TCP Sockets

  • 创建 socket 并将其绑定到服务器地址后,TCP 服务器使用 listen()accept() 来接受来自客户端的连接。
  • connect() 用于连接客户端和服务器。

5. send()/write()recv()/read()

  • 在建立连接后,TCP 主机可以使用 send()/write() 发送数据,使用 recv()/read() 接收数据。

13.14 UDP Echo 服务器-客户端程序

  • 展示使用 UDP 的简单回显服务器/客户端程序。
  • 假设服务器和客户端运行在同一台计算机上,服务器运行在默认的 localhost 上,IP = 127.0.0.1,使用固定端口号 1234。
// UDP Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>

#define BUFLEN 256 // buffer max length
#define PORT 1234 // fixed server port number

char line[BUFLEN];
struct sockaddr_in me, client;
int sock, rlen, clen = sizeof(client);

int main() {
    // ...(与文中代码一致)

    while(1) {
        // ...(与文中代码一致)
    }
}

// UDP Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>

#define SERVER_HOST "127.0.0.1" // default server IP: localhost
#define SERVER_PORT 1234 // fixed server port number
#define BUFLEN 256 // buffer max length

char line[BUFLEN];
struct sockaddr_in server;
int sock, rlen, slen = sizeof(server);

int main() {
    // ...(与文中代码一致)

    while(1) {
        // ...(与文中代码一致)
    }
}
  • 运行 UDP 服务器-客户端程序的示例输出见图。

UDP server-client program outputs

13.15 TCP 回显服务器-客户端程序

TCP 服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234

struct sockaddr_in server_addr, client_addr;
int mysock, csock; // socket descriptors
int r, len, n; // help variables

int server_init() {
    // ...(与文中代码一致)
}

int main() {
    // ...(与文中代码一致)
}

TCP 客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234

struct sockaddr_in server_addr;
int sock, r;

int client_init() {
    // ...(与文中代码一致)
}

int main() {
    // ...(与文中代码一致)
}
  • 运行 TCP 服务器-客户端程序的示例输出见图 13.8。

TCP server-client program outputs

13.16 主机名和 IP 地址

  • 通过 gethostnamegethostbyname 可以获取主机名和 IP 地址。
  • 结构体 hostent 可以存储主机信息。
char myname[64];
struct sockaddr_in server_addr, sock_addr;
gethostname(myname, 64);
struct hostent *hp = gethostbyname(myname);
if (hp == 0) {
    printf("unknown host %s\n", myname);
    exit(1);
}

// 初始化 server_addr 结构
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = *(long *)hp->h_addr;
server_addr.sin_port = 0; // 让内核分配端口号

// 创建 TCP socket
int mysock = socket(AF_INET, SOCK_STREAM, 0);

// 将 socket 与 server_addr 绑定
bind(mysock, (struct sockaddr *)&server_addr, sizeof(server_addr));

// 获取 socket 地址以显示内核分配的端口号
getsockname(mysock, (struct sockaddr *)&name_addr, &length);

// 显示服务器主机名和端口号
printf("hostname=%s IP=%s port=%d\n", hp->h_name,
       inet_ntoa(*(long *)hp->h_addr), ntohs(name_addr.sin_port));

13.17 TCP 编程项目: 互联网文件服务器

13.17.1 项目规格

  • 设计并实现一个在互联网上进行文件操作的 TCP 服务器和客户端。

服务器算法

  1. 设置虚拟根目录为当前工作目录 (CWD)。
  2. 广告服务器主机名和端口号。
  3. 接受来自客户端的连接。
  4. 从客户端获取命令行 = cmd 路径名。
  5. 对路径名执行 cmd。
  6. 将结果发送给客户端。
  7. 重复 (4) 直到客户端断开连接。
  8. 重复 (3) 以接受新的客户端连接。

客户端算法

  1. 连接到服务器,服务器主机名和端口号。
  2. 提示用户输入命令行 = cmd 路径名。
  3. 将命令行发送到服务器。
  4. 从服务器接收结果。
  5. 重复 (2) 直到命令行为 NULL 或退出命令。
  • 支持的命令:mkdir、rmdir、rm、cd、pwd、ls、get、put。

13.17.2 提示与帮助

  1. 在运行在互联网主机上时,服务器必须发布其主机名或 IP 地址和端口号,以允许客户端连接。出于安全原因,服务器应将虚拟根目录设置为服务器进程的 CWD,以防止客户端访问虚拟根目录上方的文件。

  2. 大多数命令都很容易实现。例如,前 5 个命令中的每个只需要一个 Linux 系统调用,如下所示。

    mkdir 路径名: int r = mkdir(路径名, 0755); // 默认权限
    rmdir 路径名: int r = rmdir(路径名);
    rm 路径名: int r = unlink(路径名);
    cd 路径名: int r = chdir(路径名);
    pwd: char buf[SIZE]; char *getcwd(buf, SIZE);
    

    对于 ls 命令,可以参考第 8 章第 8.6.7 节中的 ls.c 程序。对于 get filename 命令,它本质上与文件复制程序相同,分为两个部分。服务器

打开文件进行读取,读取文件内容并将其发送到客户端。客户端打开文件进行写入,从服务器接收数据并将接收到的数据写入文件。对于 put filename 命令,只需反转服务器和客户端的角色。

  1. 在使用 TCP 时,数据是连续流。为确保客户端和服务器可以发送/接收命令和简单回复,最好让双方都写入/读取固定大小的行,例如 256 字节。

  2. 读者必须设计用户级协议,以确保服务器和客户端之间的正确数据传输。以下描述了需要用户级数据传输协议的命令。

    • 对于 ls 命令,服务器有两个选项。在第一选项中,对于目录中的每个条目,服务器生成以下形式的一行:

      -rw-r--r-- 链接组ID 用户ID 大小 日期 名称
      

      并将该行保存到临时文件中。在累积所有行后,服务器将整个临时文件发送到客户端。在这种情况下,服务器必须能够告诉客户端文件从哪里开始和结束。由于 ls 命令仅生成文本行,读者可以将特殊的 ASCII 字符用作文件开始和结束标记。

      在第二个选项中,服务器可以在生成并发送下一行之前立即将一行生成并发送到客户端。在这种情况下,服务器必须能够告诉客户端何时开始发送行,以及何时不再有更多的行要发送。

    • 对于传输文件内容的 get/put 命令,无法使用特殊的 ASCII 字符作为文件开始和结束标记。这是因为二进制文件可以包含任何 ASCII 代码。解决此问题的标准方法是使用比特填充 [SDLC,IBM,1979]。在此方案中,发送器在每个连续 5 个或更多个 1 位序列之后插入一个额外的 0 位,以便传输的数据永远不包含任何 6 个或更多个连续的 1。在接收端,接收器剥离每个连续 5 个连续 1 的额外 0 位。这允许双方都使用特殊的标志位模式 01111110 作为文件开始和结束标记。没有硬件辅助的情况下,比特填充效率低下。读者必须考虑其他同步服务器和客户端的方法。

    提示:通过文件大小进行同步。

  3. 该项目适合 2 人小组合作。在开发过程中,团队成员可以讨论如何设计用户级协议,并在他们之间分配实现工作。在测试期间,一个成员可以在互联网主机上运行服务器,而另一个成员可以在不同主机上运行客户端。

  • 图 13.9 显示了运行项目程序的示例输出。

TCP server-client for file operations

13.17.3 多线程 TCP 服务器

  • 在 TCP 服务器-客户端编程项目中,服务器一次只能为一个客户端提供服务。在多线程服务器中,它可以接受多个客户端的连接并同时为它们提供服务,可以通过 fork-exec 新的服务器进程或通过同一服务器进程中的多个线程来完成。我们将这个扩展留作另一个可能的编程项目。

13.18 Web和CGI编程

介绍全球网络(WWW)

  • 全球网络(WWW)是互联网上资源和用户的集合,使用超文本传输协议(HTTP)进行信息交换。
  • 于90年代初出现,Web已成为全球日常生活中不可或缺的一部分。

Web编程基础

  • Web编程涵盖了Web开发中涉及的编写、标记和编码,包括Web内容、Web客户端和服务器脚本以及网络安全。
  • Web编程中常用的语言包括HTML、XHTML、JavaScript、Perl 5和PHP。

13.18.1 HTTP编程模型

  • HTTP是基于TCP的用于互联网应用的服务器-客户端协议。
  • HTTP编程模型涉及在Web服务器主机上运行的HTTP服务器。它等待来自HTTP客户端(通常是Web浏览器)的请求。
  • 客户端使用URL(统一资源定位符)来请求HTTP服务器上的文件。
  • HTTP是一种无状态协议,意味着在连续的请求或回复之间不维护任何信息。
  • Cookies用于在客户端和服务器之间提供和维护一些状态信息。

13.18.2 Web页面

  • Web页面是使用HTML标记语言编写的文件。
  • HTML通过由Web浏览器解释和显示的一系列元素来指定Web页面的布局。
  • 浏览器包括Internet Explorer、Firefox、Google Chrome等。
  • 创建Web页面涉及使用HTML元素作为构建块。

示例HTML文件

<html>
  <body>
    <h1>H1标题:一个简单的网页</h1>
    <p>这是一段文本</p>
    <!-- 注释行 -->
    <p><img src="firefox.jpg" width=16></p>
    <a href="http://www.eecs.wsu.edu/~cs360">链接到cs360网页</a>
    <p>
      <font color="red">红色</font>
      <font color="blue">蓝色</font>
      <font color="green">绿色</font>
    </p>
    <!-- 一个表格 -->
    <table>
      <tr>
        <th>姓名</th>
        <th>ID</th>
      </tr>
      <tr>
        <th>kwang</th>
        <th>12345</th>
      </tr>
    </table>
    <!-- 一个表单 -->
    <form>
      输入命令:<input name="command"><br>
      提交命令:<input type="submit" value="点击提交">
    </form>
  </body>
</html>

托管Web页面

  • Web页面必须托管在Web服务器上,以便客户端访问。
  • 托管选项包括商业托管服务、机构服务器或运行Web服务器的独立PC/笔记本电脑。

为Web页面配置HTTPD

  1. 下载并安装Apache Web服务器。
  2. 配置httpd.conf文件:
    • 取消对用户级网站所需行的注释。
    • 更改用户主目录的目录设置。
  3. 重新启动httpd服务器以使更改生效。

13.18.5 动态Web页面

  • Web页面可以是静态的或动态的。
  • 动态Web页面可以是客户端(使用JavaScript)或服务器端(使用PHP、CGI)的。
  • 引入PHP作为用于动态Web页面的服务器端脚本语言。

PHP基础知识

  • PHP(Hypertext Preprocessor)是用于创建服务器端动态Web页面的脚本语言。
  • PHP文件具有.php后缀,包含HTML和嵌入的PHP代码。
  • PHP语句包含在<?php ... ?>标签中。
  • PHP变量以$开头,是弱类型的。
  • PHP支持各种运算符、条件语句、循环、函数、日期/时间函数、文件操作和表单。

示例PHP文件(p1.php)

<html>
  <body>
    <?php
      echo "hello world<br>"; // hello world<br>
      print "see you later<br>"; // see you later<br>
    ?>
  </body>
</html>

示例PHP变量和运算符

<?php
  $PID = getmypid();
  echo "pid = $PID <br>";
  $STR = "hello world!";
  $A = 123; $B = "456";
  $C = $A + $B;
  echo "$STR Sum=$C<br>";
?>

示例PHP表单处理(action.php)

<html><body>
  <?php
    echo "process PID = " . getmypid() . "<br>";
    echo "user_name = " . get_current_user() . "<br>";
    $command = $_POST["command"];
    $filename = $_POST["filename"];
    $parameter= $_POST["parameter"];
    echo "you submitted the following name-value pairs<br>";
    echo "command = " . $command . "<br>";
    echo "filename = " . $filename . "<br>";
    echo "parameter= " . $parameter . " <br>";
  ?>
</body></html>

13.18.7 CGI编程

  • CGI(通用网关接口)是一种允许Web服务器根据用户输入动态生成Web页面的协议。
  • CGI编程模型涉及包含HTML表单的客户端请求,该表单包含输入和要服务器执行的CGI程序的名称。
  • Web服务器执行CGI程序,该程序可以使用用户输入查询数据库系统(如MySQL),根据用户输入生成HTML文件。子进程完成后,Web服务器将生成的HTML文件发送回客户端。
  • CGI程序可以用任何编程语言编写,如C、sh脚本和Perl。

为CGI配置HTTPD

  • CGI程序通常位于/srv/httpd/cgi-bin

录中。

  • 需要在httpd.conf中配置以启用用户级CGI编程。
  • 目录设置应允许执行CGI脚本。

13.19 CGI编程项目:通过CGI创建动态Web页面

项目目的

  • 通过CGI编程实践,结合远程文件操作、CGI编程和服务器端动态Web页面。

项目组织结构

  1. 用户网站

    • cs360.eecs.wsu.edu服务器上,每个用户都有一个登录帐户。
    • 用户的主目录中包含一个public_html目录,其中有一个index.html文件,可通过以下URL从Web浏览器访问:
      http://cs360.eecs.wsu.edu/~username
    • index.html文件示例:
    <html>
      <body bgcolor="#00FFFF">
        <H1>Welcome to KCW's Web Page</H1>
        <P><img src="kcw.jpg" width=100></P>
        <FORM METHOD="POST" ACTION="http://cs360.eecs.wsu.edu/~kcw/cgi-bin/mycgi.bin">
          Enter command: <INPUT NAME="command"> (mkdir|rmdir|rm|cat|cp|ls)<P>
          Enter filename1: <INPUT NAME="filename1"> <P>
          Enter filename2: <INPUT NAME="filename2"> <P>
          Submit command: <INPUT TYPE="submit" VALUE="Click to Submit"> <P>
        </FORM>
      </body>
    </html>
    
  2. HTML表单

    • index.html包含一个HTML表单,使用POST方法提交。
    • 在HTML表单中,METHOD指定如何提交表单输入,ACTION指定Web服务器和由Web服务器执行的CGI程序。
    • 大多数HTML表单使用POST方法,因为它更安全,输入数据量无限。
    • 用户输入在提交时将发送到http://cs360.eecs.wsu.edu/~kcw/cgi-bin/mycgi.bin
  3. CGI目录和CGI程序

    • HTTPD服务器配置为允许用户级别的CGI。

    • 用户级别的CGI设置如下:

      /home/username:
      | ---- public_html
            | ---- index.html
            | ---- cgi-bin
                  | ---- mycgi.c
                  | ---- util.o
                  | ---- mycgi.bin, sample.bin
      
    • cgi-bin目录中,mycgi.c是一个C程序,通过接收和显示提交的HTML表单中的用户输入,生成一个包含FORM的HTML文件。该文件发送回Web客户端显示。

    • mycgi.c程序示例:

    /* mycgi.c文件 */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define MAX 1000
    typedef struct {
      char *name;
      char *value;
    } ENTRY;
    ENTRY entry[MAX];
    extern int getinputs(); // 在util.o中定义的函数
    int main(int argc, char *argv[]) {
      int i, n;
      char cwd[128];
      n = getinputs(); // 获取用户输入的name=value对
      getcwd(cwd, 128); // 获取当前工作目录路径名
      // 生成包含HTML FORM的HTML文件
      printf("Content-type: text/html\n\n"); // 注意:两个换行字符
      printf("<html>");
      printf("<body bgcolor=\"#FFFF00\"); // 背景颜色=黄色
      printf("<p>pid=%d uid=%d cwd=%s\n", getpid(), getuid(), cwd);
      printf("<H2>Echo Your Inputs</H2>");
      printf("You submitted the following name/value pairs:<p>");
      for (i = 0; i <= n; i++)
        printf("%s = %s<P>", entry[i].name, entry[i].value);
      printf("<p>");
      // 为用户再次提交创建一个FORM网页
      printf("---------- Send Back a Form Again ------------<P>");
      printf("<FORM METHOD=\"POST\" ACTION=\"http://cs360.eecs.wsu.edu/~kcw/cgi-bin/mycgi.bin\">");
      printf("<font color=\"RED\">");
      printf("Enter command : <INPUT NAME=\"command\"> <P>");
      printf("Enter filename1: <INPUT NAME=\"filename1\"> <P>");
      printf("Enter filename2: <INPUT NAME=\"filename2\"> <P>");
      printf("Submit command: <INPUT TYPE=\"submit\" VALUE=\"Click to Submit\"> <P>");
      printf("</form>");
      printf("</font>");
      printf("----------------------------------------------<p>");
      printf("</body>");
      printf("</html>");
    }
    
  4. CGI程序执行

    • 当HTTPD服务器接收到CGI请求时,它使用UID 80分叉一个子进程来执行CGI程序。

    • 请求的表单提交方法、输入编码和输入数据长度在进程的环境变量REQUEST_METHODCONETENT_TYPECONTENT_LENGTH中,而输入数据在stdin中。

    • 输入数据通常是URL编码的。

    • 解码输入为name-value对是直接的但相当繁琐。

    • 提供了一个预编译的util.o文件,其中包含一个函数:

      int getinputs();
      
    • 此函数将用户输入解码为name-value字符串对。

    • 使用以下Linux命令生成CGI程序:

      gcc -o mycgi.bin mycgi.c util.o
      
  5. 动态Web页面

    • 在获取用户输入后,CGI程序可以处理用户输入以生成输出数据。

    • 在示例程序中,它只是回显用户输入。

    • 然后,通过在stdout中将HTML语句作为行写入,生成一个HTML文件。

    • 为了使这些行成为HTML文件的一部分,第一行必须是:

      printf("Content-type: text/html\n\n");
      
    • 有两个换行字符。

    • 其余行可以是任何HTML语句。

    • 在示例程序中,它生成一个与提交的表单相同的FORM,用于在下一次提交中获取新的用户输入。

  6. SETUID程序

    • 通常,CGI程序仅使用用户输入从服务器端的数据库中读取以生成HTML文件。
    • 出于安全原因,可能不允许CGI程序修改服务器端数据库。
    • 在该项目中,我们允

许用户请求执行文件操作,如mkdir、rmdir、cp文件等,这需要写入用户目录的权限。

  • 由于CGI进程的UID=80,因此它不应该能够写入用户的目录。

  • 允许CGI进程写入用户目录有两个选项:

    • 用户可以将cgi-bin目录权限设置为0777,但这是不可取的,因为这将允许任何人都能够写入该目录。

    • 第二个选项是通过以下方式使CGI程序成为SETUID程序:

      chmod u+s mycgi.bin
      
  • 当进程执行SETUID程序时,它会临时假定程序所有者的UID,从而允许它写入用户的目录。

  1. 用户对文件操作的请求

    • 由于项目的目标是CGI编程,我们仅假设以下简单的文件操作作为用户请求:
      • ls [directory]:以Linux的ls –l形式列出目录
      • mkdir dirname permission:创建一个目录
      • rmdir dirname:删除目录
      • unlink filename:删除文件
      • cat filename:显示文件内容
      • cp file1 file2:将文件1复制到文件2
  2. 示例解决方案

    • cgi-bin目录中,sample.bin是该项目的一个示例解决方案。
    • 读者可以将index.html文件中的mycgi.bin替换为sample.bin以测试用户请求并观察结果。

13.20 总结

第一部分:TCP/IP和网络编程

  • TCP/IP协议及其应用:TCP/IP堆栈、IP地址、主机名和DNS、IP数据包和路由器。
  • UDP和TCP协议、端口号、网络字节顺序和TCP/IP网络中的数据流。
  • 服务器-客户端计算模型和套接字编程接口。
  • 实现一对TCP服务器-客户端,通过互联网执行文件操作,并允许用户定义可靠传输文件内容的其他通信协议。

第二部分:Web和CGI编程

  • HTTP编程模型、Web页面和Web浏览器。
  • 配置Linux HTTPD服务器以支持用户Web页面、PHP和CGI编程。
  • 客户端和服务器端动态Web页面。
  • 通过PHP和CGI创建服务器端动态Web页面。
  • 实现在Linux HTTPD服务器机器上通过CGI编程创建服务器端动态Web页面的编程项目。