本章论述了 TCP/IP和网络编程,分为两个部分。第一部分论述了 TCP/IP协议及其应 用,具体包括TCP/IP栈、IP地址、主机名、DNS、IP数据包和路由器;介绍了 TCP/IP网 络中的UDP和TCP协议、端口号和数据流;阐述了服务器-客户机计算模型和套接字编程 接口;通过使用UDP和TCP套接字的示例演示了网络编程。第一个编程项目可实现一对通 过互联网执行文件操作的TCP服务器-客户机,可让用户定义其他通信协议来可靠地传输 文件内容。本章的第二部分介绍了 Web和CGI编程,解释了 HTTP编程模型、Web页面和Web浏 览器;展示了如何配置Linux HTTPD服务器来支持用户Web页面、PHP和CG1编程;阐 释了客户机和服务器端动态Web页面;演示了如何使用PHP和CGI创建服务器端动态Web 页面。第二个编程项目可让读者在Linux HTTPD服务器上通过CGI编程实现服务器端动态 Web页面。
思维导图
知识点归纳
网络编程简介
如今,上网已成为日常生活的需要。虽然大多数人可能只把互联网作为一种信息收集、 网上购物和社交媒体等的工具,但计算机科学的学生必须对互联网技术有一定的了解,并掌 握一定的网络编程的技能。在本章中,我们将介绍TCP/IP网络和网络编程的基础知识,包 括TCP/IP协议、UDP和TCP协议、服务器-客户机计算、HTTP和Web页面、动态Web 页面的PHP和CGI编程。
TCP/IP 协议
TCP/IP (Comer 1988, 2001; RFC1180 1991 )是互联网的基础。TCP代表传输控制协议。 IP代表互联网协议。目前有两个版本的IP,即IPv4和IPv6o IPv4使用32位地址,IPv6则 使用128位地址。本节围绕IPv4进行讨论,它仍然是目前使用最多的IP版本。TCP/IP的 组织结构分为几个层级,通常称为TCP/IP堆栈,图13.1所示为TCP/IP的各个层级以及每 一层级的代表性组件及其功能。
Layer |
-Components - |
Functions |
| Application Layer |
I ssh ping |
| Application commands |
| Transport Layer |
| TCP UCP |
| Connection Datagram |
| Internet Layer |
1 IP |
| send/receive data frames | |
| Link Layer |
Ethernet |
| send/receive data frames | |
顶层是使用TCP/IP的应用程序。用于登录到远程主机的ssh、用于交换电子邮件的mail、用于Web页面的http等应用程序需要可靠的数据传输。通常,这类应用程序在传输 层使用TCP。另一方面,有些应用程序,例如用于查询其他主机的ping命令,则不需要可 靠性。这类应用程序可以在传输层使用UDP来提高效率(RFC 768 1980; Comer 1988 )。传 输层负责以包的形式向IP主机发送/接收来自IP主机的应用程序数据。进程与主机之间的 传输层或其上方的数据传输只是逻辑传输。实际数据传输发生在互联网(【P)和链路层,这 些层将数据包分成数据帧,以便在物理网络之间传输。
IP主机和IP地址
主机是支持TCP/IP协议的计算机或设备。每个主机由一个32位的IP地址来标识。为 了方便起见,32位的IP地址号通常用点记法表示,例如:134.121.64.1,其中各个字节用点 号分开。主机也可以用主机名来表示,如dnsl.eec.wsu.edu。实际上,应用程序通常使用主 机名而不是IP地址。在这个意义上说,主机名就等同于IP地址,因为给定其中一个,我们 可以通过DNS (域名系统)(RFC 134 1987; RFC 1035 1987 )服务器找到另一个,它将IP地 址转换为主机名,反之亦然。
IP地址分为两部分,即NetworkID字段和HostID字段。根据划分,1P地址分为A~E 类。例如,一个B类JP地址被划分为一个16位NetworklD,其中前2位是10,然后是一 个16位的HostID字段。发往IP地址的数据包首先被发送到具有相同networklD的路由器。 路由器将通过HostID将数据包转发到网络中的特定主机。每个主机都有一个本地主机名 localhost,默认IP地址为127.0.0.1。本地主机的链路层是一个回送虚拟设备,它将每个数 据包路由回同一个localhost0这个特性可以让我们在同一台计算机上运行TCP/IP应用程序, 而不需要实际连接到互联网。
IP协议
ip协议用于在ip主机之间发送/接收数据包。ip尽最大努力运行。】p主机只向接收主 机发送数据包,但它不能保证数据包会被发送到它们的目的地,也不能保证按顺序发送。这 意味着ip并非可靠的协议。必要时,必须在ip层的上面实现可靠性。
IP数据包格式
ip数据包由ip头、发送方ip地址和接收方ip地址以及数据组成。每个ip数据包的大 小最大为64KB。1P头包含有关数据包的更多信息,例如数据包的总长度、数据包使用TCP 还是UDP、生存时间(TTL)计数、错误检测的校验和等。图
路由器
ip主机之间可能相距很远。通常不可能从一个主机直接向另一个主机发送数据包。路 由器是接收和转发数据包的特殊ip主机。如果有的话, 一个ip数据包可能会经过许多路由器,或者跳跃到达 某个目的地。图13.4显示了 TCP/IP网络的拓扑结构。
每个IP包在1P报头中都有一个8位生存时间 (TTL)计数,其最大值为255。在每个路由器上,TTL
会减小1。如果TTL减小到0,而包仍然没有到达目的地,则会直接丢弃它。这可以防止任 何数据包在IP网络中无限循环。
UDP
UDP (用户数据报协议)(RFC 768 1980; Comer 1988 )在IP上运行,用于发送/接收数 据报。与IP类似,UDP不能保证可靠性,但是快速高效.它可用于可靠性不重要的情况。 例如,用户可以使用ping命令探测目标主机,如
ping主机名或ping IP地址
ping是一个向目标主机发送带时间戳UDP包的应用程序。接收到一个pinging数据包 后,目标主机将带有时间戳的UDP包回送给发送者,让发送者可以计算和显示往返时间。 如果目标主机不存在或宕机,当TTL减小为0时,路由器将会丢弃pinging UDP数据包。 在这种情况下,用户会发现目标主机没有任何响应。用户可以尝试再次ping,或者断定目标 主机宕机。在这种情况下,最好使用UDP,因为不要求可靠性。
TCP
TCP (传输控制协议)是一种面向连接的协议,用于发送/接收数据流。TCP也可在IP 上运行,但它保证了可靠的数据传输。通常,UDP类似于发送邮件的USPS,而TCP类似 于电话连接。
端口编号
在各主机上,多个应用程序(进程)可同时使用TCP/UDP.每个应用程序由三个组成
部分唯一标识
应用程序=(主机IP,协议,端口号)
其中,协议是TCP或UDP,端口号是分配给应用程序的唯一无符号短整数。要想使用 UDP或TCP,应用程序(进程)必须先选择或获取一个端口号。前1024个端口号已被预留。 其他端口号可供一般使用。应用程序可以选择一个可用端口号,也可以让操作系统内核分配 端口号。图13.5给出了在传输层中使用TCP的一些应用程序及其默认端口号。
网络和主机字节序
计算机可以使用大端字节序,也可以使用小端字节序。在互联网上,数据始终按网络 序排列,这是大端。在小端机器上,例如基于Intel x86的PC, htons。、htonl。、ntohs()、 ntohl。等库函数,可在主机序和网络序之间转换数据。例如,PC中的端口号1234按主机字 节序(小端)是无符号短整数。必须先通过htons(1234)把它转换成网络序,才能使用。相 反,从互联网收到的端口号必须先通过ntohs(port)转换为主机序。
网络编程所有Unix/Linux系统都为网络编程提供TCP/IP支持。本节,我们将会阐释用于网络编 程的平台和服务器-客户机计算模型。
网络编程平台
要进行网络编程,读者必须能够访问支持网络编程的平台。可通过下面几种方法访问这 类平台。
(1 )服务器上的用户账户:现在,几乎所有的教育机构都为它们的教职工和学生提供了 网络接入,通常是以无线连接的形式。每位机构成员都要能够登录服务器以接入互联网、服 务器是否允许一般的网络编程取决于本地网络管理策略。这里,我们要介绍作者所在机构 (华盛顿州立大学电气工程与计算机科学系)的网络编程平台的设置。作者有一个专用服 务器 cs360.eecs.wsu.edu 服务器运行14.2版Slackware Linux,完全支持网络编程。这台服务器在华盛顿州立大学电 气工程与计算机科学系的DNS服务器上注册。当服务器启动时,它会使用DHCP (动态主 机配置协议)从DHCP服务器上获取一个私有IP地址(RFC 2131 1997 )o虽然它不是公共 IP地址,但可以通过NAT (网络地址转换)在互联网上访问它:然后,作者为CS360班级 的学生创建用户账户,供他们登录。学生大多通过WSU无线网络将笔记本电脑连接到互联 网上。一旦连上互联网,他们就可以登录CS360服务器了e (2)单独PC或笔记本电脑:即便学生未接入服务器.仍然可以使用计算机的本地主 机在单独计算机上进行网络编程。在这种情况下,学生需要下載安装一些网络部件。例如, Ubuntu Linux用户可能需要安装和配置用于HTTP和CGI编程的Apache服务器,这将在后 面的13.17节中讨论。
服务器-客户机计算模型
大多数网络编程任务都基于服务器-客户机计算模型。在服务器-客户机计算模财中. 我们首先在服务器主机上运行服务器进程。然后,我们从客户机主机运行客户机在UDP 中,服务器等待来自客户机的数据报,处理数据报并生成对客户机的响应。在TCP中,服 务器等待客户机连接。客户机首先连接到服务器,在客户机和服务器之间建立一个虚拟电 路,建立连接后,服务器和客户机可以交换连续的数据流:下面,我们将展示如何使用 UDP和TCP进行网络编程。
套接字编程
在网络编程中,TCP/IP的用户界面是通过一系列C语言库函数和系统调用来实现的, 这些函数和系统调用统称为套接字API (( Rago 1993; Stevens等2004 )。为了使用套接宇 API,我们需要套接字地址结构,它用于标识服务器和客户机。netdb.h和sys/socket.h中有 套接字地址结构的定义。
套接字地址在套接字地址结构中,
TCP/IP 网络的 sin_family 始终设置为 AF_INET。
sm_port包含按网络字节顺序排列的端口号。
sin_addr是按网络字节顺序排列的主机IP地址。
套接字 API
服务器必须创建一个套接字,并将其与包含服务器IP地址和端口号的套接字地址绑 定。它可以使用一个固定端口号,或者让操作系统内核选择一个端口号(如果sin_port为 0)o为了与服务器通信,客户机必须创建〜个套接字。对于UPD套接字,可以将套接字绑 定到服务器地址。如果套接字没有绑定到任何特定的服务器,那么它必须在后续的sendto()/ recvfromO调用中提供一个包含服务器IP和端口号的套接字地址。下面给出了 socket。系统 调用,它创建一个套接字并返回一个文件描述符
Lint套接字(int域,int类型,int协议)
示例:
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
将会创建一个用于发送/接收UDP数据报的套接字。
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
将会创建一个用于发送/接收数据流的面向连接的TCP套接字c
新创建的套接字没有任何相联地址c它必须与主机地址和端口号绑定,以识别接收主机 或发送主机:这通过bind。系统调用来完成,
- UDP套接字
UDP套接字使用scndto()/recvfrom()来发送/接收数据报。
aendto(int aockfdr const void *bufr size.t len, lot flags, const struct sockaddr •de8t_addrf socklen_t addrlen)| asize_t recvfrora(int sock£d, void *buf, aiza_t len, int flags, struct sockaddr *Btc_addr, aocklen_t *addrlen};
- TCP套接字
在创建套接字并将其绑定到服务器地址之后,TCP服务器使用listen()和acccpt()来接 收来自客户机的连接
int Iistcn(int sockfd, int backlog);
listen()将sockfd引用的套接字标记为将用于接收连入连接的套接字。backlog参数定义了等 待连接的最大队列长度。
int accept(int sockfd, struct sockaddr *addr, sockien_t *addrlen);
accept()系统调用与基于连接的套接字一起使用。它提取等待连接队列上的第一个连接请求 用于监听套接字sockfd,创建一个新的连接套接字,并返回一个引用该套接字的新文件描 述符,与客户机主机连接。在执行accept()系统调用时,TCP服务器阻塞,直到客户机通过 coimectO建立连接。
int connect(int sockfd, const struct sockaddr *addr, socklen t addrlen);
connect()系统调用将文件描述符sockfd引用的套接字连接到addr指定的地址,addrlen参数 指定addr的大小。addr中的地址格式由套接字sockfd的地址空间决定。
如果套接字sockfd是SOCK_DGRAM类型,即UDP套接字,addr是发送数据报的默 认地址,也是接收数据报的唯一地址。这会限制UDP套接字与特定UDP主机的通信,但 实际上很少使用。所以对于UDP套接字来说,连接是可选的或不必要的。如果套接字是 SOCK_STREAM类型,即TCP套接字,connect()调用尝试连接到绑定到addr指定地址的 套接字C
- send()/read()以及 recv/write()
建立连接后,两个TCP主机都可以使用send()/write()发送数据,并使用recv()/read。 接收数据。它们唯一的区别是send。和recv()中的nag参数不同,通常情况下可以将其设 置为0。
ssize_t send(int Bockfd, const void *bufr size.t len« int flags); write(sockfd/ void *buf, aize_t, l«n) S0izo_t recv(int sockfd, void *buf# size_t len, int flags); ssize_t read(sockfd, void *buf, size_t len)j
Web页面是用HTML标记语言编写的文件。Web文件通过一系列HTML元素指定 Web页面的布局,可在Web浏览器上解释和显示。常用的Web浏览器有Internet Explorer, Firefox、Google Chrome等。创建Web页面相当于使用HTML元素作为构建块创建文本文 件。与其说它是编程,不如说是文书类工作。因此,我们不讨论如何创建Web页面。相反, 我们将只使用一个示例HTML文件来说明Web页面的本质。下面给出了 •个简单的HTML Web文件。HTML文件内容说明
HTML文件包含多个HTML元素。每个HTML元素由一对匹配的打开和关闭标记 指定。
<tag>contents</tag>
实际上,HTML文件本身可以看作是由一对匹配的<html>标记指定的HTML元素。
<html>HTML file</html>
书中第1至26行指定了一个HTML文件。一个HTML文件包含一个由一对匹配的<body>标记 指定的主体
<body>body of HTML file</body>
第2至25行指定了 HTML文件的主体。
HTML文件可以使用标签<H1>至<H7>来显示不同字体大小的标题行。
第3行指定了一个标题行。
每对匹配的<P>标记指定一个段落,该段落显示在新行中。
第4行指定了一段文本。
第5行指定了一个注释行,浏览器将会忽略它。
第6行指定了一个图像文件,它将按每行的宽度像素显示。
第7行指定了一个链接元素
<a HREF=wlink_URLM>link</a>
其中,属性HREF指定一个link_URL和一个描述链接的文本字符串。浏览器通常以深 蓝色显示链接文本。如果用户单击链接,它将把请求定向到由link_URL标识的Web服 务器。这可能是Web页面最强大的特性。它允许用户通过跟随链接导航到Web中的任何 地方。
第8行至第10行使用<font>元素来显示不同颜色的文本。<font>元素还可以指定不同
字体大小和样式的文本。
第11行至第20行指定一个表,<tr>为行,<th> 为各行中的列。
第21至24行指定了一个表单,用于收集用户输 入并将其提交给Web服务器进行处理。我们将在下一 节的CGI编程中更详细地解释和演示HTML表单。
php
下面显示了一个简单的PHP文件pl .php。
<html> <body> <?php •cho XAhello world<br>w; // hello world<br> print ''see you later<br>**/// see you later<br> ?> </body>
echo ''hello world<br> . ''see you later<br>w;
当Web客户机请求pl.php文件时,httpd服务器的PHP预处理器将先执行PHP语句来 生成HTML行(显示在PHP行右侧),然后将生成的HTML文件发送到客户机。
(2 ) PHP变量:在PHP中,变量以$符号开头,其后是变量名。PHP变量值可以是字 符串、整数或浮点数。与C语言不同,PHP是一种松散类型的语言。用户不需要使用类型 定义变量。与C语言一样,PHP也允许类型转换更改变量类型。对于大多数情况,PHP还 可以自动将变量转换为不同的类型。
echo «$STR Sum=$C<br>w; // hello world! Sxima579<br>
与C语言或sh脚本中的变量一样,PHP变量可以是局部变量、全局变量或静态 变量。
(3) PHP运算符:在PHP中,变量和值可以由以下运算符操作。
・算术运算符 ・赋值运算符 •比较运算符 •递增/递减运算符 -逻辑运算符 ・字符串运算符 ・数组运算符
(3) .1字符串运算:大多数字符串运算,如strlen()、strcmp()等,与C语言相同。PHP 还支持许多其他字符串运算,它们的语法形式通常略有不同。例如,PHP不使用strcat(), 而是使用点运算符进行字符申连接,如*• string 1 string?" 0
(3) .2 PHP数组:PHP数组用array()关键字定义。PHP支持索引数组和多维数组。可 通过数组索引单步遍历索引数组,如此外,PHP数组可由运算符进行集合运算,如并集(+)和比较,也可进行列表运算, 可按不同的顺序排列。
关联数组:关联数组由名称-值对组成。
$A ■ array('name*=>l, *namel*=>2, *name2*=>3z wname*=>4);
关联数组允许按名称而不是索引访问元素值,如
echo wvalue of namel ■ * . $A[ Xnaxnel * ] /
(4 ) PHP 条件语句:PHP 通过 if、if・else、if-elseif-else switch-case 语句支持条件和
测试条件,这些语句与C语言中的语句全相同,但语法上略有不同。
实践练习
代码:
遇到的问题和解决
遇到的问题:套接字系统调用和对应包裹函数有哪些?
解决:通过查询csdn以及博客园和知乎等平台,归纳如下知识点:
socketint Socket (int __domain, int __type, int __protocol) { int socketfd; if((socketfd = socket(__domain , __type , __protocol)) < 0) err_quit("socket error"); return socketfd; }
int Bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len) { int rc; if((rc = bind(__fd , __addr , __len)) < 0) err_quit("bind error"); return (rc); }
int Accept (int __fd, __SOCKADDR_ARG __addr , socklen_t *__restrict __addr_len) { int fd; if((fd = accept(__fd , __addr , __addr_len)) < 0) err_quit("accept err"); return (fd); }
int Listen (int __fd, int __n) { int rc; if((rc = listen(__fd , __n)) < 0) err_quit("listen err %d" , __fd); return (rc); }