一.网络编程的背景信息
1.客户端/服务器架构
什么是客户端/服务器架构?对于不同的人来说,它意味着不同的东西,这取决于你问谁以及描述的是软件还是硬件系统。在这两种情况中的任何一种下,前提都很简单:服务器就是一系列硬件或软件,为一个或多个客户端(服务的用户)提供所需的“服务”。它存在唯一目的就是等待客户端的请求,并响应它们(提供服务),然后等待更多请求。另一方面,客户端因特定的请求而联系服务器,并发送必要的数据,然后等待服务器的回应,最后完成请求或给出故障的原因。服务器无限地运行下去,并不断地处理请求;而客户端会对服务进行一次性请求,然后接收该服务,最后结束它们之间的事务。客户端在一段时间后可能会再次发出其他请求,但这些都被当作不同的事务。此外,客户端/服务器架构既可以应用于计算机硬件,也可以应用于软件。
2.硬件客户端
打印(打印机)服务器是硬件服务器的一个例子。它们处理传入的打印作业并将其发送给系统中的打印机(或其他的打印设备)。这样的计算机通常可以通过网络进行访问,并且客户端计算机将向它发送打印请求。硬件服务器的另一个例子就是文件服务器。这些通常都是拥有庞大通用存储容量的计算机,可以被客户端远程访问。客户端计算机会挂载服务器计算机上的磁盘,看起来好像这个磁盘就在本地计算机上一样。支持文件服务器的一个最流行的网络操作系统就是Sun公司的网络文件系统(NFS)。如果你正在访问一个网络磁盘驱动器,并且无法分辨它是在本地还是网络上,那么此时客户端/服务器系统就已经完成了它的任务。它的目标就是让用户得到与访问本地磁盘完全相同的体验,抽象起来就是正常的磁盘访问,而这些都是通过编程实现来确保以这种方式进行。
3.软件客户端
软件服务器也运行在一块硬件之上,但是没有像硬件服务器那样的专用外围设备(如打印机、磁盘驱动器等)。软件服务器提供的主要服务包括程序执行、数据传输检索、聚合、更新,或其他类型的编程或数据操作。
常见的软件服务器:web服务器;数据库服务器;窗体(window)服务器。
注意:客户端---服务器的工作原理就像银行客服(服务器)和客户(客户端)
4.套接字:通讯端点
在服务器响应客户端请求之前,必须进行一些初步的设置流程来为之后的工作做准备。首先会创建一个通信端点,它能够使服务器监听请求。可以把服务器比作公司前台,或者应答公司主线呼叫的总机接线员。一旦电话号码和设备安装成功且接线员到达时,服务就可以开始了。这个过程与网络世界一样,一旦一个通信端点已经建立,监听服务器就可以进入无限循环中,等待客户端的连接并响应它们的请求。
客户端比服务器端更简单,客户端所需要做的只是创建它的单一通信端点,然后建立一个到服务器的连接。然后,客户端就可以发出请求,该请求包括任何必要的数据交换。一旦请求被服务器处理,且客户端收到结果或某种确认信息,此次通信就会被终止。
5.套接字简介
套接字是计算机网络数据结构,它体现了上节中所描述的“通信端点”的概念。在任何类型的通信开始之前,网络应用程序必须创建套接字。可以将它们比作电话插孔,没有它将无法进行通信。套接字的起源可以追溯到20世纪70年代,它是加利福尼亚大学的伯克利版本UNIX(称为BSD UNIX)的一部分。因此,有时你可能会听过将套接字称为伯克利套接字或BSD套接字。套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication, IPC)。有两种类型的套接字:基于文件(AF_UNIX )的和面向网络(AF_INET,因特网)的。
6.套接字地址:主机-端口对
如果一个套接字像一个电话插孔,那么主机名和端口号就像区号和电话号码的组合。一个网络地址由主机名和端口号对组成,这是网络通信所需要的。此外,必须事先说明有其他人在另一端接听;否则,你将听到这个熟悉的声音“对不起,您所拨打的电话是空号,请核对后再拨”。你可能已经在浏览网页的过程中见过一个网络类比,例如“无法连接服务器,服务器没有响应或者服务器不可达。”有效的端口号范围为0~65535(小于1024的端口号预留给了系统)。如果你正在使用Linux、Mac OS X等,那么可以在/etc/services文件中找到预留端口号的列表(以及服务器/协议和套接字类型)。
7.面向连接的套接字与无连接的套接字
面向连接的套接字
不管你采用的是哪种地址家族,都有两种不同风格的套接字连接。第一种是面向连接的,这意味着在进行通信之前必须先建立一个连接,例如,使用电话系统给一个朋友打电话。这种类型的通信也称为虚拟电路或流套接字。面向连接的通信提供序列化的、可靠的和不重复的数据交付,而没有记录边界。这基本上意味着每条消息可以拆分成多个片段,并且每一条消息片段都确保能够到达目的地,然后将它们按顺序组合在一起,最后将完整消息传递给正在等待的应用程序。实现这种连接类型的主要协议是传输控制协议( TCP)
无连接的套接字
与虚拟电路形成鲜明对比的是数据报类型的套接字,它是一种无连接的套接字。这意味着,在通信开始之前并不需要建立连接。此时,在数据传输过程中并无法保证它的顺序性、可靠性或重复性。然而,数据报确实保存了记录边界,这就意味着消息是以整体发送的,而并非首先分成多个片段,例如,使用面向连接的协议。使用数据报的消息传输可以比作邮政服务。信件和包裹或许并不能以发送顺序到达。事实上,它们可能不会到达。为了将其添加到并发通信中,在网络中甚至有可能存在重复的消息。实现这种连接类型的主要协议是用户数据报协议(UDP)。
前面介绍使用套接字的底层网络通信协议。这种类型的网络是当今因特网中大部分客户端/服务器协议的核心。这些网络协议分别用于文件传输(FTP、SCP 等)、阅读 Usenet新闻组(NNTP)、发送电子邮(SMTP)、从服务器上下载电子邮件(POP3、IMAP)等。现在使用 TCP/IP这样底层的协议创建了新的、有专门用途的协议,以此来实现刚刚介绍的高层服务。
8.文件传输因特网协议
因特网中最常见的事情就是传输文件。文件传输每时每刻都在发生。有很多协议可以用于在因特网上传输文件。最流行的包括文件传输协议(FTP)、UNIX 到 UNIX 复制协议(UUCP)、用于Web的超文本传输协议(HTTP)。另外,还有(UNIX下的)远程文件复制命令rcp(以及更安全、更灵活的scp和rsync)。
在当下,HTTP、FTP、scp/rsync的应用仍然非常广泛。HTTP主要用于基于Web的文件下载以及访问 Web 服务,一般客户端无须登录就可以访问服务器上的文件和服务。大部分HTTP文件传输请求都用于获取网页(即将网页文件下载到本地)。而scp和rsync需要用户登录到服务器主机。在传输文件之前必须验证客户端的身份,否则不能上传或下载文件。FTP与scp/rsync相同,它也可以上传或下载文件,并采用了UNIX的多用户概念,用户需要输入有效的用户名和密码。但FTP也允许匿名登录。
9.电子邮件
电子邮件最重要的组件是消息传输代理(MTA)。这是在邮件交换主机上运行的服务器进程,它负责邮件的路由、队列处理和发送工作。MTA 就是邮件从发送主机到接收主机所要经过的主机和“跳板”,所以也称为“消息传输”的“代理”。MTA 做两件事情:1)如何找到消息应该到达的下一台MTA;2)如何与另一台MTA通信。第一件事由域名服务(DNS)来查找目的域名的MX(Mail eXchange,邮件交换)来完成。查找到的可能不是最终收件人,而可能只是下一个能最终把消息送到目的地的主机。对于第二件事,MTA怎么把消息转给其他的MTA呢?为了发送电子邮件,邮件客户端必须要连接到一个MTA,MTA靠某种协议进行通信。MTA之间通过消息传输系统(MTS)互相通信。只有两个MTA 都使用这个协议时,才能进行通信。由于以前存在很多不同的计算机系统,每个系统都使用不同的网络软件,因此这种通信很危险,具有不可预知性。更复杂的是,有的计算机使用互连的网络,而有的计算机使用调制解调器拨号,消息的发送时间也是不可预知的。这种复杂性导致了现代电子邮件的基础之一 ——简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)的诞生。(SMTP、ESMTP、LMTP)
二.Web应用:客户端/服务器计算
Web 应用遵循前面反复提到的客户端/服务器架构。这里说的 Web 客户端是浏览器,即允许用户在万维网上查询文档的应用程序。另一边是Web服务器端,指的是运行在信息提供商的主机上的进程。这些服务器等待客户端和及其文档请求,进行相应的处理,并返回相关的数据。正如大多数客户端/服务器系统中的服务器端一样,Web服务器端“永远”运行。客户端可以向Web服务器端发出各种不同的请求。这些请求可能包括获得一个用于查看的网页视图,或者提交一个包含待处理数据的表单。Web服务器端首先处理请求,然后会以特定的格式(HTML等)返回给客户端浏览。Web客户端和服务器端交互需要用到特定的“语言”,即Web交互需要用到的标准协议,称为HTTP(HyperText Transfer Protocol,超文本传输)。HTTP是TCP/IP的上层协议,这意味着HTTP协议依靠TCP/IP来进行低层交流。
HTTP 属于无状态协议,因为其不跟踪从一个客户端到另一个客户端的请求信息,这点很像现在使用的客户端/服务器架构。服务器持续运行,但是客户端的活动以单个事件划分的,一旦完成一个客户请求,这个服务事件就停止了。客户端可以随时发送新的请求,但是新的请求会处理成独立的服务请求。由于每个请求缺乏上下文,因此你可能注意到有些 URL 中含有很长的变量和值,这些将作为请求的一部分,以提供一些状态信息。另一种方式是使用“cookie”,即保存在客户端的客户状态信息。
1.CGI简介
Web 最初目的是在全球范围内对文档进行在线存储和归档(大多用于教学和科研)。这些文件通常用静态文本表示,一般是HTML。HTML 是一个文本排版工具,而不像是一种语言,可用于指明字体的类型、大小、样式。HTML 的主要特性是其超文本的兼容性,如突出显示标明一些文本,或用图形元素作为链接,指向其他本地文档或位于网上其他地方的文档。这样就可以通过鼠标单击或者其他用户选择机制来访问相关文档。这些静态HTML文档位于Web服务器上,在需要的时候会被发送到客户端。
随着因特网和Web服务的发展,除了浏览之外,还需要处理用户的输入。如在线零售商需要处理单个订单,网上银行和搜索引擎需要为每个用户建立独立账号。因此出现了表单,它们成为Web站点从用户获得特定信息的唯一形式(在Java applet出现之前)。反过来,在客户提交了特定数据后,就要求立即生成HTML页面。
现在Web服务器仅有一点做得很不错,即了解用户需要哪个文件,接着将这个文件(即HTML文件)发送给客户端。Web服务器不能处理表单中传递过来的用户相关的数据。这不是Web服务器的职责,Web服务器将这些请求发送给外部应用,将这些外部应用动态生成的HTML页面发送回客户端。处理过程的第一步是Web服务器从客户端接到了请求(即GET或者POST),并调用相应的应用程序。它然后等待HTML页面,与此同时,客户端也在等待。一旦应用程序处理完成,它会将生成的动态HTML页面返回服务器端,然后服务器端再将这个最终结果返回给用户。对于表单处理过程,服务器与外部应用程序交互,收到并将生成的HTML页面通过CGI返回客户端。
客户端输入给 Web 服务器端的表单可能包括处理过程和一些存储在后台数据库中的表单。需要记住的是,含有需要用户输入项(如文本框、单选按钮等)、Submit 按钮、图片的Web页面,都会涉及某种CGI活动。创建 HTML的 CGI应用程序通常是用高级编程语言来实现的,可以接受、处理用户数据,向服务器端返回HTML页面。现在一般生产环境的Web应用都不再使用CGI了。
2.服务器集成
服务器集成,也称为服务器API。这其中包括如Netscape服务器应用编程接口(NSAPI)和微软的因特网服务器编程接口(ISAPI)这样的商业解决方案。当前(从20世纪90年代中期开始)应用最广泛的服务器解决方案是Apache HTTP Web服务器,这是一个开源的解决方案,通常称为Apache,拥有一个服务器API。另外,使用术语模块来描述服务器上插入的编译后的组件,这些组件可以扩展服务器的功能和用途。
3.WSGI
WSGI 不是服务器,也不是用于与程序交互的 API,更不是真实的代码,而只是定义的一个接口。其目标是在Web服务器和Web框架层之间提供一个通用的API标准,减少之间的互操作性并形成统一的调用方式。WSGI 刚出现就得到了广泛应用。基本上所有基于 Python 的Web服务器都兼容WSGI。将WSGI作为标准对应用开发者、框架作者和社区都有帮助。
4.URL
简单的网页浏览需要用到名为URL(统一资源定位符)的Web地址。这个地址用来在Web上定位一个文档,或者调用一个CGI程序来为客户端生成一个文档。URL是多种统一资源标识符(Uniform Resource Identifier,URI)的一部分。这个超集也可以应对其他将来可能出现的标识符命名约定。一个URL是一个简单的URI,它使用已有的协议或方案(即http、ftp等)作为地址的一部分。为了更完整地描述,还要介绍非URL的URI,有时它们称为统一资源名称(Uniform Resource Name,URN),但是现在唯一使用的URI只有URL,而很少听到URI和URN,后者只作为可能会用到的XML标识符了。
三.多线程MT
在多线程(multithreaded,MT)编程出现之前,计算机程序的执行是由单个步骤序列组成的,该序列在主机的CPU中按照同步顺序执行。无论是任务本身需要按照步骤顺序执行,还是整个程序实际上包含多个子任务,都需要按照这种顺序方式执行。那么,假如这些子任务相互独立,没有因果关系(也就是说,各个子任务的结果并不影响其他子任务的结果),这种做法是不是不符合逻辑呢?要是让这些独立的任务同时运行,会怎么样呢?很明显,这种并行处理方式可以显著地提高整个任务的性能。这就是多线程编程。
多线程编程对于具有如下特点的编程任务而言是非常理想的:本质上是异步的;需要多个并发活动;每个活动的处理顺序可能是不确定的,或者说是随机的、不可预测的。这种编程任务可以被组织或划分成多个执行流,其中每个执行流都有一个指定要完成的任务。根据应用的不同,这些子任务可能需要计算出中间结果,然后合并为最终的输出结果。
1.进程
计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。进程也可以通过派生(fork或spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息。
2.线程
线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷你进程”。
线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫做让步(yielding)。
一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线程间的信息共享和通信更加容易。线程一般是以并发方式执行的,正是由于这种并行和数据共享机制,使得多任务间的协作成为可能。当然,在单核CPU系统中,因为真正的并发是不可能的,所以线程的执行实际上是这样规划的:每个线程运行一小会儿,然后让步给其他线程(再次排队等待更多的CPU时间)。在整个进程的执行过程中,每个线程执行它自己特定的任务,在必要时和其他线程进行结果通信。
当然,这种共享并不是没有风险的。如果两个或多个线程访问同一片数据,由于数据访问顺序不同,可能导致结果不一致。这种情况通常称为竞态条件(race condition)。幸运的是,大多数线程库都有一些同步原语,以允许线程管理器控制执行和访问。
另一个需要注意的问题是,线程无法给予公平的执行时间。这是因为一些函数会在完成前保持阻塞状态,如果没有专门为多线程情况进行修改,会导致CPU的时间分配向这些贪婪的函数倾斜。
网友评论