有人说只学Python没用,必须要学一个框架(比如Django和web.py)才能找到工作。其实掌握一个类似于框架的高级工具是有用的,但是基础的东西可以让你永远不被淘汰,也不要被工具所限制去发展自己。今天,我们不使用Python标准库中的框架或高级包,只使用标准库中的socket接口来编写一个Python服务器。框架和底层在当今Python服务器框架(framework,如Django、Twisted、web.py等)横行的时代,从底层socket写服务器似乎是一种吃力不讨好的愚蠢方式。框架的意义在于掩盖底层细节,提供对开发者更友好的API,处理MVC等布局问题。该框架可以让我们快速搭建成熟成熟的Python服务器。但是,框架本身也依赖于底层(比如socket)。对socket底层的理解,不仅可以帮助我们更好的使用框架,还可以让我们理解框架是如何设计的。此外,如果您对底层套接字编程和其他系统编程有很好的了解,您可以设计和开发自己的框架。如果能从底层socket入手,实现一个完整的Python服务器,支持用户级协议,处理好MVC(Model-View-Control)、多线程(threading)等问题,整理出一套清晰的函数或者类,作为接口(API)呈现给用户,你就相当于设计了一个框架。socket接口实际上是操作系统提供的系统调用。sockets的使用不局限于Python语言,你可以用C或者Java来写同样的socket服务器,所有语言都以类似的方式使用socket(Apache是??用C实现的服务器)。但是你不能跨语言使用框架。框架的好处是为你处理了一些细节,实现了快速开发,但同时也受限于Python本身的性能。我们看到很多成功的网站都是使用动态语言(比如Python、Ruby或者PHP,比如twitter和facebook)快速开发的,并且在网站成功之后,将代码转换成一些更高效的语言,比如和C、JAVA一样,让服务器更高效的面对每天上亿的请求。在这种情况下,底层的重要性远远超过了框架。TCP/IP和套接字简介回到我们的任务。我们需要对网络传输有一定的了解,尤其是TCP/IP协议和socket。Socket是一种进程间通信的方法,它基于网络传输协议的上层接口。套接字的种类很多,例如基于TCP协议或UDP协议(两种网络传输协议),其中以TCP套接字最为常用。TCP套接字有点类似于双工PIPE。一个进程将文本流写入或读取到套接字的一端,而另一个进程可以从套接字的另一端读取或写入。特别地,这两个建立socket通信的进程可以分别属于两台不同的计算机。TCP协议规定了一些通信规则,使得上述进程间通信过程可以在网络环境中有效地实现。双向管道(duplexPIPE)住在同一台计算机中,因此不需要区分两个进程所在计算机的地址,而套接字必须包含地址信息才能实现网络通信。一个套接字包含四个地址信息:两台计算机的IP地址和两个进程使用的端口。IP地址用于定位计算机,端口用于定位进程(多个进程可以在一台计算机上使用不同的端口)。TCP套接字在互联网上,让某台电脑充当服务器。服务器打开自己的端口,被动等待其他计算机连接。当其他计算机作为客户端主动使用套接字连接到服务器时,服务器就开始为客户端提供服务。在Python中,我们使用标准库中的socket包进行底层套接字编程。首先是服务器端,我们使用bind()方法给socket一个固定的地址和端口,使用listen()方法被动监听端口。当客户端尝试使用connect()方法连接时,服务器使用accept()接受连接,从而建立连接的套接字:socket.socket()创建套接字对象,并指示套接字使用IPv4(AF_INET,IP版本4)和TCP协议(SOCK_STREAM)。然后使用另一台电脑作为客户端,我们主动使用connect()方法搜索服务器的IP地址(Linux下可以使用$ifconfig查询自己的IP地址)和端口,让客户端找到服务端建立连接:在上面的例子中,我们可以调用recv()方法在socket两端接收信息,调用sendall()方法发送信息。这样,我们就可以在位于两台计算机上的两个进程之间进行通信。当通信结束后,我们使用close()方法关闭socket连接。(如果你没有两台电脑来做实验,你也可以把你要连接的客户端IP改为“127.0.0.1”,这是一个用来连接本地主机的特殊IP地址。)基于TCP套接字的HTTP服务器在示例中,我们已经能够使用TCP套接字在两台远程计算机之间建立连接。但是socket传输的自由度太高,带来了很多安全性和兼容性问题。我们经常使用一些应用层协议(如HTTP协议)来规定套接字的使用规则和传输信息的格式。HTTP协议采用请求-响应(request-response)的方式来使用TCP套接字。客户端向服务器发送一个文本作为请求,服务器在收到请求后向客户端发送一个文本作为响应。完成这样的请求-响应事务后,TCP套接字将被丢弃。下一个请求将创建一个新套接字。request和response本质上是两个文本,但是HTTP协议对这两个文本有一定的格式要求。Request<——>Response现在,我们写一个HTTP服务器:HTTP服务器程序的解释上面我们已经看到,服务器会根据请求向客户端发送text_content和pic_content两种消息中的一种作为响应文本.整个响应分为三部分:起始行、头部信息(head)和正文(body)。起始行是***行:其实是用空格分成三段,HTTP/1.x表示使用的HTTP版本,200表示status(状态码),200是HTTP协议指定的,表示服务器正常接收并处理请求,OK是供人阅读的状态码。标题信息位于起始行之后,与正文之间有一个空行。这里的text_content或者pic_content只有一行header信息,text_content用来表示主体信息的类型是html文本:而pic_content(Content-Type:image/jpg)的header信息表示的是类型主体部分是一张jpg图片(image/jpg)。主要信息是html或jpg文件的内容。(注意,对于jpg文件,为了兼容windows,我们使用'rb'模式打开。因为在windows下,jpg被认为是二进制文件,而在UNIX系统下,不需要区分文本文件和二进制文件files.)我们没有写客户端程序,后面我们会使用浏览器作为客户端。请求由客户端程序发送到服务器。虽然请求也可以像响应一样分为三部分,但是请求的格式和响应的格式是不一样的。请求从客户端发送到服务器。比如下面是一个请求:起始行可以分为三部分,第一部分是请求方法(requestmethod),第二部分是URL,第三部分是HTTP版本。请求方法可以有GET、PUT、POST、DELETE、HEAD。最常用的是GET和POST。GET是请求服务器向客户端发送资源,POST是请求服务器接收客户端发送的数据。当我们打开一个网页时,我们通常使用GET方法;我们在填写表单并提交时,通常会使用POST方式。第二部分是URL,它通常指向资源(在服务器上或其他地方)。像现在,就是指向当前服务器当前目录的test.jpg。根据HTTP协议的规定,服务器需要根据请求进行一定的操作。正如我们在服务器程序中看到的那样,我们的Python程序首先检查请求的方法,然后根据URL生成不同的响应(text_content或pic_content)。随后,此响应被发送回客户端。使用浏览器实验为了配合上面的服务器程序,我在Python程序所在的文件夹中保存了一个test.jpg图片文件。我们在作为服务端的终端上运行上面的Python程序,然后打开一个浏览器作为客户端。(如果有时间,也可以用Python写一个客户端,原理和上面的TCPsocket客户端程序类似)在浏览器地址栏输入:(当然你也可以用电脑,并输入服务器的IP地址)好的,我已经有一个用Python实现并从套接字编写的服务器。从终端我们可以看到浏览器实际上发出了两次请求。第一个请求是(关键信息在起始行,请求体为空):我们的Python程序根据这个请求向服务器发送text_content的内容。浏览器收到text_content后,发现html文本中有text。知道需要获取text.jpg文件补充为图片,它立即发送第二个请求:我们的Python程序分析起始行后,找到了/test。jpg满足if条件,所以将pic_content发送给客户端。***,浏览器根据HTML语言的语法以适当的方式显示HTML文本和图片。(HTML可以参考http://www.w3schools.com/html/default.asp)探索方向1)在我们上面的服务器程序中,我们使用了一个while循环来保持服务器的工作。其实我们也可以根据多线程的知识,把while循环里面的内容改成多进程或者多线程工作。2)我们的服务器程序并不完善,我们还可以让我们的Python程序调用Python的其他函数来实现更复杂的功能。比如做一个时间服务器,让服务器返回日期时间给客户端。也可以使用Python自带的数据库来实现一个完整的LAMP服务器。3)socket包是一个比较底层的包。Python标准库中也有高级包,如SocketServer、SimpleHTTPServer、CGIHTTPServer、cgi。这些包都在帮助我们更方便地使用套接字。如果您已经了解套接字,那么这些包很容易理解。使用这些高级包,您可以编写一个相当成熟的服务器。4)千辛万苦之后,你可能会发现框架是如此的方便,并决定使用它们。或者,您已经热衷于参与框架开发。
