如何用python构建邮件服务器

有人说只学Python没用,找工作一定要学个框架(比如Django和web.py)。

其实掌握一个类似框架的高级工具是有用的,但是基础的东西可以让你永远不被淘汰,不要被工具限制。

今天我们不用框架或者Python标准库中的高级包,只用标准库中的socket接口来写一个Python服务器。

框架和底层

在当今这个Python服务器框架(如Django、Twisted、web.py等)盛行的时代。)泛滥,从底层套接字开始写服务器似乎是一种吃力不讨好的愚蠢做法。

框架的意义在于掩盖底层细节,提供一套对开发者更友好的API,处理MVC等布局问题。

该框架允许我们快速构建一个成熟的Python服务器。但是,框架本身也依赖于底层(比如socket)。了解底层套接字不仅可以帮助我们更好地使用框架,还可以让我们了解框架是如何设计的。

此外,如果您对底层套接字编程和其他系统编程有很好的了解,您可以设计和开发自己的框架。

如果你能从底层socket开始,实现一个完整的Python服务器,支持用户层的协议,处理MVC(模型-视图-控制)和多线程等问题,整理出一套清晰的函数或类作为接口(API)呈现给用户,你就相当于设计了一个框架。

套接字接口实际上是操作系统提供的系统调用。

socket的使用不仅限于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套接字有点类似于双工管道。一个进程向套接字的一端写入或读取文本流,而另一个进程可以从套接字的另一端读取或写入。特别是,这两个建立套接字通信的进程可以属于两台不同的计算机。

TCP协议提供了一些通信规则,使得上述进程间的通信过程可以在网络环境下有效实现。

双工管道存在于同一台计算机中,不需要区分两个进程所在计算机的地址,套接字必须包含地址信息才能实现网络通信。

一个套接字包含四个地址信息:两台计算机的IP地址和两个进程使用的端口。IP地址用于定位计算机,而端口用于定位进程(多个进程可以在一台计算机上使用不同的端口)。

TCP套接字

在互联网上,让一台电脑充当服务器。

服务器打开自己的端口,被动等待其他计算机连接。

当其他计算机作为客户主动使用socket连接服务器时,服务器就开始为客户提供服务。

在Python中,我们使用标准库中的套接字包进行底层套接字编程。

首先是服务器端,我们用bind()方法给socket一个固定的地址和端口,用listen()方法被动监听端口。

当客户尝试使用connect()方法连接时,服务器使用accept()来接受连接,从而建立一个连接的套接字:

Socket.Socket()创建一个socket对象,解释socket使用IPv4(AF_INET,IP version 4)和TCP协议(SOCK_STREAM)。

然后使用另一台计算机作为客户,我们主动使用connect()方法搜索服务器的IP地址(在Linux中,可以使用$ifconfig查询自己的IP地址)和端口,让客户找到服务器并建立连接:

在上面的例子中,我们可以调用recv()方法在套接字两端接收信息,调用sendall()方法发送信息。

这样,我们可以在两台计算机上的两个进程之间进行通信。

当通信结束时,我们使用close()方法关闭套接字连接。

(如果没有两台电脑做实验,也可以把客户端IP要连接的IP改成“127.0.0.1”,这是一个连接本地主机的专用IP地址。)

基于TCP套接字的HTTP服务器

在上面的例子中,我们已经可以使用TCP套接字在两台远程计算机之间建立连接。

但是socket传输的自由度太高,带来了很多安全性和兼容性问题。

我们经常使用一些应用层协议(比如HTTP协议)来规定socket的使用规则和传输信息的格式。

HTTP协议以请求-响应的方式使用TCP套接字。

客户端向服务器发送一段文本作为请求,服务器收到请求后向客户端发送一段文本作为响应。

在完成这样一个请求-响应事务后,TCP socket就被放弃了。

下一个请求将创建一个新的套接字。

请求和响应本质上是两种文本,但是HTTP协议对两种文本都有一定的格式要求。

请求& lt——& gt;反应

现在,让我们编写一个HTTP服务器:

HTTP服务器程序说明

正如我们在上面看到的,服务器将根据请求向客户发送两个消息中的一个,text_content和pic_content,作为响应文本。

整个响应分为三个部分:起始行、头部信息和正文。起跑线是第一行:

实际上是用空格分成三段,HTTP/1.x表示使用的HTTP版本,200表示状态码,200是HTTP协议中规定的,表示服务器正常接收和处理请求,OK是状态码,供人读取。

标题信息跟在起始行之后,在它和正文之间有一个空行。

这里的text_content或者pic_content只有一行头信息,用来表示主要信息的text_content类型是html文本:

pic _ Content(Content-Type:image/jpg)的头信息表示主体的类型为jpg图片(image/jpg)。

主要信息是html或jpg文件的内容。

(注意,对于jpg文件,为了与windows兼容,我们以“rb”模式打开。因为在windows下,jpg被认为是二进制文件,在UNIX系统下,不需要区分文本文件和二进制文件。)

我们没有写客户端程序,后面会用浏览器做客户端。

客户端程序将请求发送到服务器。

虽然请求可以像响应一样分为三个部分,但是请求的格式与响应的格式并不相同。

请求由客户端发送到服务器。例如,下面是一个请求:

起始行可以分为三部分,第一部分是请求方法,第二部分是URL,第三部分是HTTP版本。

请求方法可以有GET、PUT、POST、DELETE和HEAD。最常用的是GET和POST。

GET是请求服务器发送资源给客户,POST是请求服务器接收客户发送的数据。

当我们打开一个网页时,我们通常使用GET方法;当我们填写表格并提交时,我们通常使用POST方法。

第二部分是URL,通常指向一个资源(服务器上的资源或者其他地方的资源)。和现在一样,它是指向当前服务器的当前目录的test.jpg。

根据HTTP协议的规定,服务器需要根据请求执行某些操作。

在服务器程序中我们可以看到,我们的Python程序首先检查请求的方法,然后根据不同的URL生成不同的响应(text_content _ content或者pic_content)。

随后,该响应被发送回客户端。

体验浏览器

为了配合上面的服务器程序,我在放置Python程序的文件夹中保存了一个test.jpg图片文件。

我们在终端上运行Python程序作为服务器,然后打开一个浏览器作为客户端。

(有时间的话也可以用Python写个客户端。原理类似于上面的TCP socket的客户端程序。)

在浏览器的地址栏中,输入:

(当然也可以用电脑,输入服务器的IP地址。)

好了,我已经有了一个用Python实现的、用socket编写的服务器。

从终端上,我们可以看到浏览器实际上发出了两个请求。

第一个请求是(关键信息在起始行,这个请求的主体是空的):

根据这个请求,我们的Python程序将text_content的内容发送到服务器。

在浏览器接收到text_content之后,它发现

在分析了初始行之后,我们的Python程序发现/test.jpg满足if条件,因此它将pic_content发送给客户。

最后,浏览器根据html语言的语法以适当的方式显示html文本和图片。

探索的方向

1)在我们上面的服务器程序中,我们使用了一个while循环来保持服务器工作。

其实我们也可以根据多线程的知识把while循环中的内容改成多进程或者多线程工作。

2)我们的服务器程序还不完善,可以让我们的Python程序调用Python的其他函数来实现更复杂的功能。例如,制作一个时间服务器,让服务器向客户返回日期和时间。也可以用Python自己的数据库实现一个完整的LAMP服务器。

3) socket包是比较低级的包。Python标准库中也有高级包,比如SocketServer、SimpleHTTPServer、cgiHTTPServer、CGI。所有这些软件包都在帮助我们更容易地使用socket。如果你已经了解socket,那么这些包就很好理解了。有了这些高级包,你就可以编写一个相当成熟的服务器了。

4)历经千辛万苦,你可能会发现框架是如此的方便,于是你决定使用它。或者,你已经有了参与框架开发的热情。